Page MenuHomePhabricator

Rewrite the wireless interface configuration in the new style (python + XML)
Open, NormalPublicFEATURE REQUEST

Description

Configuration parsing and hostapd config generation is done in three places within vyatta-wireless:

  • Basic input testing for set interface wireless wlanX ...: Bash code in node.def files in /opt/vyatta/share/vyatta-cfg/templates/interfaces/wireless/
  • Validation of hostapd semantics: /opt/vyatta/sbin/wireless-config.pl
  • VyOS config parsing and hostapd.conf generation: /opt/vyatta/sbin/wireless-hostapd.pl

The validatior and generator part needs to go to one Python script. Coding rules apply: Python coding guidelines

Depends:

  • T452 should have been merged as statement of acceptance for syntax and workflow.

Details

Difficulty level
Hard (possibly days)
Version
-
Why the issue appeared?
Will be filled on close

I found an example file in vyatta-lldp:

#!/usr/bin/env python3
#
# Copyright (C) 2017 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 re
import sys

from vyos.config import Config
from vyos.util import ConfigError



def get_options(config):
    options ={}
    config.set_level('service lldp')
    options['listen_vlan'] = config.exists('listen-vlan')

    options["addr"] = config.return_value('management-address')

    snmp = config.exists('snmp enable')
    options["snmp"] = snmp
    if snmp:
        config.set_level('')
        options["sys_snmp"] = config.exists('service snmp')
        config.set_level('service lldp')

    config.set_level('service lldp legacy-protocols')
    options["cdp"] = config.exists("cdp")
    options["edp"] = config.exists("edp")
    options["fdp"] = config.exists("fdp")
    options["sonmp"] = config.exists("sonmp")
    return options


def get_interface_list(config):
    config.set_level('service lldp')
    intfs_names = config.list_nodes('interface')
    if len(intfs_names) < 0:
        return 0
    interface_list = []
    for name in intfs_names:
        config.set_level("service lldp interface {0}".format(name))
        disable = config.exists('disable')
        intf = {
            "name": name,
            "disable": disable
        }
        interface_list.append(intf)
    return interface_list


def get_location_intf(config, name):
    path = "service lldp interface {0}".format(name)
    config.set_level(path)
    if config.exists("location"):
        return 0
    config.set_level("{} location".format(path))
    civic_based = {}
    elin = None
    coordinate_based = {}

    if config.exists('civic-based'):
        config.set_level("{} location civic-based".format(path))
        cc = config.return_value("country-code")
        civic_based["country_code"] = cc
        civic_based["ca_type"] = []
        ca_types_names = config.list_nodes('ca-type')
        if ca_types_names:
            for ca_types_name in ca_types_names:
                config.set_level("{0} location civic-based ca-type {1}".format(path, ca_types_name))
                ca_val = config.return_value('ca-value')
                ca_type = {
                    "name": ca_types_name,
                    "ca_val": ca_val
                }
                civic_based["ca_type"].append(ca_type)

    elif config.exists("elin"):
        elin = config.return_value("elin")

    elif config.exists("coordinate-based"):
        config.set_level("{} location coordinate-based".format(path))
        alt = config.return_value("altitude")
        lat = config.return_value("latitude")
        long = config.return_value("longitude")
        datum = config.return_value("datum")
        coordinate_based["altitude"] = alt
        coordinate_based["latitude"] = lat
        coordinate_based["longitude"] = long
        coordinate_based["datum"] = datum

    intf = {
        "name": name,
        "civic_based": civic_based,
        "elin": elin,
        "coordinate_based": coordinate_based

    }
    return intf


def get_location(config):
    config.set_level('service lldp')
    intfs_names = config.list_nodes('interface')
    if len(intfs_names) < 0:
        return 0
    if config.exists("disable"):
        return 0
    intfs_location = []
    for name in intfs_names:
        intf = get_location_intf(config, name)
        intfs_location.append(intf)
    return intfs_location


def get_config():
    conf = Config()
    options = get_options(conf)
    interface_list = get_interface_list(conf)
    location = get_location(conf)
    lldp = {"options": options, "interface_list": interface_list, "location": location}
    return lldp


