From mboxrd@z Thu Jan 1 00:00:00 1970 Received: by passt.top (Postfix, from userid 1000) id 6C0D55A0269; Sun, 22 Mar 2026 15:18:43 +0100 (CET) From: Stefano Brivio To: passt-dev@passt.top Subject: [PATCH 16/18] conf: Move port parsing functions to own file, ports.c Date: Sun, 22 Mar 2026 15:18:41 +0100 Message-ID: <20260322141843.4095972-1-sbrivio@redhat.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260319061157.1983818-1-david@gibson.dropbear.id.au> References: <20260319061157.1983818-1-david@gibson.dropbear.id.au> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: DIBHRGOAVJRYCYE6LFHI453M4C3D2QGA X-Message-ID-Hash: DIBHRGOAVJRYCYE6LFHI453M4C3D2QGA X-MailFrom: sbrivio@passt.top X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: David Gibson X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Move conf_ports_range_except(), conf_ports(), and related to helpers to ports.c, so that they can be used from pesto in the future. We'll need to make those independent from passt-specific bits, but this patch just moves them out, first. Signed-off-by: Stefano Brivio --- Makefile | 13 +- conf.c | 363 +-------------------------------------------------- ports.c | 385 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ ports.h | 35 +++++ 4 files changed, 428 insertions(+), 368 deletions(-) create mode 100644 ports.c create mode 100644 ports.h diff --git a/Makefile b/Makefile index 44c396e..47d4c95 100644 --- a/Makefile +++ b/Makefile @@ -40,9 +40,9 @@ FLAGS += -DDUAL_STACK_SOCKETS=$(DUAL_STACK_SOCKETS) PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c epoll_ctl.c \ flow.c fwd.c fwd_rule.c icmp.c igmp.c inany.c iov.c ip.c isolation.c \ lineread.c log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c \ - pasta.c pcap.c pif.c repair.c serialise.c tap.c tcp.c tcp_buf.c \ - tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c vhost_user.c \ - virtio.c vu_common.c + pasta.c pcap.c pif.c ports.c repair.c serialise.c tap.c tcp.c \ + tcp_buf.c tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c \ + vhost_user.c virtio.c vu_common.c QRAP_SRCS = qrap.c PASST_REPAIR_SRCS = passt-repair.c PESTO_SRCS = pesto.c fwd_rule.c inany.c ip.c serialise.c @@ -54,9 +54,10 @@ PESTO_HEADERS = common.h fwd_rule.h inany.h ip.h pesto.h serialise.h PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h epoll_ctl.h \ flow.h fwd.h flow_table.h icmp.h icmp_flow.h iov.h isolation.h \ lineread.h log.h migrate.h ndp.h netlink.h packet.h passt.h pasta.h \ - pcap.h pif.h repair.h siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h \ - tcp_internal.h tcp_splice.h tcp_vu.h udp.h udp_flow.h udp_internal.h \ - udp_vu.h util.h vhost_user.h virtio.h vu_common.h $(PESTO_HEADERS) + pcap.h pif.h ports.h repair.h siphash.h tap.h tcp.h tcp_buf.h \ + tcp_conn.h tcp_internal.h tcp_splice.h tcp_vu.h udp.h udp_flow.h \ + udp_internal.h udp_vu.h util.h vhost_user.h virtio.h vu_common.h \ + $(PESTO_HEADERS) HEADERS = $(PASST_HEADERS) seccomp.h C := \#include \nint main(){int a=getrandom(0, 0, 0);} diff --git a/conf.c b/conf.c index b235221..de4c3c6 100644 --- a/conf.c +++ b/conf.c @@ -52,6 +52,7 @@ #include "conf.h" #include "pesto.h" #include "serialise.h" +#include "ports.h" #define NETNS_RUN_DIR "/run/netns" @@ -69,368 +70,6 @@ const char *pasta_default_ifn = "tap0"; -/** - * next_chunk() - Return the next piece of a string delimited by a character - * @s: String to search - * @c: Delimiter character - * - * Return: if another @c is found in @s, returns a pointer to the - * character *after* the delimiter, if no further @c is in @s, - * return NULL - */ -static char *next_chunk(const char *s, char c) -{ - char *sep = strchr(s, c); - return sep ? sep + 1 : NULL; -} - -/** - * port_range() - Represents a non-empty range of ports - * @first: First port number in the range - * @last: Last port number in the range (inclusive) - * - * Invariant: @last >= @first - */ -struct port_range { - in_port_t first, last; -}; - -/** - * parse_port_range() - Parse a range of port numbers '[-]' - * @s: String to parse - * @endptr: Update to the character after the parsed range (similar to - * strtol() etc.) - * @range: Update with the parsed values on success - * - * Return: -EINVAL on parsing error, -ERANGE on out of range port - * numbers, 0 on success - */ -static int parse_port_range(const char *s, char **endptr, - struct port_range *range) -{ - unsigned long first, last; - - last = first = strtoul(s, endptr, 10); - if (*endptr == s) /* Parsed nothing */ - return -EINVAL; - if (**endptr == '-') { /* we have a last value too */ - const char *lasts = *endptr + 1; - last = strtoul(lasts, endptr, 10); - if (*endptr == lasts) /* Parsed nothing */ - return -EINVAL; - } - - if ((last < first) || (last >= NUM_PORTS)) - return -ERANGE; - - range->first = first; - range->last = last; - - return 0; -} - -/** - * conf_ports_range_except() - Set up forwarding for a range of ports minus a - * bitmap of exclusions - * @c: Execution context - * @optname: Short option name, t, T, u, or U - * @optarg: Option argument (port specification) - * @fwd: Forwarding table to be updated - * @addr: Listening address - * @ifname: Listening interface - * @first: First port to forward - * @last: Last port to forward - * @exclude: Bitmap of ports to exclude (may be NULL) - * @to: Port to translate @first to when forwarding - * @flags: Flags for forwarding entries - */ -static void conf_ports_range_except(const struct ctx *c, char optname, - const char *optarg, struct fwd_table *fwd, - const union inany_addr *addr, - const char *ifname, - uint16_t first, uint16_t last, - const uint8_t *exclude, uint16_t to, - uint8_t flags) -{ - unsigned delta = to - first; - unsigned base, i; - uint8_t proto; - - if (first == 0) { - die("Can't forward port 0 for option '-%c %s'", - optname, optarg); - } - - if (optname == 't' || optname == 'T') - proto = IPPROTO_TCP; - else if (optname == 'u' || optname == 'U') - proto = IPPROTO_UDP; - else - assert(0); - - if (addr) { - if (!c->ifi4 && inany_v4(addr)) { - die("IPv4 is disabled, can't use -%c %s", - optname, optarg); - } else if (!c->ifi6 && !inany_v4(addr)) { - die("IPv6 is disabled, can't use -%c %s", - optname, optarg); - } - } - - for (base = first; base <= last; base++) { - if (exclude && bitmap_isset(exclude, base)) - continue; - - for (i = base; i <= last; i++) { - if (exclude && bitmap_isset(exclude, i)) - break; - } - - if ((optname == 'T' || optname == 'U') && c->no_bindtodevice) { - /* FIXME: Once the fwd bitmaps are removed, move this - * workaround to the caller - */ - assert(!addr && ifname && !strcmp(ifname, "lo")); - warn( -"SO_BINDTODEVICE unavailable, forwarding only 127.0.0.1 and ::1 for '-%c %s'", - optname, optarg); - - if (c->ifi4) { - fwd_rule_add(fwd, proto, flags, - &inany_loopback4, NULL, - base, i - 1, base + delta); - } - if (c->ifi6) { - fwd_rule_add(fwd, proto, flags, - &inany_loopback6, NULL, - base, i - 1, base + delta); - } - } else { - fwd_rule_add(fwd, proto, flags, addr, ifname, - base, i - 1, base + delta); - } - base = i - 1; - } -} - -/** - * enum fwd_mode - Overall forwarding mode for a direction and protocol - * @FWD_MODE_UNSET Initial value, not parsed/configured yet - * @FWD_MODE_SPEC Forward specified ports - * @FWD_MODE_NONE No forwarded ports - * @FWD_MODE_AUTO Automatic detection and forwarding based on bound ports - * @FWD_MODE_ALL Bind all free ports - */ -enum fwd_mode { - FWD_MODE_UNSET = 0, - FWD_MODE_SPEC, - FWD_MODE_NONE, - FWD_MODE_AUTO, - FWD_MODE_ALL, -}; - -/** - * conf_ports() - Parse port configuration options, initialise UDP/TCP sockets - * @c: Execution context - * @optname: Short option name, t, T, u, or U - * @optarg: Option argument (port specification) - * @fwd: Forwarding table to be updated - * @mode: Overall port forwarding mode (updated) - */ -static void conf_ports(const struct ctx *c, char optname, const char *optarg, - struct fwd_table *fwd, enum fwd_mode *mode) -{ - union inany_addr addr_buf = inany_any6, *addr = &addr_buf; - char buf[BUFSIZ], *spec, *ifname = NULL, *p; - uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; - bool exclude_only = true; - unsigned i; - - if (!strcmp(optarg, "none")) { - if (*mode) - goto mode_conflict; - - *mode = FWD_MODE_NONE; - return; - } - - if ((optname == 't' || optname == 'T') && c->no_tcp) - die("TCP port forwarding requested but TCP is disabled"); - if ((optname == 'u' || optname == 'U') && c->no_udp) - die("UDP port forwarding requested but UDP is disabled"); - - if (!strcmp(optarg, "auto")) { - if (*mode) - goto mode_conflict; - - if (c->mode != MODE_PASTA) - die("'auto' port forwarding is only allowed for pasta"); - - if ((optname == 'T' || optname == 'U') && c->no_bindtodevice) { - warn( -"'-%c auto' enabled without unprivileged SO_BINDTODEVICE", optname); - warn( -"Forwarding from addresses other than 127.0.0.1 will not work"); - } - *mode = FWD_MODE_AUTO; - return; - } - - if (!strcmp(optarg, "all")) { - if (*mode) - goto mode_conflict; - - if (c->mode == MODE_PASTA) - die("'all' port forwarding is only allowed for passt"); - - *mode = FWD_MODE_ALL; - - /* Exclude ephemeral ports */ - for (i = 0; i < NUM_PORTS; i++) - if (fwd_port_is_ephemeral(i)) - bitmap_set(exclude, i); - - conf_ports_range_except(c, optname, optarg, fwd, - NULL, NULL, - 1, NUM_PORTS - 1, exclude, - 1, FWD_WEAK); - return; - } - - if (*mode > FWD_MODE_SPEC) - die("Specific ports cannot be specified together with all/none/auto"); - - *mode = FWD_MODE_SPEC; - - strncpy(buf, optarg, sizeof(buf) - 1); - - if ((spec = strchr(buf, '/'))) { - *spec = 0; - spec++; - - if (optname != 't' && optname != 'u') - goto bad; - - if ((ifname = strchr(buf, '%'))) { - *ifname = 0; - ifname++; - - /* spec is already advanced one past the '/', - * so the length of the given ifname is: - * (spec - ifname - 1) - */ - if (spec - ifname - 1 >= IFNAMSIZ) - goto bad; - - } - - if (ifname == buf + 1) { /* Interface without address */ - addr = NULL; - } else { - p = buf; - - /* Allow square brackets for IPv4 too for convenience */ - if (*p == '[' && p[strlen(p) - 1] == ']') { - p[strlen(p) - 1] = '\0'; - p++; - } - - if (!inany_pton(p, addr)) - goto bad; - } - } else { - spec = buf; - - addr = NULL; - } - - /* Mark all exclusions first, they might be given after base ranges */ - p = spec; - do { - struct port_range xrange; - - if (*p != '~') { - /* Not an exclude range, parse later */ - exclude_only = false; - continue; - } - p++; - - if (parse_port_range(p, &p, &xrange)) - goto bad; - if ((*p != '\0') && (*p != ',')) /* Garbage after the range */ - goto bad; - - for (i = xrange.first; i <= xrange.last; i++) { - if (bitmap_isset(exclude, i)) - die("Overlapping excluded ranges %s", optarg); - - bitmap_set(exclude, i); - } - } while ((p = next_chunk(p, ','))); - - if (ifname && c->no_bindtodevice) { - die( -"Device binding for '-%c %s' unsupported (requires kernel 5.7+)", - optname, optarg); - } - /* Outbound forwards come from guest loopback */ - if ((optname == 'T' || optname == 'U') && !ifname) - ifname = "lo"; - - if (exclude_only) { - /* Exclude ephemeral ports */ - for (i = 0; i < NUM_PORTS; i++) - if (fwd_port_is_ephemeral(i)) - bitmap_set(exclude, i); - - conf_ports_range_except(c, optname, optarg, fwd, - addr, ifname, - 1, NUM_PORTS - 1, exclude, - 1, FWD_WEAK); - return; - } - - /* Now process base ranges, skipping exclusions */ - p = spec; - do { - struct port_range orig_range, mapped_range; - - if (*p == '~') - /* Exclude range, already parsed */ - continue; - - if (parse_port_range(p, &p, &orig_range)) - goto bad; - - if (*p == ':') { /* There's a range to map to as well */ - if (parse_port_range(p + 1, &p, &mapped_range)) - goto bad; - if ((mapped_range.last - mapped_range.first) != - (orig_range.last - orig_range.first)) - goto bad; - } else { - mapped_range = orig_range; - } - - if ((*p != '\0') && (*p != ',')) /* Garbage after the ranges */ - goto bad; - - conf_ports_range_except(c, optname, optarg, fwd, - addr, ifname, - orig_range.first, orig_range.last, - exclude, - mapped_range.first, 0); - } while ((p = next_chunk(p, ','))); - - return; -bad: - die("Invalid port specifier %s", optarg); -mode_conflict: - die("Port forwarding mode '%s' conflicts with previous mode", optarg); -} - /** * add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration * @c: Execution context diff --git a/ports.c b/ports.c new file mode 100644 index 0000000..5480176 --- /dev/null +++ b/ports.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* PASST - Plug A Simple Socket Transport + * for qemu/UNIX domain socket mode + * + * PASTA - Pack A Subtle Tap Abstraction + * for network namespace/tap device mode + * + * PESTO - Programmable Extensible Socket Translation Orchestrator + * front-end for passt(1) and pasta(1) forwarding configuration + * + * ports.c - Parse port options + * + * Copyright (c) 2026 Red Hat GmbH + * Author: Stefano Brivio + * Author: David Gibson + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "ip.h" +#include "passt.h" +#include "common.h" +#include "pesto.h" +#include "ports.h" + +/** + * next_chunk() - Return the next piece of a string delimited by a character + * @s: String to search + * @c: Delimiter character + * + * Return: if another @c is found in @s, returns a pointer to the + * character *after* the delimiter, if no further @c is in @s, + * return NULL + */ +static char *next_chunk(const char *s, char c) +{ + char *sep = strchr(s, c); + return sep ? sep + 1 : NULL; +} + +/** + * port_range() - Represents a non-empty range of ports + * @first: First port number in the range + * @last: Last port number in the range (inclusive) + * + * Invariant: @last >= @first + */ +struct port_range { + in_port_t first, last; +}; + +/** + * parse_port_range() - Parse a range of port numbers '[-]' + * @s: String to parse + * @endptr: Update to the character after the parsed range (similar to + * strtol() etc.) + * @range: Update with the parsed values on success + * + * Return: -EINVAL on parsing error, -ERANGE on out of range port + * numbers, 0 on success + */ +static int parse_port_range(const char *s, char **endptr, + struct port_range *range) +{ + unsigned long first, last; + + last = first = strtoul(s, endptr, 10); + if (*endptr == s) /* Parsed nothing */ + return -EINVAL; + if (**endptr == '-') { /* we have a last value too */ + const char *lasts = *endptr + 1; + last = strtoul(lasts, endptr, 10); + if (*endptr == lasts) /* Parsed nothing */ + return -EINVAL; + } + + if ((last < first) || (last >= NUM_PORTS)) + return -ERANGE; + + range->first = first; + range->last = last; + + return 0; +} + +/** + * conf_ports_range_except() - Set up forwarding for a range of ports minus a + * bitmap of exclusions + * @c: Execution context + * @optname: Short option name, t, T, u, or U + * @optarg: Option argument (port specification) + * @fwd: Forwarding table to be updated + * @addr: Listening address + * @ifname: Listening interface + * @first: First port to forward + * @last: Last port to forward + * @exclude: Bitmap of ports to exclude (may be NULL) + * @to: Port to translate @first to when forwarding + * @flags: Flags for forwarding entries + */ +void conf_ports_range_except(const struct ctx *c, char optname, + const char *optarg, struct fwd_table *fwd, + const union inany_addr *addr, + const char *ifname, uint16_t first, uint16_t last, + const uint8_t *exclude, uint16_t to, uint8_t flags) +{ + unsigned delta = to - first; + unsigned base, i; + uint8_t proto; + + if (first == 0) { + die("Can't forward port 0 for option '-%c %s'", + optname, optarg); + } + + if (optname == 't' || optname == 'T') + proto = IPPROTO_TCP; + else if (optname == 'u' || optname == 'U') + proto = IPPROTO_UDP; + else + assert(0); + + if (addr) { + if (!c->ifi4 && inany_v4(addr)) { + die("IPv4 is disabled, can't use -%c %s", + optname, optarg); + } else if (!c->ifi6 && !inany_v4(addr)) { + die("IPv6 is disabled, can't use -%c %s", + optname, optarg); + } + } + + for (base = first; base <= last; base++) { + if (exclude && bitmap_isset(exclude, base)) + continue; + + for (i = base; i <= last; i++) { + if (exclude && bitmap_isset(exclude, i)) + break; + } + + if ((optname == 'T' || optname == 'U') && c->no_bindtodevice) { + /* FIXME: Once the fwd bitmaps are removed, move this + * workaround to the caller + */ + assert(!addr && ifname && !strcmp(ifname, "lo")); + warn( +"SO_BINDTODEVICE unavailable, forwarding only 127.0.0.1 and ::1 for '-%c %s'", + optname, optarg); + + if (c->ifi4) { + fwd_rule_add(fwd, proto, flags, + &inany_loopback4, NULL, + base, i - 1, base + delta); + } + if (c->ifi6) { + fwd_rule_add(fwd, proto, flags, + &inany_loopback6, NULL, + base, i - 1, base + delta); + } + } else { + fwd_rule_add(fwd, proto, flags, addr, ifname, + base, i - 1, base + delta); + } + base = i - 1; + } +} + +/** + * conf_ports() - Parse port configuration options, initialise UDP/TCP sockets + * @c: Execution context + * @optname: Short option name, t, T, u, or U + * @optarg: Option argument (port specification) + * @fwd: Forwarding table to be updated + * @mode: Overall port forwarding mode (updated) + */ +void conf_ports(const struct ctx *c, char optname, const char *optarg, + struct fwd_table *fwd, enum fwd_mode *mode) +{ + union inany_addr addr_buf = inany_any6, *addr = &addr_buf; + char buf[BUFSIZ], *spec, *ifname = NULL, *p; + uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; + bool exclude_only = true; + unsigned i; + + if (!strcmp(optarg, "none")) { + if (*mode) + goto mode_conflict; + + *mode = FWD_MODE_NONE; + return; + } + + if ((optname == 't' || optname == 'T') && c->no_tcp) + die("TCP port forwarding requested but TCP is disabled"); + if ((optname == 'u' || optname == 'U') && c->no_udp) + die("UDP port forwarding requested but UDP is disabled"); + + if (!strcmp(optarg, "auto")) { + if (*mode) + goto mode_conflict; + + if (c->mode != MODE_PASTA) + die("'auto' port forwarding is only allowed for pasta"); + + if ((optname == 'T' || optname == 'U') && c->no_bindtodevice) { + warn( +"'-%c auto' enabled without unprivileged SO_BINDTODEVICE", optname); + warn( +"Forwarding from addresses other than 127.0.0.1 will not work"); + } + *mode = FWD_MODE_AUTO; + return; + } + + if (!strcmp(optarg, "all")) { + if (*mode) + goto mode_conflict; + + if (c->mode == MODE_PASTA) + die("'all' port forwarding is only allowed for passt"); + + *mode = FWD_MODE_ALL; + + /* Exclude ephemeral ports */ + for (i = 0; i < NUM_PORTS; i++) + if (fwd_port_is_ephemeral(i)) + bitmap_set(exclude, i); + + conf_ports_range_except(c, optname, optarg, fwd, + NULL, NULL, + 1, NUM_PORTS - 1, exclude, + 1, FWD_WEAK); + return; + } + + if (*mode > FWD_MODE_SPEC) + die("Specific ports cannot be specified together with all/none/auto"); + + *mode = FWD_MODE_SPEC; + + strncpy(buf, optarg, sizeof(buf) - 1); + + if ((spec = strchr(buf, '/'))) { + *spec = 0; + spec++; + + if (optname != 't' && optname != 'u') + goto bad; + + if ((ifname = strchr(buf, '%'))) { + *ifname = 0; + ifname++; + + /* spec is already advanced one past the '/', + * so the length of the given ifname is: + * (spec - ifname - 1) + */ + if (spec - ifname - 1 >= IFNAMSIZ) + goto bad; + + } + + if (ifname == buf + 1) { /* Interface without address */ + addr = NULL; + } else { + p = buf; + + /* Allow square brackets for IPv4 too for convenience */ + if (*p == '[' && p[strlen(p) - 1] == ']') { + p[strlen(p) - 1] = '\0'; + p++; + } + + if (!inany_pton(p, addr)) + goto bad; + } + } else { + spec = buf; + + addr = NULL; + } + + /* Mark all exclusions first, they might be given after base ranges */ + p = spec; + do { + struct port_range xrange; + + if (*p != '~') { + /* Not an exclude range, parse later */ + exclude_only = false; + continue; + } + p++; + + if (parse_port_range(p, &p, &xrange)) + goto bad; + if ((*p != '\0') && (*p != ',')) /* Garbage after the range */ + goto bad; + + for (i = xrange.first; i <= xrange.last; i++) { + if (bitmap_isset(exclude, i)) + die("Overlapping excluded ranges %s", optarg); + + bitmap_set(exclude, i); + } + } while ((p = next_chunk(p, ','))); + + if (ifname && c->no_bindtodevice) { + die( +"Device binding for '-%c %s' unsupported (requires kernel 5.7+)", + optname, optarg); + } + /* Outbound forwards come from guest loopback */ + if ((optname == 'T' || optname == 'U') && !ifname) + ifname = "lo"; + + if (exclude_only) { + /* Exclude ephemeral ports */ + for (i = 0; i < NUM_PORTS; i++) + if (fwd_port_is_ephemeral(i)) + bitmap_set(exclude, i); + + conf_ports_range_except(c, optname, optarg, fwd, + addr, ifname, + 1, NUM_PORTS - 1, exclude, + 1, FWD_WEAK); + return; + } + + /* Now process base ranges, skipping exclusions */ + p = spec; + do { + struct port_range orig_range, mapped_range; + + if (*p == '~') + /* Exclude range, already parsed */ + continue; + + if (parse_port_range(p, &p, &orig_range)) + goto bad; + + if (*p == ':') { /* There's a range to map to as well */ + if (parse_port_range(p + 1, &p, &mapped_range)) + goto bad; + if ((mapped_range.last - mapped_range.first) != + (orig_range.last - orig_range.first)) + goto bad; + } else { + mapped_range = orig_range; + } + + if ((*p != '\0') && (*p != ',')) /* Garbage after the ranges */ + goto bad; + + conf_ports_range_except(c, optname, optarg, fwd, + addr, ifname, + orig_range.first, orig_range.last, + exclude, + mapped_range.first, 0); + } while ((p = next_chunk(p, ','))); + + return; +bad: + die("Invalid port specifier %s", optarg); +mode_conflict: + die("Port forwarding mode '%s' conflicts with previous mode", optarg); +} diff --git a/ports.h b/ports.h new file mode 100644 index 0000000..3ef50e6 --- /dev/null +++ b/ports.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2026 Red Hat GmbH + * Author: Stefano Brivio + */ + +#ifndef PORTS_H +#define PORTS_H + +/** + * enum fwd_mode - Overall forwarding mode for a direction and protocol + * @FWD_MODE_UNSET Initial value, not parsed/configured yet + * @FWD_MODE_SPEC Forward specified ports + * @FWD_MODE_NONE No forwarded ports + * @FWD_MODE_AUTO Automatic detection and forwarding based on bound ports + * @FWD_MODE_ALL Bind all free ports + */ +enum fwd_mode { + FWD_MODE_UNSET = 0, + FWD_MODE_SPEC, + FWD_MODE_NONE, + FWD_MODE_AUTO, + FWD_MODE_ALL, +}; + +void conf_ports_range_except(const struct ctx *c, char optname, + const char *optarg, struct fwd_table *fwd, + const union inany_addr *addr, + const char *ifname, uint16_t first, uint16_t last, + const uint8_t *exclude, uint16_t to, + uint8_t flags); +void conf_ports(const struct ctx *c, char optname, const char *optarg, + struct fwd_table *fwd, enum fwd_mode *mode); + + +#endif /* PORTS_H */ -- 2.43.0