/* SPDX-License-Identifier: LGPL-2.1-or-later */ /*** Copyright © 2014 Intel Corporation. All rights reserved. ***/ #include <netinet/icmp6.h> #include "sd-ndisc.h" #include "alloc-util.h" #include "ndisc-internal.h" #include "ndisc-router-internal.h" #include "string-table.h" static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) { if (!rt) return NULL; icmp6_packet_unref(rt->packet); set_free(rt->options); return mfree(rt); } DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, ndisc_router_free); sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet) { sd_ndisc_router *rt; assert(packet); rt = new(sd_ndisc_router, 1); if (!rt) return NULL; *rt = (sd_ndisc_router) { .n_ref = 1, .packet = icmp6_packet_ref(packet), .iterator = ITERATOR_FIRST, }; return rt; } int sd_ndisc_router_set_sender_address(sd_ndisc_router *rt, const struct in6_addr *addr) { assert_return(rt, -EINVAL); return icmp6_packet_set_sender_address(rt->packet, addr); } int sd_ndisc_router_get_sender_address(sd_ndisc_router *rt, struct in6_addr *ret) { assert_return(rt, -EINVAL); return icmp6_packet_get_sender_address(rt->packet, ret); } int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); return icmp6_packet_get_timestamp(rt->packet, clock, ret); } #define DEFINE_GET_TIMESTAMP(name) \ int sd_ndisc_router_##name##_timestamp( \ sd_ndisc_router *rt, \ clockid_t clock, \ uint64_t *ret) { \ \ usec_t s, t; \ int r; \ \ assert_return(rt, -EINVAL); \ assert_return(ret, -EINVAL); \ \ r = sd_ndisc_router_##name(rt, &s); \ if (r < 0) \ return r; \ \ r = sd_ndisc_router_get_timestamp(rt, clock, &t); \ if (r < 0) \ return r; \ \ *ret = time_span_to_stamp(s, t); \ return 0; \ } DEFINE_GET_TIMESTAMP(get_lifetime); DEFINE_GET_TIMESTAMP(prefix_get_valid_lifetime); DEFINE_GET_TIMESTAMP(prefix_get_preferred_lifetime); DEFINE_GET_TIMESTAMP(route_get_lifetime); DEFINE_GET_TIMESTAMP(rdnss_get_lifetime); DEFINE_GET_TIMESTAMP(dnssl_get_lifetime); DEFINE_GET_TIMESTAMP(prefix64_get_lifetime); int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { const struct nd_router_advert *a; int r; assert(rt); assert(rt->packet); if (rt->packet->raw_size < sizeof(struct nd_router_advert)) return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), "Too small to be a router advertisement, ignoring."); a = (const struct nd_router_advert*) rt->packet->raw_packet; assert(a->nd_ra_type == ND_ROUTER_ADVERT); assert(a->nd_ra_code == 0); rt->hop_limit = a->nd_ra_curhoplimit; rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */ rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false); rt->reachable_time_usec = be32_msec_to_usec(a->nd_ra_reachable, /* mas_as_infinity = */ false); rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false); /* RFC 4191 section 2.2 * Prf (Default Router Preference) * 2-bit signed integer. Indicates whether to prefer this router over other default routers. If the * Router Lifetime is zero, the preference value MUST be set to (00) by the sender and MUST be * ignored by the receiver. If the Reserved (10) value is received, the receiver MUST treat the value * as if it were (00). */ rt->preference = (rt->flags >> 3) & 3; if (rt->preference == SD_NDISC_PREFERENCE_RESERVED) rt->preference = SD_NDISC_PREFERENCE_MEDIUM; r = ndisc_parse_options(rt->packet, &rt->options); if (r < 0) return log_ndisc_errno(nd, r, "Failed to parse NDisc options in router advertisement message, ignoring: %m"); return 0; } int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); *ret = rt->hop_limit; return 0; } int sd_ndisc_router_get_reachable_time(sd_ndisc_router *rt, uint64_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); *ret = rt->reachable_time_usec; return 0; } int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); *ret = rt->retransmission_time_usec; return 0; } int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_FLAGS_EXTENSION); *ret = rt->flags | (p ? p->extended_flags : 0); return 0; } int ndisc_router_flags_to_string(uint64_t flags, char **ret) { _cleanup_free_ char *s = NULL; assert(ret); if (FLAGS_SET(flags, ND_RA_FLAG_MANAGED) && !strextend_with_separator(&s, ", ", "managed")) return -ENOMEM; if (FLAGS_SET(flags, ND_RA_FLAG_OTHER) && !strextend_with_separator(&s, ", ", "other")) return -ENOMEM; if (FLAGS_SET(flags, ND_RA_FLAG_HOME_AGENT) && !strextend_with_separator(&s, ", ", "home-agent")) return -ENOMEM; *ret = TAKE_PTR(s); return 0; } int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { assert_return(rt, -EINVAL); if (ret) *ret = rt->lifetime_usec; return rt->lifetime_usec > 0; /* Indicate if the router is still valid or not. */ } int sd_ndisc_router_get_preference(sd_ndisc_router *rt, uint8_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); *ret = rt->preference; return 0; } static const char* const ndisc_router_preference_table[] = { [SD_NDISC_PREFERENCE_LOW] = "low", [SD_NDISC_PREFERENCE_MEDIUM] = "medium", [SD_NDISC_PREFERENCE_HIGH] = "high", [SD_NDISC_PREFERENCE_RESERVED] = "reserved", }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(ndisc_router_preference, int); int sd_ndisc_router_get_sender_mac(sd_ndisc_router *rt, struct ether_addr *ret) { assert_return(rt, -EINVAL); return ndisc_option_get_mac(rt->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, ret); } int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_MTU); if (!p) return -ENODATA; *ret = p->mtu; return 0; } int sd_ndisc_router_get_captive_portal(sd_ndisc_router *rt, const char **ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_CAPTIVE_PORTAL); if (!p) return -ENODATA; *ret = p->captive_portal; return 0; } int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { assert_return(rt, -EINVAL); rt->iterator = ITERATOR_FIRST; return sd_ndisc_router_option_next(rt); } int sd_ndisc_router_option_next(sd_ndisc_router *rt) { assert_return(rt, -EINVAL); return set_iterate(rt->options, &rt->iterator, (void**) &rt->current_option); } int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); if (!rt->current_option) return -ENODATA; *ret = rt->current_option->type; return 0; } int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { uint8_t t; int r; assert_return(rt, -EINVAL); r = sd_ndisc_router_option_get_type(rt, &t); if (r < 0) return r; return t == type; } int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const uint8_t **ret, size_t *ret_size) { assert_return(rt, -EINVAL); if (!rt->current_option) return -ENODATA; return ndisc_option_parse(rt->packet, rt->current_option->offset, NULL, ret_size, ret); } #define DEFINE_GETTER(name, type, element, element_type) \ int sd_ndisc_router_##name##_get_##element( \ sd_ndisc_router *rt, \ element_type *ret) { \ \ int r; \ \ assert_return(rt, -EINVAL); \ assert_return(ret, -EINVAL); \ \ r = sd_ndisc_router_option_is_type(rt, type); \ if (r < 0) \ return r; \ if (r == 0) \ return -EMEDIUMTYPE; \ \ *ret = rt->current_option->name.element; \ return 0; \ } DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, flags, uint8_t); DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, prefixlen, uint8_t); DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, address, struct in6_addr); DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, valid_lifetime, uint64_t); DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, preferred_lifetime, uint64_t); DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, preference, uint8_t); DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, prefixlen, uint8_t); DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, address, struct in6_addr); DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, lifetime, uint64_t); DEFINE_GETTER(rdnss, SD_NDISC_OPTION_RDNSS, lifetime, uint64_t); int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { int r; assert_return(rt, -EINVAL); assert_return(ret, -EINVAL); r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); if (r < 0) return r; if (r == 0) return -EMEDIUMTYPE; *ret = rt->current_option->rdnss.addresses; return (int) rt->current_option->rdnss.n_addresses; } DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, domains, char**); DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, lifetime, uint64_t); DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefixlen, uint8_t); DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefix, struct in6_addr); DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, lifetime, uint64_t);