From 560d76243fab5b7809996ac353fbc79da9a3bd92 Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Fri, 11 Jun 2021 18:27:27 -0700 Subject: [PATCH 1/6] bpf: add ip proto matching to socket-bind prog Lookup ip protocol in a socket address to allow or deny binding a socket to the address. Matching rule is extended with 'protocol' field. If its value is 0 (IPPROTO_IP) ip protocol comparison is omitted and matching is passed to the next token which is ip ports. Documentation is updated. --- src/core/bpf-socket-bind.c | 1 + .../bpf/socket_bind/socket-bind-api.bpf.h | 28 +++++++++++-------- src/core/bpf/socket_bind/socket-bind.bpf.c | 12 ++++++-- 3 files changed, 27 insertions(+), 14 deletions(-) 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; } From 159d68c3e09657b4fd252b2c5ea6ef3095a37ad2 Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Fri, 25 Jun 2021 17:14:40 -0700 Subject: [PATCH 2/6] shared: add ip_protocol_{from|to}_tcp_udp helpers Thin wrappers of ip_protocol_{from|to}_name targeting IPPROTO_TCP and IPPROTO_UDP only. Used to parse IP protocol configuration restricted only to TCP and UDP, e.g. in SocketBind{Allow|Deny}= unit property. These helpers are inspired by af_{from|to}_ipv4_ipv6 and potentially extendable with other IP protocols if there is a use-case to expose them. --- src/shared/ip-protocol-list.c | 10 ++++++++++ src/shared/ip-protocol-list.h | 3 +++ 2 files changed, 13 insertions(+) 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); From 8bd095aa9a1ec03dd8ccbbf1963d069fb7b2bdda Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Tue, 29 Jun 2021 16:04:32 -0700 Subject: [PATCH 3/6] shared: add parser for SocketBind{Allow|Deny}= Parse address family, ip protocol and ports, any of them can be optional. If neither is specified, a special value 'any' is expected. Helper is placed in shared to be reused in both fragment and dbus. Add unit tests with valid and invalid examples. --- src/shared/meson.build | 2 + src/shared/parse-socket-bind-item.c | 146 +++++++++++++++++++++++++ src/shared/parse-socket-bind-item.h | 12 ++ src/test/meson.build | 2 + src/test/test-parse-socket-bind-item.c | 91 +++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 src/shared/parse-socket-bind-item.c create mode 100644 src/shared/parse-socket-bind-item.h create mode 100644 src/test/test-parse-socket-bind-item.c 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/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; +} From 5587ce7f6ca83b5ae08ad2a73a2b96392b261e9a Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Mon, 14 Jun 2021 19:14:14 -0700 Subject: [PATCH 4/6] fragment: add ip protocol to SocketBind{Allow|Deny}= Add ip protocol token to SocketBind{Allow|Deny}= property parser. Use parse_socket_bind_item helper. Replace int32_t with int in cgroup item for socket-bind as it was requested in [0]. Update tests. [0] https://github.com/systemd/systemd/pull/19942#discussion_r652150024 --- src/core/cgroup.c | 17 ++++++++++----- src/core/cgroup.h | 4 ++-- src/core/load-fragment.c | 43 ++++++------------------------------- src/test/test-socket-bind.c | 5 +++++ 4 files changed, 25 insertions(+), 44 deletions(-) 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/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/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; } From 60477eb98a18383b2369d1d3d55c9963fa8ccb31 Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Wed, 23 Jun 2021 16:37:53 -0700 Subject: [PATCH 5/6] dbus: extend SocketBind{Allow|Deny}= with ip proto --- src/core/dbus-cgroup.c | 5 +++-- src/shared/bus-unit-util.c | 38 +++++++--------------------------- src/systemctl/systemctl-show.c | 19 ++++++++++++----- 3 files changed, 24 insertions(+), 38 deletions(-) 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/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/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) From 120338ae33c98bb2cea7c99c07cf22ebfd851fce Mon Sep 17 00:00:00 2001 From: Julia Kartseva Date: Thu, 24 Jun 2021 12:16:51 -0700 Subject: [PATCH 6/6] man: document ip proto in SocketBind{Allow|Deny}= --- man/systemd.resource-control.xml | 43 ++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) 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 …