From 7caf1568bbb6be59e5f13693c31f23ade9349daa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 May 2020 15:40:18 +0200 Subject: nat: T2198: destination nat template for iptables-restore --- data/templates/nat/nat-destination.tmpl | 13 +++++++++++++ data/templates/nat/nat-source.tmpl | 4 ++++ 2 files changed, 17 insertions(+) create mode 100644 data/templates/nat/nat-destination.tmpl create mode 100644 data/templates/nat/nat-source.tmpl (limited to 'data') diff --git a/data/templates/nat/nat-destination.tmpl b/data/templates/nat/nat-destination.tmpl new file mode 100644 index 000000000..ccd585264 --- /dev/null +++ b/data/templates/nat/nat-destination.tmpl @@ -0,0 +1,13 @@ +### Autogenerated by nat.py ### + +*nat +-A PREROUTING -j VYATTA_PRE_DNAT_HOOK +{% for r in destination -%} +{% if (',' in r.dest_port) or ('-' in r.dest_port) %} +-A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment "DST-NAT-{{ r.number }} {{ r.protocol }}" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +{% else %} +-A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m tcp --dport {{ r.dest_port }} -m comment --comment "DST-NAT-{{ r.number }} {{ r.protocol }}" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +{% endif %} +{% endfor %} +-A VYATTA_PRE_DNAT_HOOK -j RETURN +COMMIT diff --git a/data/templates/nat/nat-source.tmpl b/data/templates/nat/nat-source.tmpl new file mode 100644 index 000000000..41179ae9c --- /dev/null +++ b/data/templates/nat/nat-source.tmpl @@ -0,0 +1,4 @@ +### Autogenerated by nat.py ### +{% for r in source -%} +# {{ r.description }} +{% endfor %} -- cgit v1.2.3 From a5650abb6d575de2f696a934d52468992ac9f1e9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 May 2020 16:46:06 +0200 Subject: nat: T2198: migrate to common template for source/destination NAT --- data/templates/nat/iptables-restore.tmpl | 38 ++++++++++++++++++++++++++++++++ data/templates/nat/nat-destination.tmpl | 13 ----------- data/templates/nat/nat-source.tmpl | 4 ---- src/conf_mode/nat.py | 12 +++++----- 4 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 data/templates/nat/iptables-restore.tmpl delete mode 100644 data/templates/nat/nat-destination.tmpl delete mode 100644 data/templates/nat/nat-source.tmpl (limited to 'data') diff --git a/data/templates/nat/iptables-restore.tmpl b/data/templates/nat/iptables-restore.tmpl new file mode 100644 index 000000000..f20a05719 --- /dev/null +++ b/data/templates/nat/iptables-restore.tmpl @@ -0,0 +1,38 @@ +### Autogenerated by nat.py ### + +*nat +:PREROUTING ACCEPT [0:0] +:INPUT ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +:POSTROUTING ACCEPT [0:0] +:VYATTA_PRE_DNAT_HOOK - [0:0] +:VYATTA_PRE_SNAT_HOOK - [0:0] +-A PREROUTING -j VYATTA_PRE_DNAT_HOOK +{% for r in destination -%} +{% if (',' in r.dest_port) or ('-' in r.dest_port) %} + +{% if r.protocol == 'tcp_udp' %} +# protocol has been tcp_udp - create two distinct rules +-A PREROUTING -i {{ r.interface_in }} -p tcp -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +-A PREROUTING -i {{ r.interface_in }} -p udp -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +{% else %} +-A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment DST-NAT-{{ r.number }} -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +{%- endif %} + +{% else %} + +{% if r.protocol == 'tcp_udp' %} +# protocol has been tcp_udp - create two distinct rules +-A PREROUTING -i {{ r.interface_in }} -p tcp -m {{ r.protocol }} --dports {{ r.dest_port }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +-A PREROUTING -i {{ r.interface_in }} -p udp -m {{ r.protocol }} --dports {{ r.dest_port }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +{% else %} +-A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m {{ r.protocol }} --dport {{ r.dest_port }} -m comment --comment DST-NAT-{{ r.number }} -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} +{% endif %} + +{%- endif %} + +{% endfor %} +-A POSTROUTING -j VYATTA_PRE_SNAT_HOOK +-A VYATTA_PRE_DNAT_HOOK -j RETURN +-A VYATTA_PRE_SNAT_HOOK -j RETURN +COMMIT diff --git a/data/templates/nat/nat-destination.tmpl b/data/templates/nat/nat-destination.tmpl deleted file mode 100644 index ccd585264..000000000 --- a/data/templates/nat/nat-destination.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -### Autogenerated by nat.py ### - -*nat --A PREROUTING -j VYATTA_PRE_DNAT_HOOK -{% for r in destination -%} -{% if (',' in r.dest_port) or ('-' in r.dest_port) %} --A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment "DST-NAT-{{ r.number }} {{ r.protocol }}" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} -{% else %} --A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m tcp --dport {{ r.dest_port }} -m comment --comment "DST-NAT-{{ r.number }} {{ r.protocol }}" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} -{% endif %} -{% endfor %} --A VYATTA_PRE_DNAT_HOOK -j RETURN -COMMIT diff --git a/data/templates/nat/nat-source.tmpl b/data/templates/nat/nat-source.tmpl deleted file mode 100644 index 41179ae9c..000000000 --- a/data/templates/nat/nat-source.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -### Autogenerated by nat.py ### -{% for r in source -%} -# {{ r.description }} -{% endfor %} diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 538999f9a..b4e8c2053 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -19,6 +19,7 @@ from sys import exit from netifaces import interfaces from vyos.config import Config +from vyos.util import call from vyos.template import render from vyos import ConfigError @@ -27,8 +28,7 @@ default_config_data = { 'destination': [] } -nat_source_config = '/tmp/nat_source' -nat_destination_config = '/tmp/nat_destination' +iptables_nat_config = '/tmp/iptables_nat_config' def parse_source_destination(conf, source_dest): """ Common wrapper to read in both NAT source and destination CLI """ @@ -128,12 +128,14 @@ def generate(nat): if not nat: return None - render(nat_source_config, 'nat/nat-source.tmpl', nat, trim_blocks=True) - render(nat_destination_config, 'nat/nat-destination.tmpl', nat, trim_blocks=True) - + render(iptables_nat_config, 'nat/iptables-restore.tmpl', nat, trim_blocks=True) return None def apply(nat): + if not nat: + return None + + call(f'iptables-restore --test < {iptables_nat_config}') return None -- cgit v1.2.3 From a927192af24079e6d392e5cae0340441490c0091 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 1 May 2020 19:25:36 +0200 Subject: nat: T2198: move from iptables to nftables --- data/templates/firewall/nftables-nat.tmpl | 43 +++++++++++++++++++++++++++++++ data/templates/nat/iptables-restore.tmpl | 38 --------------------------- debian/control | 1 - src/conf_mode/nat.py | 17 +++++++++--- 4 files changed, 56 insertions(+), 43 deletions(-) create mode 100644 data/templates/firewall/nftables-nat.tmpl delete mode 100644 data/templates/nat/iptables-restore.tmpl (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl new file mode 100644 index 000000000..340ab3678 --- /dev/null +++ b/data/templates/firewall/nftables-nat.tmpl @@ -0,0 +1,43 @@ +#!/usr/sbin/nft -f + +# Start with a "clean" NAT table +flush table nat + +add chain ip raw NAT_CONNTRACK +add rule ip raw PREROUTING position 25 counter jump VYATTA_CT_HELPER +add rule ip raw PREROUTING position 17 counter jump NAT_CONNTRACK +add rule ip raw OUTPUT position 26 counter jump VYATTA_CT_HELPER +add rule ip raw OUTPUT position 21 counter jump NAT_CONNTRACK +add rule ip raw NAT_CONNTRACK counter accept + + +{% for r in destination -%} +{% if r.protocol == 'tcp_udp' %} +{# Special handling for protocol tcp_udp which is represented as two individual rules #} +add rule ip nat PREROUTING iifname "{{ r.interface_in }}" tcp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" +add rule ip nat PREROUTING iifname "{{ r.interface_in }}" udp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" +{% else %} +add rule ip nat PREROUTING iifname "{{ r.interface_in }}" {{ r.protocol }} dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }}" +{% endif %} +{% endfor %} + + +{% for r in source -%} +{% if r.log %} +{% if r.exclude %} +{% set value = 'EXCL' %} +{% elif r.translation_address == 'masquerade' %} +{% set value = 'MASQ' %} +{% endif %} +add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter log prefix "[NAT-SRC-{{ r.number }}-{{ value }}]" comment "SRC-NAT-{{ r.number }}" +{% endif %} + +{% if r.exclude %} +{% set value = 'return' %} +{% elif r.translation_address == 'masquerade' %} +{% set value = 'masquerade' %} +{% else %} +{% set value = 'snat to ' + r.translation_address %} +{% endif %} +add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter {{ value }} comment "SRC-NAT-{{ r.number }}" +{% endfor %} diff --git a/data/templates/nat/iptables-restore.tmpl b/data/templates/nat/iptables-restore.tmpl deleted file mode 100644 index f20a05719..000000000 --- a/data/templates/nat/iptables-restore.tmpl +++ /dev/null @@ -1,38 +0,0 @@ -### Autogenerated by nat.py ### - -*nat -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -:VYATTA_PRE_DNAT_HOOK - [0:0] -:VYATTA_PRE_SNAT_HOOK - [0:0] --A PREROUTING -j VYATTA_PRE_DNAT_HOOK -{% for r in destination -%} -{% if (',' in r.dest_port) or ('-' in r.dest_port) %} - -{% if r.protocol == 'tcp_udp' %} -# protocol has been tcp_udp - create two distinct rules --A PREROUTING -i {{ r.interface_in }} -p tcp -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} --A PREROUTING -i {{ r.interface_in }} -p udp -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} -{% else %} --A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m multiport --dports {{ r.dest_port | replace('-', ':') }} -m comment --comment DST-NAT-{{ r.number }} -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} -{%- endif %} - -{% else %} - -{% if r.protocol == 'tcp_udp' %} -# protocol has been tcp_udp - create two distinct rules --A PREROUTING -i {{ r.interface_in }} -p tcp -m {{ r.protocol }} --dports {{ r.dest_port }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} --A PREROUTING -i {{ r.interface_in }} -p udp -m {{ r.protocol }} --dports {{ r.dest_port }} -m comment --comment "DST-NAT-{{ r.number }} tcp_udp" -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} -{% else %} --A PREROUTING -i {{ r.interface_in }} -p {{ r.protocol }} -m {{ r.protocol }} --dport {{ r.dest_port }} -m comment --comment DST-NAT-{{ r.number }} -j DNAT --to-destination {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} -{% endif %} - -{%- endif %} - -{% endfor %} --A POSTROUTING -j VYATTA_PRE_SNAT_HOOK --A VYATTA_PRE_DNAT_HOOK -j RETURN --A VYATTA_PRE_SNAT_HOOK -j RETURN -COMMIT diff --git a/debian/control b/debian/control index c8fa8ca63..2aaca13ba 100644 --- a/debian/control +++ b/debian/control @@ -92,7 +92,6 @@ Depends: python3, pppoe, salt-minion, vyos-utils, - iptables, nftables, conntrack, ${shlibs:Depends}, diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index b4e8c2053..2e866fdf4 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os + from copy import deepcopy from sys import exit from netifaces import interfaces @@ -28,7 +30,14 @@ default_config_data = { 'destination': [] } -iptables_nat_config = '/tmp/iptables_nat_config' +iptables_nat_config = '/tmp/vyos-nat-rules.nft' + +def _check_kmod(): + modules = ['nft_nat', 'nft_chain_nat_ipv4'] + for module in modules: + if not os.path.exists(f'/sys/module/{module}'): + if call(f'modprobe {module}') != 0: + raise ConfigError(f'Loading Kernel module {module} failed') def parse_source_destination(conf, source_dest): """ Common wrapper to read in both NAT source and destination CLI """ @@ -128,19 +137,19 @@ def generate(nat): if not nat: return None - render(iptables_nat_config, 'nat/iptables-restore.tmpl', nat, trim_blocks=True) + render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755) return None def apply(nat): if not nat: return None - call(f'iptables-restore --test < {iptables_nat_config}') - + call(f'{iptables_nat_config}') return None if __name__ == '__main__': try: + _check_kmod() c = get_config() verify(c) generate(c) -- cgit v1.2.3 From 1c6ae6f7e7cf30d9598d2886bb3d2c34685a2c8c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 11 May 2020 18:58:05 +0200 Subject: nat: T2198: automatically determine handler numbers When instantiating NAT it is required to isntall some nftable jump targets. The targets need to be added after a specific other target thus we need to dynamically query the handler number. This is done by get_handler() which could be moved to vyos.util at a later point in time so it can be reused for a firewall rewrite. --- data/templates/firewall/nftables-nat.tmpl | 19 ++++++++++--- debian/control | 2 +- src/conf_mode/nat.py | 45 ++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 8 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 340ab3678..343807e79 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -3,11 +3,22 @@ # Start with a "clean" NAT table flush table nat +{% for rule in init_deinit -%} +# Add or remove conntrack helper rules for NAT operation- +{{ rule }} +{% endfor %} + add chain ip raw NAT_CONNTRACK -add rule ip raw PREROUTING position 25 counter jump VYATTA_CT_HELPER -add rule ip raw PREROUTING position 17 counter jump NAT_CONNTRACK -add rule ip raw OUTPUT position 26 counter jump VYATTA_CT_HELPER -add rule ip raw OUTPUT position 21 counter jump NAT_CONNTRACK + +# insert rule after VYATTA_CT_IGNORE +add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER +# insert rule after VYATTA_CT_PREROUTING_HOOK +add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK +# insert rule after VYATTA_CT_IGNORE +add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER +# insert rule after VYATTA_CT_PREROUTING_HOOK +add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK + add rule ip raw NAT_CONNTRACK counter accept diff --git a/debian/control b/debian/control index 2aaca13ba..609f46e4d 100644 --- a/debian/control +++ b/debian/control @@ -92,7 +92,7 @@ Depends: python3, pppoe, salt-minion, vyos-utils, - nftables, + nftables (>= 0.9.3), conntrack, ${shlibs:Depends}, ${misc:Depends} diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 2e866fdf4..128e2469c 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import json import os from copy import deepcopy @@ -21,24 +22,53 @@ from sys import exit from netifaces import interfaces from vyos.config import Config -from vyos.util import call from vyos.template import render +from vyos.util import call, cmd from vyos import ConfigError default_config_data = { - 'source': [], - 'destination': [] + 'prerouting_ct_helper': '', + 'prerouting_ct_conntrack': '', + 'output_ct_helper': '', + 'output_ct_conntrack': '', + 'destination': [], + 'source': [] } iptables_nat_config = '/tmp/vyos-nat-rules.nft' def _check_kmod(): + """ load required Kernel modules """ modules = ['nft_nat', 'nft_chain_nat_ipv4'] for module in modules: if not os.path.exists(f'/sys/module/{module}'): if call(f'modprobe {module}') != 0: raise ConfigError(f'Loading Kernel module {module} failed') + +def get_handler(chain, target): + """ Get handler number of given chain/target combination. Handler is + required when adding NAT/Conntrack helper targets """ + tmp = json.loads(cmd('nft -j list table raw')) + for rule in tmp.get('nftables'): + # We're only interested in rules - not chains + if not 'rule' in rule.keys(): + continue + + # Search for chain of interest + if rule['rule']['chain'] == chain: + for expr in rule['rule']['expr']: + # We're only interested in jump targets + if not 'jump' in expr.keys(): + continue + + # Search for target of interest + if expr['jump']['target'] == target: + return rule['rule']['handle'] + + return None + + def parse_source_destination(conf, source_dest): """ Common wrapper to read in both NAT source and destination CLI """ tmp = [] @@ -114,6 +144,11 @@ def get_config(): else: conf.set_level(['nat']) + nat['pre_ct_ignore'] = get_handler('PREROUTING', 'VYATTA_CT_IGNORE') + nat['pre_ct_conntrack'] = get_handler('PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') + nat['out_ct_ignore'] = get_handler('OUTPUT', 'VYATTA_CT_IGNORE') + nat['out_ct_conntrack'] = get_handler('OUTPUT', 'VYATTA_CT_OUTPUT_HOOK') + # use a common wrapper function to read in the source / destination # tree from the config - thus we do not need to replicate almost the # same code :-) @@ -126,6 +161,9 @@ def verify(nat): if not nat: return None + if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): + raise Exception('could not determine nftable ruleset handlers') + for rule in nat['source']: interface = rule['interface_out'] if interface and interface not in interfaces(): @@ -138,6 +176,7 @@ def generate(nat): return None render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755) + return None def apply(nat): -- cgit v1.2.3 From fda762065c03d55c05682bf9834354c0edca3e97 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 11 May 2020 19:32:32 +0200 Subject: nat: T2198: implement deletion of NAT subsystem --- data/templates/firewall/nftables-nat.tmpl | 20 ++++++++++++++------ src/conf_mode/nat.py | 31 +++++++++++++++++++------------ 2 files changed, 33 insertions(+), 18 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 343807e79..671cd0920 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -8,18 +8,26 @@ flush table nat {{ rule }} {% endfor %} + +{% if deleted %} +# NAT if going to be disabled - remove rules and targets from nftables +delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} +delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} +delete rule ip raw OUTPUT handle {{ out_ct_ignore }} +delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} + +delete chain ip raw NAT_CONNTRACK + +{% else %} +# NAT if enabled - add targets to nftables add chain ip raw NAT_CONNTRACK +add rule ip raw NAT_CONNTRACK counter accept -# insert rule after VYATTA_CT_IGNORE add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER -# insert rule after VYATTA_CT_PREROUTING_HOOK add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -# insert rule after VYATTA_CT_IGNORE add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER -# insert rule after VYATTA_CT_PREROUTING_HOOK add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK - -add rule ip raw NAT_CONNTRACK counter accept +{% endif %} {% for r in destination -%} diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 128e2469c..916f63f09 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -27,11 +27,12 @@ from vyos.util import call, cmd from vyos import ConfigError default_config_data = { - 'prerouting_ct_helper': '', - 'prerouting_ct_conntrack': '', - 'output_ct_helper': '', - 'output_ct_conntrack': '', + 'deleted': False, 'destination': [], + 'pre_ct_helper': '', + 'pre_ct_conntrack': '', + 'out_ct_helper': '', + 'out_ct_conntrack': '', 'source': [] } @@ -139,11 +140,21 @@ def parse_source_destination(conf, source_dest): def get_config(): nat = deepcopy(default_config_data) conf = Config() + if not conf.exists(['nat']): - return None + # Retrieve current table handler positions + nat['pre_ct_ignore'] = get_handler('PREROUTING', 'VYATTA_CT_HELPER') + nat['pre_ct_conntrack'] = get_handler('PREROUTING', 'NAT_CONNTRACK') + nat['out_ct_ignore'] = get_handler('OUTPUT', 'VYATTA_CT_HELPER') + nat['out_ct_conntrack'] = get_handler('OUTPUT', 'NAT_CONNTRACK') + + nat['deleted'] = True + + return nat else: conf.set_level(['nat']) + # Retrieve current table handler positions nat['pre_ct_ignore'] = get_handler('PREROUTING', 'VYATTA_CT_IGNORE') nat['pre_ct_conntrack'] = get_handler('PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') nat['out_ct_ignore'] = get_handler('OUTPUT', 'VYATTA_CT_IGNORE') @@ -158,7 +169,8 @@ def get_config(): return nat def verify(nat): - if not nat: + if nat['deleted']: + # no need to verify the CLI as NAT is going to be deactivated return None if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): @@ -172,18 +184,13 @@ def verify(nat): return None def generate(nat): - if not nat: - return None - render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755) return None def apply(nat): - if not nat: - return None + cmd(f'{iptables_nat_config}') - call(f'{iptables_nat_config}') return None if __name__ == '__main__': -- cgit v1.2.3 From cc2ad34ce61e205454c4676a5bde77629d463964 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 12 May 2020 17:43:03 +0200 Subject: nat: T2198: make use of jmespath when walking nftables JSON output --- data/templates/firewall/nftables-nat.tmpl | 91 ++++++++++++++----------------- debian/control | 1 + src/conf_mode/nat.py | 71 +++++++++++++----------- 3 files changed, 83 insertions(+), 80 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 671cd0920..161ef27fb 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -1,62 +1,55 @@ #!/usr/sbin/nft -f -# Start with a "clean" NAT table +# Start with clean NAT table flush table nat -{% for rule in init_deinit -%} -# Add or remove conntrack helper rules for NAT operation- -{{ rule }} -{% endfor %} - - -{% if deleted %} -# NAT if going to be disabled - remove rules and targets from nftables -delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} -delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} -delete rule ip raw OUTPUT handle {{ out_ct_ignore }} -delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} - -delete chain ip raw NAT_CONNTRACK - -{% else %} -# NAT if enabled - add targets to nftables -add chain ip raw NAT_CONNTRACK -add rule ip raw NAT_CONNTRACK counter accept - -add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER -add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER -add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% if helper_functions == 'remove' %} + # NAT if going to be disabled - remove rules and targets from nftables + delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} + delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} + delete rule ip raw OUTPUT handle {{ out_ct_ignore }} + delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} + + delete chain ip raw NAT_CONNTRACK +{% elif helper_functions == 'add' %} + # NAT if enabled - add targets to nftables + add chain ip raw NAT_CONNTRACK + add rule ip raw NAT_CONNTRACK counter accept + + add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER + add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK + add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER + add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK {% endif %} {% for r in destination -%} -{% if r.protocol == 'tcp_udp' %} -{# Special handling for protocol tcp_udp which is represented as two individual rules #} -add rule ip nat PREROUTING iifname "{{ r.interface_in }}" tcp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" -add rule ip nat PREROUTING iifname "{{ r.interface_in }}" udp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" -{% else %} -add rule ip nat PREROUTING iifname "{{ r.interface_in }}" {{ r.protocol }} dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }}" -{% endif %} +{% if r.protocol == 'tcp_udp' %} + {# Special handling for protocol tcp_udp which is represented as two individual rules #} + add rule ip nat PREROUTING iifname "{{ r.interface_in }}" tcp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" + add rule ip nat PREROUTING iifname "{{ r.interface_in }}" udp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" +{% else %} + add rule ip nat PREROUTING iifname "{{ r.interface_in }}" {{ r.protocol }} dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }}" +{% endif %} {% endfor %} {% for r in source -%} -{% if r.log %} -{% if r.exclude %} -{% set value = 'EXCL' %} -{% elif r.translation_address == 'masquerade' %} -{% set value = 'MASQ' %} -{% endif %} -add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter log prefix "[NAT-SRC-{{ r.number }}-{{ value }}]" comment "SRC-NAT-{{ r.number }}" -{% endif %} - -{% if r.exclude %} -{% set value = 'return' %} -{% elif r.translation_address == 'masquerade' %} -{% set value = 'masquerade' %} -{% else %} -{% set value = 'snat to ' + r.translation_address %} -{% endif %} -add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter {{ value }} comment "SRC-NAT-{{ r.number }}" +{% if r.log %} +{% if r.exclude %} +{% set value = 'EXCL' %} +{% elif r.translation_address == 'masquerade' %} +{% set value = 'MASQ' %} +{% endif %} + add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter log prefix "[NAT-SRC-{{ r.number }}-{{ value }}]" comment "SRC-NAT-{{ r.number }}" +{% endif %} + +{% if r.exclude %} +{% set value = 'return' %} +{% elif r.translation_address == 'masquerade' %} +{% set value = 'masquerade' %} +{% else %} +{% set value = 'snat to ' + r.translation_address %} +{% endif %} + add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter {{ value }} comment "SRC-NAT-{{ r.number }}" {% endfor %} diff --git a/debian/control b/debian/control index 609f46e4d..323b8130f 100644 --- a/debian/control +++ b/debian/control @@ -32,6 +32,7 @@ Depends: python3, python3-waitress, python3-netaddr, python3-zmq, + python3-jmespath, cron, easy-rsa, ipaddrcheck, diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 916f63f09..580a06136 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import jmespath import json import os @@ -29,6 +30,7 @@ from vyos import ConfigError default_config_data = { 'deleted': False, 'destination': [], + 'helper_functions': None, 'pre_ct_helper': '', 'pre_ct_conntrack': '', 'out_ct_helper': '', @@ -47,25 +49,15 @@ def _check_kmod(): raise ConfigError(f'Loading Kernel module {module} failed') -def get_handler(chain, target): - """ Get handler number of given chain/target combination. Handler is - required when adding NAT/Conntrack helper targets """ - tmp = json.loads(cmd('nft -j list table raw')) - for rule in tmp.get('nftables'): - # We're only interested in rules - not chains - if not 'rule' in rule.keys(): +def get_handler(json, chain, target): + """ Get nftable rule handler number of given chain/target combination. + Handler is required when adding NAT/Conntrack helper targets """ + for x in json: + if x['chain'] != chain: continue - - # Search for chain of interest - if rule['rule']['chain'] == chain: - for expr in rule['rule']['expr']: - # We're only interested in jump targets - if not 'jump' in expr.keys(): - continue - - # Search for target of interest - if expr['jump']['target'] == target: - return rule['rule']['handle'] + if x['target'] != target: + continue + return x['handle'] return None @@ -141,24 +133,40 @@ def get_config(): nat = deepcopy(default_config_data) conf = Config() + # read in current nftable (once) for further processing + tmp = cmd('nft -j list table raw') + nftable_json = json.loads(tmp) + + # condense the full JSON table into a list with only relevand informations + pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' + condensed_json = jmespath.search(pattern, nftable_json) + if not conf.exists(['nat']): + nat['helper_functions'] = 'remove' + # Retrieve current table handler positions - nat['pre_ct_ignore'] = get_handler('PREROUTING', 'VYATTA_CT_HELPER') - nat['pre_ct_conntrack'] = get_handler('PREROUTING', 'NAT_CONNTRACK') - nat['out_ct_ignore'] = get_handler('OUTPUT', 'VYATTA_CT_HELPER') - nat['out_ct_conntrack'] = get_handler('OUTPUT', 'NAT_CONNTRACK') + nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER') + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') + nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') nat['deleted'] = True return nat - else: - conf.set_level(['nat']) - # Retrieve current table handler positions - nat['pre_ct_ignore'] = get_handler('PREROUTING', 'VYATTA_CT_IGNORE') - nat['pre_ct_conntrack'] = get_handler('PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') - nat['out_ct_ignore'] = get_handler('OUTPUT', 'VYATTA_CT_IGNORE') - nat['out_ct_conntrack'] = get_handler('OUTPUT', 'VYATTA_CT_OUTPUT_HOOK') + # check if NAT connection tracking helpers need to be set up - this has to + # be done only once + if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): + nat['helper_functions'] = 'add' + + # Retrieve current table handler positions + nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE') + nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') + nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE') + nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK') + + # set config level for parsing in NAT configuration + conf.set_level(['nat']) # use a common wrapper function to read in the source / destination # tree from the config - thus we do not need to replicate almost the @@ -173,8 +181,9 @@ def verify(nat): # no need to verify the CLI as NAT is going to be deactivated return None - if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): - raise Exception('could not determine nftable ruleset handlers') + if nat['helper_functions']: + if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): + raise Exception('could not determine nftable ruleset handlers') for rule in nat['source']: interface = rule['interface_out'] -- cgit v1.2.3 From 682bfd2c869acbf9f7c6dc681e28ca703c290d7f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 15 May 2020 21:43:39 +0200 Subject: nat: T2198: restructure DNAT template Make the entire template more maintainable --- data/templates/firewall/nftables-nat.tmpl | 146 +++++++++++++++++++----------- 1 file changed, 91 insertions(+), 55 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 161ef27fb..01dcec19f 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -1,55 +1,91 @@ -#!/usr/sbin/nft -f - -# Start with clean NAT table -flush table nat - -{% if helper_functions == 'remove' %} - # NAT if going to be disabled - remove rules and targets from nftables - delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} - delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} - delete rule ip raw OUTPUT handle {{ out_ct_ignore }} - delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} - - delete chain ip raw NAT_CONNTRACK -{% elif helper_functions == 'add' %} - # NAT if enabled - add targets to nftables - add chain ip raw NAT_CONNTRACK - add rule ip raw NAT_CONNTRACK counter accept - - add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER - add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK - add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER - add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} - - -{% for r in destination -%} -{% if r.protocol == 'tcp_udp' %} - {# Special handling for protocol tcp_udp which is represented as two individual rules #} - add rule ip nat PREROUTING iifname "{{ r.interface_in }}" tcp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" - add rule ip nat PREROUTING iifname "{{ r.interface_in }}" udp dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }} tcp_udp" -{% else %} - add rule ip nat PREROUTING iifname "{{ r.interface_in }}" {{ r.protocol }} dport { {{ r.dest_port }} } counter dnat to {{ r.translation_address }}{{ ":" + r.translation_port if r.translation_port }} comment "DST-NAT-{{ r.number }}" -{% endif %} -{% endfor %} - - -{% for r in source -%} -{% if r.log %} -{% if r.exclude %} -{% set value = 'EXCL' %} -{% elif r.translation_address == 'masquerade' %} -{% set value = 'MASQ' %} -{% endif %} - add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter log prefix "[NAT-SRC-{{ r.number }}-{{ value }}]" comment "SRC-NAT-{{ r.number }}" -{% endif %} - -{% if r.exclude %} -{% set value = 'return' %} -{% elif r.translation_address == 'masquerade' %} -{% set value = 'masquerade' %} -{% else %} -{% set value = 'snat to ' + r.translation_address %} -{% endif %} - add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter {{ value }} comment "SRC-NAT-{{ r.number }}" -{% endfor %} +#!/usr/sbin/nft -f + +# Start with clean NAT table +flush table nat + +{% if helper_functions == 'remove' %} + # NAT if going to be disabled - remove rules and targets from nftables + delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} + delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} + delete rule ip raw OUTPUT handle {{ out_ct_ignore }} + delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} + + delete chain ip raw NAT_CONNTRACK +{% elif helper_functions == 'add' %} + # NAT if enabled - add targets to nftables + add chain ip raw NAT_CONNTRACK + add rule ip raw NAT_CONNTRACK counter accept + + add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER + add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK + add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER + add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% endif %} + + +{% for r in destination -%} +{% set chain = "PREROUTING" %} +{% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} +{% set dst_port = "dport { " + r.dest_port +" }" %} +{% set trns_addr = r.translation_address %} +{% set trns_port = ":" + r.translation_port if r.translation_port %} +{% set trns = "dnat to " + trns_addr + trns_port if trns_port %} +{% set comment = "DST-NAT-" + r.number %} +{% set iface = "iifname " + r.interface_in %} + +{% if r.log %} +{% if r.exclude %} +{% set log = "[" + comment + "-EXCL]" %} +{% elif r.translation_address == 'masquerade' %} +{% set log = "[" + comment + "-MASQ]" %} +{% else %} +{% set log = "[" + comment + "]" %} +{% endif %} +{% endif %} + +{% if r.exclude %} +{# rule has been marked as "exclude" thus we simply return here #} +{% set trns = "return" %} +{% endif %} + + +{% if r.protocol == 'tcp_udp' %} +{# Special handling for protocol tcp_udp which is represented as two individual rules #} +{% if log %} +add rule ip nat {{ chain }} {{ iface }} tcp {{ dst_port }} counter log prefix "{{ log }}" comment "{{ comment }} tcp_udp" +{% endif %} +add rule ip nat {{ chain }} {{ iface }} tcp {{ dst_port }} counter {{ trns }} comment {{ comment }} +{% if log %} +add rule ip nat {{ chain }} {{ iface }} udp {{ dst_port }} counter log prefix "{{ log }}" comment "{{ comment }} tcp_udp" +{% endif %} +add rule ip nat {{ chain }} {{ iface }} udp {{ dst_port }} counter {{ trns }} comment {{ comment }} +{% else %} + +{% if log %} +add rule ip nat {{ chain }} {{ iface }} {{ r.protocol }} counter log prefix "{{ log }}" comment {{ comment }} +{% endif %} +add rule ip nat {{ chain }} {{ iface }} {{ dst_addr }} {{ r.protocol }} {{ dst_port }} counter {{ trns }} comment {{ comment }} +{% endif %} +{% endfor %} + + +{% for r in source -%} +{% if r.log %} +{% if r.exclude %} +{% set value = 'EXCL' %} +{% elif r.translation_address == 'masquerade' %} +{% set value = 'MASQ' %} +{% endif %} + add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter log prefix "[NAT-SRC-{{ r.number }}-{{ value }}]" comment "SRC-NAT-{{ r.number }}" +{% endif %} + +{% if r.exclude %} +{% set value = 'return' %} +{% elif r.translation_address == 'masquerade' %} +{% set value = 'masquerade' %} +{% else %} +{% set value = 'snat to ' + r.translation_address %} +{% endif %} + add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter {{ value }} comment "SRC-NAT-{{ r.number }}" +{% endfor %} + -- cgit v1.2.3 From f75db67c495c0e9e251bebba46b75e9d085dece0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 15 May 2020 21:47:08 +0200 Subject: nat: T2198: do not run DNAT rule if rule is disabled --- data/templates/firewall/nftables-nat.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 01dcec19f..528c4d82a 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -23,7 +23,7 @@ flush table nat {% endif %} -{% for r in destination -%} +{% for r in destination if not r.disabled -%} {% set chain = "PREROUTING" %} {% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} {% set dst_port = "dport { " + r.dest_port +" }" %} @@ -48,7 +48,6 @@ flush table nat {% set trns = "return" %} {% endif %} - {% if r.protocol == 'tcp_udp' %} {# Special handling for protocol tcp_udp which is represented as two individual rules #} {% if log %} -- cgit v1.2.3 From 9cec8471dae531072946daf5dcb74a0a9fe1e86c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 15 May 2020 23:01:27 +0200 Subject: nat: T2198: sync generated DNAT rules with VyOS 1.2 The generated NAT rules in VyOS 1.2 are compared to the generated nftables ruleset in VyOS 1.3 this was done by converting the 1.2 iptables ruleset to nftables and then do the diff. To convert from iptables to nftables use the following command: $ iptables-save -t nat > /tmp/tmp.iptables $ iptables-restore-translate -f /tmp/tmp.iptables The following CLI options have been used for testing: set nat destination rule 10 description 'foo-10' set nat destination rule 10 destination address '1.1.1.1' set nat destination rule 10 destination port '1111' set nat destination rule 10 exclude set nat destination rule 10 inbound-interface 'eth0.202' set nat destination rule 10 log set nat destination rule 10 protocol 'tcp_udp' set nat destination rule 10 translation address '192.0.2.10' set nat destination rule 15 description 'foo-10' set nat destination rule 15 destination address '1.1.1.1' set nat destination rule 15 exclude set nat destination rule 15 inbound-interface 'eth0.202' set nat destination rule 15 log set nat destination rule 15 protocol 'tcp_udp' set nat destination rule 15 translation address '192.0.2.10' set nat destination rule 20 description 'foo-20' set nat destination rule 20 destination address '2.2.2.2' set nat destination rule 20 inbound-interface 'eth0.201' set nat destination rule 20 log set nat destination rule 20 protocol 'tcp' set nat destination rule 20 translation address '192.0.2.10' --- data/templates/firewall/nftables-nat.tmpl | 60 +++++++++++++++++-------------- src/conf_mode/nat.py | 4 +-- 2 files changed, 35 insertions(+), 29 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 528c4d82a..929cae563 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -4,34 +4,33 @@ flush table nat {% if helper_functions == 'remove' %} - # NAT if going to be disabled - remove rules and targets from nftables - delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} - delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} - delete rule ip raw OUTPUT handle {{ out_ct_ignore }} - delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} +{# NAT if going to be disabled - remove rules and targets from nftables #} +delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} +delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} +delete rule ip raw OUTPUT handle {{ out_ct_ignore }} +delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} + +delete chain ip raw NAT_CONNTRACK - delete chain ip raw NAT_CONNTRACK {% elif helper_functions == 'add' %} - # NAT if enabled - add targets to nftables - add chain ip raw NAT_CONNTRACK - add rule ip raw NAT_CONNTRACK counter accept - - add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER - add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK - add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER - add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} +{# NAT if enabled - add targets to nftables #} +add chain ip raw NAT_CONNTRACK +add rule ip raw NAT_CONNTRACK counter accept +add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER +add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK +add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER +add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% endif %} {% for r in destination if not r.disabled -%} {% set chain = "PREROUTING" %} {% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} -{% set dst_port = "dport { " + r.dest_port +" }" %} -{% set trns_addr = r.translation_address %} +{% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} +{% set trns_addr = "dnat to " + r.translation_address %} {% set trns_port = ":" + r.translation_port if r.translation_port %} -{% set trns = "dnat to " + trns_addr + trns_port if trns_port %} {% set comment = "DST-NAT-" + r.number %} -{% set iface = "iifname " + r.interface_in %} +{% set iface = r.interface_in %} {% if r.log %} {% if r.exclude %} @@ -45,25 +44,32 @@ flush table nat {% if r.exclude %} {# rule has been marked as "exclude" thus we simply return here #} -{% set trns = "return" %} +{% set trns_addr = "return" %} +{% set trns_port = "" %} {% endif %} {% if r.protocol == 'tcp_udp' %} {# Special handling for protocol tcp_udp which is represented as two individual rules #} +{% set comment = comment + " tcp_udp" %} {% if log %} -add rule ip nat {{ chain }} {{ iface }} tcp {{ dst_port }} counter log prefix "{{ log }}" comment "{{ comment }} tcp_udp" + +{% set tcp_dst_port = "tcp " + dst_port if dst_port else "ip protocol tcp" %} +{% set udp_dst_port = "udp " + dst_port if dst_port else "ip protocol udp" %} + +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} {{ iface }} tcp {{ dst_port }} counter {{ trns }} comment {{ comment }} +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% if log %} -add rule ip nat {{ chain }} {{ iface }} udp {{ dst_port }} counter log prefix "{{ log }}" comment "{{ comment }} tcp_udp" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} {{ iface }} udp {{ dst_port }} counter {{ trns }} comment {{ comment }} -{% else %} +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +{% else %} +{% set proto_dst_port = dst_port if dst_port else "ip protocol " + r.protocol %} {% if log %} -add rule ip nat {{ chain }} {{ iface }} {{ r.protocol }} counter log prefix "{{ log }}" comment {{ comment }} +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} {{ iface }} {{ dst_addr }} {{ r.protocol }} {{ dst_port }} counter {{ trns }} comment {{ comment }} +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% endif %} {% endfor %} diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 4d739068f..13edca846 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -94,7 +94,7 @@ def parse_source_destination(conf, source_dest): 'description': '', 'dest_address': '', 'dest_port': '', - 'disable': False, + 'disabled': False, 'exclude': False, 'interface_in': '', 'interface_out': '', @@ -118,7 +118,7 @@ def parse_source_destination(conf, source_dest): rule['dest_port'] = conf.return_value(['destination', 'port']) if conf.exists(['disable']): - rule['disable'] = True + rule['disabled'] = True if conf.exists(['exclude']): rule['exclude'] = True -- cgit v1.2.3 From 8062afa8a5beb73464e911cf7c5ca66f58585d0b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 15 May 2020 23:22:22 +0200 Subject: nat: T2198: sync generated SNAT rules with VyOS 1.2 The generated NAT rules in VyOS 1.2 are compared to the generated nftables ruleset in VyOS 1.3 this was done by converting the 1.2 iptables ruleset to nftables and then do the diff. To convert from iptables to nftables use the following command: $ iptables-save -t nat > /tmp/tmp.iptables $ iptables-restore-translate -f /tmp/tmp.iptables The following CLI options have been used for testing: set nat source rule 10 description 'foo-10' set nat source rule 10 destination address '1.1.1.1' set nat source rule 10 destination port '1111' set nat source rule 10 exclude set nat source rule 10 log 'enable' set nat source rule 10 outbound-interface 'eth0.202' set nat source rule 10 protocol 'tcp_udp' set nat source rule 10 translation address '192.0.2.10' set nat source rule 15 description 'foo-10' set nat source rule 15 destination address '1.1.1.1' set nat source rule 15 exclude set nat source rule 15 log 'enable' set nat source rule 15 outbound-interface 'eth0.202' set nat source rule 15 protocol 'tcp_udp' set nat source rule 15 translation address '192.0.2.10' set nat source rule 20 description 'foo-20' set nat source rule 20 destination address '2.2.2.2' set nat source rule 20 log 'enable' set nat source rule 20 outbound-interface 'eth0.201' set nat source rule 20 protocol 'tcp' set nat source rule 20 translation address '192.0.2.10' set nat source rule 100 outbound-interface 'eth0.202' set nat source rule 100 protocol 'all' set nat source rule 100 source address '192.0.2.0/26' set nat source rule 100 translation address 'masquerade' --- data/templates/firewall/nftables-nat.tmpl | 49 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 929cae563..928f4ecfe 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -74,23 +74,52 @@ add rule ip nat {{ chain }} iifname "{{ iface }}" {{ proto_dst_port }} {{ dst_ad {% endfor %} -{% for r in source -%} +{% for r in source if not r.disabled -%} +{% set chain = "POSTROUTING" %} +{% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} +{% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} +{% set trns_addr = "snat to " + r.translation_address %} +{% set trns_port = ":" + r.translation_port if r.translation_port %} +{% set comment = "SRC-NAT-" + r.number %} +{% set iface = r.interface_out %} + {% if r.log %} {% if r.exclude %} -{% set value = 'EXCL' %} +{% set log = "[" + comment + "-EXCL]" %} {% elif r.translation_address == 'masquerade' %} -{% set value = 'MASQ' %} +{% set log = "[" + comment + "-MASQ]" %} +{% else %} +{% set log = "[" + comment + "]" %} {% endif %} - add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter log prefix "[NAT-SRC-{{ r.number }}-{{ value }}]" comment "SRC-NAT-{{ r.number }}" {% endif %} {% if r.exclude %} -{% set value = 'return' %} -{% elif r.translation_address == 'masquerade' %} -{% set value = 'masquerade' %} +{# rule has been marked as "exclude" thus we simply return here #} +{% set trns_addr = "return" %} +{% set trns_port = "" %} +{% endif %} + +{% if r.protocol == 'tcp_udp' %} +{# Special handling for protocol tcp_udp which is represented as two individual rules #} +{% set comment = comment + " tcp_udp" %} +{% if log %} + +{% set tcp_dst_port = "tcp " + dst_port if dst_port else "ip protocol tcp" %} +{% set udp_dst_port = "udp " + dst_port if dst_port else "ip protocol udp" %} + +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +{% endif %} +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +{% if log %} +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +{% endif %} +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" + {% else %} -{% set value = 'snat to ' + r.translation_address %} +{% set proto_dst_port = dst_port if dst_port else "ip protocol " + r.protocol %} +{% if log %} +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +{% endif %} +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% endif %} - add rule ip nat POSTROUTING oifname "{{ r.interface_out }}" ip saddr {{ r.source_address }} counter {{ value }} comment "SRC-NAT-{{ r.number }}" {% endfor %} - -- cgit v1.2.3 From cf6dcb61e1f102f3a9b9edb86eeecac92f944d0d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 16 May 2020 00:16:40 +0200 Subject: nat: T2198: add support for SNAT based on source addresses CLI commands used for ruleset generation: set nat source rule 100 outbound-interface 'eth0.202' set nat source rule 100 protocol 'all' set nat source rule 100 source address '192.0.2.0/26' set nat source rule 100 translation address 'masquerade' set nat source rule 110 outbound-interface 'eth0.202' set nat source rule 110 protocol 'tcp' set nat source rule 110 source address '192.0.2.0/26' set nat source rule 110 source port '5556' set nat source rule 110 translation address 'masquerade' --- data/templates/firewall/nftables-nat.tmpl | 38 ++++++++++++++++++++----------- src/conf_mode/nat.py | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 928f4ecfe..9bab8b363 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -23,8 +23,11 @@ add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPE add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK {% endif %} + {% for r in destination if not r.disabled -%} {% set chain = "PREROUTING" %} +{% set src_addr = "ip saddr " + r.source_address if r.source_address %} +{% set src_port = "sport { " + r.source_port +" }" if r.source_port %} {% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} {% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} {% set trns_addr = "dnat to " + r.translation_address %} @@ -56,29 +59,33 @@ add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRAC {% set tcp_dst_port = "tcp " + dst_port if dst_port else "ip protocol tcp" %} {% set udp_dst_port = "udp " + dst_port if dst_port else "ip protocol udp" %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% if log %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% else %} {% set proto_dst_port = dst_port if dst_port else "ip protocol " + r.protocol %} +{% set proto_dst_port = "" if r.protocol == "all" %} + {% if log %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% endif %} {% endfor %} {% for r in source if not r.disabled -%} {% set chain = "POSTROUTING" %} +{% set src_addr = "ip saddr " + r.source_address if r.source_address %} +{% set src_port = "sport { " + r.source_port +" }" if r.source_port %} {% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} {% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} -{% set trns_addr = "snat to " + r.translation_address %} +{% set trns_addr = "snat to " + r.translation_address if r.translation_address != "masquerade" else "masquerade" %} {% set trns_port = ":" + r.translation_port if r.translation_port %} {% set comment = "SRC-NAT-" + r.number %} {% set iface = r.interface_out %} @@ -106,20 +113,25 @@ add rule ip nat {{ chain }} iifname "{{ iface }}" {{ proto_dst_port }} {{ dst_ad {% set tcp_dst_port = "tcp " + dst_port if dst_port else "ip protocol tcp" %} {% set udp_dst_port = "udp " + dst_port if dst_port else "ip protocol udp" %} +{% set tcp_src_port = "tcp " + src_port if src_port %} +{% set udp_src_port = "udp " + src_port if src_port %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_src_port }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_src_port }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% if log %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_src_port }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_src_port }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% else %} {% set proto_dst_port = dst_port if dst_port else "ip protocol " + r.protocol %} +{% set proto_dst_port = proto_dst_port if r.protocol != "all" %} +{% set proto_src_port = r.protocol + " " + src_port if r.protocol != "all" else src_port %} + {% if log %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ src_addr }} {{ proto_src_port }} {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" {% endif %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +add rule ip nat {{ chain }} oifname "{{ iface }}" {{ src_addr }} {{ proto_src_port }} {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" {% endif %} {% endfor %} diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index ebac6bfc0..5cb1af1f1 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -65,7 +65,7 @@ def get_handler(json, chain, target): def verify_rule(rule, err_msg): """ Common verify steps used for both source and destination NAT """ - if rule['translation_port'] or rule['dest_port']: + if rule['translation_port'] or rule['dest_port'] or rule['source_port']: if rule['protocol'] not in ['tcp', 'udp', 'tcp_udp']: proto = rule['protocol'] raise ConfigError(f'{err_msg} ports can only be specified when protocol is "tcp", "udp" or "tcp_udp" (currently "{proto}")') -- cgit v1.2.3 From d7662ecfff558192a5b5009679108ca58c8518fa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 16 May 2020 15:29:31 +0200 Subject: nat: T2198: restructure DNAT template part for less duplicated code Build up only one output rule string by appending the configuration part by part. --- data/templates/firewall/nftables-nat.tmpl | 104 +++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 32 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 9bab8b363..5ce110d82 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -5,10 +5,12 @@ flush table nat {% if helper_functions == 'remove' %} {# NAT if going to be disabled - remove rules and targets from nftables #} -delete rule ip raw PREROUTING handle {{ pre_ct_ignore }} -delete rule ip raw PREROUTING handle {{ pre_ct_conntrack }} -delete rule ip raw OUTPUT handle {{ out_ct_ignore }} -delete rule ip raw OUTPUT handle {{ out_ct_conntrack }} + +{% set base_command = "delete rule ip raw" %} +{{ base_command }} PREROUTING handle {{ pre_ct_ignore }} +{{ base_command }} OUTPUT handle {{ out_ct_ignore }} +{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} +{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} delete chain ip raw NAT_CONNTRACK @@ -17,13 +19,17 @@ delete chain ip raw NAT_CONNTRACK add chain ip raw NAT_CONNTRACK add rule ip raw NAT_CONNTRACK counter accept -add rule ip raw PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER -add rule ip raw PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -add rule ip raw OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER -add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} +{% set base_command = "add rule ip raw" %} +{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER +{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER +{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK +{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK +{% endif %} +# +# Destination NAT rules build up here +# {% for r in destination if not r.disabled -%} {% set chain = "PREROUTING" %} {% set src_addr = "ip saddr " + r.source_address if r.source_address %} @@ -32,16 +38,24 @@ add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRAC {% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} {% set trns_addr = "dnat to " + r.translation_address %} {% set trns_port = ":" + r.translation_port if r.translation_port %} +{% set interface = " iifname \"" + r.interface_in + "\"" %} {% set comment = "DST-NAT-" + r.number %} -{% set iface = r.interface_in %} + +{% if r.protocol == "tcp_udp" %} +{% set protocol = "tcp" %} +{% set comment = comment + " tcp_udp" %} +{% else %} +{% set protocol = r.protocol %} +{% endif %} {% if r.log %} +{% set base_log = "[NAT-DST-" + r.number %} {% if r.exclude %} -{% set log = "[" + comment + "-EXCL]" %} +{% set log = base_log + "-EXCL]" %} {% elif r.translation_address == 'masquerade' %} -{% set log = "[" + comment + "-MASQ]" %} +{% set log = base_log + "-MASQ]" %} {% else %} -{% set log = "[" + comment + "]" %} +{% set log = base_log + "]" %} {% endif %} {% endif %} @@ -51,34 +65,60 @@ add rule ip raw OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRAC {% set trns_port = "" %} {% endif %} -{% if r.protocol == 'tcp_udp' %} -{# Special handling for protocol tcp_udp which is represented as two individual rules #} -{% set comment = comment + " tcp_udp" %} -{% if log %} +{% set output = "add rule ip nat " + chain + interface + " counter" %} +{% set output = output + " comment \"" + comment + "\"" %} -{% set tcp_dst_port = "tcp " + dst_port if dst_port else "ip protocol tcp" %} -{% set udp_dst_port = "udp " + dst_port if dst_port else "ip protocol udp" %} +{% if src_addr %} +{% set output = output + " " + src_addr %} +{% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" -{% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" -{% if log %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" -{% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +{% if src_port %} +{% set output = output + " " + src_port %} +{% endif %} +{% if dst_addr %} +{% set output = output + " " + dst_addr %} +{% endif %} + +{% if dst_port %} +{% set output = output + " " + protocol + " " + dst_port %} {% else %} -{% set proto_dst_port = dst_port if dst_port else "ip protocol " + r.protocol %} -{% set proto_dst_port = "" if r.protocol == "all" %} +{% set output = output + " ip protocol " + protocol %} +{% endif %} -{% if log %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" -{% endif %} -add rule ip nat {{ chain }} iifname "{{ iface }}" {{ src_addr }} {{ src_port }} {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" +{# Special handling of log option, we must repeat the entire rule before the #} +{# NAT translation options are added, this is essential #} +{% if log %} +{% set log_output = output + " log prefix \"" + log + "\"" %} +{% endif %} + +{% if trns_addr %} +{% set output = output + " " + trns_addr %} +{% endif %} + +{% if trns_port %} +{# Do not add a whitespace here, translation port must be directly added after IP address #} +{# e.g. 192.0.2.10:3389 #} +{% set output = output + trns_port %} +{% endif %} + +{{ log_output if log_output }} +{{ output }} + +{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} +{% if r.protocol == "tcp_udp" %} +{# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} +{{ log_output | replace("tcp ", "udp ") if log_output }} +{{ output | replace("tcp ", "udp ") }} {% endif %} {% endfor %} + + +# +# Source NAT rules build up here +# {% for r in source if not r.disabled -%} {% set chain = "POSTROUTING" %} {% set src_addr = "ip saddr " + r.source_address if r.source_address %} -- cgit v1.2.3 From 6f349ee3b4d3da731ca22a70db6650848a0c28d9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 16 May 2020 16:16:09 +0200 Subject: nat: T2198: use Jinja2 macro for common ruleset for SNAT and DNAT By using a Jinja2 macro the same template code can be used to create both source and destination NAT rules with only minor changes introduced by e.g. the used chain (POSTROUTING vs PREROUTING). Used the following configuration for testing on two systems with VyOS 1.2 and the old implementation vs the new one here. set nat destination rule 15 description 'foo-10' set nat destination rule 15 destination address '1.1.1.1' set nat destination rule 15 inbound-interface 'eth0.202' set nat destination rule 15 protocol 'tcp_udp' set nat destination rule 15 translation address '192.0.2.10' set nat destination rule 15 translation port '3389' set nat destination rule 20 description 'foo-20' set nat destination rule 20 destination address '2.2.2.2' set nat destination rule 20 destination port '22' set nat destination rule 20 inbound-interface 'eth0.201' set nat destination rule 20 protocol 'tcp' set nat destination rule 20 translation address '192.0.2.10' set nat source rule 100 outbound-interface 'eth0.202' set nat source rule 100 protocol 'all' set nat source rule 100 source address '192.0.2.0/26' set nat source rule 100 translation address 'masquerade' set nat source rule 110 outbound-interface 'eth0.202' set nat source rule 110 protocol 'tcp' set nat source rule 110 source address '192.0.2.0/26' set nat source rule 110 source port '5556' set nat source rule 110 translation address 'masquerade' set nat source rule 120 outbound-interface 'eth0.202' set nat source rule 120 protocol 'tcp_udp' set nat source rule 120 source address '192.0.3.0/26' set nat source rule 120 translation address '2.2.2.2' --- data/templates/firewall/nftables-nat.tmpl | 138 +++++++++++------------------- 1 file changed, 50 insertions(+), 88 deletions(-) (limited to 'data') diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 5ce110d82..abb32ddc6 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -27,69 +27,76 @@ add rule ip raw NAT_CONNTRACK counter accept {{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK {% endif %} -# -# Destination NAT rules build up here -# -{% for r in destination if not r.disabled -%} -{% set chain = "PREROUTING" %} -{% set src_addr = "ip saddr " + r.source_address if r.source_address %} -{% set src_port = "sport { " + r.source_port +" }" if r.source_port %} -{% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} -{% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} -{% set trns_addr = "dnat to " + r.translation_address %} -{% set trns_port = ":" + r.translation_port if r.translation_port %} -{% set interface = " iifname \"" + r.interface_in + "\"" %} -{% set comment = "DST-NAT-" + r.number %} - -{% if r.protocol == "tcp_udp" %} +{% macro nat_rule(rule, chain) %} +{% set src_addr = "ip saddr " + rule.source_address if rule.source_address %} +{% set src_port = "sport { " + rule.source_port +" }" if rule.source_port %} +{% set dst_addr = "ip daddr " + rule.dest_address if rule.dest_address %} +{% set dst_port = "dport { " + rule.dest_port +" }" if rule.dest_port %} +{% set comment = "DST-NAT-" + rule.number %} + +{% if chain == "PREROUTING" %} +{% set interface = " iifname \"" + rule.interface_in + "\"" %} +{% set trns_addr = "dnat to " + rule.translation_address %} +{% elif chain == "POSTROUTING" %} +{% set interface = " oifname \"" + rule.interface_out + "\"" %} +{% set trns_addr = rule.translation_address %} +{% if rule.translation_address != 'masquerade' %} +{% set trns_addr = "snat to " + trns_addr %} +{% endif %} +{% endif %} +{% set trns_port = ":" + rule.translation_port if rule.translation_port %} + +{% if rule.protocol == "tcp_udp" %} {% set protocol = "tcp" %} {% set comment = comment + " tcp_udp" %} {% else %} -{% set protocol = r.protocol %} +{% set protocol = rule.protocol %} {% endif %} -{% if r.log %} -{% set base_log = "[NAT-DST-" + r.number %} -{% if r.exclude %} +{% if rule.log %} +{% set base_log = "[NAT-DST-" + rule.number %} +{% if rule.exclude %} {% set log = base_log + "-EXCL]" %} -{% elif r.translation_address == 'masquerade' %} +{% elif rule.translation_address == 'masquerade' %} {% set log = base_log + "-MASQ]" %} {% else %} {% set log = base_log + "]" %} {% endif %} {% endif %} -{% if r.exclude %} +{% if rule.exclude %} {# rule has been marked as "exclude" thus we simply return here #} {% set trns_addr = "return" %} {% set trns_port = "" %} {% endif %} -{% set output = "add rule ip nat " + chain + interface + " counter" %} -{% set output = output + " comment \"" + comment + "\"" %} +{% set output = "add rule ip nat " + chain + interface %} + +{% if protocol != "all" %} +{% set output = output + " ip protocol " + protocol %} +{% endif %} {% if src_addr %} {% set output = output + " " + src_addr %} {% endif %} - {% if src_port %} -{% set output = output + " " + src_port %} +{% set output = output + " " + protocol + " " + src_port %} {% endif %} {% if dst_addr %} {% set output = output + " " + dst_addr %} {% endif %} - {% if dst_port %} {% set output = output + " " + protocol + " " + dst_port %} -{% else %} -{% set output = output + " ip protocol " + protocol %} {% endif %} +{# Count packets #} +{% set output = output + " counter" %} + {# Special handling of log option, we must repeat the entire rule before the #} {# NAT translation options are added, this is essential #} {% if log %} -{% set log_output = output + " log prefix \"" + log + "\"" %} +{% set log_output = output + " log prefix \"" + log + "\" comment \"" + comment + "\"" %} {% endif %} {% if trns_addr %} @@ -102,76 +109,31 @@ add rule ip raw NAT_CONNTRACK counter accept {% set output = output + trns_port %} {% endif %} +{% if comment %} +{% set output = output + " comment \"" + comment + "\"" %} +{% endif %} + {{ log_output if log_output }} {{ output }} {# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} -{% if r.protocol == "tcp_udp" %} +{% if rule.protocol == "tcp_udp" %} {# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} {{ log_output | replace("tcp ", "udp ") if log_output }} {{ output | replace("tcp ", "udp ") }} {% endif %} -{% endfor %} - - +{% endmacro %} +# +# Destination NAT rules build up here +# +{% for rule in destination if not rule.disabled -%} +{{ nat_rule(rule, 'PREROUTING') }} +{% endfor %} # # Source NAT rules build up here # -{% for r in source if not r.disabled -%} -{% set chain = "POSTROUTING" %} -{% set src_addr = "ip saddr " + r.source_address if r.source_address %} -{% set src_port = "sport { " + r.source_port +" }" if r.source_port %} -{% set dst_addr = "ip daddr " + r.dest_address if r.dest_address %} -{% set dst_port = "dport { " + r.dest_port +" }" if r.dest_port %} -{% set trns_addr = "snat to " + r.translation_address if r.translation_address != "masquerade" else "masquerade" %} -{% set trns_port = ":" + r.translation_port if r.translation_port %} -{% set comment = "SRC-NAT-" + r.number %} -{% set iface = r.interface_out %} - -{% if r.log %} -{% if r.exclude %} -{% set log = "[" + comment + "-EXCL]" %} -{% elif r.translation_address == 'masquerade' %} -{% set log = "[" + comment + "-MASQ]" %} -{% else %} -{% set log = "[" + comment + "]" %} -{% endif %} -{% endif %} - -{% if r.exclude %} -{# rule has been marked as "exclude" thus we simply return here #} -{% set trns_addr = "return" %} -{% set trns_port = "" %} -{% endif %} - -{% if r.protocol == 'tcp_udp' %} -{# Special handling for protocol tcp_udp which is represented as two individual rules #} -{% set comment = comment + " tcp_udp" %} -{% if log %} - -{% set tcp_dst_port = "tcp " + dst_port if dst_port else "ip protocol tcp" %} -{% set udp_dst_port = "udp " + dst_port if dst_port else "ip protocol udp" %} -{% set tcp_src_port = "tcp " + src_port if src_port %} -{% set udp_src_port = "udp " + src_port if src_port %} - -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_src_port }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" -{% endif %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ tcp_src_port }} {{ src_port }} {{ tcp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" -{% if log %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_src_port }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" -{% endif %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ udp_src_port }} {{ src_port }} {{ udp_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" - -{% else %} -{% set proto_dst_port = dst_port if dst_port else "ip protocol " + r.protocol %} -{% set proto_dst_port = proto_dst_port if r.protocol != "all" %} -{% set proto_src_port = r.protocol + " " + src_port if r.protocol != "all" else src_port %} - -{% if log %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ src_addr }} {{ proto_src_port }} {{ proto_dst_port }} {{ dst_addr }} counter log prefix "{{ log }}" comment "{{ comment }}" -{% endif %} -add rule ip nat {{ chain }} oifname "{{ iface }}" {{ src_addr }} {{ proto_src_port }} {{ proto_dst_port }} {{ dst_addr }} counter {{ trns_addr }}{{ trns_port }} comment "{{ comment }}" -{% endif %} +{% for rule in source if not rule.disabled -%} +{{ nat_rule(rule, 'POSTROUTING') }} {% endfor %} -- cgit v1.2.3