SNAT for Carrier Grade Nat (that's many to many netmap with port translation)

I’m new here, and this is my first post , so

hi everybody , nice to meet you and thanks to accept me :smiley: }

I have to implement a CarrierGradeNat 1:8 where a single public /24 subnet maps eight private /24 subnets, with source-port translation … the configuration should be like this

PRIVATE -> PUBLIC    PORTS
100.65.0.0/24 -> XYZ.0.65.0/24   1500:9499
100.65.1.0/24 -> XYZ.0.65.0/24   9500:17499
100.65.2.0/24 -> XYZ.0.65.0/24  17500:25499
100.65.3.0/24 -> XYZ.0.65.0/24  25500:33499
100.65.4.0/24 -> XYZ.0.65.0/24  33500:41499
100.65.5.0/24 -> XYZ.0.65.0/24  41500:49499
100.65.6.0/24 -> XYZ.0.65.0/24  49500:57499
100.65.7.0/24 -> XYZ.0.65.0/24  57500:65499
 
100.66.0.0/24 -> XYZ.0.66.0/24   1500:9499
100.66.1.0/24 -> XYZ.0.66.0/24   9500:17499
100.66.2.0/24 -> XYZ.0.66.0/24  17500:25499
100.66.3.0/24 -> XYZ.0.66.0/24  25500:33499
100.66.4.0/24 -> XYZ.0.66.0/24  33500:41499
100.66.5.0/24 -> XYZ.0.66.0/24  41500:49499
100.66.6.0/24 -> XYZ.0.66.0/24  49500:57499
100.66.7.0/24 -> XYZ.0.66.0/24  57500:65499

100.67.0.0/24 -> XYZ.0.67.0/24   1500:9499
100.67.1.0/24 -> XYZ.0.67.0/24   9500:17499
100.67.2.0/24 -> XYZ.0.67.0/24  17500:25499
100.67.3.0/24 -> XYZ.0.67.0/24  25500:33499
100.67.4.0/24 -> XYZ.0.67.0/24  33500:41499
100.67.5.0/24 -> XYZ.0.67.0/24  41500:49499
100.67.6.0/24 -> XYZ.0.67.0/24  49500:57499
100.67.7.0/24 -> XYZ.0.67.0/24  57500:65499

These rules are already implemented in a MIKROTIK CCR and they work correctly

the ip 100.65.0.1 is mapped to XYZ.0.65.1 (and ports translated into range 1500:9499)
the ip 100.65.0.2 is mapped to XYZ.0.65.2 (and ports translated into range 1500:9499)
the ip 100.65.0.3 is mapped to XYZ.0.65.3 (and ports translated into range 1500:9499)
and so on
the ip 100.65.1.1 is mapped to XYZ.0.65.1 (and ports translated into range 9500:17499)
the ip 100.65.1.2 is mapped to XYZ.0.65.2 (and ports translated into range 9500:17499)
the ip 100.65.1.3 is mapped to XYZ.0.65.3 (and ports translated into range 9500:17499)
and so on

Now I need to do the same thing on a Vyos 1.3 z

After reading the public guide I wrote these simple rules

set nat source rule 6500 outbound-interface ‘eth1’
set nat source rule 6500 protocol ‘tcp_udp’
set nat source rule 6500 source address ‘100.65.0.0-100.65.0.255’
set nat source rule 6500 translation address ‘10.0.65.0-10.0.65.255’
set nat source rule 6500 translation options address-mapping ‘persistent’
set nat source rule 6500 translation port ‘1500-5499’

set nat source rule 6501 outbound-interface ‘eth1’
set nat source rule 6501 protocol ‘tcp_udp’
set nat source rule 6501 source address ‘100.65.1.0-100.65.1.255’
set nat source rule 6501 translation address ‘XYZ.0.65.0-10.0.65.255’
set nat source rule 6501 translation options address-mapping ‘persistent’
set nat source rule 6501 translation port ‘9500-17499’

… etc …

