diff options
32 files changed, 506 insertions, 166 deletions
diff --git a/data/configd-include.json b/data/configd-include.json index da6fb915f..2e44405ee 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -30,6 +30,7 @@ "nat.py", "ntp.py", "protocols_igmp.py", +"protocols_isis.py", "protocols_mpls.py", "protocols_pim.py", "protocols_rip.py", diff --git a/data/templates/frr/isis.frr.tmpl b/data/templates/frr/isis.frr.tmpl new file mode 100644 index 000000000..929f5bdb2 --- /dev/null +++ b/data/templates/frr/isis.frr.tmpl @@ -0,0 +1,121 @@ +! +router isis {{ process }} + net {{ net }} +{% if dynamic_hostname is defined %} + hostname dynamic +{% endif %} +{% if purge_originator is defined %} + purge-originator +{% endif %} +{% if set_attached_bit is defined %} + set-attached-bit +{% endif %} +{% if set_overload_bit is defined %} + set-overload-bit +{% endif %} +{% if domain_password is defined and domain_password.plaintext_password is defined and domain_password.plaintext_password is not none %} + domain-password clear {{ domain_password.plaintext_password }} +{% endif %} +{% if lsp_gen_interval is defined and lsp_gen_interval is not none %} + lsp-gen-interval {{ lsp_gen_interval }} +{% endif %} +{% if lsp_mtu is defined and lsp_mtu is not none %} + lsp-mtu {{ lsp_mtu }} +{% endif %} +{% if lsp_refresh_interval is defined and lsp_refresh_interval is not none %} + lsp-refresh-interval {{ lsp_refresh_interval }} +{% endif %} +{% if max_lsp_lifetime is defined and max_lsp_lifetime is not none %} + max-lsp-lifetime {{ max_lsp_lifetime }} +{% endif %} +{% if spf_interval is defined and spf_interval is not none %} + spf-interval {{ spf_interval }} +{% endif %} +{% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.init_delay is not none %} + spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} +{% endif %} +{% if area_password is defined and area_password.md5 is defined and area_password.md5 is not none %} + area-password md5 {{ area_password.md5 }} +{% elif area_password is defined and area_password.plaintext_password is defined and area_password.plaintext_password is not none %} + area-password clear {{ area_password.plaintext_password }} +{% endif %} +{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} +{% for level in default_information.originate.ipv4 if default_information.originate.ipv4 is defined %} + default-information originate ipv4 {{ level | replace('_', '-') }} +{% endfor %} +{% for level in default_information.originate.ipv6 if default_information.originate.ipv6 is defined %} + default-information originate ipv6 {{ level | replace('_', '-') }} always +{% endfor %} +{% endif %} +{% if redistribute is defined and redistribute.ipv4 is defined and redistribute.ipv4 is not none %} +{% for protocol in redistribute.ipv4 %} +{% for level, level_config in redistribute.ipv4[protocol].items() %} +{% if level_config.metric is defined and level_config.metric is not none %} + redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} metric {{ level_config.metric }} +{% elif level_config.route_map is defined and level_config.route_map is not none %} + redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} route-map {{ level_config.route_map }} +{% else %} + redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} +{% endif %} +{% endfor %} +{% endfor %} +{% endif %} +{% if level is defined and level is not none %} +{% if level == 'level-1' %} + is-type level-1 +{% elif level == 'level-2' %} + is-type level-2-only +{% elif level == 'level-1-2' %} + is-type level-1-2 +{% endif %} +{% endif %} +! +{% if interface_remove is defined and interface_remove is not none %} +{% for iface in interface_remove %} +interface {{ iface }} + no ip router isis +{% endfor %} +{% endif %} +! +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} + ip router isis {{ process }} +{% if iface_config.bfd is defined %} + isis bfd +{% endif %} +{% if iface_config.network is defined and iface_config.network.point_to_point is defined %} + isis network point-to-point +{% endif %} +{% if iface_config.circuit_type is defined %} + isis circuit-type {{ iface_config.circuit_type }} +{% endif %} +{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} + isis hello-interval {{ iface_config.hello_interval }} +{% endif %} +{% if iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %} + isis hello-multiplier {{ iface_config.hello_multiplier }} +{% endif %} +{% if iface_config.hello_padding is defined %} + isis hello padding +{% endif %} +{% if iface_config.metric is defined and iface_config.metric is not none %} + isis metric {{ iface_config.metric }} +{% endif %} +{% if iface_config.passive is defined %} + isis passive +{% endif %} +{% if iface_config.password is defined and iface_config.password.plaintext_password is defined and iface_config.password.plaintext_password is not none %} + isis password clear {{ iface_config.password.plaintext_password }} +{% endif %} +{% if iface_config.priority is defined and iface_config.priority is not none %} + isis priority {{ iface_config.priority }} +{% endif %} +{% if iface_config.psnp_interval is defined and iface_config.psnp_interval is not none %} + isis psnp-interval {{ iface_config.psnp_interval }} +{% endif %} +{% if iface_config.three_way_handshake is defined %} + isis three-way-handshake +{% endif %} +{% endfor %} +{% endif %} diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.tmpl index fd3d3e68d..62387ef7c 100644 --- a/data/templates/openvpn/client.conf.tmpl +++ b/data/templates/openvpn/client.conf.tmpl @@ -13,7 +13,6 @@ push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}" iroute {{ network | address_from_cidr }} {{ network | netmask_from_cidr }} {% endfor %} {% endif %} - {# ipv6_remote is only set when IPv6 server is enabled #} {% if ipv6_remote %} # IPv6 @@ -27,7 +26,6 @@ push "route-ipv6 {{ route6 }}" iroute {{ net6 }} {% endfor %} {% endif %} - {% if disable is defined %} disable {% endif %} diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index e8f7c3ab8..a510c3a84 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -13,7 +13,13 @@ dev-type {{ device_type }} dev {{ ifname }} persist-key iproute /usr/libexec/vyos/system/unpriv-ip -proto {{ protocol }} +{% if protocol == 'tcp-active' %} +proto tcp6-client +{% elif protocol == 'tcp-passive' %} +proto tcp6-server +{% else %} +proto udp6 +{% endif %} {% if local_host is defined and local_host is not none %} local {{ local_host }} {% endif %} @@ -65,7 +71,7 @@ topology p2p {% elif server.topology is defined and server.topology is not none %} topology {{ server.topology }} {% endif %} -{% for subnet in server.subnet if subnet | ipv4 %} +{% for subnet in server.subnet if subnet | is_ipv4 %} server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} nopool {# OpenVPN assigns the first IP address to its local interface so the pool used #} {# in net30 topology - where each client receives a /30 must start from the second subnet #} @@ -130,12 +136,12 @@ push "dhcp-option DNS6 {{ ns6 }}" ping {{ keep_alive.interval }} ping-restart {{ keep_alive.failure_count }} -{% for laddr, laddr_conf in local_address.items() if laddr | ipv4 %} +{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} {% if laddr_conf is defined and laddr_conf.subnet_mask is defined and laddr_conf.subnet_mask is not none %} ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }} {% else %} {% for raddr in remote_address %} -{% if raddr | ipv4 %} +{% if raddr | is_ipv4 %} ifconfig {{ laddr }} {{ raddr }} {% else %} ifconfig-ipv6 {{ laddr }} {{ raddr }} diff --git a/interface-definitions/include/isis-redistribute-ipv4.xml.i b/interface-definitions/include/isis-redistribute-ipv4.xml.i index a40d8dfc2..fd5e75918 100644 --- a/interface-definitions/include/isis-redistribute-ipv4.xml.i +++ b/interface-definitions/include/isis-redistribute-ipv4.xml.i @@ -16,28 +16,14 @@ </constraint> </properties> </leafNode> - <tagNode name="route-map"> + <leafNode name="route-map"> <properties> <help>Route map reference</help> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format><0-16777215></format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> </children> </node> <node name="level-2"> @@ -57,28 +43,14 @@ </constraint> </properties> </leafNode> - <tagNode name="route-map"> + <leafNode name="route-map"> <properties> <help>Route map reference</help> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format><0-16777215></format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> </children> </node> <!-- included end --> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0b03dfc7d..b14f96364 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -431,7 +431,7 @@ def get_accel_dict(config, base, chap_secrets): Return a dictionary with the necessary interface config keys. """ from vyos.util import get_half_cpus - from vyos.validate import is_ipv4 + from vyos.template import is_ipv4 dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index babb0feb7..675dac5b1 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -51,7 +51,7 @@ def verify_mtu_ipv6(config): recurring validation if the specified MTU can be used when IPv6 is configured on the interface. IPv6 requires a 1280 bytes MTU. """ - from vyos.validate import is_ipv6 + from vyos.template import is_ipv6 if 'mtu' in config: # IPv6 minimum required link mtu min_mtu = 1280 @@ -278,7 +278,7 @@ def verify_diffie_hellman_length(file, min_keysize): prog = re.compile('\d+\s+bit') if prog.search(out): bits = prog.search(out)[0].split()[0] - if int(min_keysize) >= int(bits): + if int(bits) >= int(min_keysize): return True return False diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 894410871..893623284 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -35,8 +35,8 @@ from vyos.configdict import dict_merge from vyos.template import render from vyos.util import mac2eui64 from vyos.util import dict_search -from vyos.validate import is_ipv4 -from vyos.validate import is_ipv6 +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned from vyos.validate import assert_boolean from vyos.validate import assert_list diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 4122d1a2f..926d66c18 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -22,6 +22,10 @@ from vyos.ifconfig.interface import Interface from vyos.ifconfig.afi import IP4, IP6 from vyos.validate import assert_list +import random +from random import seed, getrandbits +from ipaddress import IPv6Network, IPv6Address + def enable_to_on(value): if value == 'enable': return 'on' @@ -122,6 +126,16 @@ class _Tunnel(Interface): @classmethod def get_config(cls): return dict(zip(cls.options, ['']*len(cls.options))) + + def generate_link_local(): + # Linux Kernel does not generate IPv6 Link Local address do to missing MAC + # We have to generate address manually and assign to interface + net = IPv6Network("FE80::/16") + rand_net = IPv6Network((net.network_address + (random.getrandbits(64 - net.prefixlen) << 64 ),64)) + network = IPv6Network(rand_net) + address = str(IPv6Address(network.network_address + getrandbits(network.max_prefixlen - network.prefixlen)))+'/'+str(network.prefixlen) + + return address class GREIf(_Tunnel): @@ -154,6 +168,12 @@ class GREIf(_Tunnel): create = 'ip tunnel add {ifname} mode {type}' change = 'ip tunnel cha {ifname}' delete = 'ip tunnel del {ifname}' + + + def _create(self): + super()._create(self) + # Assign generated IPv6 Link Local address to the interface + self.add_addr(self.generate_link_local()) # GreTap also called GRE Bridge @@ -219,6 +239,11 @@ class IP6GREIf(_Tunnel): # sudo ip tunnel cha tun100 local: : 2 # Error: an IP address is expected rather than "::2" # works if mode is explicit + + def _create(self): + super()._create(self) + # Assign generated IPv6 Link Local address to the interface + self.add_addr(self.generate_link_local()) class IPIPIf(_Tunnel): @@ -270,6 +295,11 @@ class IPIP6If(_Tunnel): create = 'ip -6 tunnel add {ifname} mode {type}' change = 'ip -6 tunnel cha {ifname}' delete = 'ip -6 tunnel del {ifname}' + + def _create(self): + super()._create(self) + # Assign generated IPv6 Link Local address to the interface + self.add_addr(self.generate_link_local()) class IP6IP6If(IPIP6If): @@ -283,6 +313,11 @@ class IP6IP6If(IPIP6If): ip = [IP6,] default = {'type': 'ip6ip6'} + + def _create(self): + super()._create(self) + # Assign generated IPv6 Link Local address to the interface + self.add_addr(self.generate_link_local()) class SitIf(_Tunnel): @@ -306,6 +341,11 @@ class SitIf(_Tunnel): create = 'ip tunnel add {ifname} mode {type}' change = 'ip tunnel cha {ifname}' delete = 'ip tunnel del {ifname}' + + def _create(self): + super()._create(self) + # Assign generated IPv6 Link Local address to the interface + self.add_addr(self.generate_link_local()) class Sit6RDIf(SitIf): diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index d8e89229d..5e9173349 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -24,7 +24,11 @@ from hurry.filesize import alternative from vyos.config import Config from vyos.ifconfig import Interface from vyos.ifconfig import Operational -from vyos.validate import is_ipv6 +from vyos.template import is_ipv6 + +import random +from random import seed, getrandbits +from ipaddress import IPv6Network, IPv6Address class WireGuardOperational(Operational): def _dump(self): @@ -168,7 +172,23 @@ class WireGuardIf(Interface): options = Interface.options + \ ['port', 'private_key', 'pubkey', 'psk', 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] - + + + def generate_link_local(): + # Linux Kernel does not generate IPv6 Link Local address do to missing MAC + # We have to generate address manually and assign to interface + net = IPv6Network("FE80::/16") + rand_net = IPv6Network((net.network_address + (random.getrandbits(64 - net.prefixlen) << 64 ),64)) + network = IPv6Network(rand_net) + address = str(IPv6Address(network.network_address + getrandbits(network.max_prefixlen - network.prefixlen)))+'/'+str(network.prefixlen) + + return address + + def _create(self): + super()._create(self) + # Assign generated IPv6 Link Local address to the interface + self.add_addr(self.generate_link_local()) + def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered diff --git a/python/vyos/template.py b/python/vyos/template.py index 53e1dc1b5..58ba75972 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -124,7 +124,7 @@ def render( ################################## @register_filter('address_from_cidr') -def vyos_address_from_cidr(text): +def address_from_cidr(text): """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address". Example: 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8:: @@ -133,7 +133,7 @@ def vyos_address_from_cidr(text): return str(ip_network(text).network_address) @register_filter('netmask_from_cidr') -def vyos_netmask_from_cidr(text): +def netmask_from_cidr(text): """ Take CIDR prefix and convert the prefix length to a "subnet mask". Example: - 192.0.2.0/24 -> 255.255.255.0 @@ -142,22 +142,27 @@ def vyos_netmask_from_cidr(text): from ipaddress import ip_network return str(ip_network(text).netmask) -@register_filter('ipv4') -def vyos_ipv4(text): +@register_filter('is_ip') +def is_ip(addr): + """ Check addr if it is an IPv4 or IPv6 address """ + return is_ipv4(addr) or is_ipv6(addr) + +@register_filter('is_ipv4') +def is_ipv4(text): """ Filter IP address, return True on IPv4 address, False otherwise """ from ipaddress import ip_interface try: return ip_interface(text).version == 4 except: return False @register_filter('ipv6') -def vyos_ipv6(text): +def is_ipv6(text): """ Filter IP address, return True on IPv6 address, False otherwise """ from ipaddress import ip_interface try: return ip_interface(text).version == 6 except: return False @register_filter('first_host_address') -def vyos_first_host_address(text): +def first_host_address(text): """ Return first usable (host) IP address from given prefix. Example: - 10.0.0.0/24 -> 10.0.0.1 @@ -173,7 +178,7 @@ def vyos_first_host_address(text): return str(addr.ip) @register_filter('last_host_address') -def vyos_last_host_address(text): +def last_host_address(text): """ Return first usable IP address from given prefix. Example: - 10.0.0.0/24 -> 10.0.0.254 @@ -190,7 +195,7 @@ def vyos_last_host_address(text): return str(IPv6Network(addr).broadcast_address) @register_filter('inc_ip') -def vyos_inc_ip(address, increment): +def inc_ip(address, increment): """ Increment given IP address by 'increment' Example (inc by 2): @@ -201,7 +206,7 @@ def vyos_inc_ip(address, increment): return str(ip_interface(address).ip + int(increment)) @register_filter('dec_ip') -def vyos_dec_ip(address, decrement): +def dec_ip(address, decrement): """ Decrement given IP address by 'decrement' Example (inc by 2): diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 74b8adcfc..84a7bc2de 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -25,21 +25,10 @@ from vyos.util import cmd # parameters with default will be left unset # all other paramters will receive the value to check -def is_ip(addr): - """ Check addr if it is an IPv4 or IPv6 address """ - return is_ipv4(addr) or is_ipv6(addr) - -def is_ipv4(addr): - from vyos.template import vyos_ipv4 - return vyos_ipv4(addr) - -def is_ipv6(addr): - from vyos.template import vyos_ipv6 - return vyos_ipv6(addr) - def is_ipv6_link_local(addr): """ Check if addrsss is an IPv6 link-local address. Returns True/False """ from ipaddress import IPv6Address + from vyos.template import is_ipv6 addr = addr.split('%')[0] if is_ipv6(addr): if IPv6Address(addr).is_link_local: @@ -51,6 +40,7 @@ def _are_same_ip(one, two): from socket import AF_INET from socket import AF_INET6 from socket import inet_pton + from vyos.template import is_ipv4 # compare the binary representation of the IP f_one = AF_INET if is_ipv4(one) else AF_INET6 s_two = AF_INET if is_ipv4(two) else AF_INET6 @@ -68,6 +58,7 @@ def _is_intf_addr_assigned(intf, address, netmask=''): It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. """ + from vyos.template import is_ipv4 # check if the requested address type is configured at all # { @@ -138,6 +129,7 @@ def is_subnet_connected(subnet, primary=False): """ from ipaddress import ip_address from ipaddress import ip_network + from vyos.template import is_ipv6 # determine IP version (AF_INET or AF_INET6) depending on passed address addr_type = netifaces.AF_INET diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index e3e5071c1..e46a16137 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -20,10 +20,10 @@ from configparser import ConfigParser from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv4 from vyos.util import cmd from vyos.util import get_half_cpus from vyos.util import process_named_running -from vyos.validate import is_ipv4 class BasicAccelPPPTest: class BaseTest(unittest.TestCase): diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 653cc91f9..e02424073 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -23,7 +23,8 @@ from vyos.ifconfig import Interface from vyos.util import read_file from vyos.util import cmd from vyos.util import dict_search -from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local +from vyos.validate import is_intf_addr_assigned +from vyos.validate import is_ipv6_link_local class BasicInterfaceTest: class BaseTest(unittest.TestCase): diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index 63deef5c2..41e48c2f8 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -26,11 +26,11 @@ from vyos.configsession import ConfigSessionError from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file -from vyos.template import vyos_inc_ip -from vyos.template import vyos_address_from_cidr -from vyos.template import vyos_netmask_from_cidr -from vyos.template import vyos_last_host_address -from vyos.template import vyos_inc_ip +from vyos.template import address_from_cidr +from vyos.template import dec_ip +from vyos.template import inc_ip +from vyos.template import last_host_address +from vyos.template import netmask_from_cidr PROCESS_NAME = 'openvpn' @@ -319,7 +319,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): for ii in num_range: interface = f'vtun{ii}' subnet = f'192.0.{ii}.0/24' - client_ip = vyos_inc_ip(subnet, '5') + client_ip = inc_ip(subnet, '5') path = base_path + [interface] port = str(2000 + ii) @@ -349,11 +349,11 @@ class TestInterfacesOpenVPN(unittest.TestCase): interface = f'vtun{ii}' subnet = f'192.0.{ii}.0/24' - start_addr = vyos_inc_ip(subnet, '2') - stop_addr = vyos_last_host_address(subnet) + start_addr = inc_ip(subnet, '2') + stop_addr = last_host_address(subnet) - client_ip = vyos_inc_ip(subnet, '5') - client_netmask = vyos_netmask_from_cidr(subnet) + client_ip = inc_ip(subnet, '5') + client_netmask = netmask_from_cidr(subnet) port = str(2000 + ii) @@ -387,7 +387,7 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(f'ifconfig-push {client_ip} {client_netmask}', client_config) for route in client1_routes: - self.assertIn('iroute {} {}'.format(vyos_address_from_cidr(route), vyos_netmask_from_cidr(route)), client_config) + self.assertIn('iroute {} {}'.format(address_from_cidr(route), netmask_from_cidr(route)), client_config) self.assertTrue(process_named_running(PROCESS_NAME)) self.assertEqual(get_vrf(interface), vrf_name) @@ -434,8 +434,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): for ii in num_range: interface = f'vtun{ii}' subnet = f'192.0.{ii}.0/24' - start_addr = vyos_inc_ip(subnet, '4') - stop_addr = vyos_last_host_address(subnet) + start_addr = inc_ip(subnet, '4') + stop_addr = dec_ip(last_host_address(subnet), '1') port = str(2000 + ii) config_file = f'/run/openvpn/{interface}.conf' diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py index 067a3c76b..2c2e2181b 100755 --- a/smoketest/scripts/cli/test_service_snmp.py +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -18,10 +18,10 @@ import os import re import unittest -from vyos.validate import is_ipv4 from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv4 from vyos.util import read_file from vyos.util import process_named_running diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py index 92333392a..3210e622f 100755 --- a/smoketest/scripts/cli/test_service_tftp-server.py +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -24,7 +24,7 @@ from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import read_file from vyos.util import process_named_running -from vyos.validate import is_ipv6 +from vyos.template import is_ipv6 PROCESS_NAME = 'in.tftpd' base_path = ['service', 'tftp-server'] diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 4f62b62d5..822a9aff2 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -20,8 +20,8 @@ import unittest from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError -from vyos.template import vyos_address_from_cidr -from vyos.template import vyos_netmask_from_cidr +from vyos.template import address_from_cidr +from vyos.template import netmask_from_cidr from vyos.util import read_file from vyos.util import process_named_running @@ -86,8 +86,8 @@ class TestSystemNTP(unittest.TestCase): # Check generated client address configuration for network in networks: - network_address = vyos_address_from_cidr(network) - network_netmask = vyos_netmask_from_cidr(network) + network_address = address_from_cidr(network) + network_netmask = netmask_from_cidr(network) tmp = get_config_value(f'restrict {network_address}')[0] test = f'mask {network_netmask} nomodify notrap nopeer' diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 1777d4db7..c2868e078 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -22,10 +22,11 @@ from copy import deepcopy from vyos.config import Config from vyos.template import render +from vyos.template import is_ipv6 from vyos.util import call -from vyos.validate import is_subnet_connected, is_ipv6 -from vyos import ConfigError +from vyos.validate import is_subnet_connected +from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index d0c2dd252..ef52cbfd3 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -21,12 +21,12 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client +from vyos.template import render +from vyos.template import is_ipv6 from vyos.util import call from vyos.util import chown from vyos.util import dict_search -from vyos.template import render from vyos.xml import defaults -from vyos.validate import is_ipv6 from vyos import ConfigError from vyos import airbag diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index b507afcc0..c23e79948 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -33,13 +33,13 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_diffie_hellman_length from vyos.ifconfig import VTunIf from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.util import call from vyos.util import chown from vyos.util import chmod_600 from vyos.util import dict_search from vyos.validate import is_addr_assigned -from vyos.validate import is_ipv4 -from vyos.validate import is_ipv6 from vyos import ConfigError from vyos import airbag @@ -320,7 +320,7 @@ def verify(openvpn): if 'local_port' in openvpn: raise ConfigError('Cannot specify "local-port" with "tcp-active"') - if 'remote_host' in openvpn: + if 'remote_host' not in openvpn: raise ConfigError('Must specify "remote-host" with "tcp-active"') # shared secret and TLS diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 5561514bd..f1217b62d 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -23,12 +23,14 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import is_member +from vyos.configdict import list_diff +from vyos.dicts import FixedDict from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf from vyos.ifconfig.afi import IP4, IP6 -from vyos.configdict import list_diff -from vyos.validate import is_ipv4, is_ipv6 +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos import ConfigError -from vyos.dicts import FixedDict + from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index c8e791c78..d1e551cad 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -20,11 +20,11 @@ from sys import exit from copy import deepcopy from vyos.config import Config -from vyos.validate import is_ipv6_link_local, is_ipv6 -from vyos import ConfigError -from vyos.util import call +from vyos.template import is_ipv6 from vyos.template import render - +from vyos.util import call +from vyos.validate import is_ipv6_link_local +from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py new file mode 100755 index 000000000..d5e5b64fb --- /dev/null +++ b/src/conf_mode/protocols_isis.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-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/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos import ConfigError +from vyos.util import call +from vyos.template import render +from vyos.template import render_to_string +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = r'/tmp/isis.frr' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'isis'] + + isis = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # determine which members have been removed + for instance in isis: + conf.set_level(base + [instance]) + tmp = node_changed(conf, ['interface']) + if tmp: + isis[instance].update({'interface_remove': tmp}) + + return isis + +def verify(isis): + # bail out early - looks like removal from running config + if not isis: + return None + + for process, isis_config in isis.items(): + # If more then one isis process is defined (Frr only supports one) + # http://docs.frrouting.org/en/latest/isisd.html#isis-router + if len(isis) > 1: + raise ConfigError('Only one isis process can be definded') + + # If network entity title (net) not defined + if 'net' not in isis_config: + raise ConfigError('ISIS net format iso is mandatory!') + + # If interface not set + if 'interface' not in isis_config: + raise ConfigError('ISIS interface is mandatory!') + + # If md5 and plaintext-password set at the same time + if 'area_password' in isis_config: + if {'md5', 'plaintext_password'} <= set(isis_config['encryption']): + raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!') + + # If one param from deley set, but not set others + if 'spf_delay_ietf' in isis_config: + required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn'] + exist_timers = [] + for elm_timer in required_timers: + if elm_timer in isis_config['spf_delay_ietf']: + exist_timers.append(elm_timer) + + exist_timers = set(required_timers).difference(set(exist_timers)) + if len(exist_timers) > 0: + raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-')) + + # If Redistribute set, but level don't set + if 'redistribute' in isis_config: + proc_level = isis_config.get('level','').replace('-','_') + for proto, proto_config in isis_config.get('redistribute', {}).get('ipv4', {}).items(): + if 'level_1' not in proto_config and 'level_2' not in proto_config: + raise ConfigError('Redistribute level-1 or level-2 should be specified in \"protocols isis {} redistribute ipv4 {}\"'.format(process, proto)) + for redistribute_level in proto_config.keys(): + if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level: + raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level)) + + return None + +def generate(isis): + if not isis: + isis['new_frr_config'] = '' + return None + + # only one ISIS process is supported, so we can directly send the first key + # of the config dict + process = list(isis.keys())[0] + isis[process]['process'] = process + + import pprint + pprint.pprint(isis[process]) + + # render(config) not needed, its only for debug + render(config_file, 'frr/isis.frr.tmpl', isis[process], trim_blocks=True) + + isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', + isis[process], trim_blocks=True) + + return None + +def apply(isis): + + # Save original configration prior to starting any commit actions + frr_cfg = {} + frr_cfg['original_config'] = frr.get_configuration(daemon='isisd') + frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], isis['new_frr_config'], from_re='router isis .*') + + # Debugging + print('') + print('--------- DEBUGGING ----------') + print(f'Existing config:\n{frr_cfg["original_config"]}\n\n') + print(f'Replacement config:\n{isis["new_frr_config"]}\n\n') + print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n') + + # FRR mark configuration will test for syntax errors and throws an + # exception if any syntax errors is detected + frr.mark_configuration(frr_cfg['modified_config']) + + # Commit resulting configuration to FRR, this will throw CommitError + # on failure + frr.reload_configuration(frr_cfg['modified_config'], daemon='isisd') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 87c7754f3..68c554360 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -23,8 +23,9 @@ from sys import exit from vyos.config import Config from vyos.template import render +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 from vyos.util import call, get_half_cpus -from vyos.validate import is_ipv4 from vyos import ConfigError from vyos import airbag diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 117bf0274..3990e5735 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -22,8 +22,9 @@ from vyos.config import Config from vyos.configverify import verify_vrf from vyos.snmpv3_hashgen import plaintext_to_md5, plaintext_to_sha1, random from vyos.template import render +from vyos.template import is_ipv4 from vyos.util import call, chmod_755 -from vyos.validate import is_ipv4, is_addr_assigned +from vyos.validate import is_addr_assigned from vyos.version import get_version_data from vyos import ConfigError, airbag airbag.enable() diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index cac95afe2..56e195b6a 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -25,9 +25,9 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render +from vyos.template import is_ipv4 from vyos.util import call from vyos.util import chmod_755 -from vyos.validate import is_ipv4 from vyos.validate import is_addr_assigned from vyos.xml import defaults from vyos import ConfigError diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 465986d5b..80eb8daf2 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -25,10 +25,10 @@ from time import sleep from ipaddress import ip_network from vyos.config import Config +from vyos.template import is_ipv4 +from vyos.template import render from vyos.util import call, get_half_cpus -from vyos.validate import is_ipv4 from vyos import ConfigError -from vyos.template import render from vyos import airbag airbag.enable() diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index 69af427ec..c000d7d06 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -34,7 +34,11 @@ def utc2local(datetime): def parse_time(s): try: if re.match(r'^\d{1,2}$', s): - return datetime.strptime(s, "%M").time() + if (int(s) > 59): + s = str(int(s)//60) + ":" + str(int(s)%60) + return datetime.strptime(s, "%H:%M").time() + else: + return datetime.strptime(s, "%M").time() else: return datetime.strptime(s, "%H:%M").time() except ValueError: diff --git a/src/tests/test_jinja_filters.py b/src/tests/test_jinja_filters.py index acd7a5952..8a7241fe3 100644 --- a/src/tests/test_jinja_filters.py +++ b/src/tests/test_jinja_filters.py @@ -17,13 +17,13 @@ from unittest import TestCase from ipaddress import ip_network -from vyos.template import vyos_address_from_cidr -from vyos.template import vyos_netmask_from_cidr -from vyos.template import vyos_ipv4 -from vyos.template import vyos_ipv6 -from vyos.template import vyos_first_host_address -from vyos.template import vyos_last_host_address -from vyos.template import vyos_inc_ip +from vyos.template import address_from_cidr +from vyos.template import netmask_from_cidr +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 +from vyos.template import first_host_address +from vyos.template import last_host_address +from vyos.template import inc_ip class TestTeamplteHelpers(TestCase): def setUp(self): @@ -31,39 +31,39 @@ class TestTeamplteHelpers(TestCase): def test_helpers_from_cidr(self): network_v4 = '192.0.2.0/26' - self.assertEqual(vyos_address_from_cidr(network_v4), str(ip_network(network_v4).network_address)) - self.assertEqual(vyos_netmask_from_cidr(network_v4), str(ip_network(network_v4).netmask)) + self.assertEqual(address_from_cidr(network_v4), str(ip_network(network_v4).network_address)) + self.assertEqual(netmask_from_cidr(network_v4), str(ip_network(network_v4).netmask)) def test_helpers_ipv4(self): - self.assertTrue(vyos_ipv4('192.0.2.1')) - self.assertTrue(vyos_ipv4('192.0.2.0/24')) - self.assertTrue(vyos_ipv4('192.0.2.1/32')) - self.assertTrue(vyos_ipv4('10.255.1.2')) - self.assertTrue(vyos_ipv4('10.255.1.0/24')) - self.assertTrue(vyos_ipv4('10.255.1.2/32')) - self.assertFalse(vyos_ipv4('2001:db8::')) - self.assertFalse(vyos_ipv4('2001:db8::1')) - self.assertFalse(vyos_ipv4('2001:db8::/64')) + self.assertTrue(is_ipv4('192.0.2.1')) + self.assertTrue(is_ipv4('192.0.2.0/24')) + self.assertTrue(is_ipv4('192.0.2.1/32')) + self.assertTrue(is_ipv4('10.255.1.2')) + self.assertTrue(is_ipv4('10.255.1.0/24')) + self.assertTrue(is_ipv4('10.255.1.2/32')) + self.assertFalse(is_ipv4('2001:db8::')) + self.assertFalse(is_ipv4('2001:db8::1')) + self.assertFalse(is_ipv4('2001:db8::/64')) def test_helpers_ipv6(self): - self.assertFalse(vyos_ipv6('192.0.2.1')) - self.assertFalse(vyos_ipv6('192.0.2.0/24')) - self.assertFalse(vyos_ipv6('192.0.2.1/32')) - self.assertFalse(vyos_ipv6('10.255.1.2')) - self.assertFalse(vyos_ipv6('10.255.1.0/24')) - self.assertFalse(vyos_ipv6('10.255.1.2/32')) - self.assertTrue(vyos_ipv6('2001:db8::')) - self.assertTrue(vyos_ipv6('2001:db8::1')) - self.assertTrue(vyos_ipv6('2001:db8::1/64')) - self.assertTrue(vyos_ipv6('2001:db8::/32')) - self.assertTrue(vyos_ipv6('2001:db8::/64')) + self.assertFalse(is_ipv6('192.0.2.1')) + self.assertFalse(is_ipv6('192.0.2.0/24')) + self.assertFalse(is_ipv6('192.0.2.1/32')) + self.assertFalse(is_ipv6('10.255.1.2')) + self.assertFalse(is_ipv6('10.255.1.0/24')) + self.assertFalse(is_ipv6('10.255.1.2/32')) + self.assertTrue(is_ipv6('2001:db8::')) + self.assertTrue(is_ipv6('2001:db8::1')) + self.assertTrue(is_ipv6('2001:db8::1/64')) + self.assertTrue(is_ipv6('2001:db8::/32')) + self.assertTrue(is_ipv6('2001:db8::/64')) def test_helpers_first_host_address(self): - self.assertEqual(vyos_first_host_address('10.0.0.0/24'), '10.0.0.1') - self.assertEqual(vyos_first_host_address('10.0.0.128/25'), '10.0.0.129') - self.assertEqual(vyos_first_host_address('10.0.0.200/29'), '10.0.0.201') + self.assertEqual(first_host_address('10.0.0.0/24'), '10.0.0.1') + self.assertEqual(first_host_address('10.0.0.128/25'), '10.0.0.129') + self.assertEqual(first_host_address('10.0.0.200/29'), '10.0.0.201') - self.assertEqual(vyos_first_host_address('2001:db8::/64'), '2001:db8::') - self.assertEqual(vyos_first_host_address('2001:db8::/112'), '2001:db8::') - self.assertEqual(vyos_first_host_address('2001:db8::10/112'), '2001:db8::10') - self.assertEqual(vyos_first_host_address('2001:db8::100/112'), '2001:db8::100') + self.assertEqual(first_host_address('2001:db8::/64'), '2001:db8::') + self.assertEqual(first_host_address('2001:db8::/112'), '2001:db8::') + self.assertEqual(first_host_address('2001:db8::10/112'), '2001:db8::10') + self.assertEqual(first_host_address('2001:db8::100/112'), '2001:db8::100') diff --git a/src/tests/test_template.py b/src/tests/test_template.py new file mode 100644 index 000000000..6dc2f075e --- /dev/null +++ b/src/tests/test_template.py @@ -0,0 +1,46 @@ +#!/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/>. + +import vyos.template +from unittest import TestCase + +class TestVyOSTemplate(TestCase): + def setUp(self): + pass + + def test_is_ip(self): + self.assertTrue(vyos.template.is_ip('192.0.2.1')) + self.assertTrue(vyos.template.is_ip('2001:db8::1')) + self.assertFalse(vyos.template.is_ip('VyOS')) + + def test_is_ipv4(self): + self.assertTrue(vyos.template.is_ipv4('192.0.2.1')) + self.assertTrue(vyos.template.is_ipv4('192.0.2.0/24')) + self.assertTrue(vyos.template.is_ipv4('192.0.2.1/32')) + + self.assertFalse(vyos.template.is_ipv4('2001:db8::1')) + self.assertFalse(vyos.template.is_ipv4('2001:db8::/64')) + self.assertFalse(vyos.template.is_ipv4('VyOS')) + + def test_is_ipv6(self): + self.assertTrue(vyos.template.is_ipv6('2001:db8::1')) + self.assertTrue(vyos.template.is_ipv6('2001:db8::/64')) + self.assertTrue(vyos.template.is_ipv6('2001:db8::1/64')) + + self.assertFalse(vyos.template.is_ipv6('192.0.2.1')) + self.assertFalse(vyos.template.is_ipv6('192.0.2.0/24')) + self.assertFalse(vyos.template.is_ipv6('192.0.2.1/32')) + self.assertFalse(vyos.template.is_ipv6('VyOS')) diff --git a/src/tests/test_validate.py b/src/tests/test_validate.py index e9fe185ed..226e856a3 100644 --- a/src/tests/test_validate.py +++ b/src/tests/test_validate.py @@ -21,29 +21,6 @@ class TestVyOSValidate(TestCase): def setUp(self): pass - def test_is_ip(self): - self.assertTrue(vyos.validate.is_ip('192.0.2.1')) - self.assertTrue(vyos.validate.is_ip('2001:db8::1')) - self.assertFalse(vyos.validate.is_ip('VyOS')) - - def test_is_ipv4(self): - self.assertTrue(vyos.validate.is_ipv4('192.0.2.1')) - self.assertTrue(vyos.validate.is_ipv4('192.0.2.0/24')) - self.assertTrue(vyos.validate.is_ipv4('192.0.2.1/32')) - - self.assertFalse(vyos.validate.is_ipv4('2001:db8::1')) - self.assertFalse(vyos.validate.is_ipv4('2001:db8::/64')) - self.assertFalse(vyos.validate.is_ipv4('VyOS')) - - def test_is_ipv6(self): - self.assertFalse(vyos.validate.is_ipv6('192.0.2.1')) - self.assertFalse(vyos.validate.is_ipv6('192.0.2.0/24')) - self.assertFalse(vyos.validate.is_ipv6('192.0.2.1/32')) - self.assertTrue(vyos.validate.is_ipv6('2001:db8::1')) - self.assertTrue(vyos.validate.is_ipv6('2001:db8::/64')) - self.assertTrue(vyos.validate.is_ipv6('2001:db8::1/64')) - self.assertFalse(vyos.validate.is_ipv6('VyOS')) - def test_is_ipv6_link_local(self): self.assertFalse(vyos.validate.is_ipv6_link_local('169.254.0.1')) self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::')) |