diff options
author | DmitriyEshenko <dmitriy.eshenko@vyos.io> | 2021-12-06 19:41:55 +0000 |
---|---|---|
committer | DmitriyEshenko <dmitriy.eshenko@vyos.io> | 2021-12-07 17:36:43 +0000 |
commit | 3b6504d4bfc8a7f19613ee32fef5242711ca390a (patch) | |
tree | 6acb188a1441f7df9620e19b9b6a71a8ee5f7234 | |
parent | ae16a51506cdc4de8e18f4678b8f55654d4abbba (diff) | |
download | vyos-1x-3b6504d4bfc8a7f19613ee32fef5242711ca390a.tar.gz vyos-1x-3b6504d4bfc8a7f19613ee32fef5242711ca390a.zip |
pppoe-server: T3006: Add range to regex generator
-rw-r--r-- | data/templates/accel-ppp/pppoe.config.tmpl | 18 | ||||
-rw-r--r-- | interface-definitions/service_pppoe-server.xml.in | 18 | ||||
-rw-r--r-- | python/vyos/range_regex.py | 142 | ||||
-rwxr-xr-x | src/conf_mode/service_pppoe-server.py | 15 |
4 files changed, 178 insertions, 15 deletions
diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 238e7ee15..0a8e0079b 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -108,19 +108,17 @@ ac-name={{ access_concentrator }} {% if iface_config.vlan_id is not defined and iface_config.vlan_range is not defined %} interface={{ iface }} {% endif %} -{% if iface_config.vlan_id is defined and iface_config.vlan_range is not defined %} -{% for vlan in iface_config.vlan_id %} -interface={{ iface }}.{{ vlan }} -vlan-mon={{ iface }},{{ vlan }} +{% if iface_config.vlan_range is defined %} +{% for regex in iface_config.regex %} +interface=re:^{{ iface | replace('.', '\\.') }}\.({{ regex }})$ {% endfor %} -{% endif %} -{% if iface_config.vlan_range is defined and iface_config.vlan_id is not defined %} vlan-mon={{ iface }},{{ iface_config.vlan_range | join(',') }} -interface=re:{{ iface | replace('.', '\\.') }}\.\d+ {% endif %} -{% if iface_config.vlan_id is defined and iface_config.vlan_range is defined %} -vlan-mon={{ iface }},{{ iface_config.vlan_id | join(',') }},{{ iface_config.vlan_range | join(',') }} -interface=re:{{ iface | replace('.', '\\.') }}\.\d+ +{% if iface_config.vlan_id is defined %} +{% for vlan in iface_config.vlan_id %} +vlan-mon={{ iface }},{{ vlan }} +interface=re:^{{ iface | replace('.', '\\.') }}\.{{ vlan }}$ +{% endfor %} {% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 188aed6c4..97952d882 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -70,19 +70,27 @@ <children> <leafNode name="vlan-id"> <properties> - <help>VLAN monitor for the automatic creation of vlans (user per vlan)</help> + <help>VLAN monitor for the automatic creation of single vlan</help> + <valueHelp> + <format>u32:1-4094</format> + <description>VLAN monitor for the automatic creation of single vlan</description> + </valueHelp> <constraint> - <validator name="numeric" argument="--range 1-4096"/> + <validator name="numeric" argument="--range 1-4094"/> </constraint> - <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage> + <constraintErrorMessage>VLAN ID needs to be between 1 and 4094</constraintErrorMessage> <multi/> </properties> </leafNode> <leafNode name="vlan-range"> <properties> - <help>VLAN monitor for the automatic creation of vlans (user per vlan)</help> + <help>VLAN monitor for the automatic creation of vlans range</help> + <valueHelp> + <format>start-end</format> + <description>VLAN monitor range for the automatic creation of vlans (e.g. 1-4094)</description> + </valueHelp> <constraint> - <regex>(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})-(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})</regex> + <validator name="range" argument="--min=1 --max=4094"/> </constraint> <multi/> </properties> diff --git a/python/vyos/range_regex.py b/python/vyos/range_regex.py new file mode 100644 index 000000000..a8190d140 --- /dev/null +++ b/python/vyos/range_regex.py @@ -0,0 +1,142 @@ +'''Copyright (c) 2013, Dmitry Voronin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +import math + +# coding=utf8 + +# Split range to ranges that has its unique pattern. +# Example for 12-345: +# +# 12- 19: 1[2-9] +# 20- 99: [2-9]\d +# 100-299: [1-2]\d{2} +# 300-339: 3[0-3]\d +# 340-345: 34[0-5] + +def range_to_regex(inpt_range): + if isinstance(inpt_range, str): + range_list = inpt_range.split('-') + # Check input arguments + if len(range_list) == 2: + # The first element in range must be higher then the second + if int(range_list[0]) < int(range_list[1]): + return regex_for_range(int(range_list[0]), int(range_list[1])) + + return None + +def bounded_regex_for_range(min_, max_): + return r'\b({})\b'.format(regex_for_range(min_, max_)) + +def regex_for_range(min_, max_): + """ + > regex_for_range(12, 345) + '1[2-9]|[2-9]\d|[1-2]\d{2}|3[0-3]\d|34[0-5]' + """ + positive_subpatterns = [] + negative_subpatterns = [] + + if min_ < 0: + min__ = 1 + if max_ < 0: + min__ = abs(max_) + max__ = abs(min_) + + negative_subpatterns = split_to_patterns(min__, max__) + min_ = 0 + + if max_ >= 0: + positive_subpatterns = split_to_patterns(min_, max_) + + negative_only_subpatterns = ['-' + val for val in negative_subpatterns if val not in positive_subpatterns] + positive_only_subpatterns = [val for val in positive_subpatterns if val not in negative_subpatterns] + intersected_subpatterns = ['-?' + val for val in negative_subpatterns if val in positive_subpatterns] + + subpatterns = negative_only_subpatterns + intersected_subpatterns + positive_only_subpatterns + return '|'.join(subpatterns) + + +def split_to_patterns(min_, max_): + subpatterns = [] + + start = min_ + for stop in split_to_ranges(min_, max_): + subpatterns.append(range_to_pattern(start, stop)) + start = stop + 1 + + return subpatterns + + +def split_to_ranges(min_, max_): + stops = {max_} + + nines_count = 1 + stop = fill_by_nines(min_, nines_count) + while min_ <= stop < max_: + stops.add(stop) + + nines_count += 1 + stop = fill_by_nines(min_, nines_count) + + zeros_count = 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + while min_ < stop <= max_: + stops.add(stop) + + zeros_count += 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + + stops = list(stops) + stops.sort() + + return stops + + +def fill_by_nines(integer, nines_count): + return int(str(integer)[:-nines_count] + '9' * nines_count) + + +def fill_by_zeros(integer, zeros_count): + return integer - integer % 10 ** zeros_count + + +def range_to_pattern(start, stop): + pattern = '' + any_digit_count = 0 + + for start_digit, stop_digit in zip(str(start), str(stop)): + if start_digit == stop_digit: + pattern += start_digit + elif start_digit != '0' or stop_digit != '9': + pattern += '[{}-{}]'.format(start_digit, stop_digit) + else: + any_digit_count += 1 + + if any_digit_count: + pattern += r'\d' + + if any_digit_count > 1: + pattern += '{{{}}}'.format(any_digit_count) + + return pattern
\ No newline at end of file diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 9fbd531da..1f31d132d 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -24,8 +24,11 @@ from vyos.configverify import verify_accel_ppp_base_service from vyos.template import render from vyos.util import call from vyos.util import dict_search +from vyos.util import get_interface_config from vyos import ConfigError from vyos import airbag +from vyos.range_regex import range_to_regex + airbag.enable() pppoe_conf = r'/run/accel-pppd/pppoe.conf' @@ -56,6 +59,11 @@ def verify(pppoe): if 'interface' not in pppoe: raise ConfigError('At least one listen interface must be defined!') + # Check is interface exists in the system + for iface in pppoe['interface']: + if not get_interface_config(iface): + raise ConfigError(f'Interface {iface} does not exist!') + # local ippool and gateway settings config checks if not (dict_search('client_ip_pool.subnet', pppoe) or (dict_search('client_ip_pool.start', pppoe) and @@ -73,6 +81,13 @@ def generate(pppoe): if not pppoe: return None + # Generate special regex for dynamic interfaces + for iface in pppoe['interface']: + if 'vlan_range' in pppoe['interface'][iface]: + pppoe['interface'][iface]['regex'] = [] + for vlan_range in pppoe['interface'][iface]['vlan_range']: + pppoe['interface'][iface]['regex'].append(range_to_regex(vlan_range)) + render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe) if dict_search('authentication.mode', pppoe) == 'local': |