set nat source rule 6507 outbound-interface ‘eth1’
set nat source rule 6507 protocol ‘tcp_udp’
set nat source rule 6507 source address ‘100.65.7.0-100.65.7.255’
set nat source rule 6507 translation address ‘XYZ.0.65.0-10.0.65.255’
set nat source rule 6507 translation options address-mapping ‘persistent’
set nat source rule 6507 translation port ‘57500-65499’

And it almost works, the port contrains are ok, but the IP-translation is mapped randomly, and I need to translate .1->.1 .2->.2
So I tried with prefix mapping (netmap 1:1) changing to

set source address ‘100.65.X.0/24’
set translation address ‘XYZ.0.65.0/24’

but it does not commit, with this error

“Cannot use ports with an IPv4net type translation address as it statically maps a whole network of addresses onto another network of addresses”

so I tried to set two consecutive rules, one for port-translation and one for address-mapping … but only the first matching rule is applied.

Any idea to implement it?
Thanks

Hi! Welcome to the community.

The first behavior that was observed is due to the default algorithm used for selecting the translation address from the available pool of IP addresses.

Could you kindly clarify the reason behind translating IP addresses as .1->.1 and .2->.2 + source-port translation?

Thank you.

I have to mantain the current situation.
In the current implemenation, with the MIKROTIK CCR, the netmap action maps the prefix 1:1 and translate the ports, so the rule
/ip firewall nat add action=netmap chain=srcnat out-interface=sfp-sfpplus1 protocol=tcp src-address=100.65.0.0/24 to-addresses=XYZ.0.65.0/24 to-ports=1500-5499
do everything for me in one line

There is a direct mapping pubIP+pubPORT and privIP and it’s easy to obtain the customer’s private ip when I know the public so the data to be logged are minimal

In vyatta-nat (SrcNatRule.pm) I see thate when you use a prefix-mapping, the rule is done with NETMAP --to so port translation is not permitted, and when you use a range-mapping, the MASQUERADE --to-ports is used.

Isn’t there any way to obtain the wanted behavior ? … without writing tons of rules …

Try the prefix /24 for NAT

if I use prefix /24 , no port translation because the rule is of type NETMAP

it does not commit with the error
“Cannot use ports with an IPv4net type translation address as it statically maps a whole network of addresses onto another network of addresses”

As I can see, the two-stages solution

set source rule 30 outbound-interface 'eth0'
set source rule 30 protocol 'tcp_udp'
set source rule 30 source address '100.66.0.0/24'
set source rule 30 translation port '1500-9499'

set source rule 40 outbound-interface 'eth0'
set source rule 40 protocol 'all'
set source rule 40 source address '100.66.0.0/24'
set source rule 40 translation address 'XYZ.0.66.0/24'

is translated to

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:VYATTA_PRE_DNAT_HOOK - [0:0]
:VYATTA_PRE_SNAT_HOOK - [0:0]
-A PREROUTING -j VYATTA_PRE_DNAT_HOOK
-A POSTROUTING -j VYATTA_PRE_SNAT_HOOK
-A POSTROUTING -s 100.66.0.0/24 -o eth0 -p tcp -m comment --comment "SRC-NAT-30 tcp_udp" -j SNAT --to-source :1500-9499
-A POSTROUTING -s 100.66.0.0/24 -o eth0 -p udp -m comment --comment "SRC-NAT-30 tcp_udp" -j SNAT --to-source :1500-9499
-A POSTROUTING -s 100.66.0.0/24 -o eth0 -m comment --comment SRC-NAT-40 -j NETMAP --to XYZ.0.66.0/24
-A VYATTA_PRE_DNAT_HOOK -j RETURN
-A VYATTA_PRE_SNAT_HOOK -j RETURN
COMMIT

