diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index accc824e4d..95c56b0ae3 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1139,6 +1139,16 @@
A boolean. Specifies whether the rule to be inverted. Defaults to false.
+
+ Family=
+
+ Takes a special value ipv4, ipv6, or
+ both. By default, the address family is determined by the address
+ specified in To= or From=. If neither
+ To= nor From= are specified, then defaults to
+ ipv4.
+
+
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 8c7e5db4bb..d4d108ad25 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -122,6 +122,7 @@ RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip_prot
RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0
RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0
RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0
+RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0
Route.Gateway, config_parse_gateway, 0, 0
Route.Destination, config_parse_destination, 0, 0
Route.Source, config_parse_destination, 0, 0
diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c
index 1494727ead..5edc2444a7 100644
--- a/src/network/networkd-routing-policy-rule.c
+++ b/src/network/networkd-routing-policy-rule.c
@@ -11,6 +11,7 @@
#include "networkd-routing-policy-rule.h"
#include "netlink-util.h"
#include "networkd-manager.h"
+#include "networkd-util.h"
#include "parse-util.h"
#include "socket-util.h"
#include "string-util.h"
@@ -569,11 +570,45 @@ int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, link_netl
}
int routing_policy_rule_section_verify(RoutingPolicyRule *rule) {
+ int r;
+
if (section_is_invalid(rule->section))
return -EINVAL;
- if (rule->family == AF_UNSPEC)
+ if ((rule->family == AF_INET && FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV6)) ||
+ (rule->family == AF_INET6 && FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV4)))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: address family specified by Family= conflicts with the address "
+ "specified by To= or From=. Ignoring [RoutingPolicyRule] section from line %u.",
+ rule->section->filename, rule->section->line);
+
+ if (FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV4 | ADDRESS_FAMILY_IPV6)) {
+ _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule6 = NULL;
+
+ assert(rule->family == AF_UNSPEC);
+
+ /* When Family=both, we need to copy the section, AF_INET and AF_INET6. */
+
+ r = routing_policy_rule_new_static(rule->network, NULL, 0, &rule6);
+ if (r < 0)
+ return r;
+
+ r = routing_policy_rule_copy(rule6, rule);
+ if (r < 0)
+ return r;
+
rule->family = AF_INET;
+ rule6->family = AF_INET6;
+
+ TAKE_PTR(rule6);
+ }
+
+ if (rule->family == AF_UNSPEC) {
+ if (FLAGS_SET(rule->address_family, ADDRESS_FAMILY_IPV6))
+ rule->family = AF_INET6;
+ else
+ rule->family = AF_INET;
+ }
return 0;
}
@@ -973,6 +1008,46 @@ int config_parse_routing_policy_rule_invert(
return 0;
}
+int config_parse_routing_policy_rule_family(
+ 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) {
+
+ _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL;
+ Network *network = userdata;
+ AddressFamily a;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = routing_policy_rule_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ a = routing_policy_rule_address_family_from_string(rvalue);
+ if (a < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid address family '%s', ignoring.", rvalue);
+ return 0;
+ }
+
+ n->address_family = a;
+ n = NULL;
+
+ return 0;
+}
+
static int routing_policy_rule_read_full_file(const char *state_file, char **ret) {
_cleanup_free_ char *s = NULL;
size_t size;
diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h
index 8ddb85307c..6b8e310227 100644
--- a/src/network/networkd-routing-policy-rule.h
+++ b/src/network/networkd-routing-policy-rule.h
@@ -36,7 +36,8 @@ struct RoutingPolicyRule {
uint32_t fwmask;
uint32_t priority;
- int family;
+ AddressFamily address_family; /* Specified by Family= */
+ int family; /* Automatically determined by From= or To= */
unsigned char to_prefixlen;
unsigned char from_prefixlen;
@@ -77,3 +78,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol);
CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_family);
diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c
index 2f56b4c7f1..3fd26836c6 100644
--- a/src/network/networkd-util.c
+++ b/src/network/networkd-util.c
@@ -24,8 +24,15 @@ static const char * const link_local_address_family_table[_ADDRESS_FAMILY_MAX] =
[ADDRESS_FAMILY_FALLBACK_IPV4] = "ipv4-fallback",
};
+static const char * const routing_policy_rule_address_family_table[_ADDRESS_FAMILY_MAX] = {
+ [ADDRESS_FAMILY_YES] = "both",
+ [ADDRESS_FAMILY_IPV4] = "ipv4",
+ [ADDRESS_FAMILY_IPV6] = "ipv6",
+};
+
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(address_family, AddressFamily, ADDRESS_FAMILY_YES);
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(link_local_address_family, AddressFamily, ADDRESS_FAMILY_YES);
+DEFINE_STRING_TABLE_LOOKUP(routing_policy_rule_address_family, AddressFamily);
DEFINE_CONFIG_PARSE_ENUM(config_parse_link_local_address_family, link_local_address_family,
AddressFamily, "Failed to parse option");
diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h
index c96c2fd9fa..3a57819f39 100644
--- a/src/network/networkd-util.h
+++ b/src/network/networkd-util.h
@@ -32,6 +32,9 @@ AddressFamily address_family_from_string(const char *s) _pure_;
const char *link_local_address_family_to_string(AddressFamily b) _const_;
AddressFamily link_local_address_family_from_string(const char *s) _pure_;
+const char *routing_policy_rule_address_family_to_string(AddressFamily b) _const_;
+AddressFamily routing_policy_rule_address_family_from_string(const char *s) _pure_;
+
int kernel_route_expiration_supported(void);
int network_config_section_new(const char *filename, unsigned line, NetworkConfigSection **s);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 7be38b0f34..848d4bd187 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -208,6 +208,7 @@ SourcePort=
DestinationPort=
IPProtocol=
InvertRule=
+Family=
[IPv6PrefixDelegation]
RouterPreference=
DNSLifetimeSec=