diff options
| -rw-r--r-- | data/templates/dhcpv6-server/dhcpdv6.conf.tmpl | 4 | ||||
| -rw-r--r-- | interface-definitions/dhcpv6-server.xml.in | 38 | ||||
| -rw-r--r-- | op-mode-definitions/traceroute.xml | 70 | ||||
| -rw-r--r-- | python/vyos/configdict.py | 377 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vlan.py | 40 | ||||
| -rwxr-xr-x | src/conf_mode/dhcpv6_server.py | 112 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-bonding.py | 159 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-ethernet.py | 149 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-pseudo-ethernet.py | 151 | ||||
| -rwxr-xr-x | src/migration-scripts/dhcpv6-server/0-to-1 | 61 | ||||
| -rw-r--r-- | src/systemd/isc-dhcp-server6.service | 2 | ||||
| -rwxr-xr-x | src/validators/numeric | 6 | 
12 files changed, 518 insertions, 651 deletions
diff --git a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl index 80d620fcf..d6b0ae935 100644 --- a/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl +++ b/data/templates/dhcpv6-server/dhcpdv6.conf.tmpl @@ -21,7 +21,7 @@ shared-network {{ network.name }} {          range6 {{ range.start }} {{ range.stop }};          {%- endfor %}          {%- if subnet.domain_search %} -        option dhcp6.domain-search {{ subnet.domain_search | join(', ') }}; +        option dhcp6.domain-search "{{ subnet.domain_search | join('", "') }}";          {%- endif %}          {%- if subnet.lease_def %}          default-lease-time {{ subnet.lease_def }}; @@ -51,7 +51,7 @@ shared-network {{ network.name }} {          option dhcp6.sip-servers-addresses {{ subnet.sip_address | join(', ') }};          {%- endif %}          {%- if subnet.sip_hostname %} -        option dhcp6.sip-servers-names {{ subnet.sip_hostname | join(', ') }}; +        option dhcp6.sip-servers-names "{{ subnet.sip_hostname | join('", "') }}";          {%- endif %}          {%- if subnet.sntp_server %}          option dhcp6.sntp-servers {{ subnet.sntp_server | join(', ') }}; diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in index 7d4c0de23..4073b46b2 100644 --- a/interface-definitions/dhcpv6-server.xml.in +++ b/interface-definitions/dhcpv6-server.xml.in @@ -126,16 +126,37 @@                        <leafNode name="default">                          <properties>                            <help>Default time (in seconds) that will be assigned to a lease</help> +                          <valueHelp> +                            <format>1-4294967295</format> +                            <description>DHCPv6 valid lifetime</description> +                          </valueHelp> +                          <constraint> +                            <validator name="numeric" argument="--range 1-4294967295"/> +                          </constraint>                          </properties>                        </leafNode>                        <leafNode name="maximum">                          <properties>                            <help>Maximum time (in seconds) that will be assigned to a lease</help> +                          <valueHelp> +                            <format>1-4294967295</format> +                            <description>Maximum lease time in seconds</description> +                          </valueHelp> +                          <constraint> +                            <validator name="numeric" argument="--range 1-4294967295"/> +                          </constraint>                          </properties>                        </leafNode>                        <leafNode name="minimum">                          <properties>                            <help>Minimum time (in seconds) that will be assigned to a lease</help> +                          <valueHelp> +                            <format>1-4294967295</format> +                            <description>Minimum lease time in seconds</description> +                          </valueHelp> +                          <constraint> +                            <validator name="numeric" argument="--range 1-4294967295"/> +                          </constraint>                          </properties>                        </leafNode>                      </children> @@ -243,29 +264,24 @@                        </tagNode>                      </children>                    </node> -                  <leafNode name="sip-server-address"> +                  <leafNode name="sip-server">                      <properties>                        <help>IPv6 address of SIP server</help>                        <valueHelp>                          <format>ipv6</format>                          <description>IPv6 address of SIP server</description>                        </valueHelp> +                      <valueHelp> +                        <format>hostname</format> +                        <description>FQDN of SIP server</description> +                      </valueHelp>                        <constraint>                          <validator name="ipv6-address"/> +                        <validator name="fqdn"/>                        </constraint>                        <multi/>                      </properties>                    </leafNode> -                  <leafNode name="sip-server-name"> -                    <properties> -                      <help>SIP server name</help> -                      <constraint> -                        <regex>[-_a-zA-Z0-9.]+</regex> -                      </constraint> -                      <constraintErrorMessage>Invalid SIP server name. May only contain letters, numbers and .-_</constraintErrorMessage> -                      <multi/> -                    </properties> -                  </leafNode>                    <leafNode name="sntp-server">                      <properties>                        <help>IPv6 address of an SNTP server for client to use</help> diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index d623fe103..1aea8eef6 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -1,71 +1,70 @@  <?xml version="1.0"?>  <interfaceDefinition> -  <node name="traceroute"> +  <tagNode name="traceroute">      <properties>        <help>Track network path to node</help> +      <completionHelp> +        <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> +      </completionHelp>      </properties> +    <command>/usr/bin/traceroute "$2"</command> +  </tagNode> +  <node name="traceroute">      <children> -      <tagNode name=""> -        <properties> -          <help>Track network path to specified node</help> -          <completionHelp> -            <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> -          </completionHelp> -        </properties> -        <command>/usr/bin/traceroute $2</command> -      </tagNode>        <tagNode name="ipv4">          <properties> -          <help>Track network path to <hostname|IPv4 address></help> +          <help>Explicitly use IPv4 when tracing the path</help>            <completionHelp>              <list><hostname> <x.x.x.x></list>            </completionHelp>          </properties> -        <command>/usr/bin/traceroute -4 $3</command> +        <command>/usr/bin/traceroute -4 "$3"</command>        </tagNode>        <tagNode name="ipv6">          <properties> -          <help>Track network path to <hostname|IPv6 address></help> +          <help>Explicitly use IPv6 when tracing the path</help>            <completionHelp>              <list><hostname> <h:h:h:h:h:h:h:h></list>            </completionHelp>          </properties> -        <command>/usr/bin/traceroute -6 $3</command> +        <command>/usr/bin/traceroute -6 "$3"</command>        </tagNode>        <tagNode name="vrf">          <properties> -          <help>Track network path to specified node via given VRF instance</help> +          <help>Track network path to specified node via given VRF</help>            <completionHelp>              <path>vrf name</path>            </completionHelp>          </properties>          <children> +          <!-- we need an empty tagNode to pass in a plain fqdn/ip address and +               let traceroute decide how to handle this parameter -->            <tagNode name="">              <properties> -              <help>Track network path to specified node</help> +              <help>Track network path to specified node via given VRF</help>                <completionHelp>                  <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list>                </completionHelp>              </properties> -            <command>sudo ip vrf exec "$3" traceroute "$4"</command> +            <command>sudo /usr/sbin/ip vrf exec "$3" /usr/bin/traceroute "$4"</command>            </tagNode>            <tagNode name="ipv4">              <properties> -              <help>Track network path to <hostname|IPv4 address></help> +              <help>Explicitly use IPv4 when tracing the path via given VRF</help>                <completionHelp>                  <list><hostname> <x.x.x.x></list>                </completionHelp>              </properties> -            <command>sudo ip vrf exec "$3" traceroute -4 "$5"</command> +            <command>sudo /usr/sbin/ip vrf exec "$3" /usr/bin/traceroute -4 "$5"</command>            </tagNode>            <tagNode name="ipv6">              <properties> -              <help>Track network path to <hostname|IPv6 address></help> +              <help>Explicitly use IPv6 when tracing the path via given VRF</help>                <completionHelp>                  <list><hostname> <h:h:h:h:h:h:h:h></list>                </completionHelp>              </properties> -            <command>sudo ip vrf exec "$3" traceroute -6 "$5"</command> +            <command>sudo /usr/sbin/ip vrf exec "$3" /usr/bin/traceroute -6 "$5"</command>            </tagNode>          </children>        </tagNode> @@ -75,13 +74,38 @@      <children>        <tagNode name="traceroute">          <properties> -          <help>Monitor the path to a destination in realtime</help> +          <help>Monitor path to destination in realtime</help>            <completionHelp>              <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list>            </completionHelp>          </properties> -        <command>/usr/bin/mtr $3</command> +        <command>/usr/bin/mtr "$3"</command>        </tagNode> +      <node name="traceroute"> +        <children> +          <tagNode name="vrf"> +            <properties> +              <help>Monitor path to destination in realtime via given VRF</help> +              <completionHelp> +                <path>vrf name</path> +              </completionHelp> +            </properties> +            <children> +              <!-- we need an empty tagNode to pass in a plain fqdn/ip address and +                   let traceroute decide how to handle this parameter --> +              <tagNode name=""> +                <properties> +                  <help>Track network path to specified node via given VRF</help> +                  <completionHelp> +                    <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> +                  </completionHelp> +                </properties> +                <command>sudo /usr/sbin/ip vrf exec "$4" /usr/bin/mtr "$5"</command> +              </tagNode> +            </children> +          </tagNode> +        </children> +      </node>      </children>    </node>  </interfaceDefinition> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 2ce8a795f..e1b704a31 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -18,7 +18,12 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion.  """ +from enum import Enum +from copy import deepcopy +  from vyos import ConfigError +from vyos.ifconfig import Interface +  def retrieve_config(path_hash, base_path, config):      """ @@ -98,192 +103,338 @@ def get_ethertype(ethertype_val):          raise ConfigError('invalid ethertype "{}"'.format(ethertype_val)) -def vlan_to_dict(conf): +vlan_default = { +    'address': [], +    'address_remove': [], +    'description': '', +    'dhcp_client_id': '', +    'dhcp_hostname': '', +    'dhcp_vendor_class_id': '', +    'dhcpv6_prm_only': False, +    'dhcpv6_temporary': False, +    'disable': False, +    'disable_link_detect': 1, +    'egress_qos': '', +    'egress_qos_changed': False, +    'ip_disable_arp_filter': 1, +    'ip_enable_arp_accept': 0, +    'ip_enable_arp_announce': 0, +    'ip_enable_arp_ignore': 0, +    'ip_proxy_arp': 0, +    'ipv6_autoconf': 0, +    'ipv6_eui64_prefix': [], +    'ipv6_eui64_prefix_remove': [], +    'ipv6_forwarding': 1, +    'ipv6_dup_addr_detect': 1, +    'ingress_qos': '', +    'ingress_qos_changed': False, +    'mac': '', +    'mtu': 1500, +    'vif_c': [], +    'vif_c_remove': [], +    'vrf': '' +} + +# see: https://docs.python.org/3/library/enum.html#functional-api +disable = Enum('disable','none was now both') + +def disable_state(conf, check=[3,5,7]): +    """ +    return if and how a particual section of the configuration is has disable'd +    using "disable" including if it was disabled by one of its parent. + +    check: a list of the level we should check, here 7,5 and 3 +          interfaces ethernet eth1 vif-s 1 vif-c 2 disable +          interfaces ethernet eth1 vif 1 disable +          interfaces ethernet eth1 disable + +    it returns an enum (none, was, now, both) +    """ + +    # save where we are in the config +    current_level = conf.get_level() + +    # logic to figure out if the interface (or one of it parent is disabled) +    eff_disable = False +    act_disable = False + +    levels = check[:] +    working_level = current_level[:] + +    while levels: +        position = len(working_level) +        if not position: +            break +        if position not in levels: +            working_level = working_level[:-1] +            continue + +        levels.remove(position) +        conf.set_level(working_level) +        working_level = working_level[:-1] + +        eff_disable = eff_disable or conf.exists_effective('disable') +        act_disable = act_disable or conf.exists('disable') + +    conf.set_level(current_level) + +    # how the disabling changed +    if eff_disable and act_disable: +        return disable.both +    if eff_disable and not eff_disable: +        return disable.was +    if not eff_disable and act_disable: +        return disable.now +    return disable.none + + +def intf_to_dict(conf, default):      """      Common used function which will extract VLAN related information from config      and represent the result as Python dictionary.      Function call's itself recursively if a vif-s/vif-c pair is detected.      """ -    vlan = { -        'id': conf.get_level()[-1], # get the '100' in 'interfaces bonding bond0 vif-s 100' -        'address': [], -        'address_remove': [], -        'description': '', -        'dhcp_client_id': '', -        'dhcp_hostname': '', -        'dhcp_vendor_class_id': '', -        'dhcpv6_prm_only': False, -        'dhcpv6_temporary': False, -        'disable': False, -        'disable_link_detect': 1, -        'egress_qos': '', -        'egress_qos_changed': False, -        'ip_disable_arp_filter': 1, -        'ip_enable_arp_accept': 0, -        'ip_enable_arp_announce': 0, -        'ip_enable_arp_ignore': 0, -        'ip_proxy_arp': 0, -        'ipv6_autoconf': 0, -        'ipv6_eui64_prefix': [], -        'ipv6_eui64_prefix_remove': [], -        'ipv6_forwarding': 1, -        'ipv6_dup_addr_detect': 1, -        'ingress_qos': '', -        'ingress_qos_changed': False, -        'mac': '', -        'mtu': 1500, -        'vrf': '' -    } + +    intf = deepcopy(default) +      # retrieve configured interface addresses      if conf.exists('address'): -        vlan['address'] = conf.return_values('address') - -    # Determine interface addresses (currently effective) - to determine which -    # address is no longer valid and needs to be removed from the bond -    eff_addr = conf.return_effective_values('address') -    act_addr = conf.return_values('address') -    vlan['address_remove'] = list_diff(eff_addr, act_addr) +        intf['address'] = conf.return_values('address')      # retrieve interface description      if conf.exists('description'): -        vlan['description'] = conf.return_value('description') +        intf['description'] = conf.return_value('description')      # get DHCP client identifier      if conf.exists('dhcp-options client-id'): -        vlan['dhcp_client_id'] = conf.return_value('dhcp-options client-id') +        intf['dhcp_client_id'] = conf.return_value('dhcp-options client-id')      # DHCP client host name (overrides the system host name)      if conf.exists('dhcp-options host-name'): -        vlan['dhcp_hostname'] = conf.return_value('dhcp-options host-name') +        intf['dhcp_hostname'] = conf.return_value('dhcp-options host-name')      # DHCP client vendor identifier      if conf.exists('dhcp-options vendor-class-id'): -        vlan['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') +        intf['dhcp_vendor_class_id'] = conf.return_value( +            'dhcp-options vendor-class-id')      # DHCPv6 only acquire config parameters, no address      if conf.exists('dhcpv6-options parameters-only'): -        vlan['dhcpv6_prm_only'] = True +        intf['dhcpv6_prm_only'] = True      # DHCPv6 temporary IPv6 address      if conf.exists('dhcpv6-options temporary'): -        vlan['dhcpv6_temporary'] = True +        intf['dhcpv6_temporary'] = True      # ignore link state changes      if conf.exists('disable-link-detect'): -        vlan['disable_link_detect'] = 2 - -    # disable VLAN interface -    if conf.exists('disable'): -        vlan['disable'] = True +        intf['disable_link_detect'] = 2      # ARP filter configuration      if conf.exists('ip disable-arp-filter'): -        vlan['ip_disable_arp_filter'] = 0 +        intf['ip_disable_arp_filter'] = 0      # ARP enable accept      if conf.exists('ip enable-arp-accept'): -        vlan['ip_enable_arp_accept'] = 1 +        intf['ip_enable_arp_accept'] = 1      # ARP enable announce      if conf.exists('ip enable-arp-announce'): -        vlan['ip_enable_arp_announce'] = 1 +        intf['ip_enable_arp_announce'] = 1      # ARP enable ignore      if conf.exists('ip enable-arp-ignore'): -        vlan['ip_enable_arp_ignore'] = 1 +        intf['ip_enable_arp_ignore'] = 1      # Enable Proxy ARP      if conf.exists('ip enable-proxy-arp'): -        vlan['ip_proxy_arp'] = 1 +        intf['ip_proxy_arp'] = 1      # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC)      if conf.exists('ipv6 address autoconf'): -        vlan['ipv6_autoconf'] = 1 +        intf['ipv6_autoconf'] = 1      # Get prefixes for IPv6 addressing based on MAC address (EUI-64)      if conf.exists('ipv6 address eui64'): -        vlan['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - -    # Determine currently effective EUI64 addresses - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values('ipv6 address eui64') -    vlan['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, vlan['ipv6_eui64_prefix']) - -    # Remove the default link-local address if set. -    if conf.exists('ipv6 address no-default-link-local'): -        vlan['ipv6_eui64_prefix_remove'].append('fe80::/64') -    else: -        # add the link-local by default to make IPv6 work -        vlan['ipv6_eui64_prefix'].append('fe80::/64') +        intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64')      # Disable IPv6 forwarding on this interface      if conf.exists('ipv6 disable-forwarding'): -        vlan['ipv6_forwarding'] = 0 - -    # IPv6 Duplicate Address Detection (DAD) tries -    if conf.exists('ipv6 dup-addr-detect-transmits'): -        vlan['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) +        intf['ipv6_forwarding'] = 0      # Media Access Control (MAC) address      if conf.exists('mac'): -        vlan['mac'] = conf.return_value('mac') -        # always recreate EUI64 addresses if mac is set -        # I'm not sure how to check if a vlan interface exists or how to get its current mac. -        vlan['ipv6_eui64_prefix_remove'] += vlan['ipv6_eui64_prefix'] +        intf['mac'] = conf.return_value('mac') + +    # IPv6 Duplicate Address Detection (DAD) tries +    if conf.exists('ipv6 dup-addr-detect-transmits'): +        intf['ipv6_dup_addr_detect'] = int( +            conf.return_value('ipv6 dup-addr-detect-transmits'))      # Maximum Transmission Unit (MTU)      if conf.exists('mtu'): -        vlan['mtu'] = int(conf.return_value('mtu')) +        intf['mtu'] = int(conf.return_value('mtu'))      # retrieve VRF instance      if conf.exists('vrf'): -        vlan['vrf'] = conf.return_value('vrf') +        intf['vrf'] = conf.return_value('vrf') -    # VLAN egress QoS +    #  egress QoS      if conf.exists('egress-qos'): -        vlan['egress_qos'] = conf.return_value('egress-qos') +        intf['egress_qos'] = conf.return_value('egress-qos')      # egress changes QoS require VLAN interface recreation      if conf.return_effective_value('egress-qos'): -        if vlan['egress_qos'] != conf.return_effective_value('egress-qos'): -            vlan['egress_qos_changed'] = True +        if intf['egress_qos'] != conf.return_effective_value('egress-qos'): +            intf['egress_qos_changed'] = True -    # VLAN ingress QoS +    # ingress QoS      if conf.exists('ingress-qos'): -        vlan['ingress_qos'] = conf.return_value('ingress-qos') +        intf['ingress_qos'] = conf.return_value('ingress-qos')      # ingress changes QoS require VLAN interface recreation      if conf.return_effective_value('ingress-qos'): -        if vlan['ingress_qos'] != conf.return_effective_value('ingress-qos'): -            vlan['ingress_qos_changed'] = True +        if intf['ingress_qos'] != conf.return_effective_value('ingress-qos'): +            intf['ingress_qos_changed'] = True -    # ethertype is mandatory on vif-s nodes and only exists here! -    # check if this is a vif-s node at all: -    if conf.get_level()[-2] == 'vif-s': -        vlan['vif_c'] = [] -        vlan['vif_c_remove'] = [] - -        # ethertype uses a default of 0x88A8 -        tmp = '0x88A8' -        if conf.exists('ethertype'): -             tmp = conf.return_value('ethertype') -        vlan['ethertype'] = get_ethertype(tmp) - -        # get vif-c interfaces (currently effective) - to determine which vif-c +    disabled = disable_state(conf) + +    # Get the interface IPs +    eff_addr = conf.return_effective_values('address') +    act_addr = conf.return_values('address') + +    # Get prefixes for IPv6 addressing based on MAC address (EUI-64) +    eff_eui = conf.return_effective_values('ipv6 address eui64') +    act_eui = conf.return_values('ipv6 address eui64') + +    # Determine what should stay or be removed +    if disabled == disable.both: +        # was and is still disabled +        intf['disable'] = True +        intf['address'] = [] +        intf['address_remove'] = [] +        intf['ipv6_eui64_prefix'] = [] +        intf['ipv6_eui64_prefix_remove'] = [] +    elif disabled == disable.now: +        # it is now disable but was not before +        intf['disable'] = True +        intf['address'] = [] +        intf['address_remove'] = eff_addr +        intf['ipv6_eui64_prefix'] = [] +        intf['ipv6_eui64_prefix_remove'] = eff_eui +    elif disabled == disable.was: +        # it was disable but not anymore +        intf['disable'] = False +        intf['address'] = act_addr +        intf['address_remove'] = [] +        intf['ipv6_eui64_prefix'] = act_eui +        intf['ipv6_eui64_prefix_remove'] = [] +    else: +        # normal change +        intf['disable'] = False +        intf['address'] = act_addr +        intf['address_remove'] = list_diff(eff_addr, act_addr) +        intf['ipv6_eui64_prefix'] = act_eui +        intf['ipv6_eui64_prefix_remove'] = list_diff(eff_eui, act_eui) + +    # Remove the default link-local address if set. +    if conf.exists('ipv6 address no-default-link-local'): +        intf['ipv6_eui64_prefix_remove'].append('fe80::/64') +    else: +        # add the link-local by default to make IPv6 work +        intf['ipv6_eui64_prefix'].append('fe80::/64') + +    # Find out if MAC has changed +    try: +        interface = Interface(intf['intf'], create=False) +        if intf['mac'] and intf['mac'] != interface.get_mac(): +            intf['ipv6_eui64_prefix_remove'] += intf['ipv6_eui64_prefix'] +    except Exception: +        # If the interface does not exists, it can not have changed +        pass + +    return intf, disable + + + +def add_to_dict(conf, disabled, ifdict, section, key): +    """ +    parse a section of vif/vif-s/vif-c and add them to the dict +    follow the convention to: +    * use the "key" for what to add +    * use the "key" what what to remove + +    conf:     is the Config() already at the level we need to parse +    disabled: is a disable enum so we know how to handle to data +    intf:     if the interface dictionary +    section:  is the section name to parse (vif/vif-s/vif-c) +    key:      is the dict key to use (vif/vifs/vifc) +    """ + +    if not conf.exists(section): +        return ifdict + +    effect = conf.list_effective_nodes(section) +    active = conf.list_nodes(section) + +    # the section to parse for vlan +    sections = [] + +    # Determine interface addresses (currently effective) - to determine which +    # address is no longer valid and needs to be removed from the bond +    if disabled == disable.both: +        # was and is still disabled +        ifdict[f'{key}_remove'] = [] +    elif disabled == disable.now: +        # it is now disable but was not before +        ifdict[f'{key}_remove'] = effect +    elif disabled == disable.was: +        # it was disable but not anymore +        ifdict[f'{key}_remove'] = [] +        sections = active +    else: +        # normal change +        # get vif-s interfaces (currently effective) - to determine which vif-s          # interface is no longer present and needs to be removed -        eff_intf = conf.list_effective_nodes('vif-c') -        act_intf = conf.list_nodes('vif-c') -        vlan['vif_c_remove'] = list_diff(eff_intf, act_intf) - -        # check if there is a Q-in-Q vlan customer interface -        # and call this function recursively -        if conf.exists('vif-c'): -            cfg_level = conf.get_level() -            # add new key (vif-c) to dictionary -            for vif in conf.list_nodes('vif-c'): -                # set config level to vif interface -                conf.set_level(cfg_level + ['vif-c', vif]) -                vlan['vif_c'].append(vlan_to_dict(conf)) +        ifdict[f'{key}_remove'] = list_diff(effect, active) +        sections = active + +    current_level = conf.get_level() + +    # add each section, the key must already exists +    for s in sections: +        # set config level to vif interface +        conf.set_level(current_level + [section, s]) +        ifdict[f'{key}'].append(vlan_to_dict(conf)) + +    # re-set configuration level to leave things as found +    conf.set_level(current_level) + +    return ifdict + + +def vlan_to_dict(conf, default=vlan_default): +    vlan, disabled = intf_to_dict(conf, default) +    # get the '100' in 'interfaces bonding bond0 vif-s 100 +    vlan['id'] = conf.get_level()[-1] + +    current_level = conf.get_level() + +    # if this is a not within vif-s node, we are done +    if current_level[-2] != 'vif-s': +        return vlan + +    # ethertype is mandatory on vif-s nodes and only exists here! +    # ethertype uses a default of 0x88A8 +    tmp = '0x88A8' +    if conf.exists('ethertype'): +        tmp = conf.return_value('ethertype') +    vlan['ethertype'] = get_ethertype(tmp) + +    # check if there is a Q-in-Q vlan customer interface +    # and call this function recursively + +    add_to_dict(conf, disable, vlan, 'vif-c', 'vif_c')      return vlan diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py index 7b1e00d87..d68e8f6cd 100644 --- a/python/vyos/ifconfig/vlan.py +++ b/python/vyos/ifconfig/vlan.py @@ -101,26 +101,26 @@ class VLAN:          >>> i.add_vlan(10)          """          vlan_ifname = self.config['ifname'] + '.' + str(vlan_id) -        if not os.path.exists(f'/sys/class/net/{vlan_ifname}'): -            self._vlan_id = int(vlan_id) - -            if ethertype: -                self._ethertype = ethertype -                ethertype = 'proto {}'.format(ethertype) - -            # Optional ingress QOS mapping -            opt_i = '' -            if ingress_qos: -                opt_i = 'ingress-qos-map ' + ingress_qos -            # Optional egress QOS mapping -            opt_e = '' -            if egress_qos: -                opt_e = 'egress-qos-map ' + egress_qos - -            # create interface in the system -            cmd = 'ip link add link {ifname} name {ifname}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \ -                .format(ifname=self.config['ifname'], vlan=self._vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i) -            self._cmd(cmd) +        if os.path.exists(f'/sys/class/net/{vlan_ifname}'): +            return self.__class__(vlan_ifname) + +        if ethertype: +            self._ethertype = ethertype +            ethertype = 'proto {}'.format(ethertype) + +        # Optional ingress QOS mapping +        opt_i = '' +        if ingress_qos: +            opt_i = 'ingress-qos-map ' + ingress_qos +        # Optional egress QOS mapping +        opt_e = '' +        if egress_qos: +            opt_e = 'egress-qos-map ' + egress_qos + +        # create interface in the system +        cmd = 'ip link add link {ifname} name {ifname}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \ +            .format(ifname=self.ifname, vlan=vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i) +        self._cmd(cmd)          # return new object mapping to the newly created interface          # we can now work on this object for e.g. IP address setting diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 07e936906..159d16401 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -23,7 +23,7 @@ from copy import deepcopy  from vyos.config import Config  from vyos.template import render  from vyos.util import call -from vyos.validate import is_subnet_connected +from vyos.validate import is_subnet_connected, is_ipv6  from vyos import ConfigError  config_file = r'/run/dhcp-server/dhcpdv6.conf' @@ -37,24 +37,25 @@ default_config_data = {  def get_config():      dhcpv6 = deepcopy(default_config_data)      conf = Config() -    if not conf.exists('service dhcpv6-server'): +    base = ['service', 'dhcpv6-server'] +    if not conf.exists(base):          return None      else: -        conf.set_level('service dhcpv6-server') +        conf.set_level(base)      # Check for global disable of DHCPv6 service -    if conf.exists('disable'): +    if conf.exists(['disable']):          dhcpv6['disabled'] = True          return dhcpv6      # Preference of this DHCPv6 server compared with others -    if conf.exists('preference'): -        dhcpv6['preference'] = conf.return_value('preference') +    if conf.exists(['preference']): +        dhcpv6['preference'] = conf.return_value(['preference'])      # check for multiple, shared networks served with DHCPv6 addresses -    if conf.exists('shared-network-name'): -        for network in conf.list_nodes('shared-network-name'): -            conf.set_level('service dhcpv6-server shared-network-name {0}'.format(network)) +    if conf.exists(['shared-network-name']): +        for network in conf.list_nodes(['shared-network-name']): +            conf.set_level(base + ['shared-network-name', network])              config = {                  'name': network,                  'disabled': False, @@ -62,13 +63,13 @@ def get_config():              }              # If disabled, the shared-network configuration becomes inactive -            if conf.exists('disable'): +            if conf.exists(['disable']):                  config['disabled'] = True              # check for multiple subnet configurations in a shared network -            if conf.exists('subnet'): -                for net in conf.list_nodes('subnet'): -                    conf.set_level('service dhcpv6-server shared-network-name {0} subnet {1}'.format(network, net)) +            if conf.exists(['subnet']): +                for net in conf.list_nodes(['subnet']): +                    conf.set_level(base + ['shared-network-name', network, 'subnet', net])                      subnet = {                          'network': net,                          'range6_prefix': [], @@ -94,25 +95,25 @@ def get_config():                      # least one address range statement. The range statement gives the lowest and highest                      # IP addresses in a range. All IP addresses in the range should be in the subnet in                      # which the range statement is declared. -                    if conf.exists('address-range prefix'): -                        for prefix in conf.list_nodes('address-range prefix'): +                    if conf.exists(['address-range', 'prefix']): +                        for prefix in conf.list_nodes(['address-range', 'prefix']):                              range = {                                  'prefix': prefix,                                  'temporary': False                              }                              # Address range will be used for temporary addresses -                            if conf.exists('address-range prefix {0} temporary'.format(range['prefix'])): +                            if conf.exists(['address-range' 'prefix', prefix, 'temporary']):                                  range['temporary'] = True                              # Append to subnet temporary range6 list                              subnet['range6_prefix'].append(range) -                    if conf.exists('address-range start'): -                        for range in conf.list_nodes('address-range start'): +                    if conf.exists(['address-range', 'start']): +                        for range in conf.list_nodes(['address-range', 'start']):                              range = {                                  'start': range, -                                'stop': conf.return_value('address-range start {0} stop'.format(range)) +                                'stop': conf.return_value(['address-range', 'start', range, 'stop'])                              }                              # Append to subnet range6 list @@ -120,70 +121,68 @@ def get_config():                      # The domain-search option specifies a 'search list' of Domain Names to be used                      # by the client to locate not-fully-qualified domain names. -                    if conf.exists('domain-search'): -                        for domain in conf.return_values('domain-search'): -                            subnet['domain_search'].append('"' + domain + '"') +                    if conf.exists(['domain-search']): +                        subnet['domain_search'] = conf.return_values(['domain-search'])                      # IPv6 address valid lifetime                      #  (at the end the address is no longer usable by the client)                      #  (set to 30 days, the usual IPv6 default) -                    if conf.exists('lease-time default'): -                        subnet['lease_def'] = conf.return_value('lease-time default') +                    if conf.exists(['lease-time', 'default']): +                        subnet['lease_def'] = conf.return_value(['lease-time', 'default'])                      # Time should be the maximum length in seconds that will be assigned to a lease.                      # The only exception to this is that Dynamic BOOTP lease lengths, which are not                      # specified by the client, are not limited by this maximum. -                    if conf.exists('lease-time maximum'): -                        subnet['lease_max'] = conf.return_value('lease-time maximum') +                    if conf.exists(['lease-time', 'maximum']): +                        subnet['lease_max'] = conf.return_value(['lease-time', 'maximum'])                      # Time should be the minimum length in seconds that will be assigned to a lease -                    if conf.exists('lease-time minimum'): -                        subnet['lease_min'] = conf.return_value('lease-time minimum') +                    if conf.exists(['lease-time', 'minimum']): +                        subnet['lease_min'] = conf.return_value(['lease-time', 'minimum'])                      # Specifies a list of Domain Name System name servers available to the client.                      # Servers should be listed in order of preference. -                    if conf.exists('name-server'): -                        subnet['dns_server'] = conf.return_values('name-server') +                    if conf.exists(['name-server']): +                        subnet['dns_server'] = conf.return_values(['name-server'])                      # Ancient NIS (Network Information Service) domain name -                    if conf.exists('nis-domain'): -                        subnet['nis_domain'] = conf.return_value('nis-domain') +                    if conf.exists(['nis-domain']): +                        subnet['nis_domain'] = conf.return_value(['nis-domain'])                      # Ancient NIS (Network Information Service) servers -                    if conf.exists('nis-server'): -                        subnet['nis_server'] = conf.return_values('nis-server') +                    if conf.exists(['nis-server']): +                        subnet['nis_server'] = conf.return_values(['nis-server'])                      # Ancient NIS+ (Network Information Service) domain name -                    if conf.exists('nisplus-domain'): -                        subnet['nisp_domain'] = conf.return_value('nisplus-domain') +                    if conf.exists(['nisplus-domain']): +                        subnet['nisp_domain'] = conf.return_value(['nisplus-domain'])                      # Ancient NIS+ (Network Information Service) servers -                    if conf.exists('nisplus-server'): -                        subnet['nisp_server'] = conf.return_values('nisplus-server') +                    if conf.exists(['nisplus-server']): +                        subnet['nisp_server'] = conf.return_values(['nisplus-server'])                      # Prefix Delegation (RFC 3633) -                    if conf.exists('prefix-delegation'): +                    if conf.exists(['prefix-delegation']):                          print('TODO: This option is actually not implemented right now!')                      # Local SIP server that is to be used for all outbound SIP requests - IPv6 address -                    if conf.exists('sip-server-address'): -                        subnet['sip_address'] = conf.return_values('sip-server-address') - -                    # Local SIP server that is to be used for all outbound SIP requests - hostname -                    if conf.exists('sip-server-name'): -                        for hostname in conf.return_values('sip-server-name'): -                            subnet['sip_hostname'].append('"' + hostname + '"') +                    if conf.exists(['sip-server']): +                        for value in conf.return_values(['sip-server']): +                            if is_ipv6(value): +                                subnet['sip_address'].append(value) +                            else: +                                subnet['sip_hostname'].append(value)                      # List of local SNTP servers available for the client to synchronize their clocks -                    if conf.exists('sntp-server'): -                        subnet['sntp_server'] = conf.return_values('sntp-server') +                    if conf.exists(['sntp-server']): +                        subnet['sntp_server'] = conf.return_values(['sntp-server'])                      #                      # Static DHCP v6 leases                      # -                    if conf.exists('static-mapping'): -                        for mapping in conf.list_nodes('static-mapping'): -                            conf.set_level('service dhcpv6-server shared-network-name {0} subnet {1} static-mapping {2}'.format(network, net, mapping)) +                    if conf.exists(['static-mapping']): +                        for mapping in conf.list_nodes(['static-mapping']): +                            conf.set_level(base + ['shared-network-name', network, 'subnet', net, 'static-mapping', mapping])                              mapping = {                                 'name': mapping,                                 'disabled': False, @@ -192,16 +191,16 @@ def get_config():                              }                              # This static lease is disabled -                            if conf.exists('disable'): +                            if conf.exists(['disable']):                                  mapping['disabled'] = True                              # IPv6 address used for this DHCP client -                            if conf.exists('ipv6-address'): -                                mapping['ipv6_address'] = conf.return_value('ipv6-address') +                            if conf.exists(['ipv6-address']): +                                mapping['ipv6_address'] = conf.return_value(['ipv6-address'])                              # This option specifies the client’s DUID identifier. DUIDs are similar but different from DHCPv4 client identifiers -                            if conf.exists('identifier'): -                                mapping['client_identifier'] = conf.return_value('identifier') +                            if conf.exists(['identifier']): +                                mapping['client_identifier'] = conf.return_value(['identifier'])                              # append static mapping configuration tu subnet list                              subnet['static_mapping'].append(mapping) @@ -209,7 +208,6 @@ def get_config():                      # append subnet configuration to shared network subnet list                      config['subnet'].append(subnet) -              # append shared network configuration to config dictionary              dhcpv6['shared_network'].append(config) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 4ec30ca0a..a174e33e4 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -22,7 +22,7 @@ from netifaces import interfaces  from vyos.ifconfig import BondIf, Section  from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config -from vyos.configdict import list_diff, vlan_to_dict +from vyos.configdict import list_diff, intf_to_dict, add_to_dict  from vyos.config import Config  from vyos.util import call, cmd  from vyos.validate import is_bridge_member @@ -90,6 +90,13 @@ def get_bond_mode(mode):          raise ConfigError('invalid bond mode "{}"'.format(mode))  def get_config(): +    # determine tagNode instance +    if 'VYOS_TAGNODE_VALUE' not in os.environ: +        raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') + +    ifname = os.environ['VYOS_TAGNODE_VALUE'] +    conf = Config() +      # initialize kernel module if not loaded      if not os.path.isfile('/sys/class/net/bonding_masters'):          import syslog @@ -98,34 +105,21 @@ def get_config():              syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module")              raise ConfigError("failed loading bonding kernel module") -    bond = deepcopy(default_config_data) -    conf = Config() - -    # determine tagNode instance -    if 'VYOS_TAGNODE_VALUE' not in os.environ: -        raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - -    bond['intf'] = os.environ['VYOS_TAGNODE_VALUE'] -      # check if bond has been removed -    cfg_base = 'interfaces bonding ' + bond['intf'] +    cfg_base = 'interfaces bonding ' + ifname      if not conf.exists(cfg_base): +        bond = deepcopy(default_config_data) +        bond['intf'] = ifname          bond['deleted'] = True          # check if interface is member if a bridge -        bond['is_bridge_member'] = is_bridge_member(conf, bond['intf']) +        bond['is_bridge_member'] = is_bridge_member(conf, ifname)          return bond      # set new configuration level      conf.set_level(cfg_base) -    # retrieve configured interface addresses -    if conf.exists('address'): -        bond['address'] = conf.return_values('address') - -    # get interface addresses (currently effective) - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values('address') -    bond['address_remove'] = list_diff(eff_addr, bond['address']) +    bond, disabled = intf_to_dict(conf, default_config_data) +    bond['intf'] = ifname      # ARP link monitoring frequency in milliseconds      if conf.exists('arp-monitor interval'): @@ -135,38 +129,6 @@ def get_config():      if conf.exists('arp-monitor target'):          bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') -    # retrieve interface description -    if conf.exists('description'): -        bond['description'] = conf.return_value('description') - -    # get DHCP client identifier -    if conf.exists('dhcp-options client-id'): -        bond['dhcp_client_id'] = conf.return_value('dhcp-options client-id') - -    # DHCP client host name (overrides the system host name) -    if conf.exists('dhcp-options host-name'): -        bond['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - -    # DHCP client vendor identifier -    if conf.exists('dhcp-options vendor-class-id'): -        bond['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') - -    # DHCPv6 only acquire config parameters, no address -    if conf.exists('dhcpv6-options parameters-only'): -        bond['dhcpv6_prm_only'] = True - -    # DHCPv6 temporary IPv6 address -    if conf.exists('dhcpv6-options temporary'): -        bond['dhcpv6_temporary'] = True - -    # ignore link state changes -    if conf.exists('disable-link-detect'): -        bond['disable_link_detect'] = 2 - -    # disable bond interface -    if conf.exists('disable'): -        bond['disable'] = True -      # Bonding transmit hash policy      if conf.exists('hash-policy'):          bond['hash_policy'] = conf.return_value('hash-policy') @@ -175,68 +137,10 @@ def get_config():      if conf.exists('ip arp-cache-timeout'):          bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) -    # ARP filter configuration -    if conf.exists('ip disable-arp-filter'): -        bond['ip_disable_arp_filter'] = 0 - -    # ARP enable accept -    if conf.exists('ip enable-arp-accept'): -        bond['ip_enable_arp_accept'] = 1 - -    # ARP enable announce -    if conf.exists('ip enable-arp-announce'): -        bond['ip_enable_arp_announce'] = 1 - -    # ARP enable ignore -    if conf.exists('ip enable-arp-ignore'): -        bond['ip_enable_arp_ignore'] = 1 - -    # Enable proxy-arp on this interface -    if conf.exists('ip enable-proxy-arp'): -        bond['ip_proxy_arp'] = 1 -      # Enable private VLAN proxy ARP on this interface      if conf.exists('ip proxy-arp-pvlan'):          bond['ip_proxy_arp_pvlan'] = 1 -    # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) -    if conf.exists('ipv6 address autoconf'): -        bond['ipv6_autoconf'] = 1 - -    # Get prefixes for IPv6 addressing based on MAC address (EUI-64) -    if conf.exists('ipv6 address eui64'): -        bond['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - -    # Determine currently effective EUI64 addresses - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values('ipv6 address eui64') -    bond['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, bond['ipv6_eui64_prefix']) - -    # Remove the default link-local address if set. -    if conf.exists('ipv6 address no-default-link-local'): -        bond['ipv6_eui64_prefix_remove'].append('fe80::/64') -    else: -        # add the link-local by default to make IPv6 work -        bond['ipv6_eui64_prefix'].append('fe80::/64') - -    # Disable IPv6 forwarding on this interface -    if conf.exists('ipv6 disable-forwarding'): -        bond['ipv6_forwarding'] = 0 - -    # IPv6 Duplicate Address Detection (DAD) tries -    if conf.exists('ipv6 dup-addr-detect-transmits'): -        bond['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - -    # Media Access Control (MAC) address -    if conf.exists('mac'): -        bond['mac'] = conf.return_value('mac') - -    # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses -    # before re-adding them -    if ( bond['mac'] and bond['intf'] in Section.interfaces(section='bonding') -            and bond['mac'] != BondIf(bond['intf'], create=False).get_mac() ): -        bond['ipv6_eui64_prefix_remove'] += bond['ipv6_eui64_prefix'] -      # Bonding mode      if conf.exists('mode'):          act_mode = conf.return_value('mode') @@ -246,10 +150,6 @@ def get_config():          bond['mode'] = get_bond_mode(act_mode) -    # Maximum Transmission Unit (MTU) -    if conf.exists('mtu'): -        bond['mtu'] = int(conf.return_value('mtu')) -      # determine bond member interfaces (currently configured)      if conf.exists('member interface'):          bond['member'] = conf.return_values('member interface') @@ -266,35 +166,8 @@ def get_config():      if conf.exists('primary'):          bond['primary'] = conf.return_value('primary') -    # retrieve VRF instance -    if conf.exists('vrf'): -        bond['vrf'] = conf.return_value('vrf') - -    # get vif-s interfaces (currently effective) - to determine which vif-s -    # interface is no longer present and needs to be removed -    eff_intf = conf.list_effective_nodes('vif-s') -    act_intf = conf.list_nodes('vif-s') -    bond['vif_s_remove'] = list_diff(eff_intf, act_intf) - -    if conf.exists('vif-s'): -        for vif_s in conf.list_nodes('vif-s'): -            # set config level to vif-s interface -            conf.set_level(cfg_base + ' vif-s ' + vif_s) -            bond['vif_s'].append(vlan_to_dict(conf)) - -    # re-set configuration level to parse new nodes -    conf.set_level(cfg_base) -    # Determine vif interfaces (currently effective) - to determine which -    # vif interface is no longer present and needs to be removed -    eff_intf = conf.list_effective_nodes('vif') -    act_intf = conf.list_nodes('vif') -    bond['vif_remove'] = list_diff(eff_intf, act_intf) - -    if conf.exists('vif'): -        for vif in conf.list_nodes('vif'): -            # set config level to vif interface -            conf.set_level(cfg_base + ' vif ' + vif) -            bond['vif'].append(vlan_to_dict(conf)) +    add_to_dict(conf, disabled, bond, 'vif', 'vif') +    add_to_dict(conf, disabled, bond, 'vif-s', 'vif_s')      return bond diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 43d97916d..3ddd394d7 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -22,7 +22,7 @@ from netifaces import interfaces  from vyos.ifconfig import EthernetIf, Section  from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config -from vyos.configdict import list_diff, vlan_to_dict +from vyos.configdict import list_diff, intf_to_dict, add_to_dict  from vyos.config import Config  from vyos import ConfigError @@ -70,18 +70,18 @@ default_config_data = {  }  def get_config(): -    eth = deepcopy(default_config_data) -    conf = Config() -      # determine tagNode instance      if 'VYOS_TAGNODE_VALUE' not in os.environ:          raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') -    eth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] +    ifname = os.environ['VYOS_TAGNODE_VALUE'] +    conf = Config()      # check if ethernet interface has been removed -    cfg_base = ['interfaces', 'ethernet', eth['intf']] +    cfg_base = ['interfaces', 'ethernet', ifname]      if not conf.exists(cfg_base): +        eth = deepcopy(default_config_data) +        eth['intf'] = ifname          eth['deleted'] = True          # we can not bail out early as ethernet interface can not be removed          # Kernel will complain with: RTNETLINK answers: Operation not supported. @@ -91,42 +91,8 @@ def get_config():      # set new configuration level      conf.set_level(cfg_base) -    # retrieve configured interface addresses -    if conf.exists('address'): -        eth['address'] = conf.return_values('address') - -    # get interface addresses (currently effective) - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values('address') -    eth['address_remove'] = list_diff(eff_addr, eth['address']) - -    # retrieve interface description -    if conf.exists('description'): -        eth['description'] = conf.return_value('description') - -    # get DHCP client identifier -    if conf.exists('dhcp-options client-id'): -        eth['dhcp_client_id'] = conf.return_value('dhcp-options client-id') - -    # DHCP client host name (overrides the system host name) -    if conf.exists('dhcp-options host-name'): -        eth['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - -    # DHCP client vendor identifier -    if conf.exists('dhcp-options vendor-class-id'): -        eth['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') - -    # DHCPv6 only acquire config parameters, no address -    if conf.exists('dhcpv6-options parameters-only'): -        eth['dhcpv6_prm_only'] = True - -    # DHCPv6 temporary IPv6 address -    if conf.exists('dhcpv6-options temporary'): -        eth['dhcpv6_temporary'] = True - -    # ignore link state changes -    if conf.exists('disable-link-detect'): -        eth['disable_link_detect'] = 2 +    eth, disabled = intf_to_dict(conf, default_config_data) +    eth['intf'] = ifname      # disable ethernet flow control (pause frames)      if conf.exists('disable-flow-control'): @@ -136,10 +102,6 @@ def get_config():      if conf.exists('hw-id'):          eth['hw_id'] = conf.return_value('hw-id') -    # disable interface -    if conf.exists('disable'): -        eth['disable'] = True -      # interface duplex      if conf.exists('duplex'):          eth['duplex'] = conf.return_value('duplex') @@ -148,72 +110,10 @@ def get_config():      if conf.exists('ip arp-cache-timeout'):          eth['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) -    # ARP filter configuration -    if conf.exists('ip disable-arp-filter'): -        eth['ip_disable_arp_filter'] = 0 - -    # ARP enable accept -    if conf.exists('ip enable-arp-accept'): -        eth['ip_enable_arp_accept'] = 1 - -    # ARP enable announce -    if conf.exists('ip enable-arp-announce'): -        eth['ip_enable_arp_announce'] = 1 - -    # ARP enable ignore -    if conf.exists('ip enable-arp-ignore'): -        eth['ip_enable_arp_ignore'] = 1 - -    # Enable proxy-arp on this interface -    if conf.exists('ip enable-proxy-arp'): -        eth['ip_proxy_arp'] = 1 -      # Enable private VLAN proxy ARP on this interface      if conf.exists('ip proxy-arp-pvlan'):          eth['ip_proxy_arp_pvlan'] = 1 -    # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) -    if conf.exists('ipv6 address autoconf'): -        eth['ipv6_autoconf'] = 1 - -    # Get prefixes for IPv6 addressing based on MAC address (EUI-64) -    if conf.exists('ipv6 address eui64'): -        eth['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - -    # Determine currently effective EUI64 addresses - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values('ipv6 address eui64') -    eth['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, eth['ipv6_eui64_prefix']) - -    # Remove the default link-local address if set. -    if conf.exists('ipv6 address no-default-link-local'): -        eth['ipv6_eui64_prefix_remove'].append('fe80::/64') -    else: -        # add the link-local by default to make IPv6 work -        eth['ipv6_eui64_prefix'].append('fe80::/64') - -    # Disable IPv6 forwarding on this interface -    if conf.exists('ipv6 disable-forwarding'): -        eth['ipv6_forwarding'] = 0 - -    # IPv6 Duplicate Address Detection (DAD) tries -    if conf.exists('ipv6 dup-addr-detect-transmits'): -        eth['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - -    # Media Access Control (MAC) address -    if conf.exists('mac'): -        eth['mac'] = conf.return_value('mac') - -    # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses -    # before re-adding them -    if ( eth['mac'] and eth['intf'] in Section.interfaces(section='ethernet') -            and eth['mac'] != EthernetIf(eth['intf'], create=False).get_mac() ): -        eth['ipv6_eui64_prefix_remove'] += eth['ipv6_eui64_prefix'] - -    # Maximum Transmission Unit (MTU) -    if conf.exists('mtu'): -        eth['mtu'] = int(conf.return_value('mtu')) -      # GRO (generic receive offload)      if conf.exists('offload-options generic-receive'):          eth['offload_gro'] = conf.return_value('offload-options generic-receive') @@ -238,37 +138,8 @@ def get_config():      if conf.exists('speed'):          eth['speed'] = conf.return_value('speed') -    # retrieve VRF instance -    if conf.exists('vrf'): -        eth['vrf'] = conf.return_value('vrf') - -    # re-set configuration level to parse new nodes -    conf.set_level(cfg_base) -    # get vif-s interfaces (currently effective) - to determine which vif-s -    # interface is no longer present and needs to be removed -    eff_intf = conf.list_effective_nodes('vif-s') -    act_intf = conf.list_nodes('vif-s') -    eth['vif_s_remove'] = list_diff(eff_intf, act_intf) - -    if conf.exists('vif-s'): -        for vif_s in conf.list_nodes('vif-s'): -            # set config level to vif-s interface -            conf.set_level(cfg_base + ['vif-s', vif_s]) -            eth['vif_s'].append(vlan_to_dict(conf)) - -    # re-set configuration level to parse new nodes -    conf.set_level(cfg_base) -    # Determine vif interfaces (currently effective) - to determine which -    # vif interface is no longer present and needs to be removed -    eff_intf = conf.list_effective_nodes('vif') -    act_intf = conf.list_nodes('vif') -    eth['vif_remove'] = list_diff(eff_intf, act_intf) - -    if conf.exists('vif'): -        for vif in conf.list_nodes('vif'): -            # set config level to vif interface -            conf.set_level(cfg_base + ['vif', vif]) -            eth['vif'].append(vlan_to_dict(conf)) +    add_to_dict(conf, disabled, eth, 'vif', 'vif') +    add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s')      return eth diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 57b282291..f0f893b44 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -21,7 +21,7 @@ from sys import exit  from netifaces import interfaces  from vyos.config import Config -from vyos.configdict import list_diff, vlan_to_dict +from vyos.configdict import list_diff, vlan_to_dict, intf_to_dict, add_to_dict  from vyos.ifconfig import MACVLANIf, Section  from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config  from vyos.validate import is_bridge_member @@ -65,123 +65,36 @@ default_config_data = {  }  def get_config(): -    peth = deepcopy(default_config_data) -    conf = Config() -      # determine tagNode instance      if 'VYOS_TAGNODE_VALUE' not in os.environ:          raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') -    peth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] -    cfg_base = ['interfaces', 'pseudo-ethernet', peth['intf']] +    ifname = os.environ['VYOS_TAGNODE_VALUE'] +    conf = Config()      # Check if interface has been removed +    cfg_base = ['interfaces', 'pseudo-ethernet', ifname]      if not conf.exists(cfg_base): +        peth = deepcopy(default_config_data)          peth['deleted'] = True          # check if interface is member if a bridge -        peth['is_bridge_member'] = is_bridge_member(conf, peth['intf']) +        peth['is_bridge_member'] = is_bridge_member(conf, ifname)          return peth      # set new configuration level      conf.set_level(cfg_base) -    # retrieve configured interface addresses -    if conf.exists(['address']): -        peth['address'] = conf.return_values(['address']) - -    # get interface addresses (currently effective) - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values(['address']) -    peth['address_remove'] = list_diff(eff_addr, peth['address']) - -    # retrieve interface description -    if conf.exists(['description']): -        peth['description'] = conf.return_value(['description']) - -    # get DHCP client identifier -    if conf.exists(['dhcp-options', 'client-id']): -        peth['dhcp_client_id'] = conf.return_value(['dhcp-options', 'client-id']) - -    # DHCP client host name (overrides the system host name) -    if conf.exists(['dhcp-options', 'host-name']): -        peth['dhcp_hostname'] = conf.return_value(['dhcp-options', 'host-name']) - -    # DHCP client vendor identifier -    if conf.exists(['dhcp-options', 'vendor-class-id']): -        peth['dhcp_vendor_class_id'] = conf.return_value(['dhcp-options', 'vendor-class-id']) - -    # DHCPv6 only acquire config parameters, no address -    if conf.exists(['dhcpv6-options parameters-only']): -        peth['dhcpv6_prm_only'] = True - -    # DHCPv6 temporary IPv6 address -    if conf.exists(['dhcpv6-options temporary']): -        peth['dhcpv6_temporary'] = True - -    # disable interface -    if conf.exists(['disable']): -        peth['disable'] = True - -    # ignore link state changes -    if conf.exists(['disable-link-detect']): -        peth['disable_link_detect'] = 2 +    peth, disabled = intf_to_dict(conf, default_config_data) +    peth['intf'] = ifname      # ARP cache entry timeout in seconds      if conf.exists(['ip', 'arp-cache-timeout']):          peth['ip_arp_cache_tmo'] = int(conf.return_value(['ip', 'arp-cache-timeout'])) -    # ARP filter configuration -    if conf.exists(['ip', 'disable-arp-filter']): -        peth['ip_disable_arp_filter'] = 0 - -    # ARP enable accept -    if conf.exists(['ip', 'enable-arp-accept']): -        peth['ip_enable_arp_accept'] = 1 - -    # ARP enable announce -    if conf.exists(['ip', 'enable-arp-announce']): -        peth['ip_enable_arp_announce'] = 1 - -    # ARP enable ignore -    if conf.exists(['ip', 'enable-arp-ignore']): -        peth['ip_enable_arp_ignore'] = 1 - -    # Enable proxy-arp on this interface -    if conf.exists(['ip', 'enable-proxy-arp']): -        peth['ip_proxy_arp'] = 1 -      # Enable private VLAN proxy ARP on this interface      if conf.exists(['ip', 'proxy-arp-pvlan']):          peth['ip_proxy_arp_pvlan'] = 1 -    # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) -    if conf.exists('ipv6 address autoconf'): -        peth['ipv6_autoconf'] = 1 - -    # Get prefixes for IPv6 addressing based on MAC address (EUI-64) -    if conf.exists('ipv6 address eui64'): -        peth['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - -    # Determine currently effective EUI64 addresses - to determine which -    # address is no longer valid and needs to be removed -    eff_addr = conf.return_effective_values('ipv6 address eui64') -    peth['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, peth['ipv6_eui64_prefix']) - -    # Remove the default link-local address if set. -    if conf.exists('ipv6 address no-default-link-local'): -        peth['ipv6_eui64_prefix_remove'].append('fe80::/64') -    else: -        # add the link-local by default to make IPv6 work -        peth['ipv6_eui64_prefix'].append('fe80::/64') - -    # Disable IPv6 forwarding on this interface -    if conf.exists('ipv6 disable-forwarding'): -        peth['ipv6_forwarding'] = 0 - -    # IPv6 Duplicate Address Detection (DAD) tries -    if conf.exists('ipv6 dup-addr-detect-transmits'): -        peth['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) -      # Physical interface      if conf.exists(['source-interface']):          peth['source_interface'] = conf.return_value(['source-interface']) @@ -189,52 +102,12 @@ def get_config():          if tmp != peth['source_interface']:              peth['source_interface_changed'] = True -    # Media Access Control (MAC) address -    if conf.exists(['mac']): -        peth['mac'] = conf.return_value(['mac']) - -    # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses -    # before re-adding them -    if ( peth['mac'] and peth['intf'] in Section.interfaces(section='pseudo-ethernet') -            and peth['mac'] != MACVLANIf(peth['intf'], create=False).get_mac() ): -        peth['ipv6_eui64_prefix_remove'] += peth['ipv6_eui64_prefix'] -      # MACvlan mode      if conf.exists(['mode']):          peth['mode'] = conf.return_value(['mode']) -    # retrieve VRF instance -    if conf.exists('vrf'): -        peth['vrf'] = conf.return_value('vrf') - -    # re-set configuration level to parse new nodes -    conf.set_level(cfg_base) -    # get vif-s interfaces (currently effective) - to determine which vif-s -    # interface is no longer present and needs to be removed -    eff_intf = conf.list_effective_nodes('vif-s') -    act_intf = conf.list_nodes('vif-s') -    peth['vif_s_remove'] = list_diff(eff_intf, act_intf) - -    if conf.exists('vif-s'): -        for vif_s in conf.list_nodes('vif-s'): -            # set config level to vif-s interface -            conf.set_level(cfg_base + ['vif-s', vif_s]) -            peth['vif_s'].append(vlan_to_dict(conf)) - -    # re-set configuration level to parse new nodes -    conf.set_level(cfg_base) -    # Determine vif interfaces (currently effective) - to determine which -    # vif interface is no longer present and needs to be removed -    eff_intf = conf.list_effective_nodes('vif') -    act_intf = conf.list_nodes('vif') -    peth['vif_remove'] = list_diff(eff_intf, act_intf) - -    if conf.exists('vif'): -        for vif in conf.list_nodes('vif'): -            # set config level to vif interface -            conf.set_level(cfg_base + ['vif', vif]) -            peth['vif'].append(vlan_to_dict(conf)) - +    add_to_dict(conf, disabled, peth, 'vif', 'vif') +    add_to_dict(conf, disabled, peth, 'vif-s', 'vif_s')      return peth @@ -248,10 +121,10 @@ def verify(peth):          return None      if not peth['source_interface']: -        raise ConfigError('Link device must be set for virtual ethernet {}'.format(peth['intf'])) +        raise ConfigError('source-interface must be set for virtual ethernet {}'.format(peth['intf']))      if not peth['source_interface'] in interfaces(): -        raise ConfigError('Pseudo-ethernet source interface does not exist') +        raise ConfigError('Pseudo-ethernet source-interface does not exist')      vrf_name = peth['vrf']      if vrf_name and vrf_name not in interfaces(): diff --git a/src/migration-scripts/dhcpv6-server/0-to-1 b/src/migration-scripts/dhcpv6-server/0-to-1 new file mode 100755 index 000000000..6f1150da1 --- /dev/null +++ b/src/migration-scripts/dhcpv6-server/0-to-1 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +# combine both sip-server-address and sip-server-name nodes to common sip-server + +from sys import argv, exit +from vyos.configtree import ConfigTree + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'dhcpv6-server', 'shared-network-name'] +if not config.exists(base): +    # Nothing to do +    exit(0) +else: +    # we need to run this for every configured network +    for network in config.list_nodes(base): +        for subnet in config.list_nodes(base + [network, 'subnet']): +            sip_server = [] + +            # Do we have 'sip-server-address' configured? +            if config.exists(base + [network, 'subnet', subnet, 'sip-server-address']): +                sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-address']) +                config.delete(base + [network, 'subnet', subnet, 'sip-server-address']) + +            # Do we have 'sip-server-name' configured? +            if config.exists(base + [network, 'subnet', subnet, 'sip-server-name']): +                sip_server += config.return_values(base + [network, 'subnet', subnet, 'sip-server-name']) +                config.delete(base + [network, 'subnet', subnet, 'sip-server-name']) + +            # Write new CLI value for sip-server +            for server in sip_server: +                config.set(base + [network, 'subnet', subnet, 'sip-server'], value=server, replace=False) + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        exit(1) diff --git a/src/systemd/isc-dhcp-server6.service b/src/systemd/isc-dhcp-server6.service index 743f16840..27bebc57f 100644 --- a/src/systemd/isc-dhcp-server6.service +++ b/src/systemd/isc-dhcp-server6.service @@ -2,7 +2,7 @@  Description=ISC DHCP IPv6 server  Documentation=man:dhcpd(8)  RequiresMountsFor=/run -ConditionPathExists=/run/dhcp-server/dhcpd.conf +ConditionPathExists=/run/dhcp-server/dhcpdv6.conf  After=vyos-router.service  [Service] diff --git a/src/validators/numeric b/src/validators/numeric index 0a2d83d14..2cd5178b9 100755 --- a/src/validators/numeric +++ b/src/validators/numeric @@ -19,7 +19,6 @@  import sys  import argparse -import re  parser = argparse.ArgumentParser()  parser.add_argument("-f", "--float", action="store_true", help="Accept floating point values") @@ -50,8 +49,9 @@ if args.range:      valid = False      for r in args.range:          try: -            lower, upper = re.match(r'(\d+)\s*\-\s*(\d+)', r).groups() -            lower, upper = int(lower), int(upper) +            list = r.split('-') +            lower = int(list[0]) +            upper = int(list[1])          except:              print("{0} is not a valid number range",format(args.range), file=sys.stderr)              sys.exit(1)  | 
