From 4d41ac73985f554e53f0871405af775376aedd8d Mon Sep 17 00:00:00 2001 From: Mohd Husaam Mehdi Date: Tue, 13 Jan 2026 11:31:06 +0530 Subject: [PATCH] redirects: make sure one-to-one mapping is used for the port range The linux kernel provides NF_NAT_RANGE_PROTO_OFFSET flag (netfilter nf_nat_core.c for reference), to make sure that when port ranges are used, then if possible, one-to-one mapping is maintained between external port range and internal port range. But for that flag to be set, iptables extension libxt_DNAT.c requires that base port is also passed along with the port range. Therefore, make sure that if the conditions are right, then we also pass a base port to iptables. One such example where this behaviour is expected is TR-181 data model's Device.NAT.PortMapping. object's InternalPort attribute. --- redirects.c | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/redirects.c b/redirects.c index 9a827b6..647a05f 100644 --- a/redirects.c +++ b/redirects.c @@ -473,11 +473,33 @@ set_redirect(struct fw3_ipt_rule *r, struct fw3_port *port) } } +static bool +needs_nat_base_fix(enum fw3_flag target, + struct fw3_port *in_ports, + struct fw3_port *out_ports) +{ + if (target != FW3_FLAG_DNAT) + return false; + + if (!in_ports || !out_ports || !in_ports->set || !out_ports->set) + return false; + + if (in_ports->port_min == in_ports->port_max) + return false; + + if (out_ports->port_min == out_ports->port_max) + return false; + + return (in_ports->port_max - in_ports->port_min) == + (out_ports->port_max - out_ports->port_min); +} + static void set_snat_dnat(struct fw3_ipt_rule *r, enum fw3_flag target, - struct fw3_address *addr, struct fw3_port *port) + struct fw3_address *addr, struct fw3_port *port, + struct fw3_port *in_ports) { - char buf[sizeof("255.255.255.255:65535-65535")] = {}; + char buf[sizeof("255.255.255.255:65535-65535/65535")] = {}; char ip[INET_ADDRSTRLEN], *p = buf; size_t rem = sizeof(buf); int len; @@ -499,8 +521,12 @@ set_snat_dnat(struct fw3_ipt_rule *r, enum fw3_flag target, { if (port->port_min == port->port_max) snprintf(p, rem, ":%u", port->port_min); - else - snprintf(p, rem, ":%u-%u", port->port_min, port->port_max); + else { + if (needs_nat_base_fix(target, in_ports, port)) + snprintf(p, rem, ":%u-%u/%u", port->port_min, port->port_max, in_ports->port_min); + else + snprintf(p, rem, ":%u-%u", port->port_min, port->port_max); + } } if (target == FW3_FLAG_DNAT) @@ -521,9 +547,9 @@ set_target_nat(struct fw3_ipt_rule *r, struct fw3_redirect *redir) if (redir->local) set_redirect(r, &redir->port_redir); else if (redir->target == FW3_FLAG_DNAT) - set_snat_dnat(r, redir->target, &redir->ip_redir, &redir->port_redir); + set_snat_dnat(r, redir->target, &redir->ip_redir, &redir->port_redir, &redir->port_dest); else - set_snat_dnat(r, redir->target, &redir->ip_dest, &redir->port_dest); + set_snat_dnat(r, redir->target, &redir->ip_dest, &redir->port_dest, NULL); } static void @@ -633,7 +659,7 @@ print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state, fw3_ipt_rule_limit(r, &redir->limit); fw3_ipt_rule_time(r, &redir->time); set_comment(r, redir->name, num, "reflection"); - set_snat_dnat(r, FW3_FLAG_DNAT, &redir->ip_redir, &redir->port_redir); + set_snat_dnat(r, FW3_FLAG_DNAT, &redir->ip_redir, &redir->port_redir, NULL); fw3_ipt_rule_replace(r, "zone_%s_prerouting", rz->name); r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, &redir->ip_redir); @@ -641,7 +667,7 @@ print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state, fw3_ipt_rule_limit(r, &redir->limit); fw3_ipt_rule_time(r, &redir->time); set_comment(r, redir->name, num, "reflection"); - set_snat_dnat(r, FW3_FLAG_SNAT, ra, NULL); + set_snat_dnat(r, FW3_FLAG_SNAT, ra, NULL, NULL); fw3_ipt_rule_replace(r, "zone_%s_postrouting", rz->name); break;