summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/completion/list_bgp_peer_groups.sh23
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py4
-rwxr-xr-xsrc/conf_mode/nat66.py9
-rwxr-xr-xsrc/conf_mode/policy-lists.py117
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py57
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py61
-rwxr-xr-xsrc/conf_mode/protocols_static.py24
-rwxr-xr-xsrc/conf_mode/protocols_vrf.py72
-rwxr-xr-xsrc/conf_mode/service_console-server.py32
-rwxr-xr-xsrc/migration-scripts/vrf/1-to-261
-rwxr-xr-xsrc/op_mode/ppp-server-ctrl.py5
-rwxr-xr-xsrc/op_mode/show_nat66_rules.py74
-rwxr-xr-xsrc/op_mode/show_nat66_statistics.py63
-rwxr-xr-xsrc/op_mode/show_nat66_translations.py204
-rwxr-xr-xsrc/op_mode/show_nat_rules.py74
-rwxr-xr-xsrc/op_mode/show_nat_statistics.py2
-rwxr-xr-xsrc/services/vyos-configd41
17 files changed, 800 insertions, 123 deletions
diff --git a/src/completion/list_bgp_peer_groups.sh b/src/completion/list_bgp_peer_groups.sh
new file mode 100755
index 000000000..4503d608f
--- /dev/null
+++ b/src/completion/list_bgp_peer_groups.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Return BGP peer-groups from CLI
+
+declare -a vals
+eval "bgp_as=$(cli-shell-api listNodes protocols bgp)"
+eval "vals=($(cli-shell-api listNodes protocols bgp $bgp_as peer-group))"
+
+echo -n ${vals[@]}
+exit 0
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index b63312750..cab94a5b0 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -34,7 +34,7 @@ from vyos.ifconfig import Interface
from vyos.ifconfig import TunnelIf
from vyos.template import is_ipv4
from vyos.template import is_ipv6
-from vyos.util import get_json_iface_options
+from vyos.util import get_interface_config
from vyos.util import dict_search
from vyos import ConfigError
from vyos import airbag
@@ -103,7 +103,7 @@ def apply(tunnel):
# There is no other solution to destroy and recreate the tunnel.
encap = ''
remote = ''
- tmp = get_json_iface_options(interface)
+ tmp = get_interface_config(interface)
if tmp:
encap = dict_search('linkinfo.info_kind', tmp)
remote = dict_search('linkinfo.info_data.remote', tmp)
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index ce1db316c..e2bd6417d 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -28,7 +28,6 @@ from vyos.util import cmd
from vyos.util import check_kmod
from vyos.util import dict_search
from vyos.template import is_ipv6
-from vyos.template import is_ip_network
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -80,8 +79,10 @@ def get_config(config=None):
if not conf.exists(base):
nat['helper_functions'] = 'remove'
+ 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_conntrack'] = get_handler(condensed_json, 'OUTPUT','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'] = ''
return nat
@@ -91,8 +92,10 @@ def get_config(config=None):
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_conntrack'] = get_handler(condensed_json, 'OUTPUT','VYATTA_CT_OUTPUT_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')
else:
nat['helper_functions'] = 'has'
diff --git a/src/conf_mode/policy-lists.py b/src/conf_mode/policy-lists.py
new file mode 100755
index 000000000..94a020e7b
--- /dev/null
+++ b/src/conf_mode/policy-lists.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.template import render_to_string
+from vyos.util import call
+from vyos.util import dict_search
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+from pprint import pprint
+airbag.enable()
+
+config_file = r'/tmp/policy.frr'
+frr_daemon = 'zebra'
+
+DEBUG = os.path.exists('/tmp/policy.debug')
+if DEBUG:
+ import logging
+ lg = logging.getLogger("vyos.frr")
+ lg.setLevel(logging.DEBUG)
+ ch = logging.StreamHandler()
+ lg.addHandler(ch)
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['npolicy']
+ policy = conf.get_config_dict(base, key_mangling=('-', '_'))
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return policy
+
+ pprint(policy)
+ exit(1)
+ return policy
+
+def verify(policy):
+ if not policy:
+ return None
+
+ return None
+
+def generate(policy):
+ if not policy:
+ policy['new_frr_config'] = ''
+ return None
+
+ # render(config) not needed, its only for debug
+ # render(config_file, 'frr/policy.frr.tmpl', policy)
+ # policy['new_frr_config'] = render_to_string('frr/policy.frr.tmpl')
+
+ return None
+
+def apply(policy):
+ # Save original configuration prior to starting any commit actions
+ # frr_cfg = frr.FRRConfig()
+ # frr_cfg.load_configuration(frr_daemon)
+ # frr_cfg.modify_section(f'ip', '')
+ # frr_cfg.add_before(r'(line vty)', policy['new_frr_config'])
+
+ # Debugging
+ if DEBUG:
+ from pprint import pprint
+ print('')
+ print('--------- DEBUGGING ----------')
+ pprint(dir(frr_cfg))
+ print('Existing config:\n')
+ for line in frr_cfg.original_config:
+ print(line)
+ print(f'Replacement config:\n')
+ print(f'{policy["new_frr_config"]}')
+ print(f'Modified config:\n')
+ print(f'{frr_cfg}')
+
+ # frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, rerun the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ # if policy['new_frr_config'] == '':
+ # for a in range(5):
+ # frr_cfg.commit_configuration(frr_daemon)
+
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 7dede74a1..d5cc169c2 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -37,11 +38,24 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'bgp']
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'bgp']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path
bgp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # Bail out early if configuration tree does not exist
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: bgp.update({'vrf' : vrf})
+
if not conf.exists(base):
+ bgp.update({'deleted' : ''})
return bgp
# We also need some additional information from the config,
@@ -80,11 +94,20 @@ def verify(bgp):
if not bgp:
return None
- # Check if declared more than one ASN
- if len(bgp) > 1:
- raise ConfigError('Only one BGP AS number can be defined!')
+ # FRR bgpd only supports one Autonomous System Number, verify this!
+ asn = 0
+ for key in bgp:
+ if key.isnumeric():
+ asn +=1
+ if asn > 1:
+ raise ConfigError('Only one BGP AS number can be defined!')
for asn, asn_config in bgp.items():
+ # Workaround for https://phabricator.vyos.net/T1711
+ # We also have a vrf, and deleted key now - so we can only veriy "numbers"
+ if not asn.isnumeric():
+ continue
+
# Common verification for both peer-group and neighbor statements
for neighbor in ['neighbor', 'peer_group']:
# bail out early if there is no neighbor or peer-group statement
@@ -166,16 +189,18 @@ def verify(bgp):
# we can not use dict_search() here as prefix contains dots ...
if 'peer_group' not in asn_config['listen']['range'][prefix]:
raise ConfigError(f'Listen range for prefix "{prefix}" has no peer group configured.')
- else:
- peer_group = asn_config['listen']['range'][prefix]['peer_group']
- # the peer group must also exist
- if not dict_search(f'peer_group.{peer_group}', asn_config):
- raise ConfigError(f'Peer-group "{peer_group}" for listen range "{prefix}" does not exist!')
+
+ peer_group = asn_config['listen']['range'][prefix]['peer_group']
+ if 'peer_group' not in asn_config or peer_group not in asn_config['peer_group']:
+ raise ConfigError(f'Peer-group "{peer_group}" for listen range "{prefix}" does not exist!')
+
+ if not verify_remote_as(asn_config['listen']['range'][prefix], asn_config):
+ raise ConfigError(f'Peer-group "{peer_group}" requires remote-as to be set!')
return None
def generate(bgp):
- if not bgp:
+ if not bgp or 'deleted' in bgp:
bgp['new_frr_config'] = ''
return None
@@ -183,6 +208,8 @@ def generate(bgp):
# of the config dict
asn = list(bgp.keys())[0]
bgp[asn]['asn'] = asn
+ if 'vrf' in bgp:
+ bgp[asn]['vrf'] = bgp['vrf']
bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp[asn])
return None
@@ -191,7 +218,13 @@ def apply(bgp):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(f'^router bgp \d+$', '')
+
+ if 'vrf' in bgp:
+ vrf = bgp['vrf']
+ frr_cfg.modify_section(f'^router bgp \d+ vrf {vrf}$', '')
+ else:
+ frr_cfg.modify_section('^router bgp \d+$', '')
+
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index aefe7c23e..ef2aeda7f 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -17,14 +17,17 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
from vyos.configverify import verify_route_maps
from vyos.configverify import verify_interface_exists
from vyos.template import render_to_string
from vyos.util import call
from vyos.util import dict_search
+from vyos.util import get_interface_config
from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
@@ -38,16 +41,33 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'ospf']
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'ospf']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path
ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # Assign the name of our VRF context. This MUST be done before the return
+ # statement below, else on deletion we will delete the default instance
+ # instead of the VRF instance.
+ if vrf: ospf['vrf'] = vrf
+
# Bail out early if configuration tree does not exist
if not conf.exists(base):
+ ospf.update({'deleted' : ''})
return ospf
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
- default_values = defaults(base)
+ # XXX: Note that we can not call defaults(base), as defaults does not work
+ # on an instance of a tag node. As we use the exact same CLI definition for
+ # both the non-vrf and vrf version this is absolutely safe!
+ default_values = defaults(base_path)
# We have to cleanup the default dict, as default values could enable features
# which are not explicitly enabled on the CLI. Example: default-information
@@ -99,6 +119,14 @@ def get_config(config=None):
ospf['interface'][interface] = dict_merge(default_values,
ospf['interface'][interface])
+ # As we no re-use this Python handler for both VRF and non VRF instances for
+ # OSPF we need to find out if any interfaces changed so properly adjust
+ # the FRR configuration and not by acctident change interfaces from a
+ # different VRF.
+ interfaces_removed = node_changed(conf, base + ['interface'])
+ if interfaces_removed:
+ ospf['interface_removed'] = list(interfaces_removed)
+
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify()
base = ['policy']
@@ -121,12 +149,22 @@ def verify(ospf):
# time. FRR will only activate the last option set via CLI.
if {'hello_multiplier', 'dead_interval'} <= set(ospf['interface'][interface]):
raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \
- f'concurrently for "{interface}"!')
+ f'concurrently for {interface}!')
+
+ if 'vrf' in ospf:
+ # If interface specific options are set, we must ensure that the
+ # interface is bound to our requesting VRF. Due to the VyOS/Vyatta
+ # priorities the interface is bound to the VRF after creation of
+ # the VRF itself, and before any routing protocol is configured.
+ vrf = ospf['vrf']
+ tmp = get_interface_config(interface)
+ if 'master' not in tmp or tmp['master'] != vrf:
+ raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!')
return None
def generate(ospf):
- if not ospf:
+ if not ospf or 'deleted' in ospf:
ospf['new_frr_config'] = ''
return None
@@ -137,8 +175,19 @@ def apply(ospf):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(r'^interface \S+', '')
- frr_cfg.modify_section('^router ospf$', '')
+
+ if 'vrf' in ospf:
+ vrf = ospf['vrf']
+ frr_cfg.modify_section(f'^router ospf vrf {vrf}$', '')
+ else:
+ frr_cfg.modify_section('^router ospf$', '')
+
+ for key in ['interface', 'interface_removed']:
+ if key not in ospf:
+ continue
+ for interface in ospf[key]:
+ frr_cfg.modify_section(f'^interface {interface}$', '')
+
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 5d101b33e..51b4acfc8 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.template import render_to_string
@@ -34,8 +35,19 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['protocols', 'static']
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'static']
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path
static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # Assign the name of our VRF context
+ if vrf: static['vrf'] = vrf
+
return static
def verify(static):
@@ -50,8 +62,14 @@ def apply(static):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(r'^ip route .*', '')
- frr_cfg.modify_section(r'^ipv6 route .*', '')
+
+ if 'vrf' in static:
+ vrf = static['vrf']
+ frr_cfg.modify_section(f'^vrf {vrf}$', '')
+ else:
+ frr_cfg.modify_section(r'^ip route .*', '')
+ frr_cfg.modify_section(r'^ipv6 route .*', '')
+
frr_cfg.add_before(r'(interface .*|line vty)', static['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
diff --git a/src/conf_mode/protocols_vrf.py b/src/conf_mode/protocols_vrf.py
deleted file mode 100755
index 227e7d5e1..000000000
--- a/src/conf_mode/protocols_vrf.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from sys import exit
-
-from vyos.config import Config
-from vyos.template import render_to_string
-from vyos.util import call
-from vyos import ConfigError
-from vyos import frr
-from vyos import airbag
-airbag.enable()
-
-frr_daemon = 'staticd'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['protocols', 'vrf']
- vrf = conf.get_config_dict(base, key_mangling=('-', '_'))
- return vrf
-
-def verify(vrf):
-
- return None
-
-def generate(vrf):
- vrf['new_frr_config'] = render_to_string('frr/vrf.frr.tmpl', vrf)
- return None
-
-def apply(vrf):
- # Save original configuration prior to starting any commit actions
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(r'vrf \S+', '')
- frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', vrf['new_frr_config'])
- frr_cfg.commit_configuration(frr_daemon)
-
- # If FRR config is blank, rerun the blank commit x times due to frr-reload
- # behavior/bug not properly clearing out on one commit.
- if vrf['new_frr_config'] == '':
- for a in range(5):
- frr_cfg.commit_configuration(frr_daemon)
-
- return None
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index 6e94a19ae..51050e702 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,6 +17,7 @@
import os
from sys import exit
+from psutil import process_iter
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -60,14 +61,19 @@ def verify(proxy):
if not proxy:
return None
+ processes = process_iter(['name', 'cmdline'])
if 'device' in proxy:
- for device in proxy['device']:
- if 'speed' not in proxy['device'][device]:
- raise ConfigError(f'Serial port speed must be defined for "{device}"!')
+ for device, device_config in proxy['device'].items():
+ for process in processes:
+ if 'agetty' in process.name() and device in process.cmdline():
+ raise ConfigError(f'Port "{device}" already provides a '\
+ 'console used by "system console"!')
+
+ if 'speed' not in device_config:
+ raise ConfigError(f'Port "{device}" requires speed to be set!')
- if 'ssh' in proxy['device'][device]:
- if 'port' not in proxy['device'][device]['ssh']:
- raise ConfigError(f'SSH port must be defined for "{device}"!')
+ if 'ssh' in device_config and 'port' not in device_config['ssh']:
+ raise ConfigError(f'Port "{device}" requires SSH port to be set!')
return None
@@ -77,13 +83,13 @@ def generate(proxy):
render(config_file, 'conserver/conserver.conf.tmpl', proxy)
if 'device' in proxy:
- for device in proxy['device']:
- if 'ssh' not in proxy['device'][device]:
+ for device, device_config in proxy['device'].items():
+ if 'ssh' not in device_config:
continue
tmp = {
'device' : device,
- 'port' : proxy['device'][device]['ssh']['port'],
+ 'port' : device_config['ssh']['port'],
}
render(dropbear_systemd_file.format(**tmp),
'conserver/dropbear@.service.tmpl', tmp)
@@ -102,10 +108,10 @@ def apply(proxy):
call('systemctl restart conserver-server.service')
if 'device' in proxy:
- for device in proxy['device']:
- if 'ssh' not in proxy['device'][device]:
+ for device, device_config in proxy['device'].items():
+ if 'ssh' not in device_config:
continue
- port = proxy['device'][device]['ssh']['port']
+ port = device_config['ssh']['port']
call(f'systemctl restart dropbear@{port}.service')
return None
diff --git a/src/migration-scripts/vrf/1-to-2 b/src/migration-scripts/vrf/1-to-2
new file mode 100755
index 000000000..20128e957
--- /dev/null
+++ b/src/migration-scripts/vrf/1-to-2
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - T3344: migrate routing options from "protocols vrf" to "vrf <name> protocols"
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 2):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['protocols', 'vrf']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+vrf_base = ['vrf', 'name']
+config.set(vrf_base)
+config.set_tag(vrf_base)
+
+# Copy all existing static routes to the new base node under "vrf name <name> protocols static"
+for vrf in config.list_nodes(base):
+ static_base = base + [vrf, 'static']
+ if not config.exists(static_base):
+ continue
+
+ new_static_base = vrf_base + [vrf, 'protocols']
+ config.set(new_static_base)
+ config.copy(static_base, new_static_base + ['static'])
+
+# Now delete the old configuration
+config.delete(base)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py
index 171107b4a..8a4e846b2 100755
--- a/src/op_mode/ppp-server-ctrl.py
+++ b/src/op_mode/ppp-server-ctrl.py
@@ -59,7 +59,10 @@ def main():
output, err = popen(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][args.proto]) + args.action + ses_pattern, stderr=DEVNULL, decode='utf-8')
if not err:
- print(output)
+ try:
+ print(output)
+ except BrokenPipeError:
+ sys.exit(0)
else:
print("{} server is not running".format(args.proto))
diff --git a/src/op_mode/show_nat66_rules.py b/src/op_mode/show_nat66_rules.py
new file mode 100755
index 000000000..cbab2d03b
--- /dev/null
+++ b/src/op_mode/show_nat66_rules.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jmespath
+import json
+
+from argparse import ArgumentParser
+from jinja2 import Template
+from sys import exit
+from vyos.util import cmd
+from vyos.util import dict_search
+
+parser = ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true")
+group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true")
+args = parser.parse_args()
+
+if args.source or args.destination:
+ tmp = cmd('sudo nft -j list table ip6 nat')
+ tmp = json.loads(tmp)
+
+ format_nat66_rule = '%-10s %-50s %-50s %-10s'
+ print(format_nat66_rule % ("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
+ print(format_nat66_rule % ("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
+
+ data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp)
+ for idx in range(0, len(data_json)):
+ data = data_json[idx]
+ comment = data['comment']
+ chain = data['chain']
+ if not (args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING'):
+ exit(0)
+ interface = dict_search('match.right', data['expr'][0])
+ srcdest = dict_search('match.right.prefix.addr', data['expr'][2])
+ if srcdest:
+ addr_tmp = dict_search('match.right.prefix.len', data['expr'][2])
+ if addr_tmp:
+ srcdest = srcdest + '/' + str(addr_tmp)
+ else:
+ srcdest = dict_search('match.right', data['expr'][2])
+ tran_addr = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
+ if tran_addr:
+ addr_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
+ if addr_tmp:
+ srcdest = srcdest + '/' + str(addr_tmp)
+ else:
+ if 'masquerade' in data['expr'][3]:
+ tran_addr = 'masquerade'
+ elif 'log' in data['expr'][3]:
+ continue
+ else:
+ tran_addr = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
+
+ print(format_nat66_rule % (comment, srcdest, tran_addr, interface))
+
+ exit(0)
+else:
+ parser.print_help()
+ exit(1)
+
diff --git a/src/op_mode/show_nat66_statistics.py b/src/op_mode/show_nat66_statistics.py
new file mode 100755
index 000000000..0f0b05978
--- /dev/null
+++ b/src/op_mode/show_nat66_statistics.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jmespath
+import json
+
+from argparse import ArgumentParser
+from jinja2 import Template
+from sys import exit
+from vyos.util import cmd
+
+OUT_TMPL_SRC="""
+rule pkts bytes interface
+---- ---- ----- ---------
+{% for r in output %}
+{% if r.comment %}
+{% set packets = r.counter.packets %}
+{% set bytes = r.counter.bytes %}
+{% set interface = r.interface %}
+{# remove rule comment prefix #}
+{% set comment = r.comment | replace('SRC-NAT-', '') | replace('DST-NAT-', '') | replace(' tcp_udp', '') %}
+{{ "%-4s" | format(comment) }} {{ "%9s" | format(packets) }} {{ "%12s" | format(bytes) }} {{ interface }}
+{% endif %}
+{% endfor %}
+"""
+
+parser = ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true")
+group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true")
+args = parser.parse_args()
+
+if args.source or args.destination:
+ tmp = cmd('sudo nft -j list table ip6 nat')
+ tmp = json.loads(tmp)
+
+ source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
+ destination = r"nftables[?rule.chain=='PREROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
+ data = {
+ 'output' : jmespath.search(source if args.source else destination, tmp),
+ 'direction' : 'source' if args.source else 'destination'
+ }
+
+ tmpl = Template(OUT_TMPL_SRC, lstrip_blocks=True)
+ print(tmpl.render(data))
+ exit(0)
+else:
+ parser.print_help()
+ exit(1)
+
diff --git a/src/op_mode/show_nat66_translations.py b/src/op_mode/show_nat66_translations.py
new file mode 100755
index 000000000..045d64065
--- /dev/null
+++ b/src/op_mode/show_nat66_translations.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+'''
+show nat translations
+'''
+
+import os
+import sys
+import ipaddress
+import argparse
+import xmltodict
+
+from vyos.util import popen
+from vyos.util import DEVNULL
+
+conntrack = '/usr/sbin/conntrack'
+
+verbose_format = "%-20s %-18s %-20s %-18s"
+normal_format = "%-20s %-20s %-4s %-8s %s"
+
+
+def headers(verbose, pipe):
+ if verbose:
+ return verbose_format % ('Pre-NAT src', 'Pre-NAT dst', 'Post-NAT src', 'Post-NAT dst')
+ return normal_format % ('Pre-NAT', 'Post-NAT', 'Prot', 'Timeout', 'Type' if pipe else '')
+
+
+def command(srcdest, proto, ipaddr):
+ command = f'{conntrack} -o xml -L -f ipv6'
+
+ if proto:
+ command += f' -p {proto}'
+
+ if srcdest == 'source':
+ command += ' -n'
+ if ipaddr:
+ command += f' --orig-src {ipaddr}'
+ if srcdest == 'destination':
+ command += ' -g'
+ if ipaddr:
+ command += f' --orig-dst {ipaddr}'
+
+ return command
+
+
+def run(command):
+ xml, code = popen(command,stderr=DEVNULL)
+ if code:
+ sys.exit('conntrack failed')
+ return xml
+
+
+def content(xmlfile):
+ xml = ''
+ with open(xmlfile,'r') as r:
+ xml += r.read()
+ return xml
+
+
+def pipe():
+ xml = ''
+ while True:
+ line = sys.stdin.readline()
+ xml += line
+ if '</conntrack>' in line:
+ break
+
+ sys.stdin = open('/dev/tty')
+ return xml
+
+
+def process(data, stats, protocol, pipe, verbose, flowtype=''):
+ if not data:
+ return
+
+ parsed = xmltodict.parse(data)
+
+ print(headers(verbose, pipe))
+
+ # to help the linter to detect typos
+ ORIGINAL = 'original'
+ REPLY = 'reply'
+ INDEPENDANT = 'independent'
+ SPORT = 'sport'
+ DPORT = 'dport'
+ SRC = 'src'
+ DST = 'dst'
+
+ for rule in parsed['conntrack']['flow']:
+ src, dst, sport, dport, proto = {}, {}, {}, {}, {}
+ packet_count, byte_count = {}, {}
+ timeout, use = 0, 0
+
+ rule_type = rule.get('type', '')
+
+ for meta in rule['meta']:
+ # print(meta)
+ direction = meta['@direction']
+
+ if direction in (ORIGINAL, REPLY):
+ if 'layer3' in meta:
+ l3 = meta['layer3']
+ src[direction] = l3[SRC]
+ dst[direction] = l3[DST]
+
+ if 'layer4' in meta:
+ l4 = meta['layer4']
+ sp = l4.get(SPORT, '')
+ dp = l4.get(DPORT, '')
+ if sp:
+ sport[direction] = sp
+ if dp:
+ dport[direction] = dp
+ proto[direction] = l4.get('@protoname','')
+
+ if stats and 'counters' in meta:
+ packet_count[direction] = meta['packets']
+ byte_count[direction] = meta['bytes']
+ continue
+
+ if direction == INDEPENDANT:
+ timeout = meta['timeout']
+ use = meta['use']
+ continue
+
+ in_src = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if ORIGINAL in sport else src[ORIGINAL]
+ in_dst = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if ORIGINAL in dport else dst[ORIGINAL]
+
+ # inverted the the perl code !!?
+ out_dst = '%s:%s' % (dst[REPLY], dport[REPLY]) if REPLY in dport else dst[REPLY]
+ out_src = '%s:%s' % (src[REPLY], sport[REPLY]) if REPLY in sport else src[REPLY]
+
+ if flowtype == 'source':
+ v = ORIGINAL in sport and REPLY in dport
+ f = '%s:%s' % (src[ORIGINAL], sport[ORIGINAL]) if v else src[ORIGINAL]
+ t = '%s:%s' % (dst[REPLY], dport[REPLY]) if v else dst[REPLY]
+ else:
+ v = ORIGINAL in dport and REPLY in sport
+ f = '%s:%s' % (dst[ORIGINAL], dport[ORIGINAL]) if v else dst[ORIGINAL]
+ t = '%s:%s' % (src[REPLY], sport[REPLY]) if v else src[REPLY]
+
+ # Thomas: I do not believe proto should be an option
+ p = proto.get('original', '')
+ if protocol and p != protocol:
+ continue
+
+ if verbose:
+ msg = verbose_format % (in_src, in_dst, out_dst, out_src)
+ p = f'{p}: ' if p else ''
+ msg += f'\n {p}{f} ==> {t}'
+ msg += f' timeout: {timeout}' if timeout else ''
+ msg += f' use: {use} ' if use else ''
+ msg += f' type: {rule_type}' if rule_type else ''
+ print(msg)
+ else:
+ print(normal_format % (f, t, p, timeout, rule_type if rule_type else ''))
+
+ if stats:
+ for direction in ('original', 'reply'):
+ if direction in packet_count:
+ print(' %-8s: packets %s, bytes %s' % direction, packet_count[direction], byte_count[direction])
+
+
+def main():
+ parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
+ parser.add_argument('--verbose', help='provide more details about the flows', action='store_true')
+ parser.add_argument('--proto', help='filter by protocol', default='', type=str)
+ parser.add_argument('--file', help='read the conntrack xml from a file', type=str)
+ parser.add_argument('--stats', help='add usage statistics', action='store_true')
+ parser.add_argument('--type', help='NAT type (source, destination)', required=True, type=str)
+ parser.add_argument('--ipaddr', help='source ip address to filter on', type=ipaddress.ip_address)
+ parser.add_argument('--pipe', help='read conntrack xml data from stdin', action='store_true')
+
+ arg = parser.parse_args()
+
+ if arg.type not in ('source', 'destination'):
+ sys.exit('Unknown NAT type!')
+
+ if arg.pipe:
+ process(pipe(), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
+ elif arg.file:
+ process(content(arg.file), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
+ else:
+ try:
+ process(run(command(arg.type, arg.proto, arg.ipaddr)), arg.stats, arg.proto, arg.pipe, arg.verbose, arg.type)
+ except:
+ pass
+
+if __name__ == '__main__':
+ main()
diff --git a/src/op_mode/show_nat_rules.py b/src/op_mode/show_nat_rules.py
new file mode 100755
index 000000000..0ddb7ddd4
--- /dev/null
+++ b/src/op_mode/show_nat_rules.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jmespath
+import json
+
+from argparse import ArgumentParser
+from jinja2 import Template
+from sys import exit
+from vyos.util import cmd
+from vyos.util import dict_search
+
+parser = ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true")
+group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true")
+args = parser.parse_args()
+
+if args.source or args.destination:
+ tmp = cmd('sudo nft -j list table ip nat')
+ tmp = json.loads(tmp)
+
+ format_nat66_rule = '%-10s %-50s %-50s %-10s'
+ print(format_nat66_rule % ("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
+ print(format_nat66_rule % ("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
+
+ data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp)
+ for idx in range(0, len(data_json)):
+ data = data_json[idx]
+ comment = data['comment']
+ chain = data['chain']
+ if not (args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING'):
+ exit(0)
+ interface = dict_search('match.right', data['expr'][0])
+ srcdest = dict_search('match.right.prefix.addr', data['expr'][1])
+ if srcdest:
+ addr_tmp = dict_search('match.right.prefix.len', data['expr'][1])
+ if addr_tmp:
+ srcdest = srcdest + '/' + str(addr_tmp)
+ else:
+ srcdest = dict_search('match.right', data['expr'][1])
+ tran_addr = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3])
+ if tran_addr:
+ addr_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
+ if addr_tmp:
+ srcdest = srcdest + '/' + str(addr_tmp)
+ else:
+ if 'masquerade' in data['expr'][3]:
+ tran_addr = 'masquerade'
+ elif 'log' in data['expr'][3]:
+ continue
+ else:
+ tran_addr = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3])
+
+ print(format_nat66_rule % (comment, srcdest, tran_addr, interface))
+
+ exit(0)
+else:
+ parser.print_help()
+ exit(1)
+
diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py
index 482993d06..c568c8305 100755
--- a/src/op_mode/show_nat_statistics.py
+++ b/src/op_mode/show_nat_statistics.py
@@ -44,7 +44,7 @@ group.add_argument("--destination", help="Show statistics for configured destina
args = parser.parse_args()
if args.source or args.destination:
- tmp = cmd('sudo nft -j list table nat')
+ tmp = cmd('sudo nft -j list table ip nat')
tmp = json.loads(tmp)
source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }"
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 1e60e53df..8547b5f56 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -25,7 +25,7 @@ import logging
import signal
import importlib.util
import zmq
-from contextlib import redirect_stdout, redirect_stderr
+from contextlib import contextmanager
from vyos.defaults import directories
from vyos.configsource import ConfigSourceString, ConfigSourceError
@@ -108,20 +108,40 @@ conf_mode_scripts = dict(zip(imports, modules))
exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include}
include_set = {key_name_from_file_name(f) for f in filenames if f in include}
+@contextmanager
+def stdout_redirected(filename, mode):
+ saved_stdout_fd = None
+ destination_file = None
+ try:
+ sys.stdout.flush()
+ saved_stdout_fd = os.dup(sys.stdout.fileno())
+ destination_file = open(filename, mode)
+ os.dup2(destination_file.fileno(), sys.stdout.fileno())
+ yield
+ finally:
+ if saved_stdout_fd is not None:
+ os.dup2(saved_stdout_fd, sys.stdout.fileno())
+ os.close(saved_stdout_fd)
+ if destination_file is not None:
+ destination_file.close()
+
+def explicit_print(path, mode, msg):
+ try:
+ with open(path, mode) as f:
+ f.write(f"\n{msg}\n\n")
+ except OSError:
+ logger.critical("error explicit_print")
def run_script(script, config) -> int:
config.set_level([])
try:
- with open(session_out, session_mode) as f, redirect_stdout(f):
- with redirect_stderr(f):
- c = script.get_config(config)
- script.verify(c)
- script.generate(c)
- script.apply(c)
+ c = script.get_config(config)
+ script.verify(c)
+ script.generate(c)
+ script.apply(c)
except ConfigError as e:
logger.critical(e)
- with open(session_out, session_mode) as f, redirect_stdout(f):
- print(f"{e}\n")
+ explicit_print(session_out, session_mode, str(e))
return R_ERROR_COMMIT
except Exception as e:
logger.critical(e)
@@ -201,7 +221,8 @@ def process_node_data(config, data) -> int:
if script_name in exclude_set:
return R_PASS
- result = run_script(conf_mode_scripts[script_name], config)
+ with stdout_redirected(session_out, session_mode):
+ result = run_script(conf_mode_scripts[script_name], config)
return result