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=cEKAKOUk; dkim-atps=neutral Received: from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3]) by passt.top (Postfix) with ESMTPS id 422915A026E for ; Thu, 02 Jul 2026 09:32:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202606; t=1782977541; bh=/ewqzPDGNIepJSzez4Lk+HD5mipLm8y3TocNEwtM+N8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cEKAKOUkuh0DwQXhnrnj5yBsPwaPwqzOV22IZgjIGXQ8nx4DU78XiyFIl5FgNVmD1 lAPNISdVTX8tw+nbrXH3yNUWKr9e2LIBLWqM9eP5UDLmux1ZWzHLSw22Qtdtus2Ehh 9zqYNKdNZzeyd7TjBBL+Q6q46z5Ocd+6iJ4OjT0cRXSD/Bi84+Bp6pvkJtDsgkHuiU SfnErZ0Szqqb4epEqhCAy0pf677+IZ48ZN4DMVUCkTfrRTpfnicvNjIqmUdULL7WOr v3CTn7Y0vzIDTF6BpPBxo6lmOWRQ2QRDrhmBqNqFeUDie83XQgER4qrwqlDcZPJyx3 DzTWg+8SfAmDQ== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4grT9d6m9kz58f8; Thu, 02 Jul 2026 17:32:21 +1000 (AEST) From: David Gibson To: passt-dev@passt.top, Stefano Brivio Subject: [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules Date: Thu, 2 Jul 2026 17:32:14 +1000 Message-ID: <20260702073215.751291-2-david@gibson.dropbear.id.au> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260702073215.751291-1-david@gibson.dropbear.id.au> References: <20260702073215.751291-1-david@gibson.dropbear.id.au> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: I5VNAPM3W2Z2OZEQDJ2OK4ZIG3AOI4J3 X-Message-ID-Hash: I5VNAPM3W2Z2OZEQDJ2OK4ZIG3AOI4J3 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 | 90 +++++++++++++++++++++++++++++++++++++++++++++--------- passt.1 | 31 +++++++++++++------ 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/fwd_rule.c b/fwd_rule.c index ef35e1b4..bed29ed9 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,31 @@ 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]; + + if (!inany_is_unicast(tgt_addr)) { + die("Target address %s is not unicast", + inany_ntop(tgt_addr, astr, sizeof(astr))); + } + if (!addr || !!inany_v4(addr) != !!inany_v4(tgt_addr)) { + char bstr[INANY_ADDRSTRLEN]; + + die( +"Forwarding between IP versions (%s => %s) not implemented", + inany_ntop(addr, bstr, sizeof(bstr)), + inany_ntop(tgt_addr, astr, sizeof(astr))); + } + + 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 +482,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 +514,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 +545,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 +606,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 +617,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 +642,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 +665,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 +682,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 c3722ef9..9ece0e0c 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