1

Our router machine has multiple public IPs (/27) on its WAN interface. Now, I want to add dnat rules which match specific dport/saddr/daddr combinations. My dream would be something like this:

map one2one_dnat {
    # dst_addr     . src_addr  . proto      . dst_port     : dnat_to   . dnat_to_port
    type ipv4_addr . ipv4_addr . inet_proto . inet_service : ipv4_addr . inet_service
    flags interval
    counter
    comment "1-1 dnat"
    elements = {
        42.42.42.5 . 0.0.0.0/0 . tcp . 8888 : 10.42.42.5 . 8888
    }
}

# And then later in a chain
ip daddr . ip saddr . ip protocol . th dport dnat to @one2one_dnat

However, this results in:

root@XXX# nft -c -f assembled.nft
assembled.nft:252:59-60: Error: syntax error, unexpected to, expecting newline or semicolon
        ip daddr . ip saddr . ip protocol . th dport dnat to @one2one_dnat
                                                          ^^

The following syntax examples do work (however not with the intended fancy all-in-one map):

dnat ip addr . port to ip saddr . tcp dport map { 42.42.42.5 . 8888 : 10.42.42.5 . 8888}

# And even with saddr restrictions
ip saddr 0.0.0.0/0 dnat ip addr . port to ip saddr . tcp dport map { 42.42.42.5 . 8888 : 10.42.42.5 . 8888}

Any ideas/suggestions are highly appreciated

1 Answer 1

0

The idea was here but with a wrong syntax used for the named map case, while the proper syntax was used for the anonymous map case.

A map replaces a key with this key's value if found (or the expression just evaluates to false, stopping further processing). Even when used along a dnat rule a map named keytovalue must be used with proper syntax: key map @keytovalue. These 3 parts will then be replaced with the value according to the packet's properties and consumed by the other part of the rule.

OP's attempt doesn't follow the syntax:

ip daddr . ip saddr . ip protocol . th dport dnat to @one2one_dnat

It should be written like this instead:

dnat to ip daddr . ip saddr . ip protocol . th dport map @one2one_dnat

No surprise here: it's the same syntax OP successfully used with anonymous maps: the key (made of concatenations) followed by the keyword map followed by the map reference (which is the definition in the anonymous case). dnat [to] will be the consumer of the resulting ip:port value (only when a match happened).


Further notes.

For other readers, this also requires recent enough nftables support, both in userland and kernel parts, for the purpose of doing NAT: nftables 0.9.4 and Linux kernel 5.6:

  • NAT mappings with concatenations. This allows you to specify the address and port to be used in the NAT mangling from maps, eg.

    nft add rule ip nat pre dnat ip addr . port to ip saddr map { 1.1.1.1 : 2.2.2.2 . 30 }
    

    You can also use this new feature with named sets:

    nft add map ip nat destinations { type ipv4_addr . inet_service : ipv4_addr . inet_service \; }
    nft add rule ip nat pre dnat ip addr . port to ip saddr . tcp dport map @destinations
    

Replacing the type syntax with a typeof syntax along concatenations, which is usually preferable for readability and to avoid having to figure out all the involved type names, some of them poorly documented, doesn't appear to work currently for OP's case: the use of ip protocol and th appears to clash between the map and the rule at least with nftables 1.0.7 and kernel 6.1.x. So better not use typeof here and keep type, or else split the map into two separate maps, one for UDP one for TCP to avoid this clash.

Splitting would also probably be needed for a similar IPv6 setup, since ip6 nexthdr can't be used safely to replace ip protocol, and the correct replacement, meta l4proto won't play along either.

1
  • Thank you very much, the "replacement" behavior of maps was the missing puzzle piece.
    – tobi_b
    May 10, 2023 at 14:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .