* [PATCH v2 0/2] Target address mapping @ 2026-07-02 7:32 David Gibson 2026-07-02 7:32 ` [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules David Gibson 2026-07-02 7:32 ` [PATCH v2 2/2] fwd, fwd_rule: Implement configurable target address mapping David Gibson 0 siblings, 2 replies; 4+ messages in thread From: David Gibson @ 2026-07-02 7:32 UTC (permalink / raw) To: passt-dev, Stefano Brivio; +Cc: David Gibson Here's another draft for target address remapping. I've now given this some basic, though not extensive testing. Based on v4 of my parsing rework series. v2: * Remove the patch altering --host-lo-to-ns-lo; it's not relevant to the change at hand. * Allow either just target address, just target port or both to be specified * Allow target address to be specified with all * Prohibit (for now) forwarding between IPv4 and IPv6 (including from to a specific address of either family). * Add the target address when formatting rules for debug display. * Updated man page for new syntax David Gibson (1): fwd_rule: Parse target addresses for forwarding rules Stefano Brivio (1): fwd, fwd_rule: Implement configurable target address mapping fwd.c | 8 +++-- fwd_rule.c | 101 +++++++++++++++++++++++++++++++++++++++++++---------- fwd_rule.h | 7 ++-- passt.1 | 31 +++++++++++----- pesto.h | 6 +++- 5 files changed, 121 insertions(+), 32 deletions(-) -- 2.55.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules 2026-07-02 7:32 [PATCH v2 0/2] Target address mapping David Gibson @ 2026-07-02 7:32 ` David Gibson 2026-07-04 15:27 ` Stefano Brivio 2026-07-02 7:32 ` [PATCH v2 2/2] fwd, fwd_rule: Implement configurable target address mapping David Gibson 1 sibling, 1 reply; 4+ messages in thread From: David Gibson @ 2026-07-02 7:32 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 | 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 ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules 2026-07-02 7:32 ` [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules David Gibson @ 2026-07-04 15:27 ` Stefano Brivio 0 siblings, 0 replies; 4+ messages in thread From: Stefano Brivio @ 2026-07-04 15:27 UTC (permalink / raw) To: David Gibson; +Cc: passt-dev On Thu, 2 Jul 2026 17:32:14 +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 | 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))); > + } Nit: an extra newline here would help readability. > + if (!addr || !!inany_v4(addr) != !!inany_v4(tgt_addr)) { I wonder: if !addr, shouldn't we replace it, for the moment, with inany_any4 or inany_any6 depending on !!inany_v4(tgt_addr)? I see that an empty address already works with -4 or -6, and I understand that, by doing this, we'll have a change in behaviour once forwarding between IP versions is implemented. But it will take a while before we get there, and, meanwhile, I guess almost all users will just want to do stuff like -t 8080:192.0.2.1/80, just to hit: Forwarding between IP versions (* => 192.0.2.1) not implemented which isn't obvious if you aren't familiar with the implementation. I can also picture a constant flow of incoming tickets as a result. The change in behaviour once we implement forwarding between IP versions, by the way, looks a bit like an extension rather than a real change that could reasonably cause trouble to anybody. > + 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))); > + } About both validations: shouldn't they live in fwd_rule_add(), where we already have this kind of stuff? Or at least be duplicated there? Now, I tried dropping those in pesto (#ifndef PESTO ...) to see what happens, and I couldn't really spot anything really bad, not with multicast and not with mixing IPv4 and IPv6: 14 20.647728 fe80::1 → ::ffff:127.0.0.1 82 TCP 57812 → 5201 [SYN] Seq=0 Win=65535 Len=0 MSS=61440 WS=256 but still I wonder if we shouldn't make it a bit more robust. > + > + 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 I think this is a bit difficult to follow without a comma, it should be: For the local address 127.0.0.6, forward all unbound, [...] also for consistency with the existing: For the local address 192.0.2.1, forward ports between 20 and 24 [...] > +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 the rest and 2/2 look good to me, no further comments. -- Stefano ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] fwd, fwd_rule: Implement configurable target address mapping 2026-07-02 7:32 [PATCH v2 0/2] Target address mapping David Gibson 2026-07-02 7:32 ` [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules David Gibson @ 2026-07-02 7:32 ` David Gibson 1 sibling, 0 replies; 4+ messages in thread From: David Gibson @ 2026-07-02 7:32 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 | 19 +++++++++++-------- fwd_rule.h | 7 +++++-- pesto.h | 6 +++++- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/fwd.c b/fwd.c index 042158cf..3ae25fde 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 */ @@ -1074,7 +1076,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 bed29ed9..d1ecd39e 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -115,10 +115,15 @@ __attribute__((noinline)) const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size) { const char *percent = *rule->ifname ? "%" : ""; + char taddr[INANY_ADDRSTRLEN] = { 0 }; const char *weak = "", *scan = ""; char addr[INANY_ADDRSTRLEN]; int len; + if (!inany_is_unspecified(&rule->taddr)) { + (void)snprintf(taddr, sizeof(taddr), "%s:", + inany_ntop(&rule->taddr, addr, sizeof(addr))); + } inany_ntop(fwd_rule_addr(rule), addr, sizeof(addr)); if (rule->flags & FWD_WEAK) weak = " (best effort)"; @@ -127,16 +132,17 @@ const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size) if (rule->first == rule->last) { len = snprintf(dst, size, - "%s [%s]%s%s:%hu => %hu %s%s", + "%s [%s]%s%s:%hu => %s%hu %s%s", ipproto_name(rule->proto), addr, percent, - rule->ifname, rule->first, rule->to, weak, scan); + rule->ifname, rule->first, + taddr, rule->to, weak, scan); } else { in_port_t tolast = rule->last - rule->first + rule->to; len = snprintf(dst, size, - "%s [%s]%s%s:%hu-%hu => %hu-%hu %s%s", + "%s [%s]%s%s:%hu-%hu => %s%hu-%hu %s%s", ipproto_name(rule->proto), addr, percent, rule->ifname, rule->first, rule->last, - rule->to, tolast, weak, scan); + taddr, rule->to, tolast, weak, scan); } if (len < 0 || (size_t)len >= size) @@ -393,6 +399,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, @@ -416,10 +423,6 @@ static void fwd_rule_range_except(struct fwd_table *fwd, bool del, 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) diff --git a/fwd_rule.h b/fwd_rule.h index 435be5bd..a8788318 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; @@ -91,10 +93,11 @@ void fwd_probe_ephemeral(void); #define FWD_RULE_STRLEN \ (IPPROTO_STRLEN - 1 \ - + INANY_ADDRSTRLEN - 1 \ + + INANY_ADDRSTRLEN - 1 /* listen addr */ \ + + INANY_ADDRSTRLEN - 1 /* target addr */ \ + IFNAMSIZ - 1 \ + 4 * (UINT16_STRLEN - 1) \ - + sizeof(" []%:- => - (best effort) (auto-scan)")) + + sizeof(" []%:- => :- (best effort) (auto-scan)")) const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule); const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size); 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.55.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-04 15:27 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-07-02 7:32 [PATCH v2 0/2] Target address mapping David Gibson 2026-07-02 7:32 ` [PATCH v2 1/2] fwd_rule: Parse target addresses for forwarding rules David Gibson 2026-07-04 15:27 ` Stefano Brivio 2026-07-02 7:32 ` [PATCH v2 2/2] 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).