From c9eaafd9f808aba8d29be73054e11d37577e539a Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 30 Dec 2023 23:25:20 +0100 Subject: T5474: establish common file name pattern for XML conf mode commands We will use _ as CLI level divider. The XML definition filename and also the Python helper should match the CLI node. Example: set interfaces ethernet -> interfaces_ethernet.xml.in set interfaces bond -> interfaces_bond.xml.in set service dhcp-server -> service_dhcp-server-xml.in (cherry picked from commit 4ef110fd2c501b718344c72d495ad7e16d2bd465) --- src/conf_mode/interfaces_pppoe.py | 148 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100755 src/conf_mode/interfaces_pppoe.py (limited to 'src/conf_mode/interfaces_pppoe.py') diff --git a/src/conf_mode/interfaces_pppoe.py b/src/conf_mode/interfaces_pppoe.py new file mode 100755 index 000000000..42f084309 --- /dev/null +++ b/src/conf_mode/interfaces_pppoe.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-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 . + +import os + +from sys import exit +from copy import deepcopy +from netifaces import interfaces + +from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configdict import get_pppoe_interfaces +from vyos.configverify import verify_authentication +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_vrf +from vyos.configverify import verify_mtu_ipv6 +from vyos.configverify import verify_mirror_redirect +from vyos.ifconfig import PPPoEIf +from vyos.template import render +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'pppoe'] + ifname, pppoe = get_interface_dict(conf, base) + + # We should only terminate the PPPoE session if critical parameters change. + # All parameters that can be changed on-the-fly (like interface description) + # should not lead to a reconnect! + for options in ['access-concentrator', 'connect-on-demand', 'service-name', + 'source-interface', 'vrf', 'no-default-route', + 'authentication', 'host_uniq']: + if is_node_changed(conf, base + [ifname, options]): + pppoe.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break + + if 'deleted' not in pppoe: + # We always set the MRU value to the MTU size. This code path only re-creates + # the old behavior if MRU is not set on the CLI. + if 'mru' not in pppoe: + pppoe['mru'] = pppoe['mtu'] + + return pppoe + +def verify(pppoe): + if 'deleted' in pppoe: + # bail out early + return None + + verify_source_interface(pppoe) + verify_authentication(pppoe) + verify_vrf(pppoe) + verify_mtu_ipv6(pppoe) + verify_mirror_redirect(pppoe) + + if {'connect_on_demand', 'vrf'} <= set(pppoe): + raise ConfigError('On-demand dialing and VRF can not be used at the same time') + + # both MTU and MRU have default values, thus we do not need to check + # if the key exists + if int(pppoe['mru']) > int(pppoe['mtu']): + raise ConfigError('PPPoE MRU needs to be lower then MTU!') + + 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}' + + if 'deleted' in pppoe or 'disable' in pppoe: + if os.path.exists(config_pppoe): + os.unlink(config_pppoe) + + return None + + # Create PPP configuration files + render(config_pppoe, 'pppoe/peer.j2', pppoe, permission=0o640) + + return None + +def apply(pppoe): + ifname = pppoe['ifname'] + if 'deleted' in pppoe or 'disable' in pppoe: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when certain config options change, + # like ACS name, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in pppoe): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/99-vyos-pppoe-callback + # which triggers PPPoEIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = PPPoEIf(ifname) + p.update(pppoe) + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3