diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
index a1856f06dd..f5cbe688ee 100644
--- a/man/systemd.resource-control.xml
+++ b/man/systemd.resource-control.xml
@@ -772,26 +772,32 @@ BPFProgram=bind6:/sys/fs/bpf/sock-addr-hook
Allow or deny binding a socket address to a socket by matching it with the bind-rule and
applying a corresponding action if there is a match.
- bind-rule describes socket properties such as address-family
- and ip-ports.
+ bind-rule describes socket properties such as address-family,
+ transport-protocol and ip-ports.
- bind-rule := [address-family:]ip-ports
+ bind-rule :=
+ { [address-family:][transport-protocol:][ip-ports] | any }
address-family := { ipv4 | ipv6 }
- ip-ports := { ip-port | ip-port-range |
- any }
+ transport-protocol := { tcp | udp }
- An optional address-family expects IPv4 or IPv6 values.
- If not specified, a rule will be matched for both IPv4 and IPv6 addresses and applied depending on other socket fields, e.g.
+ ip-ports := { ip-port | ip-port-range }
+
+ An optional address-family expects ipv4 or ipv6 values.
+ If not specified, a rule will be matched for both IPv4 and IPv6 addresses and applied depending on other socket fields, e.g. transport-protocol,
ip-port.
- ip-port value must lie within 1…65535 interval inclusively, i.e.
+ An optional transport-protocol expects tcp or udp transport protocol names.
+ If not specified, a rule will be matched for any transport protocol.
+
+ An optional ip-port value must lie within 1…65535 interval inclusively, i.e.
dynamic port 0 is not allowed. A range of sequential ports is described by
ip-port-range := ip-port-low-ip-port-high,
where ip-port-low is smaller than or equal to ip-port-high
- and both are within 1…65535 inclusively. A special value any
- should be used to apply a rule to any port with a positive value.
+ and both are within 1…65535 inclusively.
+
+ A special value any can be used to apply a rule to any address family, transport protocol and any port with a positive value.
To allow multiple rules assign SocketBindAllow= or SocketBindDeny= multiple times.
To clear the existing assignments pass an empty SocketBindAllow= or SocketBindDeny=
@@ -825,11 +831,26 @@ SocketBindDeny=any
…
# Deny binding IPv6 socket addresses.
[Service]
-SocketBindDeny=ipv6:any
+SocketBindDeny=ipv6
…
# Deny binding IPv4 and IPv6 socket addresses.
[Service]
SocketBindDeny=any
+…
+# Allow binding only over TCP
+[Service]
+SocketBindAllow=tcp
+SocketBindDeny=any
+…
+# Allow binding only over IPv6/TCP
+[Service]
+SocketBindAllow=ipv6:tcp
+SocketBindDeny=any
+…
+# Allow binding ports within 10000-65535 range over IPv4/UDP.
+[Service]
+SocketBindAllow=ipv4:udp:10000-65535
+SocketBindDeny=any
…
diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c
index 66c82d5469..9983691575 100644
--- a/src/core/bpf-socket-bind.c
+++ b/src/core/bpf-socket-bind.c
@@ -35,6 +35,7 @@ static int update_rules_map(
LIST_FOREACH(socket_bind_items, item, head) {
struct socket_bind_rule val = {
.address_family = (uint32_t) item->address_family,
+ .protocol = item->ip_protocol,
.nr_ports = item->nr_ports,
.port_min = item->port_min,
};
diff --git a/src/core/bpf/socket_bind/socket-bind-api.bpf.h b/src/core/bpf/socket_bind/socket-bind-api.bpf.h
index 418889955b..277b9bbde2 100644
--- a/src/core/bpf/socket_bind/socket-bind-api.bpf.h
+++ b/src/core/bpf/socket_bind/socket-bind-api.bpf.h
@@ -11,35 +11,39 @@
/*
* Bind rule is matched with socket fields accessible to cgroup/bind{4,6} hook
* through bpf_sock_addr struct.
- * address_family is expected to be one of AF_UNSPEC, AF_INET or AF_INET6.
+ * 'address_family' is expected to be one of AF_UNSPEC, AF_INET or AF_INET6.
* Matching by family is bypassed for rules with AF_UNSPEC set, which makes the
* rest of a rule applicable for both IPv4 and IPv6 addresses.
* If matching by family is either successful or bypassed, a rule and a socket
- * are matched by ports.
- * nr_ports and port_min fields specify a set of ports to match a user port
+ * are matched by ip protocol.
+ * If 'protocol' is 0, matching is bypassed.
+ * 'nr_ports' and 'port_min' fields specify a set of ports to match a user port
* with.
- * If nr_ports is 0, matching by port is bypassed, making that rule applicable
+ * If 'nr_ports' is 0, matching by port is bypassed, making that rule applicable
* for all possible ports, e.g. [1, 65535] range. Thus a rule with
- * address_family and nr_ports equal to AF_UNSPEC and 0 correspondingly forms
- * 'allow any' or 'deny any' cases.
- * For positive nr_ports, a user_port lying in a range from port_min to
- * port_min + nr_ports exclusively is considered to be a match. nr_ports
+ * 'address_family', 'protocol' and 'nr_ports' equal to AF_UNSPEC, 0 and 0
+ * correspondingly forms 'allow any' or 'deny any' cases.
+ * For positive 'nr_ports', a user_port lying in a range from 'port_min' to'
+ * 'port_min' + 'nr_ports' exclusively is considered to be a match. 'nr_ports'
* equalling to 1 forms a rule for a single port.
* Ports are in host order.
*
* Examples:
- * AF_UNSPEC, 1, 7777: match IPv4 and IPv6 addresses with 7777 user port;
+ * AF_UNSPEC, 1, 0, 7777: match IPv4 and IPv6 addresses with 7777 user port;
*
- * AF_INET, 1023, 1: match IPv4 addresses with user port in [1, 1023]
+ * AF_INET, 1023, 0, 1: match IPv4 addresses with user port in [1, 1023]
* range inclusively;
*
- * AF_INET6, 0, 0: match IPv6 addresses;
+ * AF_INET6, 0, 0, 0: match IPv6 addresses;
*
- * AF_UNSPEC, 0, 0: match IPv4 and IPv6 addresses.
+ * AF_UNSPEC, 0, 0, 0: match IPv4 and IPv6 addresses;
+ *
+ * AF_INET6, IPPROTO_TCP, 0, 0: match IPv6/TCP addresses.
*/
struct socket_bind_rule {
__u32 address_family;
+ __u32 protocol;
__u16 nr_ports;
__u16 port_min;
};
diff --git a/src/core/bpf/socket_bind/socket-bind.bpf.c b/src/core/bpf/socket_bind/socket-bind.bpf.c
index 474808d824..8004400e6c 100644
--- a/src/core/bpf/socket_bind/socket-bind.bpf.c
+++ b/src/core/bpf/socket_bind/socket-bind.bpf.c
@@ -39,6 +39,11 @@ static __always_inline bool match_af(
return r->address_family == AF_UNSPEC || address_family == r->address_family;
}
+static __always_inline bool match_protocol(
+ __u32 protocol, const struct socket_bind_rule *r) {
+ return r->protocol == 0 || r->protocol == protocol;
+}
+
static __always_inline bool match_user_port(
__u16 port, const struct socket_bind_rule *r) {
return r->nr_ports == 0 ||
@@ -47,9 +52,12 @@ static __always_inline bool match_user_port(
static __always_inline bool match(
__u8 address_family,
+ __u32 protocol,
__u16 port,
const struct socket_bind_rule *r) {
- return match_af(address_family, r) && match_user_port(port, r);
+ return match_af(address_family, r) &&
+ match_protocol(protocol, r) &&
+ match_user_port(port, r);
}
static __always_inline bool match_rules(
@@ -67,7 +75,7 @@ static __always_inline bool match_rules(
if (!rule)
break;
- if (match(ctx->user_family, port, rule))
+ if (match(ctx->user_family, ctx->protocol, port, rule))
return true;
}
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index 3cec8a5786..1dc5a5b034 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -20,6 +20,7 @@
#include "fileio.h"
#include "fs-util.h"
#include "io-util.h"
+#include "ip-protocol-list.h"
#include "limits-util.h"
#include "nulstr-util.h"
#include "parse-util.h"
@@ -593,18 +594,24 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
}
void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) {
- const char *family, *colon;
+ const char *family, *colon1, *protocol = "", *colon2 = "";
family = strempty(af_to_ipv4_ipv6(item->address_family));
- colon = isempty(family) ? "" : ":";
+ colon1 = isempty(family) ? "" : ":";
+
+ if (item->ip_protocol != 0) {
+ protocol = ip_protocol_to_tcp_udp(item->ip_protocol);
+ colon2 = ":";
+ }
if (item->nr_ports == 0)
- fprintf(f, " %s%sany", family, colon);
+ fprintf(f, " %s%s%s%sany", family, colon1, protocol, colon2);
else if (item->nr_ports == 1)
- fprintf(f, " %s%s%" PRIu16, family, colon, item->port_min);
+ fprintf(f, " %s%s%s%s%" PRIu16, family, colon1, protocol, colon2, item->port_min);
else {
uint16_t port_max = item->port_min + item->nr_ports - 1;
- fprintf(f, " %s%s%" PRIu16 "-%" PRIu16, family, colon, item->port_min, port_max);
+ fprintf(f, " %s%s%s%s%" PRIu16 "-%" PRIu16, family, colon1, protocol, colon2,
+ item->port_min, port_max);
}
}
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
index 526f056d1e..ea929368cb 100644
--- a/src/core/cgroup.h
+++ b/src/core/cgroup.h
@@ -104,8 +104,8 @@ struct CGroupBPFForeignProgram {
struct CGroupSocketBindItem {
LIST_FIELDS(CGroupSocketBindItem, socket_bind_items);
- int32_t address_family;
- int32_t ip_protocol;
+ int address_family;
+ int ip_protocol;
uint16_t nr_ports;
uint16_t port_min;
};
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
index 9f20d547cb..84c3caf3a5 100644
--- a/src/core/dbus-cgroup.c
+++ b/src/core/dbus-cgroup.c
@@ -15,6 +15,7 @@
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "ip-protocol-list.h"
#include "limits-util.h"
#include "parse-util.h"
#include "path-util.h"
@@ -1895,8 +1896,8 @@ int bus_cgroup_set_property(
if (!IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects INET or INET6 family, if specified.", name);
- if (ip_protocol != 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects ip protocol equals to 0, for the time being.", name);
+ if (!IN_SET(ip_protocol, 0, IPPROTO_TCP, IPPROTO_UDP))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects TCP or UDP protocol, if specified.", name);
if (port_min + (uint32_t) nr_ports > (1 << 16))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s= expects maximum port value lesser than 65536.", name);
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index b0f2469540..87dfe5d1b0 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -47,6 +47,7 @@
#include "log.h"
#include "mountpoint-util.h"
#include "nulstr-util.h"
+#include "parse-socket-bind-item.h"
#include "parse-util.h"
#include "path-util.h"
#include "percent-util.h"
@@ -5633,62 +5634,30 @@ int config_parse_cgroup_socket_bind(
void *data,
void *userdata) {
_cleanup_free_ CGroupSocketBindItem *item = NULL;
- const char *user_port;
- uint16_t nr_ports = 0, port_min = 0;
CGroupSocketBindItem **head = data;
- _cleanup_free_ char *word = NULL;
- int af, r;
+ uint16_t nr_ports, port_min;
+ int af, ip_protocol, r;
if (isempty(rvalue)) {
cgroup_context_remove_socket_bind(head);
return 0;
}
- r = extract_first_word(&rvalue, &word, ":", 0);
+ r = parse_socket_bind_item(rvalue, &af, &ip_protocol, &nr_ports, &port_min);
if (r == -ENOMEM)
return log_oom();
- if (r <= 0) {
+ if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Unable to parse %s= assignment, ignoring: %s", lvalue, rvalue);
return 0;
}
- if (rvalue) {
- af = af_from_ipv4_ipv6(word);
- if (af == AF_UNSPEC) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Only \"ipv4\" and \"ipv6\" protocols are supported, ignoring.");
- return 0;
- }
-
- user_port = rvalue;
- } else {
- af = AF_UNSPEC;
- user_port = word;
- }
-
- if (!streq(user_port, "any")) {
- uint16_t port_max;
-
- r = parse_ip_port_range(user_port, &port_min, &port_max);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid port or port range, ignoring: %m");
- return 0;
- }
-
- nr_ports = 1 + port_max - port_min;
- }
-
item = new(CGroupSocketBindItem, 1);
if (!item)
return log_oom();
*item = (CGroupSocketBindItem) {
.address_family = af,
- /* No ip protocol specified for now. */
- .ip_protocol = 0,
+ .ip_protocol = ip_protocol,
.nr_ports = nr_ports,
.port_min = port_min,
};
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 4c9fb305e4..31a6c63f0c 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -27,6 +27,7 @@
#include "mountpoint-util.h"
#include "nsflags.h"
#include "numa-util.h"
+#include "parse-socket-bind-item.h"
#include "parse-util.h"
#include "path-util.h"
#include "percent-util.h"
@@ -868,42 +869,17 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
if (isempty(eq))
r = sd_bus_message_append(m, "(sv)", field, "a(iiqq)", 0);
else {
- /* No ip protocol specified for now. */
- int32_t family = AF_UNSPEC, ip_protocol = 0;
- const char *address_family, *user_port;
- _cleanup_free_ char *word = NULL;
+ int32_t family, ip_protocol;
+ uint16_t nr_ports, port_min;
- r = extract_first_word(&eq, &word, ":", 0);
+ r = parse_socket_bind_item(eq, &family, &ip_protocol, &nr_ports, &port_min);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
- return log_error_errno(r, "Failed to parse %s: %m", field);
+ return log_error_errno(r, "Failed to parse %s", field);
- address_family = eq ? word : NULL;
- if (address_family) {
- family = af_from_ipv4_ipv6(address_family);
- if (family == AF_UNSPEC)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Only \"ipv4\" and \"ipv6\" protocols are supported");
- }
-
- user_port = eq ? eq : word;
- if (streq(user_port, "any")) {
- r = sd_bus_message_append(m, "(sv)", field, "a(iiqq)", 1, family, ip_protocol, 0, 0);
- if (r < 0)
- return bus_log_create_error(r);
- } else {
- uint16_t port_min, port_max;
-
- r = parse_ip_port_range(user_port, &port_min, &port_max);
- if (r == -ENOMEM)
- return log_oom();
- if (r < 0)
- return log_error_errno(r, "Invalid port or port range: %s", user_port);
-
- r = sd_bus_message_append(
- m, "(sv)", field, "a(iiqq)", 1, family, ip_protocol, port_max - port_min + 1, port_min);
- }
+ r = sd_bus_message_append(
+ m, "(sv)", field, "a(iiqq)", 1, family, ip_protocol, nr_ports, port_min);
}
if (r < 0)
return bus_log_create_error(r);
diff --git a/src/shared/ip-protocol-list.c b/src/shared/ip-protocol-list.c
index 0623d5e9be..21d88f2660 100644
--- a/src/shared/ip-protocol-list.c
+++ b/src/shared/ip-protocol-list.c
@@ -65,3 +65,13 @@ int parse_ip_protocol(const char *s) {
return i;
}
+
+const char *ip_protocol_to_tcp_udp(int id) {
+ return IN_SET(id, IPPROTO_TCP, IPPROTO_UDP) ?
+ ip_protocol_to_name(id) : NULL;
+}
+
+int ip_protocol_from_tcp_udp(const char *ip_protocol) {
+ int id = ip_protocol_from_name(ip_protocol);
+ return IN_SET(id, IPPROTO_TCP, IPPROTO_UDP) ? id : -EINVAL;
+}
diff --git a/src/shared/ip-protocol-list.h b/src/shared/ip-protocol-list.h
index abe3f5fc28..b40ec08301 100644
--- a/src/shared/ip-protocol-list.h
+++ b/src/shared/ip-protocol-list.h
@@ -4,3 +4,6 @@
const char *ip_protocol_to_name(int id);
int ip_protocol_from_name(const char *name);
int parse_ip_protocol(const char *s);
+
+const char *ip_protocol_to_tcp_udp(int id);
+int ip_protocol_from_tcp_udp(const char *ip_protocol);
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 5008cda500..7eb7050062 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -225,6 +225,8 @@ shared_sources = files('''
pager.h
parse-argument.c
parse-argument.h
+ parse-socket-bind-item.c
+ parse-socket-bind-item.h
pe-header.h
pkcs11-util.c
pkcs11-util.h
diff --git a/src/shared/parse-socket-bind-item.c b/src/shared/parse-socket-bind-item.c
new file mode 100644
index 0000000000..214abd8b4f
--- /dev/null
+++ b/src/shared/parse-socket-bind-item.c
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "af-list.h"
+#include "extract-word.h"
+#include "ip-protocol-list.h"
+#include "parse-socket-bind-item.h"
+#include "parse-util.h"
+
+static int parse_af_token(
+ const char *token,
+ int *family,
+ int *ip_protocol,
+ uint16_t *nr_ports,
+ uint16_t *port_min) {
+ int af;
+
+ assert(token);
+ assert(family);
+
+ af = af_from_ipv4_ipv6(token);
+ if (af == AF_UNSPEC)
+ return -EINVAL;
+
+ *family = af;
+ return 0;
+}
+
+static int parse_ip_protocol_token(
+ const char *token,
+ int *family,
+ int *ip_protocol,
+ uint16_t *nr_ports,
+ uint16_t *port_min) {
+ int proto;
+
+ assert(token);
+ assert(ip_protocol);
+
+ proto = ip_protocol_from_tcp_udp(token);
+ if (proto < 0)
+ return -EINVAL;
+
+ *ip_protocol = proto;
+ return 0;
+}
+
+static int parse_ip_ports_token(
+ const char *token,
+ int *family,
+ int *ip_protocol,
+ uint16_t *nr_ports,
+ uint16_t *port_min) {
+ assert(token);
+ assert(nr_ports);
+ assert(port_min);
+
+ if (streq(token, "any"))
+ *nr_ports = *port_min = 0;
+ else {
+ uint16_t mn = 0, mx = 0;
+ int r = parse_ip_port_range(token, &mn, &mx);
+ if (r < 0)
+ return r;
+
+ *nr_ports = mx - mn + 1;
+ *port_min = mn;
+ }
+
+ return 0;
+}
+
+typedef int (*parse_token_f)(
+ const char *,
+ int *,
+ int *,
+ uint16_t *,
+ uint16_t *);
+
+int parse_socket_bind_item(
+ const char *str,
+ int *address_family,
+ int *ip_protocol,
+ uint16_t *nr_ports,
+ uint16_t *port_min) {
+ /* Order of token parsers is important. */
+ const parse_token_f parsers[] = {
+ &parse_af_token,
+ &parse_ip_protocol_token,
+ &parse_ip_ports_token,
+ };
+ parse_token_f const *parser_ptr = parsers;
+ int af = AF_UNSPEC, proto = 0, r;
+ uint16_t nr = 0, mn = 0;
+ const char *p = str;
+
+ assert(str);
+ assert(address_family);
+ assert(ip_protocol);
+ assert(nr_ports);
+ assert(port_min);
+
+ if (isempty(p))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ char *token = NULL;
+
+ r = extract_first_word(&p, &token, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r == 0)
+ break;
+ if (r < 0)
+ return r;
+
+ if (isempty(token))
+ return -EINVAL;
+
+ while (parser_ptr != parsers + ELEMENTSOF(parsers)) {
+ r = (*parser_ptr)(token, &af, &proto, &nr, &mn);
+ if (r == -ENOMEM)
+ return r;
+
+ ++parser_ptr;
+ /* Continue to next token if parsing succeeded,
+ * otherwise apply next parser to the same token.
+ */
+ if (r >= 0)
+ break;
+ }
+ if (parser_ptr == parsers + ELEMENTSOF(parsers))
+ break;
+ }
+
+ /* Failed to parse a token. */
+ if (r < 0)
+ return r;
+
+ /* Parsers applied succesfully, but end of the string not reached. */
+ if (p)
+ return -EINVAL;
+
+ *address_family = af;
+ *ip_protocol = proto;
+ *nr_ports = nr;
+ *port_min = mn;
+ return 0;
+}
diff --git a/src/shared/parse-socket-bind-item.h b/src/shared/parse-socket-bind-item.h
new file mode 100644
index 0000000000..c8cff8d730
--- /dev/null
+++ b/src/shared/parse-socket-bind-item.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+#include
+
+int parse_socket_bind_item(
+ const char *str,
+ int *address_family,
+ int *ip_protocol,
+ uint16_t *nr_ports,
+ uint16_t *port_min);
diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c
index 178270b4b0..470ff617d6 100644
--- a/src/systemctl/systemctl-show.c
+++ b/src/systemctl/systemctl-show.c
@@ -17,6 +17,7 @@
#include "hexdecoct.h"
#include "hostname-util.h"
#include "in-addr-util.h"
+#include "ip-protocol-list.h"
#include "journal-file.h"
#include "list.h"
#include "locale-util.h"
@@ -1718,19 +1719,27 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
if (r < 0)
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(m, "(iiqq)", &af, &ip_protocol, &nr_ports, &port_min)) > 0) {
- const char *family, *colon;
+ const char *family, *colon1, *protocol = "", *colon2 = "";
family = strempty(af_to_ipv4_ipv6(af));
- colon = isempty(family) ? "" : ":";
+ colon1 = isempty(family) ? "" : ":";
+
+ if (ip_protocol != 0) {
+ protocol = ip_protocol_to_tcp_udp(ip_protocol);
+ colon2 = "";
+ }
if (nr_ports == 0)
- bus_print_property_valuef(name, expected_value, flags, "%s%sany", family, colon);
+ bus_print_property_valuef(name, expected_value, flags, "%s%s%s%sany",
+ family, colon1, protocol, colon2);
else if (nr_ports == 1)
bus_print_property_valuef(
- name, expected_value, flags, "%s%s%hu", family, colon, port_min);
+ name, expected_value, flags, "%s%s%s%s%hu",
+ family, colon1, protocol, colon2, port_min);
else
bus_print_property_valuef(
- name, expected_value, flags, "%s%s%hu-%hu", family, colon, port_min,
+ name, expected_value, flags, "%s%s%s%s%hu-%hu",
+ family, colon1, protocol, colon2, port_min,
(uint16_t) (port_min + nr_ports - 1));
}
if (r < 0)
diff --git a/src/test/meson.build b/src/test/meson.build
index 4999697804..e106059182 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -242,6 +242,8 @@ tests += [
[['src/test/test-parse-argument.c']],
+ [['src/test/test-parse-socket-bind-item.c']],
+
[['src/test/test-parse-util.c']],
[['src/test/test-sysctl-util.c']],
diff --git a/src/test/test-parse-socket-bind-item.c b/src/test/test-parse-socket-bind-item.c
new file mode 100644
index 0000000000..6d52582bcd
--- /dev/null
+++ b/src/test/test-parse-socket-bind-item.c
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+#include
+#include
+
+#include "macro.h"
+#include "parse-socket-bind-item.h"
+
+static void test_valid_item(
+ const char *str,
+ int expected_af,
+ int expected_ip_protocol,
+ uint16_t expected_nr_ports,
+ uint16_t expected_port_min) {
+ uint16_t nr_ports, port_min;
+ int af, ip_protocol;
+
+ assert_se(parse_socket_bind_item(str, &af, &ip_protocol, &nr_ports, &port_min) >= 0);
+ assert_se(af == expected_af);
+ assert_se(ip_protocol == expected_ip_protocol);
+ assert_se(nr_ports == expected_nr_ports);
+ assert_se(port_min == expected_port_min);
+
+ log_info("%s: \"%s\" ok", __func__, str);
+}
+
+static void test_invalid_item(const char *str) {
+ uint16_t nr_ports, port_min;
+ int af, ip_protocol;
+
+ assert_se(parse_socket_bind_item(str, &af, &ip_protocol, &nr_ports, &port_min) == -EINVAL);
+
+ log_info("%s: \"%s\" ok", __func__, str);
+}
+
+int main(int argc, char *argv[]) {
+ test_valid_item("any", AF_UNSPEC, 0, 0, 0);
+ test_valid_item("ipv4", AF_INET, 0, 0, 0);
+ test_valid_item("ipv6", AF_INET6, 0, 0, 0);
+ test_valid_item("ipv4:any", AF_INET, 0, 0, 0);
+ test_valid_item("ipv6:any", AF_INET6, 0, 0, 0);
+ test_valid_item("tcp", AF_UNSPEC, IPPROTO_TCP, 0, 0);
+ test_valid_item("udp", AF_UNSPEC, IPPROTO_UDP, 0, 0);
+ test_valid_item("tcp:any", AF_UNSPEC, IPPROTO_TCP, 0, 0);
+ test_valid_item("udp:any", AF_UNSPEC, IPPROTO_UDP, 0, 0);
+ test_valid_item("6666", AF_UNSPEC, 0, 1, 6666);
+ test_valid_item("6666-6667", AF_UNSPEC, 0, 2, 6666);
+ test_valid_item("65535", AF_UNSPEC, 0, 1, 65535);
+ test_valid_item("1-65535", AF_UNSPEC, 0, 65535, 1);
+ test_valid_item("ipv4:tcp", AF_INET, IPPROTO_TCP, 0, 0);
+ test_valid_item("ipv4:udp", AF_INET, IPPROTO_UDP, 0, 0);
+ test_valid_item("ipv6:tcp", AF_INET6, IPPROTO_TCP, 0, 0);
+ test_valid_item("ipv6:udp", AF_INET6, IPPROTO_UDP, 0, 0);
+ test_valid_item("ipv4:6666", AF_INET, 0, 1, 6666);
+ test_valid_item("ipv6:6666", AF_INET6, 0, 1, 6666);
+ test_valid_item("tcp:6666", AF_UNSPEC, IPPROTO_TCP, 1, 6666);
+ test_valid_item("udp:6666", AF_UNSPEC, IPPROTO_UDP, 1, 6666);
+ test_valid_item("ipv4:tcp:6666", AF_INET, IPPROTO_TCP, 1, 6666);
+ test_valid_item("ipv6:tcp:6666", AF_INET6, IPPROTO_TCP, 1, 6666);
+ test_valid_item("ipv6:udp:6666-6667", AF_INET6, IPPROTO_UDP, 2, 6666);
+ test_valid_item("ipv6:tcp:any", AF_INET6, IPPROTO_TCP, 0, 0);
+
+ test_invalid_item("");
+ test_invalid_item(":");
+ test_invalid_item("::");
+ test_invalid_item("any:");
+ test_invalid_item("meh");
+ test_invalid_item("zupa:meh");
+ test_invalid_item("zupa:meh:eh");
+ test_invalid_item("ip");
+ test_invalid_item("dccp");
+ test_invalid_item("ipv6meh");
+ test_invalid_item("ipv6::");
+ test_invalid_item("ipv6:ipv6");
+ test_invalid_item("ipv6:icmp");
+ test_invalid_item("ipv6:tcp:0");
+ test_invalid_item("65536");
+ test_invalid_item("0-65535");
+ test_invalid_item("ipv6:tcp:6666-6665");
+ test_invalid_item("ipv6:tcp:6666-100000");
+ test_invalid_item("ipv6::6666");
+ test_invalid_item("ipv6:tcp:any:");
+ test_invalid_item("ipv6:tcp:any:ipv6");
+ test_invalid_item("ipv6:tcp:6666:zupa");
+ test_invalid_item("ipv6:tcp:6666:any");
+ test_invalid_item("ipv6:tcp:6666 zupa");
+ test_invalid_item("ipv6:tcp:6666: zupa");
+ test_invalid_item("ipv6:tcp:6666\n zupa");
+ return 0;
+}
diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c
index 996aeebbcc..989172eee3 100644
--- a/src/test/test-socket-bind.c
+++ b/src/test/test-socket-bind.c
@@ -146,6 +146,11 @@ int main(int argc, char *argv[]) {
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("6667", "6668", ""), STRV_MAKE("any")) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "7777", STRV_MAKE_EMPTY, STRV_MAKE_EMPTY) >= 0);
assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "8888", STRV_MAKE("any"), STRV_MAKE("any")) >= 0);
+ assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "8888", STRV_MAKE("ipv6:tcp:8888-8889"), STRV_MAKE("any")) >= 0);
+ assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "10000", STRV_MAKE("ipv6:udp:9999-10000"), STRV_MAKE("any")) >= 0);
+ assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("ipv4:tcp:6666"), STRV_MAKE("any")) >= 0);
+ assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("ipv4:udp:6666"), STRV_MAKE("any")) >= 0);
+ assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("tcp:6666"), STRV_MAKE("any")) >= 0);
return 0;
}