diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 6ff4fb2440..ed5abeff6c 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1084,6 +1084,13 @@
ipv4.
+
+ User=
+
+ Takes a username, a user ID, or a range of user IDs separated by a dash. Defaults to
+ unset.
+
+
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index 6d22fe31c3..a491f5505e 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -62,6 +62,29 @@ int parse_uid(const char *s, uid_t *ret) {
return 0;
}
+int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper) {
+ uint32_t u, l;
+ int r;
+
+ assert(s);
+ assert(ret_lower);
+ assert(ret_upper);
+
+ r = parse_range(s, &l, &u);
+ if (r < 0)
+ return r;
+
+ if (l > u)
+ return -EINVAL;
+
+ if (!uid_is_valid(l) || !uid_is_valid(u))
+ return -ENXIO;
+
+ *ret_lower = l;
+ *ret_upper = u;
+ return 0;
+}
+
char* getlogname_malloc(void) {
uid_t uid;
struct stat st;
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
index 714e83082c..6796ac42c1 100644
--- a/src/basic/user-util.h
+++ b/src/basic/user-util.h
@@ -19,6 +19,7 @@ static inline bool gid_is_valid(gid_t gid) {
}
int parse_uid(const char *s, uid_t* ret_uid);
+int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper);
static inline int parse_gid(const char *s, gid_t *ret_gid) {
return parse_uid(s, (uid_t*) ret_gid);
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 90f734a03c..2528fb5267 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -1138,6 +1138,12 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, voi
return 0;
}
+ r = sd_netlink_message_read(message, FRA_UID_RANGE, sizeof(tmp->uid_range), &tmp->uid_range);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_UID_RANGE attribute, ignoring: %m");
+ return 0;
+ }
+
(void) routing_policy_rule_get(m, tmp, &rule);
if (DEBUG_LOGGING) {
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 2a7c223129..699691dc87 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -130,6 +130,7 @@ RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_ra
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
+RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 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 e7d09c4ad9..52f25209d0 100644
--- a/src/network/networkd-routing-policy-rule.c
+++ b/src/network/networkd-routing-policy-rule.c
@@ -7,6 +7,7 @@
#include "alloc-util.h"
#include "conf-parser.h"
#include "fileio.h"
+#include "format-util.h"
#include "ip-protocol-list.h"
#include "networkd-routing-policy-rule.h"
#include "netlink-util.h"
@@ -16,6 +17,7 @@
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
+#include "user-util.h"
int routing_policy_rule_new(RoutingPolicyRule **ret) {
RoutingPolicyRule *rule;
@@ -26,6 +28,8 @@ int routing_policy_rule_new(RoutingPolicyRule **ret) {
*rule = (RoutingPolicyRule) {
.table = RT_TABLE_MAIN,
+ .uid_range.start = UID_INVALID,
+ .uid_range.end = UID_INVALID,
};
*ret = rule;
@@ -93,6 +97,7 @@ static int routing_policy_rule_copy(RoutingPolicyRule *dest, RoutingPolicyRule *
dest->protocol = src->protocol;
dest->sport = src->sport;
dest->dport = src->dport;
+ dest->uid_range = src->uid_range;
return 0;
}
@@ -122,6 +127,7 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct
siphash24_compress(&rule->protocol, sizeof(rule->protocol), state);
siphash24_compress(&rule->sport, sizeof(rule->sport), state);
siphash24_compress(&rule->dport, sizeof(rule->dport), state);
+ siphash24_compress(&rule->uid_range, sizeof(rule->uid_range), state);
if (rule->iif)
siphash24_compress(rule->iif, strlen(rule->iif), state);
@@ -198,6 +204,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro
if (r != 0)
return r;
+ r = memcmp(&a->uid_range, &b->uid_range, sizeof(a->uid_range));
+ if (r != 0)
+ return r;
+
r = strcmp_ptr(a->iif, b->iif);
if (r != 0)
return r;
@@ -554,6 +564,12 @@ int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, link_netl
return log_link_error_errno(link, r, "Could not append FRA_DPORT_RANGE attribute: %m");
}
+ if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) {
+ r = sd_netlink_message_append_data(m, FRA_UID_RANGE, &rule->uid_range, sizeof(rule->uid_range));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append FRA_UID_RANGE attribute: %m");
+ }
+
if (rule->invert_rule) {
r = sd_rtnl_message_routing_policy_rule_set_flags(m, FIB_RULE_INVERT);
if (r < 0)
@@ -1056,6 +1072,51 @@ int config_parse_routing_policy_rule_family(
return 0;
}
+int config_parse_routing_policy_rule_uid_range(
+ 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;
+ uid_t start, end;
+ 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;
+
+ r = get_user_creds(&rvalue, &start, NULL, NULL, NULL, 0);
+ if (r >= 0)
+ end = start;
+ else {
+ r = parse_uid_range(rvalue, &start, &end);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Invalid uid or uid range '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+ }
+
+ n->uid_range.start = start;
+ n->uid_range.end = end;
+ 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;
@@ -1170,6 +1231,14 @@ int routing_policy_serialize_rules(Set *rules, FILE *f) {
space = true;
}
+ if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) {
+ assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+ fprintf(f, "%suidrange="UID_FMT"-"UID_FMT,
+ space ? " " : "",
+ rule->uid_range.start, rule->uid_range.end);
+ space = true;
+ }
+
fprintf(f, "%stable=%"PRIu32 "\n",
space ? " " : "",
rule->table);
@@ -1294,7 +1363,7 @@ int routing_policy_load_rules(const char *state_file, Set **rules) {
r = parse_ip_port_range(b, &low, &high);
if (r < 0) {
- log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment:'%s'", b);
+ log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment: '%s'", b);
continue;
}
@@ -1305,12 +1374,24 @@ int routing_policy_load_rules(const char *state_file, Set **rules) {
r = parse_ip_port_range(b, &low, &high);
if (r < 0) {
- log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment:'%s'", b);
+ log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment: '%s'", b);
continue;
}
rule->dport.start = low;
rule->dport.end = high;
+
+ } else if (streq(a, "uidrange")) {
+ uid_t lower, upper;
+
+ r = parse_uid_range(b, &lower, &upper);
+ if (r < 0) {
+ log_error_errno(r, "Invalid routing policy rule uid range, ignoring assignment: '%s'", b);
+ continue;
+ }
+
+ rule->uid_range.start = lower;
+ rule->uid_range.end = upper;
}
}
diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h
index 6b8e310227..2afd8f75a6 100644
--- a/src/network/networkd-routing-policy-rule.h
+++ b/src/network/networkd-routing-policy-rule.h
@@ -49,6 +49,7 @@ struct RoutingPolicyRule {
struct fib_rule_port_range sport;
struct fib_rule_port_range dport;
+ struct fib_rule_uid_range uid_range;
LIST_FIELDS(RoutingPolicyRule, rules);
};
@@ -79,3 +80,4 @@ 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);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_uid_range);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 0e3adac5ce..8a953b75d9 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -225,6 +225,7 @@ DestinationPort=
IPProtocol=
InvertRule=
Family=
+User=
[IPv6PrefixDelegation]
RouterPreference=
DNSLifetimeSec=
diff --git a/test/test-network/conf/25-fibrule-uidrange.network b/test/test-network/conf/25-fibrule-uidrange.network
new file mode 100644
index 0000000000..f42dfee325
--- /dev/null
+++ b/test/test-network/conf/25-fibrule-uidrange.network
@@ -0,0 +1,9 @@
+[Match]
+Name=test1
+
+[RoutingPolicyRule]
+TypeOfService=0x08
+Table=7
+From= 192.168.100.18
+Priority=111
+User=100-200
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index eb4eebebae..01292d7c3b 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -100,6 +100,23 @@ def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable():
return f
+def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable():
+ def f(func):
+ support = False
+ rc = call('ip rule add from 192.168.100.19 table 7 uidrange 200-300', stderr=subprocess.DEVNULL)
+ if rc == 0:
+ ret = run('ip rule list from 192.168.100.19 table 7', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ if ret.returncode == 0 and 'uidrange 200-300' in ret.stdout.rstrip():
+ support = True
+ call('ip rule del from 192.168.100.19 table 7 uidrange 200-300')
+
+ if support:
+ return func
+ else:
+ return unittest.expectedFailure(func)
+
+ return f
+
def expectedFailureIfLinkFileFieldIsNotSet():
def f(func):
support = False
@@ -1572,6 +1589,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'25-bond-active-backup-slave.netdev',
'25-fibrule-invert.network',
'25-fibrule-port-range.network',
+ '25-fibrule-uidrange.network',
'25-gre-tunnel-remote-any.netdev',
'25-ip6gre-tunnel-remote-any.netdev',
'25-ipv6-address-label-section.network',
@@ -1776,6 +1794,19 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'tcp')
self.assertRegex(output, 'lookup 7')
+ @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable()
+ def test_routing_policy_rule_uidrange(self):
+ copy_unit_to_networkd_unit_path('25-fibrule-uidrange.network', '11-dummy.netdev')
+ start_networkd()
+ self.wait_online(['test1:degraded'])
+
+ output = check_output('ip rule')
+ print(output)
+ self.assertRegex(output, '111')
+ self.assertRegex(output, 'from 192.168.100.18')
+ self.assertRegex(output, 'lookup 7')
+ self.assertRegex(output, 'uidrange 100-200')
+
def test_route_static(self):
copy_unit_to_networkd_unit_path('25-route-static.network', '12-dummy.netdev')
start_networkd()