#!/usr/bin/env python3 # # Copyright (C) 2019-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 . import os import jmespath from sys import exit from copy import deepcopy from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_source_interface from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ conf = Config() # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') # retrieve interface default values base = ['interfaces', 'pppoe'] default_values = defaults(base) # PPPoE is "special" the default MTU is 1492 - update accordingly default_values['mtu'] = '1492' ifname = os.environ['VYOS_TAGNODE_VALUE'] base = base + [ifname] pppoe = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # Check if interface has been removed if pppoe == {}: pppoe.update({'deleted' : ''}) # We have gathered the dict representation of the CLI, but there are # default options which we need to update into the dictionary # retrived. pppoe = dict_merge(default_values, pppoe) # Add interface instance name into dictionary pppoe.update({'ifname': ifname}) return pppoe def verify(pppoe): if 'deleted' in pppoe.keys(): # bail out early return None verify_source_interface(pppoe) verify_vrf(pppoe) if {'connect_on_demand', 'vrf'} <= set(pppoe): raise ConfigError('On-demand dialing and VRF can not be used at the same time') return None def generate(pppoe): # set up configuration file path variables where our templates will be # rendered into ifname = pppoe['ifname'] config_pppoe = f'/etc/ppp/peers/{ifname}' script_pppoe_pre_up = f'/etc/ppp/ip-pre-up.d/1000-vyos-pppoe-{ifname}' script_pppoe_ip_up = f'/etc/ppp/ip-up.d/1000-vyos-pppoe-{ifname}' script_pppoe_ip_down = f'/etc/ppp/ip-down.d/1000-vyos-pppoe-{ifname}' script_pppoe_ipv6_up = f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{ifname}' config_wide_dhcp6c = f'/run/dhcp6c/dhcp6c.{ifname}.conf' config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c] if 'deleted' in pppoe.keys(): # stop DHCPv6-PD client call(f'systemctl stop dhcp6c@{ifname}.service') # Hang-up PPPoE connection call(f'systemctl stop ppp@{ifname}.service') # Delete PPP configuration files for file in config_files: if os.path.exists(file): os.unlink(file) return None # Create PPP configuration files render(config_pppoe, 'pppoe/peer.tmpl', pppoe, trim_blocks=True, permission=0o755) # Create script for ip-pre-up.d render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) # Create script for ip-up.d render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) # Create script for ip-down.d render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe, trim_blocks=True, permission=0o755) # Create script for ipv6-up.d render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) tmp = jmespath.search('dhcpv6_options.prefix_delegation.interface', pppoe) if tmp and len(tmp) > 0: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value render(config_wide_dhcp6c, 'dhcp-client/ipv6_new.tmpl', pppoe, trim_blocks=True) return None def apply(pppoe): if 'deleted' in pppoe.keys(): # bail out early return None if 'disable' not in pppoe.keys(): # Dial PPPoE connection call('systemctl restart ppp@{ifname}.service'.format(**pppoe)) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)