the correct way to implement what I need should be

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:VYATTA_PRE_DNAT_HOOK - [0:0]
:VYATTA_PRE_SNAT_HOOK - [0:0]
-A PREROUTING -j VYATTA_PRE_DNAT_HOOK
-A POSTROUTING -j VYATTA_PRE_SNAT_HOOK
-A POSTROUTING -s 100.66.0.0/24 -o eth0 -m comment --comment SRC-NAT-40 -j NETMAP --to XYZ.0.66.0/24
-A VYATTA_PRE_DNAT_HOOK -j RETURN
-A VYATTA_PRE_SNAT_HOOK -s 100.66.0.0/24 -o eth0 -p tcp -m comment --comment "SRC-NAT-30 tcp_udp" -j SNAT --to-source :1500-9499
-A VYATTA_PRE_SNAT_HOOK -s 100.66.0.0/24 -o eth0 -p udp -m comment -- comment "SRC-NAT-30 tcp_udp" -j SNAT --to-source :1500-9499
-A VYATTA_PRE_SNAT_HOOK -j RETURN
COMMIT

is there a way to put a rule in the VYATTA_PRE_SNAT_HOOK chain ?

You can use your own nft nat tables and rules

sudo nft list ruleset

I can use my own nft table and rules, but it’s not inside the vyos configuration file … or not?
Anyway … any example of mixing nft rules with VyOS ones?

That’s , could I (for example) translate ports in my own chain and then use the (postrouting) snat by prefix rule (NETMAP) ? How ?

Nobody ? No solutions ?

It the last example you have shared seems that only difference, at the end, is order (rule 40 processed before rule 30)… So, Instead of defining rule 40, you use rule 20 (for example).
Do you get expected behavior?

If you have nft rule on how to achieve this on one single entry, as you got it in Mikrotik, can you share it? NAT backend documentation: https://manpages.debian.org/buster-backports/nftables/nftables.8.en.html#NAT_STATEMENTS

I don’t have a working nft rule, beacuse the first matching rule accept the packet and any second rule is ignored :frowning:
I’m still searching a working solution

Am I right that you are looking for such a solution?

https://github.com/Beiriz/GRCN

is that fullconenat?

As i know full-cone-nat mantains sourcPort … or not?
I need to have a obtain 8 private prefixes/24 mapped to a single public prefix/24, and to shrink programmatically port range from 65k to 8k segmenting ports in correlation with the third octet of the source address … this way a public IP+PORTrange is always directly attributable to a well-known private ip

Maybe … I give it a look , thanks

This solution could surely work, but …
I have about 2000 pppoe-users to manage, so for eachone I’d have three rules
add rule tcp ip saddr PRIVIP → PUBIP:PORTRANGE
add rule udp ip saddr PRIVIP → PUBIP:PORTRANGE
add rule all ip saddr PRIVIP → PUBIP

more than 6000 nat rules !!! is it safe?
and it’s completely outside VyOS configuration … invisible for those who have to edit or manage the VyOS stuff

Thanks @Viacheslav , but if I really have to write 6000 nftable rules I might as well write 6000 snat rules inside vyos conf, with the same result (I think).

Any other idea ? Don’t be shy :smiley:

You shouldn’t write any rules.
This script generates all rules automatically and will be loaded atomically.
I loaded atomically ~200K entries in 2sec with nftables itself

Maybe you will find another solution.
By the way, it is an excellent point to create a feature request on https://vyos.dev

As I understand nftables doesn’t support portmap with netmap

i.e correct rule which works

oifname "eth0" meta l4proto { tcp, udp } th dport 10-9499 ip saddr 100.64.0.0/24 counter snat to 192.168.122.14:10-9499

rule that doesn’t apply

oifname "eth0" meta l4proto { tcp, udp } th dport 10-9499 ip saddr 100.64.0.0/24 counter snat to 192.168.122.0/24:10-9499

Error

vyos@r14# sudo nft -f tmp.nft 
tmp.nft:16:128-132: Error: syntax error, unexpected string, expecting number
                oifname "eth0" meta l4proto { tcp, udp } th dport 10-9499 ip saddr 100.64.0.0/24 counter snat to 192.168.122.0/24:10-9499
                                                                                                                               ^^^^^

For that reason it were used maps per search ip

I created a feature request T5169

1 Like