* [PATCH 0/3] RFC: Target address mapping
@ 2026-07-01 7:08 David Gibson
2026-07-01 7:08 ` [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules David Gibson
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: David Gibson @ 2026-07-01 7:08 UTC (permalink / raw)
To: passt-dev, Stefano Brivio; +Cc: David Gibson
Here's a next draft for target address remapping. I'm posting this as
RFC because I have done minimal testing so far. I've tested the
address parsing, and that we pass the existing tests, but I've done
only a tiny bit of checking of the new funcionality. Posting so that
it can be reviewed in parallel with that continued testing.
Based on v2 of my parsing rework series.
David Gibson (2):
fwd_rule: Parse target adddresses for forwarding rules
fwd: Clarify semantics of --host-lo-to-ns-lo
Stefano Brivio (1):
fwd, fwd_rule: Implement configurable target address mapping
conf.c | 2 ++
fwd.c | 30 ++++++++++++++++--------------
fwd_rule.c | 31 ++++++++++++++++++++++++-------
fwd_rule.h | 2 ++
passt.1 | 9 +++++----
pesto.h | 6 +++++-
6 files changed, 54 insertions(+), 26 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules 2026-07-01 7:08 [PATCH 0/3] RFC: Target address mapping David Gibson @ 2026-07-01 7:08 ` David Gibson 2026-07-02 4:58 ` Stefano Brivio 2026-07-01 7:08 ` [PATCH 2/3] fwd: Clarify semantics of --host-lo-to-ns-lo David Gibson 2026-07-01 7:08 ` [PATCH 3/3] fwd, fwd_rule: Implement configurable target address mapping David Gibson 2 siblings, 1 reply; 8+ messages in thread From: David Gibson @ 2026-07-01 7:08 UTC (permalink / raw) To: passt-dev, Stefano Brivio; +Cc: David Gibson 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 <sbrivio@redhat.com> [dwg: Syntax from Stefano's earlier draft, largely rewritten on top of new parsing helpers] Signed-off-by: David Gibson <david@gibson.dropbear.id.au> --- fwd_rule.c | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/fwd_rule.c b/fwd_rule.c index ca409eaf..e8abc884 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 traget 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 = { @@ -395,9 +398,17 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, .flags = flags, }; char rulestr[FWD_RULE_STRLEN]; - unsigned delta = to - first; + unsigned delta = tgt_first - 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,14 +469,17 @@ 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) * @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; @@ -481,6 +495,12 @@ static bool parse_port_chunk(const char **cursor, kind = CHUNK_INCLUDE; if (parse_literal(&p, ":")) { + const char *tgtspec = p; + + if (!parse_inany(&p, &taddr_tmp) || + !parse_literal(&p, "/")) + p = tgtspec; /* No target address, backtrack */ + if (!parse_port_range(&p, &tr)) return false; } else { @@ -492,6 +512,8 @@ static bool parse_port_chunk(const char **cursor, *kindp = kind; *lrange = lr; + if (taddr) + *taddr = taddr_tmp; if (trange) *trange = tr; *cursor = p; @@ -561,7 +583,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,8 +608,9 @@ 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) { @@ -604,7 +627,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 +644,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); + NULL, 1, flags | FWD_WEAK); } return; bad: -- 2.54.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules 2026-07-01 7:08 ` [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules David Gibson @ 2026-07-02 4:58 ` Stefano Brivio 2026-07-02 5:21 ` David Gibson 0 siblings, 1 reply; 8+ messages in thread From: Stefano Brivio @ 2026-07-02 4:58 UTC (permalink / raw) To: David Gibson; +Cc: passt-dev Nit, in the title: addresses. On Wed, 1 Jul 2026 17:08:09 +1000 David Gibson <david@gibson.dropbear.id.au> wrote: > 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 <sbrivio@redhat.com> > [dwg: Syntax from Stefano's earlier draft, largely rewritten on top of new > parsing helpers] > Signed-off-by: David Gibson <david@gibson.dropbear.id.au> > --- > fwd_rule.c | 38 +++++++++++++++++++++++++++++++------- > 1 file changed, 31 insertions(+), 7 deletions(-) > > diff --git a/fwd_rule.c b/fwd_rule.c > index ca409eaf..e8abc884 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 traget side Nit: target. > * @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 = { > @@ -395,9 +398,17 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, > .flags = flags, > }; > char rulestr[FWD_RULE_STRLEN]; > - unsigned delta = to - first; > + unsigned delta = tgt_first - first; Nit: should be moved above now. > 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,14 +469,17 @@ 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) > * @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; > > @@ -481,6 +495,12 @@ static bool parse_port_chunk(const char **cursor, > kind = CHUNK_INCLUDE; > > if (parse_literal(&p, ":")) { > + const char *tgtspec = p; > + > + if (!parse_inany(&p, &taddr_tmp) || > + !parse_literal(&p, "/")) This is to support ":/PORT" together with ":ADDR/PORT", right? I think it's nice to have for users, but it looks inconsistent here, so maybe you could add a comment before that, say: /* Accept :/PORT as well as :ADDR/PORT */ > + p = tgtspec; /* No target address, backtrack */ > + > if (!parse_port_range(&p, &tr)) > return false; > } else { > @@ -492,6 +512,8 @@ static bool parse_port_chunk(const char **cursor, > > *kindp = kind; > *lrange = lr; > + if (taddr) > + *taddr = taddr_tmp; > if (trange) > *trange = tr; > *cursor = p; > @@ -561,7 +583,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,8 +608,9 @@ 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) { > @@ -604,7 +627,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 +644,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); > + NULL, 1, flags | FWD_WEAK); > } > return; > bad: The rest of the series looks good to me, but I didn't test things at all. By the way, man page and usage changes for 3/3 are missing. I haven't seen anything preventing mapping between IPv4 and IPv6, but I guess I missed something. I actually think it would be a nice feature but I guess it needs some more effort to properly support it. -- Stefano ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules 2026-07-02 4:58 ` Stefano Brivio @ 2026-07-02 5:21 ` David Gibson 2026-07-02 5:31 ` Stefano Brivio 0 siblings, 1 reply; 8+ messages in thread From: David Gibson @ 2026-07-02 5:21 UTC (permalink / raw) To: Stefano Brivio; +Cc: passt-dev [-- Attachment #1: Type: text/plain, Size: 7207 bytes --] On Thu, Jul 02, 2026 at 06:58:23AM +0200, Stefano Brivio wrote: > Nit, in the title: addresses. Received this moments before I was going to send out a v2 :) Fixed the title. > On Wed, 1 Jul 2026 17:08:09 +1000 > David Gibson <david@gibson.dropbear.id.au> wrote: > > > 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 <sbrivio@redhat.com> > > [dwg: Syntax from Stefano's earlier draft, largely rewritten on top of new > > parsing helpers] > > Signed-off-by: David Gibson <david@gibson.dropbear.id.au> > > --- > > fwd_rule.c | 38 +++++++++++++++++++++++++++++++------- > > 1 file changed, 31 insertions(+), 7 deletions(-) > > > > diff --git a/fwd_rule.c b/fwd_rule.c > > index ca409eaf..e8abc884 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 traget side > > Nit: target. Fixed. > > > * @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 = { > > @@ -395,9 +398,17 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, > > .flags = flags, > > }; > > char rulestr[FWD_RULE_STRLEN]; > > - unsigned delta = to - first; > > + unsigned delta = tgt_first - first; > > Nit: should be moved above now. Fixed. > > 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,14 +469,17 @@ 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) > > * @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; > > > > @@ -481,6 +495,12 @@ static bool parse_port_chunk(const char **cursor, > > kind = CHUNK_INCLUDE; > > > > if (parse_literal(&p, ":")) { > > + const char *tgtspec = p; > > + > > + if (!parse_inany(&p, &taddr_tmp) || > > + !parse_literal(&p, "/")) > > This is to support ":/PORT" together with ":ADDR/PORT", right? No, this is checking for the case where we didn't get a target address at all. De Morgan's law might make it clearer: !(<parsed IP> && <parsed "/") But I already reworked this to allow port to be omitted with address, and I think the new code is clearer. Or at least unclear in an unrelated way :/. > I think > it's nice to have for users, but it looks inconsistent here, so maybe > you could add a comment before that, say: > > /* Accept :/PORT as well as :ADDR/PORT */ > > > + p = tgtspec; /* No target address, backtrack */ > > + > > if (!parse_port_range(&p, &tr)) > > return false; > > } else { > > @@ -492,6 +512,8 @@ static bool parse_port_chunk(const char **cursor, > > > > *kindp = kind; > > *lrange = lr; > > + if (taddr) > > + *taddr = taddr_tmp; > > if (trange) > > *trange = tr; > > *cursor = p; > > @@ -561,7 +583,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,8 +608,9 @@ 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) { > > @@ -604,7 +627,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 +644,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); > > + NULL, 1, flags | FWD_WEAK); > > } > > return; > > bad: > > The rest of the series looks good to me, but I didn't test things at > all. By the way, man page and usage changes for 3/3 are missing. Ugh, yeah, I belatedly realised that. > I haven't seen anything preventing mapping between IPv4 and IPv6, but I > guess I missed something. I actually think it would be a nice feature > but I guess it needs some more effort to properly support it. Oh sod, good point. I forgot to add that check. I think we need it for now, because I'm pretty sure it won't work end to end yet. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules 2026-07-02 5:21 ` David Gibson @ 2026-07-02 5:31 ` Stefano Brivio 2026-07-02 6:08 ` David Gibson 0 siblings, 1 reply; 8+ messages in thread From: Stefano Brivio @ 2026-07-02 5:31 UTC (permalink / raw) To: David Gibson; +Cc: passt-dev On Thu, 2 Jul 2026 15:21:29 +1000 David Gibson <david@gibson.dropbear.id.au> wrote: > On Thu, Jul 02, 2026 at 06:58:23AM +0200, Stefano Brivio wrote: > > Nit, in the title: addresses. > > Received this moments before I was going to send out a v2 :) > > Fixed the title. > > > On Wed, 1 Jul 2026 17:08:09 +1000 > > David Gibson <david@gibson.dropbear.id.au> wrote: > > > > > 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 <sbrivio@redhat.com> > > > [dwg: Syntax from Stefano's earlier draft, largely rewritten on top of new > > > parsing helpers] > > > Signed-off-by: David Gibson <david@gibson.dropbear.id.au> > > > --- > > > fwd_rule.c | 38 +++++++++++++++++++++++++++++++------- > > > 1 file changed, 31 insertions(+), 7 deletions(-) > > > > > > diff --git a/fwd_rule.c b/fwd_rule.c > > > index ca409eaf..e8abc884 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 traget side > > > > Nit: target. > > Fixed. > > > > > > * @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 = { > > > @@ -395,9 +398,17 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, > > > .flags = flags, > > > }; > > > char rulestr[FWD_RULE_STRLEN]; > > > - unsigned delta = to - first; > > > + unsigned delta = tgt_first - first; > > > > Nit: should be moved above now. > > Fixed. > > > > 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,14 +469,17 @@ 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) > > > * @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; > > > > > > @@ -481,6 +495,12 @@ static bool parse_port_chunk(const char **cursor, > > > kind = CHUNK_INCLUDE; > > > > > > if (parse_literal(&p, ":")) { > > > + const char *tgtspec = p; > > > + > > > + if (!parse_inany(&p, &taddr_tmp) || > > > + !parse_literal(&p, "/")) > > > > This is to support ":/PORT" together with ":ADDR/PORT", right? > > No, this is checking for the case where we didn't get a target address > at all. De Morgan's law might make it clearer: > !(<parsed IP> && <parsed "/") Right, yes, I understand, but you're doing the check this way because "/PORT" without address would be accepted, right? Otherwise checking for !parse_inany(&p, &taddr_tmp) would have been enough? Anyway, not so important, if the next version looks different anyway. > But I already reworked this to allow port to be omitted with address, > and I think the new code is clearer. Or at least unclear in an > unrelated way :/. > > > I think > > it's nice to have for users, but it looks inconsistent here, so maybe > > you could add a comment before that, say: > > > > /* Accept :/PORT as well as :ADDR/PORT */ > > > > > + p = tgtspec; /* No target address, backtrack */ > > > + > > > if (!parse_port_range(&p, &tr)) > > > return false; > > > } else { > > > @@ -492,6 +512,8 @@ static bool parse_port_chunk(const char **cursor, > > > > > > *kindp = kind; > > > *lrange = lr; > > > + if (taddr) > > > + *taddr = taddr_tmp; > > > if (trange) > > > *trange = tr; > > > *cursor = p; > > > @@ -561,7 +583,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,8 +608,9 @@ 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) { > > > @@ -604,7 +627,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 +644,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); > > > + NULL, 1, flags | FWD_WEAK); > > > } > > > return; > > > bad: > > > > The rest of the series looks good to me, but I didn't test things at > > all. By the way, man page and usage changes for 3/3 are missing. > > Ugh, yeah, I belatedly realised that. > > > I haven't seen anything preventing mapping between IPv4 and IPv6, but I > > guess I missed something. I actually think it would be a nice feature > > but I guess it needs some more effort to properly support it. > > Oh sod, good point. I forgot to add that check. I think we need it > for now, because I'm pretty sure it won't work end to end yet. I guess probably worth a quick check, I wouldn't be surprised if things actually kind of work for TCP. -- Stefano ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules 2026-07-02 5:31 ` Stefano Brivio @ 2026-07-02 6:08 ` David Gibson 0 siblings, 0 replies; 8+ messages in thread From: David Gibson @ 2026-07-02 6:08 UTC (permalink / raw) To: Stefano Brivio; +Cc: passt-dev [-- Attachment #1: Type: text/plain, Size: 5831 bytes --] On Thu, Jul 02, 2026 at 07:31:10AM +0200, Stefano Brivio wrote: > On Thu, 2 Jul 2026 15:21:29 +1000 > David Gibson <david@gibson.dropbear.id.au> wrote: > > > On Thu, Jul 02, 2026 at 06:58:23AM +0200, Stefano Brivio wrote: [snip] > > > > @@ -481,6 +495,12 @@ static bool parse_port_chunk(const char **cursor, > > > > kind = CHUNK_INCLUDE; > > > > > > > > if (parse_literal(&p, ":")) { > > > > + const char *tgtspec = p; > > > > + > > > > + if (!parse_inany(&p, &taddr_tmp) || > > > > + !parse_literal(&p, "/")) > > > > > > This is to support ":/PORT" together with ":ADDR/PORT", right? > > > > No, this is checking for the case where we didn't get a target address > > at all. De Morgan's law might make it clearer: > > !(<parsed IP> && <parsed "/") > > Right, yes, I understand, but you're doing the check this way because > "/PORT" without address would be accepted, right? No, I'm not, and it won't - in fact it's almost exactly the opposite. If parse_inany() fails (which it will on an empty "address"), we won't even call parse_literal() because of the rules of || > Otherwise checking > for !parse_inany(&p, &taddr_tmp) would have been enough? Not really, this is here so that we *don't* do anything with an address that *isn't* followed by a /. Instead we backtrack and try parse as something else instead. In fact that will (I'm pretty sure, necessarily) also fail, but that's looking at parts of the grammar outside what this specific piece is dealing with. The idea here is that we consume either "ADDR/" or we consume nothing at all before going on to parse the port range. > Anyway, not so important, if the next version looks different anyway. Right, though whether it's any less confusing is another question :/. Like I said, I'm not thrilled with how this parsing stuff turned out, but I spent two weeks and couldn't come up with something that wasn't at least as bad in one way or another. > > But I already reworked this to allow port to be omitted with address, > > and I think the new code is clearer. Or at least unclear in an > > unrelated way :/. > > > > > I think > > > it's nice to have for users, but it looks inconsistent here, so maybe > > > you could add a comment before that, say: > > > > > > /* Accept :/PORT as well as :ADDR/PORT */ > > > > > > > + p = tgtspec; /* No target address, backtrack */ > > > > + > > > > if (!parse_port_range(&p, &tr)) > > > > return false; > > > > } else { > > > > @@ -492,6 +512,8 @@ static bool parse_port_chunk(const char **cursor, > > > > > > > > *kindp = kind; > > > > *lrange = lr; > > > > + if (taddr) > > > > + *taddr = taddr_tmp; > > > > if (trange) > > > > *trange = tr; > > > > *cursor = p; > > > > @@ -561,7 +583,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,8 +608,9 @@ 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) { > > > > @@ -604,7 +627,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 +644,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); > > > > + NULL, 1, flags | FWD_WEAK); > > > > } > > > > return; > > > > bad: > > > > > > The rest of the series looks good to me, but I didn't test things at > > > all. By the way, man page and usage changes for 3/3 are missing. > > > > Ugh, yeah, I belatedly realised that. > > > > > I haven't seen anything preventing mapping between IPv4 and IPv6, but I > > > guess I missed something. I actually think it would be a nice feature > > > but I guess it needs some more effort to properly support it. > > > > Oh sod, good point. I forgot to add that check. I think we need it > > for now, because I'm pretty sure it won't work end to end yet. > > I guess probably worth a quick check, I wouldn't be surprised if things > actually kind of work for TCP. Yes, but there are a lot of paths to check, so I'd prefer to explicitly exclude this for the first spin. I've now added that, though it has the mildly unfortunate side effect that it's now (usually) impossible to specify a target address without listening address (Because -t 5000:192.0.2.1 means */5000:192.0.2.1/5000.. and since * listens on IPv6 it would mean family translation in at least some cases). Rechecking and relaxing this is high on the list of followons. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/3] fwd: Clarify semantics of --host-lo-to-ns-lo 2026-07-01 7:08 [PATCH 0/3] RFC: Target address mapping David Gibson 2026-07-01 7:08 ` [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules David Gibson @ 2026-07-01 7:08 ` David Gibson 2026-07-01 7:08 ` [PATCH 3/3] fwd, fwd_rule: Implement configurable target address mapping David Gibson 2 siblings, 0 replies; 8+ messages in thread From: David Gibson @ 2026-07-01 7:08 UTC (permalink / raw) To: passt-dev, Stefano Brivio; +Cc: David Gibson The semantics of --host-lo-to-ns-lo as described in the man page don't quite make sense: It says without the option forwarded packets will appear to come _from_ the guest's public address, which is not usually true. Instead the packets will arrive *to* the guest's public address. The exact semantics are also a bit confusing in general. Rewrite both the man page and code to clarify this. The new rule is that it redirects connections addressed to a host loopback address to the same loopback address in the guest. This is notionally different from what we had in two ways: * We can now deliver to nonstandard loopback addresses within the guest, not just the default one. This is technically a behavioural change, but I think will be less surprising behaviour. * The decision is now made on the original _destination_ address, rather than source address. That's different theoreically, but not in practice, since loopback packets must have loopback addresses for both source and destination. We make it explicitly incompatible with --no-splice - previously it was allowed, but would have no effect in that case. As well as being more precise right now, these semantics will intersect better with upcoming remapping of target address by forwarding rules. Signed-off-by: David Gibson <david@gibson.dropbear.id.au> --- conf.c | 2 ++ fwd.c | 22 ++++++++++------------ passt.1 | 9 +++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/conf.c b/conf.c index c4a36dee..912543fa 100644 --- a/conf.c +++ b/conf.c @@ -1792,6 +1792,8 @@ void conf(struct ctx *c, int argc, char **argv) if (c->splice_only) die("--splice-only is for pasta mode only"); } + if (c->no_splice && c->host_lo_to_ns_lo) + die("--host-lo-to-ns-lo is incompatible with --no-splice"); if (c->mode == MODE_PASTA && !c->pasta_conf_ns) { if (copy_routes_opt) diff --git a/fwd.c b/fwd.c index 042158cf..659f8d9f 100644 --- a/fwd.c +++ b/fwd.c @@ -1036,21 +1036,19 @@ uint8_t fwd_nat_from_host(const struct ctx *c, * In either case, let the kernel pick the source address to * match. */ - if (inany_v4(&ini->eaddr)) { - if (c->host_lo_to_ns_lo) - tgt->eaddr = inany_loopback4; - else - tgt->eaddr = inany_from_v4(c->ip4.addr_seen); + if (c->host_lo_to_ns_lo && inany_is_loopback(&ini->oaddr)) + tgt->eaddr = ini->oaddr; + else if (inany_v4(&ini->eaddr)) + tgt->eaddr = inany_from_v4(c->ip4.addr_seen); + else + tgt->eaddr.a6 = c->ip6.addr_seen; + + /* Let the kernel pick source address and port */ + if (inany_v4(&tgt->eaddr)) tgt->oaddr = inany_any4; - } else { - if (c->host_lo_to_ns_lo) - tgt->eaddr = inany_loopback6; - else - tgt->eaddr.a6 = c->ip6.addr_seen; + else tgt->oaddr = inany_any6; - } - /* Let the kernel pick source port */ tgt->oport = 0; if (proto == IPPROTO_UDP) /* But for UDP preserve the source port */ diff --git a/passt.1 b/passt.1 index 908fd4a4..d057aebc 100644 --- a/passt.1 +++ b/passt.1 @@ -650,10 +650,11 @@ Default is \fBauto\fR. .TP .BR \-\-host-lo-to-ns-lo -If specified, connections forwarded with \fB\-t\fR and \fB\-u\fR from -the host's loopback address will appear on the loopback address in the -guest as well. Without this option such forwarded packets will appear -to come from the guest's public address. +If specified, connections to a host loopback address forwarded with +\fB\-t\fR or \fB\-u\fR will be delivered to the same loopback address +on the guest. Without this option such connections are forwarded to +the guest's public address. This option is incompatible with +\fB--no-splice\fR. .TP .BR \-\-userns " " \fIspec -- 2.54.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/3] fwd, fwd_rule: Implement configurable target address mapping 2026-07-01 7:08 [PATCH 0/3] RFC: Target address mapping David Gibson 2026-07-01 7:08 ` [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules David Gibson 2026-07-01 7:08 ` [PATCH 2/3] fwd: Clarify semantics of --host-lo-to-ns-lo David Gibson @ 2026-07-01 7:08 ` David Gibson 2 siblings, 0 replies; 8+ messages in thread From: David Gibson @ 2026-07-01 7:08 UTC (permalink / raw) To: passt-dev, Stefano Brivio; +Cc: David Gibson From: Stefano Brivio <sbrivio@redhat.com> Add a 'taddr' field to forwarding rules, which controls the destination address on the target side. Since changing the structure alters the pesto update protocol, bump the protocol version number Signed-off-by: Stefano Brivio <sbrivio@redhat.com> [dwg: Split from option parsing code, added protocol version bump, explicitly exclude splicing with target address for now] Signed-off-by: David Gibson <david@gibson.dropbear.id.au> --- fwd.c | 8 ++++++-- fwd_rule.c | 9 +-------- fwd_rule.h | 2 ++ pesto.h | 6 +++++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/fwd.c b/fwd.c index 659f8d9f..84400948 100644 --- a/fwd.c +++ b/fwd.c @@ -1023,7 +1023,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, /* Common for spliced and non-spliced cases */ tgt->eport = rule->to + (ini->oport - rule->first); - if (!c->no_splice && inany_is_loopback(&ini->eaddr) && + /* TODO: Allow splicing with specified target address */ + if (!c->no_splice && inany_is_unspecified(&rule->taddr) && + inany_is_loopback(&ini->eaddr) && (proto == IPPROTO_TCP || proto == IPPROTO_UDP)) { /* spliceable */ @@ -1072,7 +1074,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, } tgt->oport = ini->eport; - if (inany_v4(&tgt->oaddr)) { + if (!inany_is_unspecified(&rule->taddr)) { + tgt->eaddr = rule->taddr; + } else if (inany_v4(&tgt->oaddr)) { tgt->eaddr = inany_from_v4(c->ip4.addr_seen); } else { if (inany_is_linklocal6(&tgt->oaddr)) diff --git a/fwd_rule.c b/fwd_rule.c index e8abc884..494d3fc3 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -393,6 +393,7 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, { struct fwd_rule rule = { .addr = addr ? *addr : inany_any6, + .taddr = tgt_addr ? *tgt_addr : inany_any6, .ifname = { 0 }, .proto = proto, .flags = flags, @@ -401,14 +402,6 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, unsigned delta = tgt_first - 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) { diff --git a/fwd_rule.h b/fwd_rule.h index 435be5bd..c782f9d4 100644 --- a/fwd_rule.h +++ b/fwd_rule.h @@ -33,6 +33,7 @@ /** * struct fwd_rule - Forwarding rule governing a range of ports * @addr: Address to forward from + * @taddr: Target side destination address * @ifname: Interface to forward from * @first: First port number to forward * @last: Last port number to forward @@ -45,6 +46,7 @@ */ struct fwd_rule { union inany_addr addr; + union inany_addr taddr; char ifname[IFNAMSIZ]; in_port_t first; in_port_t last; diff --git a/pesto.h b/pesto.h index 980cc17d..8db701b4 100644 --- a/pesto.h +++ b/pesto.h @@ -15,7 +15,11 @@ #define PESTO_SERVER_MAGIC "basil:s" /* Version 0 is reserved for unreleased / unsupported experimental versions */ -#define PESTO_PROTOCOL_VERSION 1 +/* Version 1 had no target address field in struct fwd_rule. It was released, + * but was little enough used that we decided not to implement backwards + * compatiblity code (i.e. a v2 pesto will not work with a v1 pasta) + */ +#define PESTO_PROTOCOL_VERSION 2 /* Maximum size of a pif name, including \0 */ #define PIF_NAME_SIZE (128) -- 2.54.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-07-02 6:31 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-07-01 7:08 [PATCH 0/3] RFC: Target address mapping David Gibson 2026-07-01 7:08 ` [PATCH 1/3] fwd_rule: Parse target adddresses for forwarding rules David Gibson 2026-07-02 4:58 ` Stefano Brivio 2026-07-02 5:21 ` David Gibson 2026-07-02 5:31 ` Stefano Brivio 2026-07-02 6:08 ` David Gibson 2026-07-01 7:08 ` [PATCH 2/3] fwd: Clarify semantics of --host-lo-to-ns-lo David Gibson 2026-07-01 7:08 ` [PATCH 3/3] fwd, fwd_rule: Implement configurable target address mapping David Gibson
Code repositories for project(s) associated with this public inbox https://passt.top/passt This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for IMAP folder(s).