</variablelist>
</refsect1>
- <refsect1>
+ <refsect1>
<title>[IPv6Prefix] Section Options</title>
<para>One or more <literal>[IPv6Prefix]</literal> sections contain the IPv6
prefixes that are announced via Router Advertisements. See
</variablelist>
</refsect1>
+ <refsect1>
+ <title>[IPv6RoutePrefix] Section Options</title>
+ <para>One or more <literal>[IPv6RoutePrefix]</literal> sections contain the IPv6
+ prefix routes that are announced via Router Advertisements. See
+ <ulink url="https://tools.ietf.org/html/rfc4191">RFC 4191</ulink>
+ for further details.</para>
+
+ <variablelist class='network-directives'>
+
+ <varlistentry>
+ <term><varname>Route=</varname></term>
+
+ <listitem><para>The IPv6 route that is to be distributed to hosts.
+ Similarly to configuring static IPv6 routes, the setting is
+ configured as an IPv6 prefix routes and its prefix route length,
+ separated by a<literal>/</literal> character. Use multiple
+ <literal>[IPv6PrefixRoutes]</literal> sections to configure multiple IPv6
+ prefix routes.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>LifetimeSec=</varname></term>
+
+ <listitem><para>Lifetime for the route prefix measured in
+ seconds. <varname>LifetimeSec=</varname> defaults to 604800 seconds (one week).
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>[Bridge] Section Options</title>
<para>The <literal>[Bridge]</literal> section accepts the
#define SD_RADV_MIN_DELAY_BETWEEN_RAS 3
#define SD_RADV_MAX_RA_DELAY_TIME_USEC (500*USEC_PER_MSEC)
+#define SD_RADV_OPT_ROUTE_INFORMATION 24
#define SD_RADV_OPT_RDNSS 25
#define SD_RADV_OPT_DNSSL 31
unsigned n_prefixes;
LIST_HEAD(sd_radv_prefix, prefixes);
+ unsigned n_route_prefixes;
+ LIST_HEAD(sd_radv_route_prefix, route_prefixes);
+
size_t n_rdnss;
struct sd_radv_opt_dns *rdnss;
struct sd_radv_opt_dns *dnssl;
usec_t preferred_until;
};
+#define radv_route_prefix_opt__contents { \
+ uint8_t type; \
+ uint8_t length; \
+ uint8_t prefixlen; \
+ uint8_t flags_reserved; \
+ be32_t lifetime; \
+ struct in6_addr in6_addr; \
+}
+
+struct radv_route_prefix_opt radv_route_prefix_opt__contents;
+
+struct radv_route_prefix_opt__packed radv_route_prefix_opt__contents _packed_;
+assert_cc(sizeof(struct radv_route_prefix_opt) == sizeof(struct radv_route_prefix_opt__packed));
+
+struct sd_radv_route_prefix {
+ unsigned n_ref;
+
+ struct radv_route_prefix_opt opt;
+
+ LIST_FIELDS(struct sd_radv_route_prefix, prefix);
+};
+
#define log_radv_full(level, error, fmt, ...) log_internal(level, error, PROJECT_FILE, __LINE__, __func__, "RADV: " fmt, ##__VA_ARGS__)
#define log_radv_errno(error, fmt, ...) log_radv_full(LOG_DEBUG, error, fmt, ##__VA_ARGS__)
#define log_radv(fmt, ...) log_radv_errno(0, fmt, ##__VA_ARGS__)
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv, sd_radv, radv_free);
static int radv_send(sd_radv *ra, const struct in6_addr *dst, uint32_t router_lifetime) {
+ sd_radv_route_prefix *rt;
sd_radv_prefix *p;
struct sockaddr_in6 dst_addr = {
.sin6_family = AF_INET6,
.nd_opt_mtu_type = ND_OPT_MTU,
.nd_opt_mtu_len = 1,
};
- /* Reserve iov space for RA header, linkaddr, MTU, N prefixes, RDNSS
+ /* Reserve iov space for RA header, linkaddr, MTU, N prefixes, N routes, RDNSS
and DNSSL */
- struct iovec iov[5 + ra->n_prefixes];
+ struct iovec iov[5 + ra->n_prefixes + ra->n_route_prefixes];
struct msghdr msg = {
.msg_name = &dst_addr,
.msg_namelen = sizeof(dst_addr),
iov[msg.msg_iovlen++] = IOVEC_MAKE(&p->opt, sizeof(p->opt));
}
+ LIST_FOREACH(prefix, rt, ra->route_prefixes)
+ iov[msg.msg_iovlen++] = IOVEC_MAKE(&rt->opt, sizeof(rt->opt));
+
if (ra->rdnss)
iov[msg.msg_iovlen++] = IOVEC_MAKE(ra->rdnss, ra->rdnss->length * 8);
return cur;
}
+_public_ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p, int dynamic) {
+ char time_string_valid[FORMAT_TIMESPAN_MAX];
+ usec_t time_now, valid, valid_until;
+ _cleanup_free_ char *pretty = NULL;
+ sd_radv_route_prefix *cur;
+ int r;
+
+ assert_return(ra, -EINVAL);
+
+ if (!p)
+ return -EINVAL;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &p->opt.in6_addr,
+ &pretty);
+
+ LIST_FOREACH(prefix, cur, ra->route_prefixes) {
+ _cleanup_free_ char *addr = NULL;
+
+ r = in_addr_prefix_intersect(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ cur->opt.prefixlen,
+ (union in_addr_union*) &p->opt.in6_addr,
+ p->opt.prefixlen);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (dynamic && cur->opt.prefixlen == p->opt.prefixlen)
+ goto update;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ &addr);
+ log_radv("IPv6 route prefix %s/%u already configured, ignoring %s/%u",
+ strempty(addr), cur->opt.prefixlen,
+ strempty(pretty), p->opt.prefixlen);
+
+ return -EEXIST;
+ }
+
+ p = sd_radv_route_prefix_ref(p);
+
+ LIST_APPEND(prefix, ra->route_prefixes, p);
+ ra->n_route_prefixes++;
+
+ cur = p;
+ if (!dynamic) {
+ log_radv("Added prefix %s/%u", strempty(pretty), p->opt.prefixlen);
+ return 0;
+ }
+
+ update:
+ r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return r;
+
+ valid = be32toh(p->opt.lifetime) * USEC_PER_SEC;
+ valid_until = usec_add(valid, time_now);
+ if (valid_until == USEC_INFINITY)
+ return -EOVERFLOW;
+
+ log_radv("%s route prefix %s/%u valid %s",
+ cur? "Updated": "Added",
+ strempty(pretty), p->opt.prefixlen,
+ format_timespan(time_string_valid, FORMAT_TIMESPAN_MAX, valid, USEC_PER_SEC));
+
+ return 0;
+}
+
_public_ int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime,
const struct in6_addr *dns, size_t n_dns) {
_cleanup_free_ struct sd_radv_opt_dns *opt_rdnss = NULL;
return 0;
}
+
+_public_ int sd_radv_route_prefix_new(sd_radv_route_prefix **ret) {
+ sd_radv_route_prefix *p;
+
+ assert_return(ret, -EINVAL);
+
+ p = new(sd_radv_route_prefix, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (sd_radv_route_prefix) {
+ .n_ref = 1,
+
+ .opt.type = SD_RADV_OPT_ROUTE_INFORMATION,
+ .opt.length = DIV_ROUND_UP(sizeof(p->opt), 8),
+ .opt.prefixlen = 64,
+
+ .opt.lifetime = htobe32(604800),
+ };
+
+ *ret = p;
+ return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_route_prefix, sd_radv_route_prefix, mfree);
+
+_public_ int sd_radv_prefix_set_route_prefix(sd_radv_route_prefix *p, const struct in6_addr *in6_addr,
+ unsigned char prefixlen) {
+ assert_return(p, -EINVAL);
+ assert_return(in6_addr, -EINVAL);
+
+ if (prefixlen > 128)
+ return -EINVAL;
+
+ if (prefixlen > 64)
+ /* unusual but allowed, log it */
+ log_radv("Unusual prefix length %u greater than 64", prefixlen);
+
+ p->opt.in6_addr = *in6_addr;
+ p->opt.prefixlen = prefixlen;
+
+ return 0;
+}
+
+_public_ int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint32_t valid_lifetime) {
+ assert_return(p, -EINVAL);
+
+ p->opt.lifetime = htobe32(valid_lifetime);
+
+ return 0;
+}
IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0
IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0
+IPv6RoutePrefix.Route, config_parse_route_prefix, 0, 0
+IPv6RoutePrefix.LifetimeSec, config_parse_route_prefix_lifetime, 0, 0
CAN.BitRate, config_parse_si_size, 0, offsetof(Network, can_bitrate)
CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
"BridgeVLAN\0"
"IPv6PrefixDelegation\0"
"IPv6Prefix\0"
+ "IPv6RoutePrefix\0"
"CAN\0",
config_item_perf_lookup, network_network_gperf_lookup,
CONFIG_PARSE_WARN, network);
LIST_HEAD(Neighbor, neighbors);
LIST_HEAD(AddressLabel, address_labels);
LIST_HEAD(Prefix, static_prefixes);
+ LIST_HEAD(Prefix, static_route_prefixes);
LIST_HEAD(RoutingPolicyRule, rules);
unsigned n_static_addresses;
unsigned n_neighbors;
unsigned n_address_labels;
unsigned n_static_prefixes;
+ unsigned n_static_route_prefixes;
unsigned n_rules;
Hashmap *addresses_by_section;
Hashmap *neighbors_by_section;
Hashmap *address_labels_by_section;
Hashmap *prefixes_by_section;
+ Hashmap *route_prefixes_by_section;
Hashmap *rules_by_section;
/* All kinds of DNS configuration */
return 0;
}
+int route_prefix_new(Prefix **ret) {
+ _cleanup_(prefix_freep) Prefix *prefix = NULL;
+
+ prefix = new0(Prefix, 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ if (sd_radv_route_prefix_new(&prefix->radv_route_prefix) < 0)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(prefix);
+
+ return 0;
+}
+
+void route_prefix_free(Prefix *prefix) {
+ if (!prefix)
+ return;
+
+ if (prefix->network) {
+ LIST_REMOVE(prefixes, prefix->network->static_route_prefixes, prefix);
+ assert(prefix->network->n_static_route_prefixes > 0);
+ prefix->network->n_static_route_prefixes--;
+
+ if (prefix->section)
+ hashmap_remove(prefix->network->route_prefixes_by_section,
+ prefix->section);
+ }
+
+ network_config_section_free(prefix->section);
+
+ free(prefix);
+}
+
+static int route_prefix_new_static(Network *network, const char *filename,
+ unsigned section_line, Prefix **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(prefix_freep) Prefix *prefix = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (section_line) {
+ prefix = hashmap_get(network->route_prefixes_by_section, n);
+ if (prefix) {
+ *ret = TAKE_PTR(prefix);
+
+ return 0;
+ }
+ }
+ }
+
+ r = route_prefix_new(&prefix);
+ if (r < 0)
+ return r;
+
+ prefix->network = network;
+ LIST_APPEND(prefixes, network->static_route_prefixes, prefix);
+ network->n_static_route_prefixes++;
+
+ if (filename) {
+ prefix->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->route_prefixes_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->route_prefixes_by_section, prefix->section, prefix);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(prefix);
+
+ return 0;
+}
+
int config_parse_prefix(const char *unit,
- const char *filename,
- unsigned line,
- const char *section,
- unsigned section_line,
- const char *lvalue,
- int ltype,
- const char *rvalue,
- void *data,
- void *userdata) {
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
Network *network = userdata;
_cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL;
return 0;
}
+int config_parse_route_prefix(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL;
+ uint8_t prefixlen = 64;
+ union in_addr_union in6addr;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_prefix_new_static(network, filename, section_line, &p);
+ if (r < 0)
+ return r;
+
+ r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Route prefix is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (sd_radv_prefix_set_route_prefix(p->radv_route_prefix, &in6addr.in6, prefixlen) < 0)
+ return -EADDRNOTAVAIL;
+
+ log_syntax(unit, LOG_INFO, filename, line, r, "Found route prefix %s", rvalue);
+
+ p = NULL;
+
+ return 0;
+}
+
+int config_parse_route_prefix_lifetime(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(prefix_free_or_set_invalidp) Prefix *p = NULL;
+ usec_t usec;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_prefix_new_static(network, filename, section_line, &p);
+ if (r < 0)
+ return r;
+
+ r = parse_sec(rvalue, &usec);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Roure lifetime is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ /* a value of 0xffffffff represents infinity */
+ r = sd_radv_route_prefix_set_lifetime(p->radv_route_prefix, DIV_ROUND_UP(usec, USEC_PER_SEC));
+ if (r < 0)
+ return r;
+
+ p = NULL;
+
+ return 0;
+}
+
static int radv_get_ip6dns(Network *network, struct in6_addr **dns,
size_t *n_dns) {
_cleanup_free_ struct in6_addr *addresses = NULL;
if (r < 0)
return r;
}
+
+ LIST_FOREACH(prefixes, p, link->network->static_route_prefixes) {
+ r = sd_radv_add_route_prefix(link->radv, p->radv_route_prefix, false);
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return r;
+ }
+
}
return radv_emit_dns(link);
NetworkConfigSection *section;
sd_radv_prefix *radv_prefix;
+ sd_radv_route_prefix *radv_route_prefix;
LIST_FIELDS(Prefix, prefixes);
+ LIST_FIELDS(Prefix, route_prefixes);
};
int prefix_new(Prefix **ret);
DEFINE_NETWORK_SECTION_FUNCTIONS(Prefix, prefix_free);
+int route_prefix_new(Prefix **ret);
+void route_prefix_free(Prefix *prefix);
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(Prefix, route_prefix_free);
+
int radv_emit_dns(Link *link);
int radv_configure(Link *link);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime);
CONFIG_PARSER_PROTOTYPE(config_parse_radv_dns);
CONFIG_PARSER_PROTOTYPE(config_parse_radv_search_domains);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix_lifetime);
typedef struct sd_radv sd_radv;
typedef struct sd_radv_prefix sd_radv_prefix;
+typedef struct sd_radv_route_prefix sd_radv_route_prefix;
/* Router Advertisement */
int sd_radv_new(sd_radv **ret);
int sd_radv_set_other_information(sd_radv *ra, int other);
int sd_radv_set_preference(sd_radv *ra, unsigned preference);
int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, int dynamic);
+int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p, int dynamic);
sd_radv_prefix *sd_radv_remove_prefix(sd_radv *ra, const struct in6_addr *prefix,
unsigned char prefixlen);
int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime,
int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p,
uint32_t preferred_lifetime);
+int sd_radv_route_prefix_new(sd_radv_route_prefix **ret);
+sd_radv_route_prefix *sd_radv_route_prefix_ref(sd_radv_route_prefix *ra);
+sd_radv_route_prefix *sd_radv_route_prefix_unref(sd_radv_route_prefix *ra);
+
+int sd_radv_prefix_set_route_prefix(sd_radv_route_prefix *p, const struct in6_addr *in6_addr, unsigned char prefixlen);
+int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint32_t valid_lifetime);
+
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv, sd_radv_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_prefix, sd_radv_prefix_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_route_prefix, sd_radv_route_prefix_unref);
_SD_END_DECLARATIONS;
PreferredLifetimeSec=
AddressAutoconfiguration=
ValidLifetimeSec=
+[IPv6RoutePrefix]
+Route=
+LifetimeSec=
[BridgeVLAN]
EgressUntagged=
VLAN=