From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202606 header.b=X3P2vyx0; dkim-atps=neutral Received: from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3]) by passt.top (Postfix) with ESMTPS id BBE615A026D for ; Mon, 06 Jul 2026 07:09:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202606; t=1783314591; bh=NEhTpwXWmK8U6ApkHwDBN4xmvP8k0WQfWtbfNXeOkGE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=X3P2vyx0ciut7Z7Dc/2Fous7hiu1O0PGwbyqCCASl629D9dUvW5BpV46h1WsSU5Rn +Y0wgyMmSeptib7FaPBhsAZu9hzioeAhYlogNUV1KeVXD3M5MrAsbX1jAoRVPtpvG+ tf1YG+fHbJo1Md2EgvGWIEW2nElSX/Rxo1r1jzzfYSheNW7VzCCl5XHMsfpA5S5Nlv ZAxojFEm0HUEuYCbyCaUXKc8/aJ1N6ZqFRJUKaY+wNHPqKU6WLTw343c3r19VjwexL FQW8Q15G9Brc0tMmkUKClhGVqg9xo75mIZzCKi09cEFg8RjHH/TiQxMVZYsUFlYGcz XjGszH/IfhJhQ== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4gtsqM0g8Cz4wLd; Mon, 06 Jul 2026 15:09:51 +1000 (AEST) From: David Gibson To: Stefano Brivio , passt-dev@passt.top Subject: [PATCH v3 1/2] fwd_rule: Parse target addresses for forwarding rules Date: Mon, 6 Jul 2026 15:04:37 +1000 Message-ID: <20260706050438.1036391-2-david@gibson.dropbear.id.au> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260706050438.1036391-1-david@gibson.dropbear.id.au> References: <20260706050438.1036391-1-david@gibson.dropbear.id.au> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: 3RKVEBQVLVMGSEBWDGGRAEBIXEG3KM5X X-Message-ID-Hash: 3RKVEBQVLVMGSEBWDGGRAEBIXEG3KM5X X-MailFrom: dgibson@gandalf.ozlabs.org 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: Extend the parsing of forwarding rules (-[tu]) to allow the destination address on the target side to be specified. For now just parse them, and give an error if we try to create rules with a specified target address. We'll implement the actual forwarding logic in another patch. Format (for either command line or pesto): -t 2222:192.0.2.1/2222 This should work along with all the other bits, that is, say: -t 192.0.2.1%eth0/2222-2225:192.0.2.2/22-25 FIXME: Ban for -[TU] for now FIXME: Check interaction with splice handling Signed-off-by: Stefano Brivio [dwg: Syntax from Stefano's earlier draft, largely rewritten on top of new parsing helpers] Signed-off-by: David Gibson --- fwd_rule.c | 77 ++++++++++++++++++++++++++++++++++++++++++++---------- passt.1 | 31 +++++++++++++++------- 2 files changed, 85 insertions(+), 23 deletions(-) diff --git a/fwd_rule.c b/fwd_rule.c index ef35e1b4..42ec68e3 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -378,14 +378,17 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) * @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 + * @tgt_addr: Destination address on the target side + * @tgt_first: Destination port to use for @first on the target side * @flags: Flags for forwarding entries */ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, uint8_t proto, const union inany_addr *addr, const char *ifname, uint16_t first, uint16_t last, - const uint8_t *exclude, uint16_t to, + const uint8_t *exclude, + const union inany_addr *tgt_addr, + uint16_t tgt_first, uint8_t flags) { struct fwd_rule rule = { @@ -394,10 +397,18 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, .proto = proto, .flags = flags, }; + unsigned delta = tgt_first - first; char rulestr[FWD_RULE_STRLEN]; - unsigned delta = to - first; unsigned base, i; + if (tgt_addr && !inany_is_unspecified(tgt_addr)) { + char astr[INANY_ADDRSTRLEN]; + + info("Target address: %s", + inany_ntop(tgt_addr, astr, sizeof(astr))); + die("Target address remapping not yet implemented"); + } + if (!addr) rule.flags |= FWD_DUAL_STACK_ANY; if (ifname) { @@ -458,19 +469,31 @@ enum fwd_port_chunk_kind { * @cursor: Parsing point (see parse.c) * @kindp: Updated with kind of chunk we parsed * @lrange: Updated with listening port range (for INCLUDE & EXCLUDE) + * @taddr: Updated with target address (for INCLUDE & ALL) * @trange: Updated with target port range (for INCLUDE) */ static bool parse_port_chunk(const char **cursor, enum fwd_port_chunk_kind *kindp, struct port_range *lrange, + union inany_addr *taddr, struct port_range *trange) { struct port_range lr = { 0 }, tr = { 0 }; + union inany_addr taddr_tmp = inany_any6; enum fwd_port_chunk_kind kind; const char *p = *cursor; if (parse_literal(&p, "all")) { + const char *tgtspec = p; + kind = CHUNK_ALL; + if (p = tgtspec, + parse_literal(&p, ":") && + parse_inany(&p, &taddr_tmp)) { + /* Target address */ + } else { + p = tgtspec; + } } else if (parse_literal(&p, "auto")) { kind = CHUNK_AUTO; } else if (parse_literal(&p, "~")) { @@ -478,12 +501,29 @@ static bool parse_port_chunk(const char **cursor, if (!parse_port_range(&p, &lr)) return false; } else if (parse_port_range(&p, &lr)) { - kind = CHUNK_INCLUDE; + const char *tgtspec = p; - if (parse_literal(&p, ":")) { - if (!parse_port_range(&p, &tr)) - return false; + kind = CHUNK_INCLUDE; + if (p = tgtspec, + parse_literal(&p, ":") && + parse_inany(&p, &taddr_tmp) && + parse_literal(&p, "/") && + parse_port_range(&p, &tr)) { + /* Target address & range */ + } else if (p = tgtspec, + parse_literal(&p, ":") && + parse_inany(&p, &taddr_tmp)) { + /* Target address only */ + tr = lr; + } else if (p = tgtspec, + parse_literal(&p, ":") && + parse_port_range(&p, &tr)) { + /* Target range only */ + taddr_tmp = inany_any6; } else { + p = tgtspec; + /* No target specification */ + taddr_tmp = inany_any6; tr = lr; } } else { @@ -492,6 +532,8 @@ static bool parse_port_chunk(const char **cursor, *kindp = kind; *lrange = lr; + if (taddr) + *taddr = taddr_tmp; if (trange) *trange = tr; *cursor = p; @@ -551,6 +593,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, const char *spec) { uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; + union inany_addr all_taddr = inany_any6; enum fwd_port_chunk_kind kind; struct port_range lrange; bool exclude_only = true; @@ -561,7 +604,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, /* Consider excluded ranges and "auto" in the first pass */ p = spec; do { - if (!parse_port_chunk(&p, &kind, &lrange, NULL)) + if (!parse_port_chunk(&p, &kind, &lrange, NULL, NULL)) goto bad; switch (kind) { @@ -586,14 +629,19 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, p = spec; do { struct port_range trange; + union inany_addr taddr; - if (!parse_port_chunk(&p, &kind, &lrange, &trange)) + if (!parse_port_chunk(&p, &kind, &lrange, &taddr, &trange)) goto bad; switch (kind) { - case CHUNK_AUTO: /* already handled */ - case CHUNK_EXCLUDE: /* already handled */ - case CHUNK_ALL: /* handled later */ + case CHUNK_AUTO: + case CHUNK_EXCLUDE: + continue; /* already handled */ + + case CHUNK_ALL: + /* Save the address to use later */ + all_taddr = taddr; continue; case CHUNK_INCLUDE: @@ -604,7 +652,8 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, fwd_rule_range_except(fwd, del, proto, addr, ifname, lrange.first, lrange.last, - exclude, trange.first, flags); + exclude, &taddr, trange.first, + flags); break; default: goto bad; @@ -620,7 +669,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, fwd_rule_range_except(fwd, del, proto, addr, ifname, 1, NUM_PORTS - 1, exclude, - 1, flags | FWD_WEAK); + &all_taddr, 1, flags | FWD_WEAK); } return; bad: diff --git a/passt.1 b/passt.1 index 183f8883..e9fe07f2 100644 --- a/passt.1 +++ b/passt.1 @@ -449,12 +449,15 @@ interface name (since Linux 5.7) can be specified. \fIports\fR is a comma-separated list of entries which may be any of: .TP -\fIfirst\fR[\fB-\fR\fIlast\fR][\fB:\fR\fItofirst\fR[\fB-\fR\fItolast\fR]] +\fIfirst\fR[\fB-\fR\fIlast\fR][\fB:\fR[\fItoaddr\fR\fB/\fR]\fItofirst\fR[\fB-\fR\fItolast\fR]] +.TP +\fIfirst\fR[\fB-\fR\fIlast\fR][\fB:\fR\fItoaddr\fR] Include range. Forward port numbers between \fIfirst\fR and \fIlast\fR -(inclusive) to ports between \fItofirst\fR and \fItolast\fR. If -\fItofirst\fR and \fItolast\fR are omitted, assume the same as -\fIfirst\fR and \fIlast\fR. If \fIlast\fR is omitted, assume the same -as \fIfirst\fR. +(inclusive) to ports between \fItofirst\fR and \fItolast\fR to address +\fItoaddr\fR. If \fItoaddr\fR is omitted, automatically determine the +guest or namespace address. If \fItofirst\fR and \fItolast\fR are +omitted, assume the same as \fIfirst\fR and \fIlast\fR. If \fIlast\fR +is omitted, assume the same as \fIfirst\fR. .TP \fB~\fR\fIfirst\fR[\fB-\fR\fIlast\fR] @@ -462,11 +465,13 @@ Exclude range. Don't forward port numbers between \fIfirst\fR and \fIlast\fR. This takes precedences over include ranges. .TP -.BR all +.BR all\fR[\fB:\fItoaddr\fR] Forward all unbound, non-ephemeral ports, not covered by exclude -ranges above, as permitted by current capabilities. For low (< 1024) -ports, see \fBNOTES\fR. No failures are reported for unavailable -ports, unless no ports could be forwarded at all. +ranges above, as permitted by current capabilities, to the +corresponding ports on address \fItoaddr\fR. If \fItoaddr\fR is +omitted, automatically determine the guest or namespace address. For +low (< 1024) ports, see \fBNOTES\fR. No failures are reported for +unavailable ports, unless no ports could be forwarded at all. .TP .BR auto @@ -516,6 +521,14 @@ Forward local port 22, bound to 192.0.2.1 and interface eth0, to port 22 -t %eth0/22 Forward local port 22, bound to any address on interface eth0, to port 22 .TP +-t 0.0.0.0/5000:192.0.2.5/6000 +Forward local port 5000, bound to any IPv4 address, to port 6000 on address 192.0.2.5. +.TP +-t 127.0.0.6/all:192.0.2.6 +For the local address 127.0.0.6, forward all unbound, non-ephemeral +ports as permitted by current capabilities to the corresponding port +on 192.0.2.6. +.TP -t 2000-5000,~3000-3010 Forward local ports between 2000 and 5000, except for those between 3000 and 3010 -- 2.55.0