def verify(lldp):

    # check location
    for location in lldp["location"]:

        # check civic-based
        if len(location["civic_based"]) > 0:
            if len(location["coordinate_based"]) > 0 or location["elin"]:
                raise ConfigError("Can only configure 1 location type for interface {0}".format(location["name"]))

            # check country-code
            if not location["civic_based"]["country_code"]:
                raise ConfigError("Invalid location for interface {0}: must configure the country code".format(location["name"]))

            if not re.match(r"^[a-zA-Z]{2}$", location["civic_based"]["country_code"]):
                raise ConfigError("Invalid location for interface {0}: country-code must be 2 characters".format(location["name"]))
            # check ca-type
            if len(location["civic_based"]["ca_type"]) < 0:
                raise ConfigError("Invalid location for interface {0}: must define at least 1 ca-type".format(location["name"]))

            for ca_type in location["civic_based"]["ca_type"]:
                if not int(ca_type["name"]) in range(0, 129):
                    raise ConfigError("Invalid location for interface {0}: ca-type must between 0-128".format(location["name"]))

                if not ca_type["ca_val"]:
                    raise ConfigError("Invalid location for interface {0}: must configure the ca-value for ca-type {1}".format(location["name"],ca_type["name"]))

        # check coordinate-based
        elif len(location["coordinate_based"]) > 0:
            # check longitude and latitude
            if not location["coordinate_based"]["longitude"]:
                raise ConfigError("Must define longitude for interface {0}".format(location["name"]))

            if not location["coordinate_based"]["latitude"]:
                raise ConfigError("Must define latitude for interface {0}".format(location["name"]))

            if not re.match(r"^(\d+)(\.\d+)?[nNsS]$", location["coordinate_based"]["latitude"]):
                raise ConfigError("Invalid location for interface {0}: latitude should be a number followed by S or N".format(location["name"]))

            if not re.match(r"^(\d+)(\.\d+)?[eEwW]$", location["coordinate_based"]["longitude"]):
                raise ConfigError("Invalid location for interface {0}: longitude should be a number followed by E or W".format(location["name"]))

            # check altitude and datum if exist
            if location["coordinate_based"]["altitude"]:
                if not re.match(r"^[-+0-9\.]+$", location["coordinate_based"]["altitude"]):
                    raise ConfigError("Invalid location for interface {0}: altitude should be a positive or negative number".format(location["name"]))

            if location["coordinate_based"]["datum"]:
                if not re.match(r"^(WGS84|NAD83|MLLW)$", location["coordinate_based"]["datum"]):
                    raise ConfigError("Invalid location for interface {0}: datum should be WGS84, NAD83, or MLLW".format(location["name"]))

        # check elin
        elif len(location["elin"]) > 0:
            if not re.match(r"^[0-9]{10,25}$", location["elin"]):
                raise ConfigError("Invalid location for interface {0}: ELIN number must be between 10-25 numbers".format(location["name"]))

    # check options
    if lldp["options"]["snmp"]:
        if not lldp["options"]["sys_snmp"]:
            raise ConfigError("SNMP must be configured to enable LLDP SNMP")


def generate(config):
    pass


def apply(config):
    pass

if __name__ == '__main__':
    try:
        c = get_config()
        verify(c)
        generate(c)
        apply(c)
    except ConfigError as e:
        print(e)
        sys.exit(1)
alainlamar moved this task from Need Triage to Backlog on the VyOS 1.3.x board.Jan 27 2018, 9:44 PM
alainlamar claimed this task.
alainlamar changed Difficulty level from Unknown (require assessment) to Hard (possibly days).
alainlamar updated the task description. (Show Details)Jan 27 2018, 9:48 PM
dmbaturin renamed this task from Port configuration validator and parser to Python to Rewrite the wireless interface configuration in the new style (python + XML).May 24 2018, 5:36 PM
dmbaturin moved this task from Need Triage to Wishlist on the VyOS 1.2.x board.
dmbaturin moved this task from Wishlist to Finished on the VyOS 1.2.x board.Sep 2 2018, 9:26 PM
dmbaturin moved this task from Finished to Wishlist on the VyOS 1.2.x board.