diff options
-rw-r--r-- | interface-definitions/protocols-mpls.xml.in | 98 | ||||
-rw-r--r-- | op-mode-definitions/show-mpls.xml | 62 | ||||
-rw-r--r-- | python/vyos/ifconfig/control.py | 22 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 22 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/protocols_mpls.py | 237 | ||||
-rwxr-xr-x | src/conf_mode/system-login-banner.py | 9 |
7 files changed, 430 insertions, 24 deletions
diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in new file mode 100644 index 000000000..376323855 --- /dev/null +++ b/interface-definitions/protocols-mpls.xml.in @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<!-- Multiprotocol Label Switching (MPLS) configuration --> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="mpls" owner="${vyos_conf_scripts_dir}/protocols_mpls.py"> + <properties> + <help>Multiprotocol Label Switching (MPLS)</help> + <priority>299</priority> + </properties> + <children> + <node name="ldp"> + <properties> + <help>LDP options</help> + </properties> + <children> + <leafNode name="router-id"> + <properties> + <help>x.x.x.x Label Switch Router (LSR) id</help> + <valueHelp> + <format>ipv4</format> + <description>LSR ipv4 id</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <tagNode name="neighbor"> + <properties> + <help>LDP Id of neighbor</help> + <valueHelp> + <format>ipv4</format> + <description>neighbor IPv4 id</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="password"> + <properties> + <help>Peer password</help> + </properties> + </leafNode> + </children> + </tagNode> + <node name="discovery"> + <properties> + <help>Discovery parameters</help> + <valueHelp> + <format>ipv4</format> + <description>Discovery parameters</description> + </valueHelp> + </properties> + <children> + <leafNode name="transport-ipv4-address"> + <properties> + <help>Transport ipv4 address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 bind as transport</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="transport-ipv6-address"> + <properties> + <help>Transport ipv6 address</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 bind as transport</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <leafNode name="interface"> + <properties> + <help>Listen interface for LDP</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition>
\ No newline at end of file diff --git a/op-mode-definitions/show-mpls.xml b/op-mode-definitions/show-mpls.xml new file mode 100644 index 000000000..6610788c7 --- /dev/null +++ b/op-mode-definitions/show-mpls.xml @@ -0,0 +1,62 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="mpls"> + <children> + <node name="ldp"> + <properties> + <help>Label Distribution Protocol (LDP)</help> + </properties> + <children> + <node name="binding"> + <properties> + <help>Label Information Base</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls ldp binding"</command> + </node> + <node name="discovery"> + <properties> + <help>Discovery hello information</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls ldp discovery"</command> + </node> + <node name="interface"> + <properties> + <help>LDP interface information</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls ldp interface"</command> + </node> + <node name="neighbor"> + <properties> + <help>LDP neighbor information</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls ldp neighbor"</command> + <children> + <node name="detail"> + <properties> + <help>Show neighbor detail</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls ldp neighbor detail"</command> + </node> + </children> + </node> + </children> + </node> + <node name="pseudowire"> + <properties> + <help>Show MPLS pseudowire interfaces</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls pseudowires"</command> + </node> + <node name="table"> + <properties> + <help>Show MPLS table</help> + </properties> + <command>/usr/bin/vtysh -c "show mpls table"</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index 508b4e279..635b626e8 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -29,12 +29,13 @@ class Control: def _cmd(self, command): p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True) tmp = p.communicate()[0].strip() - self._debug_msg("cmd '{}'".format(command)) - if tmp.decode(): - self._debug_msg("returned:\n{}".format(tmp.decode())) - - # do we need some error checking code here? - return tmp.decode() + self._debug_msg(f"cmd '{command}'") + decoded = tmp.decode() + if decoded: + self._debug_msg(f"returned:\n{decoded}") + if p.returncode != 0: + raise RuntimeError(f'{command}\nreturned: {decoded}') + return decoded def _get_command(self, config, name): """ @@ -55,14 +56,17 @@ class Control: validate = self._command_set[name].get('validate', None) if validate: - validate(value) - - config = {**config, **{'value': value}} + try: + validate(value) + except Exception as e: + raise e.__class__(f'Could not set {name}. {e}') convert = self._command_set[name].get('convert', None) if convert: value = convert(value) + config = {**config, **{'value': value}} + cmd = self._command_set[name]['shellcmd'].format(**config) return self._cmd(cmd) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index b002e0171..21ffa88f6 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -77,6 +77,10 @@ class Interface(Control): } _command_set = { + 'state': { + 'validate': lambda v: assert_list(v, ['up', 'down']), + 'shellcmd': 'ip link set dev {ifname} {value}', + }, 'mac': { 'validate': assert_mac, 'shellcmd': 'ip link set dev {ifname} address {value}', @@ -166,13 +170,15 @@ class Interface(Control): if k in kargs: self.config[k] = kargs[k] - for k in self.required: - if k not in kargs: - raise ConfigError('missing required option {} for {}'.format(k,self.__class__)) - if not os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])): if not self.config['type']: raise Exception('interface "{}" not found'.format(self.config['ifname'])) + + for k in self.required: + if k not in kargs: + name = self.default['type'] + raise ConfigError(f'missing required option {k} for {name} {ifname} creation') + self._create() # per interface DHCP config files @@ -452,13 +458,7 @@ class Interface(Control): >>> Interface('eth0').get_state() 'down' """ - if state not in ['up', 'down']: - raise ValueError('state must be "up" or "down"') - - # Assemble command executed on system. Unfortunately there is no way - # to up/down an interface via sysfs - cmd = 'ip link set dev {} {}'.format(self.config['ifname'], state) - return self._cmd(cmd) + return self.set_interface('state', state) def set_proxy_arp(self, enable): """ diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 6ccbaed5b..463ae9fd7 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -365,7 +365,7 @@ def fixup_permission(filename, permission=S_IRUSR): def checkCertHeader(header, filename): """ Verify if filename contains specified header. - Returns True on success or on file not found to not trigger the exceptions + Returns True if match is found, False if no match or file is not found """ if not os.path.isfile(filename): return False @@ -375,7 +375,7 @@ def checkCertHeader(header, filename): if re.match(header, line): return True - return True + return False def get_config(): openvpn = deepcopy(default_config_data) diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py new file mode 100755 index 000000000..5f2497660 --- /dev/null +++ b/src/conf_mode/protocols_mpls.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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 sys +import jinja2 +import copy +import os +import vyos.validate + +from vyos import ConfigError +from vyos.config import Config + +config_file = r'/tmp/ldpd.frr' + +# Please be careful if you edit the template. +config_tmpl = """ +! +{% if mpls_ldp -%} +mpls ldp +{% if old_router_id -%} +no router-id {{ old_router_id }} +{% endif -%} +{% if router_id -%} +router-id {{ router_id }} +{% endif -%} +{% for neighbor_id in old_ldp.neighbors -%} +no neighbor {{neighbor_id}} password {{old_ldp.neighbors[neighbor_id].password}} +{% endfor -%} +{% for neighbor_id in ldp.neighbors -%} +neighbor {{neighbor_id}} password {{ldp.neighbors[neighbor_id].password}} +{% endfor -%} +address-family ipv4 +label local allocate host-routes +{% if old_ldp.d_transp_ipv4 -%} +no discovery transport-address {{ old_ldp.d_transp_ipv4 }} +{% endif -%} +{% if ldp.d_transp_ipv4 -%} +discovery transport-address {{ ldp.d_transp_ipv4 }} +{% endif -%} +{% for interface in old_ldp.interfaces -%} +no interface {{interface}} +{% endfor -%} +{% for interface in ldp.interfaces -%} +interface {{interface}} +{% endfor -%} +! +! +exit-address-family +! +{% if ldp.d_transp_ipv6 -%} +address-family ipv6 +label local allocate host-routes +{% if old_ldp.d_transp_ipv6 -%} +no discovery transport-address {{ old_ldp.d_transp_ipv6 }} +{% endif -%} +{% if ldp.d_transp_ipv6 -%} +discovery transport-address {{ ldp.d_transp_ipv6 }} +{% endif -%} +{% for interface in old_ldp.interfaces -%} +no interface {{interface}} +{% endfor -%} +{% for interface in ldp.interfaces -%} +interface {{interface}} +{% endfor -%} +! +exit-address-family +{% else -%} +no address-family ipv6 +{% endif -%} +! +{% else -%} +no mpls ldp +{% endif -%} +! +""" + +def sysctl(name, value): + os.system('sysctl -wq {}={}'.format(name, value)) + +def get_config(): + conf = Config() + mpls_conf = { + 'router_id' : None, + 'mpls_ldp' : False, + 'old_ldp' : { + 'interfaces' : [], + 'neighbors' : {}, + 'd_transp_ipv4' : None, + 'd_transp_ipv6' : None + }, + 'ldp' : { + 'interfaces' : [], + 'neighbors' : {}, + 'd_transp_ipv4' : None, + 'd_transp_ipv6' : None + } + } + if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): + return None + + if conf.exists('protocols mpls ldp'): + mpls_conf['mpls_ldp'] = True + + conf.set_level('protocols mpls ldp') + + # Get router-id + if conf.exists_effective('router-id'): + mpls_conf['old_router_id'] = conf.return_effective_value('router-id') + if conf.exists('router-id'): + mpls_conf['router_id'] = conf.return_value('router-id') + + # Get discovery transport-ipv4-address + if conf.exists_effective('discovery transport-ipv4-address'): + mpls_conf['old_ldp']['d_transp_ipv4'] = conf.return_effective_value('discovery transport-ipv4-address') + + if conf.exists('discovery transport-ipv4-address'): + mpls_conf['ldp']['d_transp_ipv4'] = conf.return_value('discovery transport-ipv4-address') + + # Get discovery transport-ipv6-address + if conf.exists_effective('discovery transport-ipv6-address'): + mpls_conf['old_ldp']['d_transp_ipv6'] = conf.return_effective_value('discovery transport-ipv6-address') + + if conf.exists('discovery transport-ipv6-address'): + mpls_conf['ldp']['d_transp_ipv6'] = conf.return_value('discovery transport-ipv6-address') + + # Get interfaces + if conf.exists_effective('interface'): + mpls_conf['old_ldp']['interfaces'] = conf.return_effective_values('interface') + + if conf.exists('interface'): + mpls_conf['ldp']['interfaces'] = conf.return_values('interface') + + # Get neighbors + for neighbor in conf.list_effective_nodes('neighbor'): + mpls_conf['old_ldp']['neighbors'].update({ + neighbor : { + 'password' : conf.return_effective_value('neighbor {0} password'.format(neighbor)) + } + }) + + for neighbor in conf.list_nodes('neighbor'): + mpls_conf['ldp']['neighbors'].update({ + neighbor : { + 'password' : conf.return_value('neighbor {0} password'.format(neighbor)) + } + }) + + # print(mpls_conf) + # sys.exit(1) + return mpls_conf + +def operate_mpls_on_intfc(interfaces, action): + rp_filter = 0 + if action == 1: + rp_filter = 2 + for iface in interfaces: + sysctl('net.mpls.conf.{0}.input'.format(iface), action) + # Operate rp filter + sysctl('net.ipv4.conf.{0}.rp_filter'.format(iface), rp_filter) + +def verify(mpls): + if mpls is None: + return None + + if mpls['mpls_ldp']: + # Requre router-id + if not mpls['router_id']: + raise ConfigError(f"MPLS ldp router-id is mandatory!") + + # Requre discovery transport-address + if not mpls['ldp']['d_transp_ipv4'] and not mpls['ldp']['d_transp_ipv6']: + raise ConfigError(f"MPLS ldp discovery transport address is mandatory!") + + # Requre interface + if not mpls['ldp']['interfaces']: + raise ConfigError(f"MPLS ldp interface is mandatory!") + +def generate(mpls): + if mpls is None: + return None + + tmpl = jinja2.Template(config_tmpl) + config_text = tmpl.render(mpls) + with open(config_file, 'w') as f: + f.write(config_text) + + return None + +def apply(mpls): + if mpls is None: + return None + + # Set number of entries in the platform label table + if mpls['mpls_ldp']: + sysctl('net.mpls.platform_labels', '1048575') + else: + sysctl('net.mpls.platform_labels', '0') + + # Do not copy IP TTL to MPLS header + sysctl('net.mpls.ip_ttl_propagate', '0') + + # Allow mpls on interfaces + operate_mpls_on_intfc(mpls['ldp']['interfaces'], 1) + + # Disable mpls on deleted interfaces + diactive_ifaces = set(mpls['old_ldp']['interfaces']).difference(mpls['ldp']['interfaces']) + operate_mpls_on_intfc(diactive_ifaces, 0) + + if os.path.exists(config_file): + os.system("sudo vtysh -d ldpd -f " + config_file) + os.remove(config_file) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py index 20cc16f97..5a34a0b06 100755 --- a/src/conf_mode/system-login-banner.py +++ b/src/conf_mode/system-login-banner.py @@ -25,6 +25,7 @@ individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. + """ PRELOGIN_FILE = r'/etc/issue' @@ -32,8 +33,8 @@ PRELOGIN_NET_FILE = r'/etc/issue.net' POSTLOGIN_FILE = r'/etc/motd' default_config_data = { - 'issue': 'Welcome to VyOS - \n \l', - 'issue_net': 'Welcome to VyOS', + 'issue': 'Welcome to VyOS - \n \l\n', + 'issue_net': 'Welcome to VyOS\n', 'motd': motd } @@ -54,6 +55,8 @@ def get_config(): if tmp: tmp = tmp.replace('\\n','\n') tmp = tmp.replace('\\t','\t') + # always add newline character + tmp += '\n' else: tmp = '' @@ -66,6 +69,8 @@ def get_config(): if tmp: tmp = tmp.replace('\\n','\n') tmp = tmp.replace('\\t','\t') + # always add newline character + tmp += '\n' else: tmp = '' |