network: read peer address, label, broadcast from rtnl message

Then, Address objects in Network and Link can be easily compared by
address_equal().
This commit is contained in:
Yu Watanabe 2020-10-13 14:53:46 +09:00
parent 9b9c5fff16
commit fe841414ef
4 changed files with 166 additions and 152 deletions

View File

@ -135,20 +135,6 @@ Address *address_free(Address *address) {
return mfree(address);
}
static uint32_t address_prefix(const Address *a) {
assert(a);
/* make sure we don't try to shift by 32.
* See ISO/IEC 9899:TC3 § 6.5.7.3. */
if (a->prefixlen == 0)
return 0;
if (a->in_addr_peer.in.s_addr != 0)
return be32toh(a->in_addr_peer.in.s_addr) >> (32 - a->prefixlen);
else
return be32toh(a->in_addr.in.s_addr) >> (32 - a->prefixlen);
}
void address_hash_func(const Address *a, struct siphash *state) {
assert(a);
@ -156,16 +142,16 @@ void address_hash_func(const Address *a, struct siphash *state) {
switch (a->family) {
case AF_INET:
siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state);
/* peer prefix */
uint32_t prefix = address_prefix(a);
siphash24_compress(&prefix, sizeof(prefix), state);
siphash24_compress(&a->broadcast, sizeof(a->broadcast), state);
siphash24_compress_string(a->label, state);
_fallthrough_;
case AF_INET6:
siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state);
/* local address */
siphash24_compress(&a->in_addr, FAMILY_ADDRESS_SIZE(a->family), state);
/* peer address */
siphash24_compress(&a->in_addr_peer, FAMILY_ADDRESS_SIZE(a->family), state);
break;
default:
@ -184,19 +170,25 @@ int address_compare_func(const Address *a1, const Address *a2) {
switch (a1->family) {
/* use the same notion of equality as the kernel does */
case AF_INET:
r = CMP(a1->prefixlen, a2->prefixlen);
r = CMP(a1->broadcast.s_addr, a2->broadcast.s_addr);
if (r != 0)
return r;
uint32_t prefix1 = address_prefix(a1);
uint32_t prefix2 = address_prefix(a2);
r = CMP(prefix1, prefix2);
r = strcmp_ptr(a1->label, a2->label);
if (r != 0)
return r;
_fallthrough_;
case AF_INET6:
return memcmp(&a1->in_addr, &a2->in_addr, FAMILY_ADDRESS_SIZE(a1->family));
r = CMP(a1->prefixlen, a2->prefixlen);
if (r != 0)
return r;
r = memcmp(&a1->in_addr, &a2->in_addr, FAMILY_ADDRESS_SIZE(a1->family));
if (r != 0)
return r;
return memcmp(&a1->in_addr_peer, &a2->in_addr_peer, FAMILY_ADDRESS_SIZE(a1->family));
default:
/* treat any other address family as AF_UNSPEC */
return 0;
@ -274,25 +266,22 @@ static int address_set_masquerade(Address *address, bool add) {
return 0;
}
static int address_add_internal(Link *link, Set **addresses,
int family,
const union in_addr_union *in_addr,
unsigned char prefixlen,
Address **ret) {
static int address_add_internal(Link *link, Set **addresses, const Address *in, Address **ret) {
_cleanup_(address_freep) Address *address = NULL;
int r;
assert(link);
assert(addresses);
assert(in_addr);
assert(in);
r = address_new(&address);
if (r < 0)
return r;
address->family = family;
address->in_addr = *in_addr;
address->prefixlen = prefixlen;
r = address_copy(address, in);
if (r < 0)
return r;
/* Consider address tentative until we get the real flags from the kernel */
address->flags = IFA_F_TENTATIVE;
@ -310,18 +299,21 @@ static int address_add_internal(Link *link, Set **addresses,
return 0;
}
static int address_add_foreign(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
return address_add_internal(link, &link->addresses_foreign, family, in_addr, prefixlen, ret);
static int address_add_foreign(Link *link, const Address *in, Address **ret) {
return address_add_internal(link, &link->addresses_foreign, in, ret);
}
static int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
static int address_add(Link *link, const Address *in, Address **ret) {
Address *address;
int r;
r = address_get(link, family, in_addr, prefixlen, &address);
assert(link);
assert(in);
r = address_get(link, in, &address);
if (r == -ENOENT) {
/* Address does not exist, create a new one */
r = address_add_internal(link, &link->addresses, family, in_addr, prefixlen, &address);
r = address_add_internal(link, &link->addresses, in, &address);
if (r < 0)
return r;
} else if (r == 0) {
@ -343,24 +335,19 @@ static int address_add(Link *link, int family, const union in_addr_union *in_add
return 0;
}
static int address_update(
Address *address,
unsigned char flags,
unsigned char scope,
const struct ifa_cacheinfo *cinfo) {
static int address_update(Address *address, const Address *src) {
bool ready;
int r;
assert(address);
assert(address->link);
assert(cinfo);
assert(src);
ready = address_is_ready(address);
address->flags = flags;
address->scope = scope;
address->cinfo = *cinfo;
address->flags = src->flags;
address->scope = src->scope;
address->cinfo = src->cinfo;
if (IN_SET(address->link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return 0;
@ -412,31 +399,20 @@ static int address_drop(Address *address) {
return 0;
}
int address_get(Link *link,
int family,
const union in_addr_union *in_addr,
unsigned char prefixlen,
Address **ret) {
Address address, *existing;
int address_get(Link *link, const Address *in, Address **ret) {
Address *existing;
assert(link);
assert(in_addr);
assert(in);
address = (Address) {
.family = family,
.in_addr = *in_addr,
.prefixlen = prefixlen,
};
existing = set_get(link->addresses, &address);
existing = set_get(link->addresses, in);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
existing = set_get(link->addresses_foreign, &address);
existing = set_get(link->addresses_foreign, in);
if (existing) {
if (ret)
*ret = existing;
@ -548,18 +524,6 @@ static bool link_is_static_address_configured(Link *link, Address *address) {
ORDERED_HASHMAP_FOREACH(net_address, link->network->addresses_by_section)
if (address_equal(net_address, address))
return true;
else if (address->family == AF_INET6 && net_address->family == AF_INET6 &&
in_addr_equal(AF_INET6, &address->in_addr, &net_address->in_addr_peer) > 0)
/* When Peer= is set, then address_equal() in the above returns false, as
* address->in_addr is the peer address. */
return true;
else if (address->family == AF_INET && net_address->family == AF_INET &&
in_addr_equal(AF_INET, &address->in_addr, &net_address->in_addr) > 0)
/* Even if both in_addr elements are equivalent, address_equal() may return
* false when Peer= is set, as Address object in Network contains the peer
* address but Address stored in Link does not, and address_prefix() in
* address_compare_func() may provide different prefix. */
return true;
return false;
}
@ -658,7 +622,7 @@ int link_drop_foreign_addresses(Link *link) {
continue;
if (link_is_static_address_configured(link, address)) {
k = address_add(link, address->family, &address->in_addr, address->prefixlen, NULL);
k = address_add(link, address, NULL);
if (k < 0) {
log_link_error_errno(link, k, "Failed to add address: %m");
if (r >= 0)
@ -813,7 +777,7 @@ int address_configure(
assert(callback);
/* If this is a new address, then refuse adding more than the limit */
if (address_get(link, address->family, &address->in_addr, address->prefixlen, NULL) <= 0 &&
if (address_get(link, address, NULL) <= 0 &&
set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX)
return log_link_error_errno(link, SYNTHETIC_ERRNO(E2BIG),
"Too many addresses are configured, refusing: %m");
@ -883,14 +847,10 @@ int address_configure(
if (r < 0)
return log_link_error_errno(link, r, "Could not append IFA_CACHEINFO attribute: %m");
if (address->family == AF_INET6 && !in_addr_is_null(address->family, &address->in_addr_peer))
r = address_add(link, address->family, &address->in_addr_peer, address->prefixlen, &a);
else
r = address_add(link, address->family, &address->in_addr, address->prefixlen, &a);
r = address_add(link, address, &a);
if (r < 0)
return log_link_error_errno(link, r, "Could not add address: %m");
a->scope = address->scope;
r = address_set_masquerade(a, true);
if (r < 0)
log_link_warning_errno(link, r, "Could not enable IP masquerading, ignoring: %m");
@ -1034,11 +994,7 @@ int link_set_addresses(Link *link) {
ORDERED_HASHMAP_FOREACH(ad, link->network->addresses_by_section) {
bool update;
if (ad->family == AF_INET6 && !in_addr_is_null(ad->family, &ad->in_addr_peer))
update = address_get(link, ad->family, &ad->in_addr_peer, ad->prefixlen, NULL) > 0;
else
update = address_get(link, ad->family, &ad->in_addr, ad->prefixlen, NULL) > 0;
update = address_get(link, ad, NULL) > 0;
r = static_address_configure(ad, link, update);
if (r < 0)
return r;
@ -1087,16 +1043,16 @@ int link_set_addresses(Link *link) {
}
int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
_cleanup_free_ char *buf = NULL;
_cleanup_(address_freep) Address *tmp = NULL;
_cleanup_free_ char *buf = NULL, *buf_peer = NULL;
Link *link = NULL;
uint16_t type;
unsigned char flags, prefixlen, scope;
union in_addr_union in_addr = IN_ADDR_NULL;
struct ifa_cacheinfo cinfo;
unsigned char flags;
Address *address = NULL;
char valid_buf[FORMAT_TIMESPAN_MAX];
const char *valid_str = NULL;
int ifindex, family, r;
int ifindex, r;
bool has_peer = false;
assert(rtnl);
assert(message);
@ -1137,22 +1093,26 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
return 0;
}
r = sd_rtnl_message_addr_get_family(message, &family);
r = address_new(&tmp);
if (r < 0)
return log_oom();
r = sd_rtnl_message_addr_get_family(message, &tmp->family);
if (r < 0) {
log_link_warning(link, "rtnl: received address message without family, ignoring.");
return 0;
} else if (!IN_SET(family, AF_INET, AF_INET6)) {
log_link_debug(link, "rtnl: received address message with invalid family '%i', ignoring.", family);
} else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) {
log_link_debug(link, "rtnl: received address message with invalid family '%i', ignoring.", tmp->family);
return 0;
}
r = sd_rtnl_message_addr_get_prefixlen(message, &prefixlen);
r = sd_rtnl_message_addr_get_prefixlen(message, &tmp->prefixlen);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received address message without prefixlen, ignoring: %m");
return 0;
}
r = sd_rtnl_message_addr_get_scope(message, &scope);
r = sd_rtnl_message_addr_get_scope(message, &tmp->scope);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received address message without scope, ignoring: %m");
return 0;
@ -1163,21 +1123,61 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
log_link_warning_errno(link, r, "rtnl: received address message without flags, ignoring: %m");
return 0;
}
tmp->flags = flags;
switch (family) {
switch (tmp->family) {
case AF_INET:
r = sd_netlink_message_read_in_addr(message, IFA_LOCAL, &in_addr.in);
r = sd_netlink_message_read_in_addr(message, IFA_LOCAL, &tmp->in_addr.in);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received address message without valid address, ignoring: %m");
return 0;
}
r = sd_netlink_message_read_in_addr(message, IFA_ADDRESS, &tmp->in_addr_peer.in);
if (r < 0 && r != -ENODATA) {
log_link_warning_errno(link, r, "rtnl: could not get peer address from address message, ignoring: %m");
return 0;
} else if (r >= 0) {
if (in4_addr_equal(&tmp->in_addr.in, &tmp->in_addr_peer.in))
tmp->in_addr_peer = IN_ADDR_NULL;
else
has_peer = true;
}
r = sd_netlink_message_read_in_addr(message, IFA_BROADCAST, &tmp->broadcast);
if (r < 0 && r != -ENODATA) {
log_link_warning_errno(link, r, "rtnl: could not get broadcast from address message, ignoring: %m");
return 0;
}
r = sd_netlink_message_read_string_strdup(message, IFA_LABEL, &tmp->label);
if (r < 0 && r != -ENODATA) {
log_link_warning_errno(link, r, "rtnl: could not get label from address message, ignoring: %m");
return 0;
} else if (r >= 0 && streq_ptr(tmp->label, link->ifname))
tmp->label = mfree(tmp->label);
break;
case AF_INET6:
r = sd_netlink_message_read_in6_addr(message, IFA_ADDRESS, &in_addr.in6);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received address message without valid address, ignoring: %m");
r = sd_netlink_message_read_in6_addr(message, IFA_LOCAL, &tmp->in_addr.in6);
if (r >= 0) {
/* Have peer address. */
r = sd_netlink_message_read_in6_addr(message, IFA_ADDRESS, &tmp->in_addr_peer.in6);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: could not get peer address from address message, ignoring: %m");
return 0;
}
has_peer = true;
} else if (r == -ENODATA) {
/* Does not have peer address. */
r = sd_netlink_message_read_in6_addr(message, IFA_ADDRESS, &tmp->in_addr.in6);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received address message without valid address, ignoring: %m");
return 0;
}
} else {
log_link_warning_errno(link, r, "rtnl: could not get local address from address message, ignoring: %m");
return 0;
}
@ -1187,40 +1187,43 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
assert_not_reached("Received unsupported address family");
}
(void) in_addr_to_string(family, &in_addr, &buf);
(void) in_addr_to_string(tmp->family, &tmp->in_addr, &buf);
(void) in_addr_to_string(tmp->family, &tmp->in_addr_peer, &buf_peer);
r = sd_netlink_message_read_cache_info(message, IFA_CACHEINFO, &cinfo);
r = sd_netlink_message_read_cache_info(message, IFA_CACHEINFO, &tmp->cinfo);
if (r < 0 && r != -ENODATA) {
log_link_warning_errno(link, r, "rtnl: cannot get IFA_CACHEINFO attribute, ignoring: %m");
return 0;
} else if (r >= 0 && cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME)
} else if (r >= 0 && tmp->cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME)
valid_str = format_timespan(valid_buf, FORMAT_TIMESPAN_MAX,
cinfo.ifa_valid * USEC_PER_SEC,
tmp->cinfo.ifa_valid * USEC_PER_SEC,
USEC_PER_SEC);
(void) address_get(link, family, &in_addr, prefixlen, &address);
(void) address_get(link, tmp, &address);
switch (type) {
case RTM_NEWADDR:
if (address)
log_link_debug(link, "Remembering updated address: %s/%u (valid %s%s)",
strnull(buf), prefixlen,
log_link_debug(link, "Remembering updated address: %s%s%s/%u (valid %s%s)",
strnull(buf), has_peer ? " peer " : "",
has_peer ? strnull(buf_peer) : "", tmp->prefixlen,
valid_str ? "for " : "forever", strempty(valid_str));
else {
/* An address appeared that we did not request */
r = address_add_foreign(link, family, &in_addr, prefixlen, &address);
r = address_add_foreign(link, tmp, &address);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to remember foreign address %s/%u, ignoring: %m",
strnull(buf), prefixlen);
strnull(buf), tmp->prefixlen);
return 0;
} else
log_link_debug(link, "Remembering foreign address: %s/%u (valid %s%s)",
strnull(buf), prefixlen,
log_link_debug(link, "Remembering foreign address: %s%s%s/%u (valid %s%s)",
strnull(buf), has_peer ? " peer " : "",
has_peer ? strnull(buf_peer) : "", tmp->prefixlen,
valid_str ? "for " : "forever", strempty(valid_str));
}
/* address_update() logs internally, so we don't need to here. */
r = address_update(address, flags, scope, &cinfo);
r = address_update(address, tmp);
if (r < 0)
link_enter_failed(link);
@ -1228,13 +1231,15 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
case RTM_DELADDR:
if (address) {
log_link_debug(link, "Forgetting address: %s/%u (valid %s%s)",
strnull(buf), prefixlen,
log_link_debug(link, "Forgetting address: %s%s%s/%u (valid %s%s)",
strnull(buf), has_peer ? " peer " : "",
has_peer ? strnull(buf_peer) : "", tmp->prefixlen,
valid_str ? "for " : "forever", strempty(valid_str));
(void) address_drop(address);
} else
log_link_debug(link, "Kernel removed an address we don't remember: %s/%u (valid %s%s), ignoring.",
strnull(buf), prefixlen,
log_link_debug(link, "Kernel removed an address we don't remember: %s%s%s/%u (valid %s%s), ignoring.",
strnull(buf), has_peer ? " peer " : "",
has_peer ? strnull(buf_peer) : "", tmp->prefixlen,
valid_str ? "for " : "forever", strempty(valid_str));
break;
@ -1273,11 +1278,8 @@ int link_deserialize_addresses(Link *link, const char *addresses) {
assert(link);
for (const char *p = addresses;; ) {
_cleanup_(address_freep) Address *tmp = NULL;
_cleanup_free_ char *address_str = NULL;
union in_addr_union address;
unsigned char prefixlen;
char *prefixlen_str;
int family;
r = extract_first_word(&p, &address_str, NULL, 0);
if (r < 0)
@ -1285,28 +1287,19 @@ int link_deserialize_addresses(Link *link, const char *addresses) {
if (r == 0)
return 0;
prefixlen_str = strchr(address_str, '/');
if (!prefixlen_str) {
log_link_debug(link, "Failed to parse address and prefix length, ignoring: %s", address_str);
continue;
}
*prefixlen_str++ = '\0';
r = sscanf(prefixlen_str, "%hhu", &prefixlen);
if (r != 1) {
log_link_debug(link, "Failed to parse prefixlen: %s", prefixlen_str);
continue;
}
r = in_addr_from_string_auto(address_str, &family, &address);
if (r < 0) {
log_link_debug_errno(link, r, "Failed to parse address: %s", address_str);
continue;
}
r = address_add(link, family, &address, prefixlen, NULL);
r = address_new(&tmp);
if (r < 0)
log_link_debug_errno(link, r, "Failed to add address: %m");
return log_oom();
r = in_addr_prefix_from_string_auto(address_str, &tmp->family, &tmp->in_addr, &tmp->prefixlen);
if (r < 0) {
log_link_debug_errno(link, r, "Failed to parse address, ignoring: %s", address_str);
continue;
}
r = address_add(link, tmp, NULL);
if (r < 0)
log_link_debug_errno(link, r, "Failed to add address %s, ignoring: %m", address_str);
}
return 0;
@ -1589,9 +1582,6 @@ int config_parse_address(
else
n->in_addr_peer = buffer;
if (n->family == AF_INET && n->broadcast.s_addr == 0 && n->prefixlen <= 30)
n->broadcast.s_addr = n->in_addr.in.s_addr | htobe32(0xfffffffflu >> n->prefixlen);
n = NULL;
return 0;
@ -1863,6 +1853,25 @@ static int address_section_verify(Address *address) {
address->section->filename, address->section->line);
}
if (address->family == AF_INET && in_addr_is_null(address->family, &address->in_addr_peer) &&
address->broadcast.s_addr == 0 && address->prefixlen <= 30)
address->broadcast.s_addr = address->in_addr.in.s_addr | htobe32(0xfffffffflu >> address->prefixlen);
else if (address->broadcast.s_addr != 0) {
log_warning("%s: broadcast address is set for IPv6 address or IPv4 address with prefixlength larger than 30. "
"Ignoring Broadcast= setting in the [Address] section from line %u.",
address->section->filename, address->section->line);
address->broadcast.s_addr = 0;
}
if (address->family == AF_INET6 && address->label) {
log_warning("%s: address label is set for IPv6 address in the [Address] section from line %u. "
"Ignoring Label= setting.",
address->section->filename, address->section->line);
address->label = mfree(address->label);
}
if (in_addr_is_localhost(address->family, &address->in_addr) > 0 &&
(address->family == AF_INET || !address->scope_set)) {
/* For IPv4, scope must be always RT_SCOPE_HOST.

View File

@ -48,7 +48,7 @@ typedef struct Address {
int address_new(Address **ret);
Address *address_free(Address *address);
int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
int address_get(Link *link, const Address *in, Address **ret);
bool address_exists(Link *link, int family, const union in_addr_union *in_addr);
int address_configure(Address *address, Link *link, link_netlink_message_handler_t callback, bool update, Address **ret);
int address_remove(Address *address, Link *link, link_netlink_message_handler_t callback);

View File

@ -725,8 +725,10 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
SET_FOREACH(a, addresses) {
Address *existing_address;
address->in_addr.in6 = *a;
/* see RFC4862 section 5.5.3.e */
r = address_get(link, AF_INET6, (union in_addr_union *) a, prefixlen, &existing_address);
r = address_get(link, address, &existing_address);
if (r > 0) {
lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
@ -743,8 +745,6 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r
if (address->cinfo.ifa_valid == 0)
continue;
address->in_addr.in6 = *a;
r = ndisc_address_configure(address, link, rt);
if (r < 0)
return log_link_error_errno(link, r, "Could not set SLAAC address: %m");

View File

@ -159,8 +159,10 @@ static void test_address_equality(void) {
assert_se(in_addr_from_string(AF_INET, "192.168.3.9", &a2->in_addr) >= 0);
assert_se(address_equal(a1, a2));
assert_se(in_addr_from_string(AF_INET, "192.168.3.10", &a1->in_addr_peer) >= 0);
assert_se(address_equal(a1, a2));
assert_se(!address_equal(a1, a2));
assert_se(in_addr_from_string(AF_INET, "192.168.3.11", &a2->in_addr_peer) >= 0);
assert_se(!address_equal(a1, a2));
a2->in_addr_peer = a1->in_addr_peer;
assert_se(address_equal(a1, a2));
a1->prefixlen = 10;
assert_se(!address_equal(a1, a2));
@ -171,10 +173,13 @@ static void test_address_equality(void) {
assert_se(!address_equal(a1, a2));
a2->family = AF_INET6;
a1->in_addr_peer = a2->in_addr_peer = IN_ADDR_NULL;
assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::2", &a1->in_addr) >= 0);
assert_se(in_addr_from_string(AF_INET6, "2001:4ca0:4f01::2", &a2->in_addr) >= 0);
assert_se(address_equal(a1, a2));
a1->prefixlen = 8;
assert_se(!address_equal(a1, a2));
a2->prefixlen = 8;
assert_se(address_equal(a1, a2));