diff options
-rw-r--r-- | Jenkinsfile | 8 | ||||
-rw-r--r-- | interface-definitions/interfaces-ethernet.xml | 12 | ||||
-rw-r--r-- | op-mode-definitions/ipoe-server.xml | 10 | ||||
-rw-r--r-- | op-mode-definitions/pppoe-server.xml | 10 | ||||
-rw-r--r-- | python/vyos/ifconfig.py | 648 | ||||
-rwxr-xr-x | src/conf_mode/interface-bonding.py | 51 | ||||
-rwxr-xr-x | src/conf_mode/interface-bridge.py | 41 | ||||
-rwxr-xr-x | src/conf_mode/interface-dummy.py | 23 | ||||
-rwxr-xr-x | src/conf_mode/interface-ethernet.py | 32 | ||||
-rwxr-xr-x | src/conf_mode/interface-loopback.py | 23 | ||||
-rwxr-xr-x | src/conf_mode/interface-openvpn.py | 46 | ||||
-rwxr-xr-x | src/conf_mode/interface-vxlan.py | 18 | ||||
-rwxr-xr-x | src/conf_mode/interface-wireguard.py | 333 | ||||
-rwxr-xr-x | src/conf_mode/ipoe_server.py | 3 | ||||
-rwxr-xr-x | src/conf_mode/ntp.py | 3 | ||||
-rwxr-xr-x | src/services/vyos-hostsd | 4 | ||||
-rw-r--r-- | src/tests/test_ntp.py | 259 |
17 files changed, 500 insertions, 1024 deletions
diff --git a/Jenkinsfile b/Jenkinsfile index 8e70fdc1d..20eb2531d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -72,7 +72,6 @@ setDescription() pipeline { agent { docker { - label 'Docker' args '--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006' image 'vyos/vyos-build:current' } @@ -97,12 +96,7 @@ pipeline { steps { script { dir('build') { - sh """ - #!/bin/bash - sudo apt-get -o Acquire::Check-Valid-Until=false update - sudo mk-build-deps -i -r -t \'apt-get --no-install-recommends -yq\' debian/control - dpkg-buildpackage -b -us -uc -tc - """ + sh "dpkg-buildpackage -b -us -uc -tc" } } } diff --git a/interface-definitions/interfaces-ethernet.xml b/interface-definitions/interfaces-ethernet.xml index 9244f3b5f..e4a56b216 100644 --- a/interface-definitions/interfaces-ethernet.xml +++ b/interface-definitions/interfaces-ethernet.xml @@ -394,6 +394,10 @@ <tagNode name="vif-s"> <properties> <help>QinQ TAG-S Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>0-4094</format> + <description>QinQ TAG-S Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> @@ -551,6 +555,10 @@ <tagNode name="vif-c"> <properties> <help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>0-4094</format> + <description>QinQ TAG-C Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> @@ -692,6 +700,10 @@ <tagNode name="vif"> <properties> <help>Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>0-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> diff --git a/op-mode-definitions/ipoe-server.xml b/op-mode-definitions/ipoe-server.xml index ea14e9a5c..369ceebea 100644 --- a/op-mode-definitions/ipoe-server.xml +++ b/op-mode-definitions/ipoe-server.xml @@ -23,4 +23,14 @@ </node> </children> </node> + <node name="restart"> + <children> + <leafNode name="ipoe-server"> + <properties> + <help>show ipoe-server status</help> + </properties> + <command>if [ -e /var/run/accel_ipoe.pid ]; then /usr/bin/accel-cmd restart -p 2002; else echo "ipoe-server not running"; fi</command> + </leafNode> + </children> + </node> </interfaceDefinition> diff --git a/op-mode-definitions/pppoe-server.xml b/op-mode-definitions/pppoe-server.xml index a2366c710..1f6f7f844 100644 --- a/op-mode-definitions/pppoe-server.xml +++ b/op-mode-definitions/pppoe-server.xml @@ -29,4 +29,14 @@ </node> </children> </node> + <node name="restart"> + <children> + <leafNode name="pppoe-server"> + <properties> + <help>Restarts pppoe-server</help> + </properties> + <command>if [ -e /var/run/accel_pppoe.pid ]; then /usr/bin/accel-cmd restart -p 2001; else echo "pppoe-server not running"; fi</command> + </leafNode> + </children> + </node> </interfaceDefinition> diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 8d4923957..a77cde5e7 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -60,7 +60,6 @@ class Interface: >>> i = Interface('eth0') """ self._ifname = str(ifname) - self._state = 'down' if not os.path.exists('/sys/class/net/{}'.format(ifname)) and not type: raise Exception('interface "{}" not found'.format(self._ifname)) @@ -145,28 +144,26 @@ class Interface: cmd = 'ip link del dev {}'.format(self._ifname) self._cmd(cmd) - @property - def mtu(self): + def get_mtu(self): """ Get/set interface mtu in bytes. Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').mtu + >>> Interface('eth0').get_mtu() '1500' """ return self._read_sysfs('/sys/class/net/{}/mtu' .format(self._ifname)) - @mtu.setter - def mtu(self, mtu): + def set_mtu(self, mtu): """ Get/set interface mtu in bytes. Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').mtu = 1400 - >>> Interface('eth0').mtu + >>> Interface('eth0').set_mtu(1400) + >>> Interface('eth0').get_mtu() '1400' """ if mtu < 68 or mtu > 9000: @@ -175,29 +172,13 @@ class Interface: return self._write_sysfs('/sys/class/net/{}/mtu' .format(self._ifname), mtu) - @property - def mac(self): + def set_mac(self, mac): """ - Get/set interface mac address + Set interface MAC (Media Access Contrl) address to given value. Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').mac - '00:0c:29:11:aa:cc' - """ - return self._read_sysfs('/sys/class/net/{}/address' - .format(self._ifname)) - - @mac.setter - def mac(self, mac): - """ - Get/set interface mac address - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').mac = '00:90:43:fe:fe:1b' - >>> Interface('eth0').mac - '00:90:43:fe:fe:1b' + >>> Interface('eth0').set_mac('00:50:ab:cd:ef:01') """ # on interface removal (ethernet) an empty string is passed - ignore it if not mac: @@ -226,50 +207,22 @@ class Interface: cmd = 'ip link set dev {} address {}'.format(self._ifname, mac) self._cmd(cmd) - @property - def arp_cache_tmo(self): - """ - Get configured ARP cache timeout value from interface in seconds. - Internal Kernel representation is in milliseconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').arp_cache_tmo - '30' - """ - return (self._read_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms' - .format(self._ifname)) / 1000) - @arp_cache_tmo.setter - def arp_cache_tmo(self, tmo): + def set_arp_cache_tmo(self, tmo): """ Set ARP cache timeout value in seconds. Internal Kernel representation is in milliseconds. Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').arp_cache_tmo = '40' + >>> Interface('eth0').set_arp_cache_tmo(40) """ return self._write_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms' .format(self._ifname), (int(tmo) * 1000)) - @property - def link_detect(self): - """ - How does the kernel act when receiving packets on 'down' interfaces - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').link_detect - '0' - """ - return self._read_sysfs('/proc/sys/net/ipv4/conf/{0}/link_filter' - .format(self._ifname)) - - @link_detect.setter - def link_detect(self, link_filter): + def set_link_detect(self, link_filter): """ - Konfigure kernel response in packets received on interfaces that are 'down' + Configure kernel response in packets received on interfaces that are 'down' 0 - Allow packets to be received for the address on this interface even if interface is disabled or no carrier. @@ -285,44 +238,25 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').link_detect = '1' + >>> Interface('eth0').set_link_detect(1) """ - if link_filter >= 0 and link_filter <= 2: + if int(link_filter) >= 0 and int(link_filter) <= 2: return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/link_filter' .format(self._ifname), link_filter) else: raise ValueError("Value out of range") - @property - def ifalias(self): - """ - Get/set interface alias name - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').ifalias - '' - """ - return self._read_sysfs('/sys/class/net/{}/ifalias' - .format(self._ifname)) - - @ifalias.setter - def ifalias(self, ifalias=None): + def set_alias(self, ifalias=None): """ - Get/set interface alias name + Set interface alias name used by e.g. SNMP Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').ifalias = 'VyOS upstream interface' - >>> Interface('eth0').ifalias - 'VyOS upstream interface' + >>> Interface('eth0').set_alias('VyOS upstream interface') - to clear interface alias e.g. delete it use: + to clear alias e.g. delete it use: - >>> Interface('eth0').ifalias = '' - >>> Interface('eth0').ifalias - '' + >>> Interface('eth0').set_ifalias('') """ if not ifalias: # clear interface alias @@ -331,63 +265,43 @@ class Interface: self._write_sysfs('/sys/class/net/{}/ifalias' .format(self._ifname), ifalias) - @property - def state(self): + def get_state(self): """ Enable (up) / Disable (down) an interface Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').state + >>> Interface('eth0').get_state() 'up' """ return self._read_sysfs('/sys/class/net/{}/operstate' .format(self._ifname)) - @state.setter - def state(self, state): + def set_state(self, state): """ Enable (up) / Disable (down) an interface Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').state = 'down' - >>> Interface('eth0').state + >>> Interface('eth0').set_state('down') + >>> Interface('eth0').get_state() 'down' """ if state not in ['up', 'down']: raise ValueError('state must be "up" or "down"') - self._state = state - # Assemble command executed on system. Unfortunately there is no way # to up/down an interface via sysfs cmd = 'ip link set dev {} {}'.format(self._ifname, state) self._cmd(cmd) - @property - def proxy_arp(self): - """ - Get current proxy ARP configuration from sysfs. Default: 0 - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').proxy_arp - '0' - """ - return self._read_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp' - .format(self._ifname)) - - @proxy_arp.setter - def proxy_arp(self, enable): + def set_proxy_arp(self, enable): """ Set per interface proxy ARP configuration Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').proxy_arp = 1 - >>> Interface('eth0').proxy_arp - '1' + >>> Interface('eth0').set_proxy_arp(1) """ if int(enable) >= 0 and int(enable) <= 1: return self._write_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp' @@ -395,8 +309,7 @@ class Interface: else: raise ValueError("Value out of range") - @property - def proxy_arp_pvlan(self): + def set_proxy_arp_pvlan(self, enable): """ Private VLAN proxy arp. Basically allow proxy arp replies back to the same interface @@ -418,38 +331,7 @@ class Interface: Example: >>> from vyos.ifconfig import Interface - >>> Interface('eth0').proxy_arp_pvlan - '0' - """ - return self._read_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp_pvlan' - .format(self._ifname)) - - @proxy_arp_pvlan.setter - def proxy_arp_pvlan(self, enable): - """ - Private VLAN proxy arp. - Basically allow proxy arp replies back to the same interface - (from which the ARP request/solicitation was received). - - This is done to support (ethernet) switch features, like RFC - 3069, where the individual ports are NOT allowed to - communicate with each other, but they are allowed to talk to - the upstream router. As described in RFC 3069, it is possible - to allow these hosts to communicate through the upstream - router by proxy_arp'ing. Don't need to be used together with - proxy_arp. - - This technology is known by different names: - In RFC 3069 it is called VLAN Aggregation. - Cisco and Allied Telesyn call it Private VLAN. - Hewlett-Packard call it Source-Port filtering or port-isolation. - Ericsson call it MAC-Forced Forwarding (RFC Draft). - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').proxy_arp_pvlan = 1 - >>> Interface('eth0').proxy_arp_pvlan - '1' + >>> Interface('eth0').set_proxy_arp_pvlan(1) """ if int(enable) >= 0 and int(enable) <= 1: return self._write_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp_pvlan' @@ -581,7 +463,7 @@ class Interface: with open(self._dhcp_cfg_file, 'w') as f: f.write(dhcp_text) - if self._state == 'up': + if self.get_state() == 'up': cmd = 'start-stop-daemon --start --quiet --pidfile ' + \ self._dhcp_pid_file cmd += ' --exec /sbin/dhclient --' @@ -659,7 +541,7 @@ class Interface: with open(self._dhcpv6_cfg_file, 'w') as f: f.write(dhcpv6_text) - if self._state == 'up': + if self.get_state() == 'up': # https://bugs.launchpad.net/ubuntu/+source/ifupdown/+bug/1447715 # # wee need to wait for IPv6 DAD to finish once and interface is added @@ -731,6 +613,21 @@ class LoopbackIf(Interface): def __init__(self, ifname): super().__init__(ifname, type='loopback') + def remove(self): + """ + Loopback interface can not be deleted from operating system. We can + only remove all assigned IP addresses. + + Example: + >>> from vyos.ifconfig import Interface + >>> i = LoopbackIf('lo').remove() + """ + # remove all assigned IP addresses from interface + for addr in self.get_addr(): + self.del_addr(addr) + + # question: do we also delerte the loopback address? 127.0.0.1/8 + class DummyIf(Interface): @@ -744,6 +641,47 @@ class DummyIf(Interface): super().__init__(ifname, type='dummy') +class STPIf(Interface): + """ + A spanning-tree capable interface. This applies only to bridge port member + interfaces! + """ + def __init__(self, ifname): + super().__init__(ifname) + + def set_path_cost(self, cost): + """ + Set interface path cost, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_path_cost(4) + """ + if not os.path.isfile('/sys/class/net/{}/brport/path_cost' + .format(self._ifname)): + raise TypeError('{} is not a bridge port member'.format(self._ifname)) + + return self._write_sysfs('/sys/class/net/{}/brport/path_cost' + .format(self._ifname), cost) + + def set_path_priority(self, priority): + """ + Set interface path priority, only relevant for STP enabled interfaces + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_path_priority(4) + """ + if not os.path.isfile('/sys/class/net/{}/brport/priority' + .format(self._ifname)): + raise TypeError('{} is not a bridge port member'.format(self._ifname)) + + return self._write_sysfs('/sys/class/net/{}/brport/priority' + .format(self._ifname), priority) + + class BridgeIf(Interface): """ @@ -758,167 +696,73 @@ class BridgeIf(Interface): def __init__(self, ifname): super().__init__(ifname, type='bridge') - @property - def ageing_time(self): - """ - Return configured bridge interface MAC address aging time in seconds. - Internal kernel representation is in centiseconds, thus its converted - in the end. Kernel default is 300 seconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').aging_time - '300' - """ - return (self._read_sysfs('/sys/class/net/{}/bridge/ageing_time' - .format(self._ifname)) / 100) - - @ageing_time.setter - def ageing_time(self, time): + def set_ageing_time(self, time): """ Set bridge interface MAC address aging time in seconds. Internal kernel representation is in centiseconds. Kernel default is 300 seconds. Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').ageing_time = 2 + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').ageing_time(2) """ time = int(time) * 100 return self._write_sysfs('/sys/class/net/{}/bridge/ageing_time' .format(self._ifname), time) - @property - def forward_delay(self): - """ - Get bridge forwarding delay in seconds. Internal Kernel representation - is in centiseconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').ageing_time - '3' - """ - return (self._read_sysfs('/sys/class/net/{}/bridge/forward_delay' - .format(self._ifname)) / 100) - - @forward_delay.setter - def forward_delay(self, time): + def set_forward_delay(self, time): """ Set bridge forwarding delay in seconds. Internal Kernel representation is in centiseconds. Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').forward_delay = 15 + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').forward_delay(15) """ return self._write_sysfs('/sys/class/net/{}/bridge/forward_delay' .format(self._ifname), (int(time) * 100)) - @property - def hello_time(self): - """ - Get bridge hello time in seconds. Internal Kernel representation - is in centiseconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').hello_time - '2' - """ - return (self._read_sysfs('/sys/class/net/{}/bridge/hello_time' - .format(self._ifname)) / 100) - - @hello_time.setter - def hello_time(self, time): + def set_hello_time(self, time): """ Set bridge hello time in seconds. Internal Kernel representation is in centiseconds. Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').hello_time = 2 + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_hello_time(2) """ return self._write_sysfs('/sys/class/net/{}/bridge/hello_time' .format(self._ifname), (int(time) * 100)) - @property - def max_age(self): - """ - Get bridge max max message age in seconds. Internal Kernel representation - is in centiseconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').max_age - '20' - """ - - return (self._read_sysfs('/sys/class/net/{}/bridge/max_age' - .format(self._ifname)) / 100) - - @max_age.setter - def max_age(self, time): + def set_max_age(self, time): """ Set bridge max message age in seconds. Internal Kernel representation is in centiseconds. Example: >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').max_age = 30 + >>> BridgeIf('br0').set_max_age(30) """ return self._write_sysfs('/sys/class/net/{}/bridge/max_age' .format(self._ifname), (int(time) * 100)) - @property - def priority(self): - """ - Get bridge max aging time in seconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').priority - '32768' - """ - return self._read_sysfs('/sys/class/net/{}/bridge/priority' - .format(self._ifname)) - - @priority.setter - def priority(self, priority): + def set_priority(self, priority): """ Set bridge max aging time in seconds. Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').priority = 8192 + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_priority(8192) """ return self._write_sysfs('/sys/class/net/{}/bridge/priority' .format(self._ifname), priority) - @property - def stp_state(self): - """ - Get current bridge STP (Spanning Tree) state. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').stp_state - '0' - """ - - state = 0 - with open('/sys/class/net/{}/bridge/stp_state'.format(self._ifname), 'r') as f: - state = int(f.read().rstrip('\n')) - - return state - - @stp_state.setter - def stp_state(self, state): + def set_stp(self, state): """ - Set bridge STP (Spannign Tree) state. 0 -> STP disabled, 1 -> STP enabled + Set bridge STP (Spanning Tree) state. 0 -> STP disabled, 1 -> STP enabled Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').stp_state = 1 + >>> from vyos.ifconfig import BridgeIf + >>> BridgeIf('br0').set_stp(1) """ if int(state) >= 0 and int(state) <= 1: @@ -927,21 +771,8 @@ class BridgeIf(Interface): else: raise ValueError("Value out of range") - @property - def multicast_querier(self): - """ - Get bridge multicast querier membership state. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').multicast_querier - '0' - """ - return self._read_sysfs('/sys/class/net/{}/bridge/multicast_querier' - .format(self._ifname)) - @multicast_querier.setter - def multicast_querier(self, enable): + def set_multicast_querier(self, enable): """ Sets whether the bridge actively runs a multicast querier or not. When a bridge receives a 'multicast host membership' query from another network @@ -952,7 +783,7 @@ class BridgeIf(Interface): Example: >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').multicast_querier = 1 + >>> BridgeIf('br0').set_multicast_querier(1) """ if int(enable) >= 0 and int(enable) <= 1: return self._write_sysfs('/sys/class/net/{}/bridge/multicast_querier' @@ -960,6 +791,7 @@ class BridgeIf(Interface): else: raise ValueError("Value out of range") + def add_port(self, interface): """ Add physical interface to bridge (member port) @@ -983,31 +815,6 @@ class BridgeIf(Interface): cmd = 'ip link set dev {} nomaster'.format(interface) self._cmd(cmd) - def set_cost(self, interface, cost): - """ - Set interface path cost, only relevant for STP enabled interfaces - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').path_cost(4) - """ - return self._write_sysfs('/sys/class/net/{}/brif/{}/path_cost' - .format(self._ifname, interface), cost) - - def set_priority(self, interface, priority): - """ - Set interface path priority, only relevant for STP enabled interfaces - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').priority(4) - """ - return self._write_sysfs('/sys/class/net/{}/brif/{}/priority' - .format(self._ifname, interface), priority) - - class VLANIf(Interface): """ This class handels the creation and removal of a VLAN interface. It serves @@ -1027,12 +834,22 @@ class VLANIf(Interface): >>> i = Interface('eth0') >>> i.remove() """ - # do we have sub interfaces (VLANs)? - # we apply a regex matching subinterfaces (indicated by a .) of a - # parent interface. 'bond0(?:\.\d+){1,2}' will match vif and vif-s/vif-c - # subinterfaces + # Do we have sub interfaces (VLANs)? We apply a regex matching + # subinterfaces (indicated by a .) of a parent interface. + # + # As interfaces need to be deleted "in order" starting from Q-in-Q + # we delete them first. + vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \ + if re.match(self._ifname + r'(?:\.\d+)(?:\.\d+)', f)] + + for vlan in vlan_ifs: + Interface(vlan).remove() + + # After deleting all Q-in-Q interfaces delete other VLAN interfaces + # which probably acted as parent to Q-in-Q or have been regular 802.1q + # interface. vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \ - if re.match(self._ifname + r'(?:\.\d+){1,2}', f)] + if re.match(self._ifname + r'(?:\.\d+)', f)] for vlan in vlan_ifs: Interface(vlan).remove() @@ -1106,8 +923,7 @@ class VLANIf(Interface): >>> i.del_vlan() """ vlan_ifname = self._ifname + '.' + str(vlan_id) - tmp = VLANIf(vlan_ifname) - tmp.remove() + VLANIf(vlan_ifname).remove() class EthernetIf(VLANIf): @@ -1131,32 +947,6 @@ class EthernetIf(VLANIf): link = os.readlink('/sys/class/net/{}/device/driver/module'.format(self._ifname)) return os.path.basename(link) - - def has_autoneg(self): - """ - Not all drivers support autonegotiation. - - returns True -> Autonegotiation is supported by driver - False -> Autonegotiation is not supported by driver - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.has_autoneg() - 'True' - """ - regex = 'Supports auto-negotiation:[ ]\w+' - tmp = self._cmd('/sbin/ethtool {}'.format(self._ifname)) - tmp = re.search(regex, tmp.decode()) - - # Output is either 'Supports auto-negotiation: Yes' or - # 'Supports auto-negotiation: No' - if tmp.group().split(':')[1].lstrip() == "Yes": - return True - else: - return False - - def set_flow_control(self, enable): """ Changes the pause parameters of the specified Ethernet device. @@ -1319,7 +1109,7 @@ class BondIf(VLANIf): for s in self.get_slaves(): slave = { 'ifname' : s, - 'state': Interface(s).state + 'state': Interface(s).get_state() } slave_list.append(slave) @@ -1330,29 +1120,10 @@ class BondIf(VLANIf): # physical interface for slave in slave_list: i = Interface(slave['ifname']) - i.state = slave['state'] - - @property - def xmit_hash_policy(self): - """ - Selects the transmit hash policy to use for slave selection in - balance-xor, 802.3ad, and tlb modes. Possible values are: layer2, - layer2+3, layer3+4, encap2+3, encap3+4. + i.set_state(slave['state']) - The default value is layer2 - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').xmit_hash_policy - 'layer3+4' - """ - # Linux Kernel appends has policy value to string, e.g. 'layer3+4 1', - # so remove the later part and only return the mode as string. - return self._read_sysfs('/sys/class/net/{}/bonding/xmit_hash_policy' - .format(self._ifname)).split()[0] - @xmit_hash_policy.setter - def xmit_hash_policy(self, mode): + def set_hash_policy(self, mode): """ Selects the transmit hash policy to use for slave selection in balance-xor, 802.3ad, and tlb modes. Possible values are: layer2, @@ -1362,59 +1133,50 @@ class BondIf(VLANIf): Example: >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').xmit_hash_policy = 'layer2+3' - >>> BondIf('bond0').proxy_arp - '1' + >>> BondIf('bond0').set_hash_policy('layer2+3') """ if not mode in ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']: raise ValueError("Value out of range") return self._write_sysfs('/sys/class/net/{}/bonding/xmit_hash_policy' .format(self._ifname), mode) - @property - def arp_interval(self): + def set_arp_interval(self, interval): """ Specifies the ARP link monitoring frequency in milliseconds. - The ARP monitor works by periodically checking the slave devices to - determine whether they have sent or received traffic recently (the - precise criteria depends upon the bonding mode, and the state of the - slave). Regular traffic is generated via ARP probes issued for the - addresses specified by the arp_ip_target option. - - The default value is 0. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').arp_interval - '0' - """ - return self._read_sysfs('/sys/class/net/{}/bonding/arp_interval' - .format(self._ifname)) + The ARP monitor works by periodically checking the slave devices + to determine whether they have sent or received traffic recently + (the precise criteria depends upon the bonding mode, and the + state of the slave). Regular traffic is generated via ARP probes + issued for the addresses specified by the arp_ip_target option. - @arp_interval.setter - def arp_interval(self, time): - """ - Specifies the IP addresses to use as ARP monitoring peers when - arp_interval is > 0. These are the targets of the ARP request sent to - determine the health of the link to the targets. Specify these values - in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by - a comma. At least one IP address must be given for ARP monitoring to - function. The maximum number of targets that can be specified is 16. + If ARP monitoring is used in an etherchannel compatible mode + (modes 0 and 2), the switch should be configured in a mode that + evenly distributes packets across all links. If the switch is + configured to distribute the packets in an XOR fashion, all + replies from the ARP targets will be received on the same link + which could cause the other team members to fail. - The default value is no IP addresses. + value of 0 disables ARP monitoring. The default value is 0. Example: >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').arp_interval = '100' - >>> BondIf('bond0').arp_interval - '100' - """ - return self._write_sysfs('/sys/class/net/{}/bonding/arp_interval' - .format(self._ifname), time) + >>> BondIf('bond0').set_arp_interval('100') + """ + if int(interval) == 0: + """ + Specifies the MII link monitoring frequency in milliseconds. + This determines how often the link state of each slave is + inspected for link failures. A value of zero disables MII + link monitoring. A value of 100 is a good starting point. + """ + return self._write_sysfs('/sys/class/net/{}/bonding/miimon' + .format(self._ifname), interval) + else: + return self._write_sysfs('/sys/class/net/{}/bonding/arp_interval' + .format(self._ifname), interval) - @property - def arp_ip_target(self): + def get_arp_ip_target(self): """ Specifies the IP addresses to use as ARP monitoring peers when arp_interval is > 0. These are the targets of the ARP request sent to @@ -1427,14 +1189,13 @@ class BondIf(VLANIf): Example: >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').arp_ip_target + >>> BondIf('bond0').get_arp_ip_target() '192.0.2.1' """ return self._read_sysfs('/sys/class/net/{}/bonding/arp_ip_target' .format(self._ifname)) - @arp_ip_target.setter - def arp_ip_target(self, target): + def set_arp_ip_target(self, target): """ Specifies the IP addresses to use as ARP monitoring peers when arp_interval is > 0. These are the targets of the ARP request sent to @@ -1447,52 +1208,13 @@ class BondIf(VLANIf): Example: >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').arp_ip_target = '192.0.2.1' - >>> BondIf('bond0').arp_ip_target + >>> BondIf('bond0').set_arp_ip_target('192.0.2.1') + >>> BondIf('bond0').get_arp_ip_target() '192.0.2.1' """ return self._write_sysfs('/sys/class/net/{}/bonding/arp_ip_target' .format(self._ifname), target) - @property - def miimon(self): - """ - Specifies the MII link monitoring frequency in milliseconds. - This determines how often the link state of each slave is - inspected for link failures. A value of zero disables MII - link monitoring. A value of 100 is a good starting point. - - The default value is 0. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').miimon - '250' - """ - return self._read_sysfs('/sys/class/net/{}/bonding/miimon' - .format(self._ifname)) - - - @miimon.setter - def miimon(self, time): - """ - Specifies the MII link monitoring frequency in milliseconds. - This determines how often the link state of each slave is - inspected for link failures. A value of zero disables MII - link monitoring. A value of 100 is a good starting point. - - The default value is 0. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').miimon = 250 - >>> BondIf('bond0').miimon - '250' - """ - return self._write_sysfs('/sys/class/net/{}/bonding/miimon' - .format(self._ifname), time) - - def add_port(self, interface): """ Enslave physical interface to bond. @@ -1505,7 +1227,7 @@ class BondIf(VLANIf): # An interface can only be added to a bond if it is in 'down' state. If # interface is in 'up' state, the following Kernel error will be thrown: # bond0: eth1 is up - this may be due to an out of date ifenslave. - Interface(interface).state = 'down' + Interface(interface).set_state('down') return self._write_sysfs('/sys/class/net/{}/bonding/slaves' .format(self._ifname), '+' + interface) @@ -1534,28 +1256,8 @@ class BondIf(VLANIf): .format(self._ifname)) return list(map(str, slaves.split())) - @property - def primary(self): - """ - A string (eth0, eth2, etc) specifying which slave is the primary - device. The specified device will always be the active slave while it - is available. Only when the primary is off-line will alternate devices - be used. This is useful when one slave is preferred over another, e.g., - when one slave has higher throughput than another. - The primary option is only valid for active-backup, balance-tlb and - balance-alb mode. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').primary - 'eth1' - """ - return self._read_sysfs('/sys/class/net/{}/bonding/primary' - .format(self._ifname)) - - @primary.setter - def primary(self, interface): + def set_primary(self, interface): """ A string (eth0, eth2, etc) specifying which slave is the primary device. The specified device will always be the active slave while it @@ -1568,9 +1270,7 @@ class BondIf(VLANIf): Example: >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').primary = 'eth2' - >>> BondIf('bond0').primary - 'eth2' + >>> BondIf('bond0').set_primary('eth2') """ if not interface: # reset primary interface @@ -1579,25 +1279,7 @@ class BondIf(VLANIf): return self._write_sysfs('/sys/class/net/{}/bonding/primary' .format(self._ifname), interface) - @property - def mode(self): - """ - Specifies one of the bonding policies. The default is balance-rr - (round robin). - - Possible values are: balance-rr (0), active-backup (1), balance-xor (2), - broadcast (3), 802.3ad (4), balance-tlb (5), balance-alb (6) - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').mode - 'balance-rr' - """ - return self._read_sysfs('/sys/class/net/{}/bonding/mode' - .format(self._ifname)).split()[0] - - @mode.setter - def mode(self, mode): + def set_mode(self, mode): """ Specifies one of the bonding policies. The default is balance-rr (round robin). @@ -1610,9 +1292,7 @@ class BondIf(VLANIf): Example: >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').mode = '802.3ad' - >>> BondIf('bond0').mode - '802.3ad' + >>> BondIf('bond0').set_mode('802.3ad') """ if not mode in [ 'balance-rr', 'active-backup', 'balance-xor', 'broadcast', diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py index 9049913e6..4d5009c73 100755 --- a/src/conf_mode/interface-bonding.py +++ b/src/conf_mode/interface-bonding.py @@ -13,8 +13,6 @@ # # 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 @@ -86,20 +84,20 @@ def apply_vlan_config(vlan, config): raise TypeError() # update interface description used e.g. within SNMP - vlan.ifalias = config['description'] + vlan.set_alias(config['description']) # ignore link state changes - vlan.link_detect = config['disable_link_detect'] + vlan.set_link_detect(config['disable_link_detect']) # Maximum Transmission Unit (MTU) - vlan.mtu = config['mtu'] + vlan.set_mtu(config['mtu']) # Change VLAN interface MAC address if config['mac']: - vlan.mac = config['mac'] + vlan.set_mac(config['mac']) # enable/disable VLAN interface if config['disable']: - vlan.state = 'down' + vlan.set_state('down') else: - vlan.state = 'up' + vlan.set_state('up') # Configure interface address(es) # - not longer required addresses get removed first @@ -339,7 +337,7 @@ def apply(bond): else: # Some parameters can not be changed when the bond is up. # Always disable the bond prior changing anything - b.state = 'down' + b.set_state('down') # The bonding mode can not be changed when there are interfaces enslaved # to this bond, thus we will free all interfaces from the bond first! @@ -347,11 +345,8 @@ def apply(bond): b.del_port(intf) # ARP link monitoring frequency, reset miimon when arp-montior is inactive - if bond['arp_mon_intvl'] == 0: - # reset miimon to default - b.miimon = 250 - else: - b.arp_interval = bond['arp_mon_intvl'] + # this is done inside BondIf automatically + b.set_arp_interval(bond['arp_mon_intvl']) # ARP monitor targets need to be synchronized between sysfs and CLI. # Unfortunately an address can't be send twice to sysfs as this will @@ -362,44 +357,44 @@ def apply(bond): # from the kernel side this looks valid to me. We won't run into an error # when a user added manual adresses which would result in having more # then 16 adresses in total. - arp_tgt_addr = list(map(str, b.arp_ip_target.split())) + arp_tgt_addr = list(map(str, b.get_arp_ip_target().split())) for addr in arp_tgt_addr: - b.arp_ip_target = '-' + addr + b.set_arp_ip_target('-' + addr) # Add configured ARP target addresses for addr in bond['arp_mon_tgt']: - b.arp_ip_target = '+' + addr + b.set_arp_ip_target('+' + addr) # update interface description used e.g. within SNMP - b.ifalias = bond['description'] + b.set_alias(bond['description']) # # missing DHCP/DHCPv6 options go here # # ignore link state changes - b.link_detect = bond['disable_link_detect'] + b.set_link_detect(bond['disable_link_detect']) # Bonding transmit hash policy - b.xmit_hash_policy = bond['hash_policy'] + b.set_hash_policy(bond['hash_policy']) # configure ARP cache timeout in milliseconds - b.arp_cache_tmp = bond['ip_arp_cache_tmo'] + b.set_arp_cache_tmo(bond['ip_arp_cache_tmo']) # Enable proxy-arp on this interface - b.proxy_arp = bond['ip_proxy_arp'] + b.set_proxy_arp(bond['ip_proxy_arp']) # Enable private VLAN proxy ARP on this interface - b.proxy_arp_pvlan = bond['ip_proxy_arp_pvlan'] + b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan']) # Change interface MAC address if bond['mac']: - b.mac = bond['mac'] + b.set_mac(bond['mac']) # Bonding policy - b.mode = bond['mode'] + b.set_mode(bond['mode']) # Maximum Transmission Unit (MTU) - b.mtu = bond['mtu'] + b.set_mtu(bond['mtu']) # Primary device interface if bond['primary']: - b.primary = bond['primary'] + b.set_primary(bond['primary']) # Add (enslave) interfaces to bond for intf in bond['member']: @@ -409,7 +404,7 @@ def apply(bond): # parameters we will only re-enable the interface if it is not # administratively disabled if not bond['disable']: - b.state = 'up' + b.set_state('up') # Configure interface address(es) # - not longer required addresses get removed first diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 62589c798..37b5c4979 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -13,8 +13,6 @@ # # 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 @@ -22,7 +20,7 @@ from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BridgeIf, Interface +from vyos.ifconfig import BridgeIf, STPIf from vyos.configdict import list_diff from vyos.config import Config from vyos import ConfigError @@ -187,27 +185,27 @@ def apply(bridge): br.remove() else: # enable interface - br.state = 'up' + br.set_state('up') # set ageing time - br.ageing_time = bridge['aging'] + br.set_ageing_time(bridge['aging']) # set bridge forward delay - br.forward_delay = bridge['forwarding_delay'] + br.set_forward_delay(bridge['forwarding_delay']) # set hello time - br.hello_time = bridge['hello_time'] + br.set_hello_time(bridge['hello_time']) # set max message age - br.max_age = bridge['max_age'] + br.set_max_age(bridge['max_age']) # set bridge priority - br.priority = bridge['priority'] + br.set_priority(bridge['priority']) # turn stp on/off - br.stp_state = bridge['stp'] + br.set_stp(bridge['stp']) # enable or disable IGMP querier - br.multicast_querier = bridge['igmp_querier'] + br.set_multicast_querier(bridge['igmp_querier']) # update interface description used e.g. within SNMP - br.ifalias = bridge['description'] + br.set_alias(bridge['description']) # Change interface MAC address if bridge['mac']: - br.mac = bridge['mac'] + br.set_mac(bridge['mac']) # remove interface from bridge for intf in bridge['member_remove']: @@ -219,7 +217,7 @@ def apply(bridge): # up/down interface if bridge['disable']: - br.state = 'down' + br.set_state('down') # Configure interface address(es) # - not longer required addresses get removed first @@ -231,16 +229,15 @@ def apply(bridge): # configure additional bridge member options for member in bridge['member']: - # set bridge port cost - br.set_cost(member['name'], member['cost']) - # set bridge port priority - br.set_priority(member['name'], member['priority']) - - i = Interface(member['name']) + i = STPIf(member['name']) # configure ARP cache timeout - i.arp_cache_tmo = bridge['arp_cache_tmo'] + i.set_arp_cache_tmo(bridge['arp_cache_tmo']) # ignore link state changes - i.link_detect = bridge['disable_link_detect'] + i.set_link_detect(bridge['disable_link_detect']) + # set bridge port path cost + i.set_path_cost(member['cost']) + # set bridge port path priority + i.set_path_priority(member['priority']) return None diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py index 614fe08db..eb0145f65 100755 --- a/src/conf_mode/interface-dummy.py +++ b/src/conf_mode/interface-dummy.py @@ -13,10 +13,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# -from os import environ +import os + from copy import deepcopy from sys import exit @@ -40,7 +39,7 @@ def get_config(): # determine tagNode instance try: - dummy['intf'] = environ['VYOS_TAGNODE_VALUE'] + dummy['intf'] = os.environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -79,28 +78,28 @@ def generate(dummy): return None def apply(dummy): - du = DummyIf(dummy['intf']) + d = DummyIf(dummy['intf']) # Remove dummy interface if dummy['deleted']: - du.remove() + d.remove() else: - # enable interface - du.state = 'up' # update interface description used e.g. within SNMP - du.ifalias = dummy['description'] + d.set_alias(dummy['description']) # Configure interface address(es) # - not longer required addresses get removed first # - newly addresses will be added second for addr in dummy['address_remove']: - du.del_addr(addr) + d.del_addr(addr) for addr in dummy['address']: - du.add_addr(addr) + d.add_addr(addr) # disable interface on demand if dummy['disable']: - du.state = 'down' + d.set_state('down') + else: + d.set_state('up') return None diff --git a/src/conf_mode/interface-ethernet.py b/src/conf_mode/interface-ethernet.py index 5d597fd0a..99450b19e 100755 --- a/src/conf_mode/interface-ethernet.py +++ b/src/conf_mode/interface-ethernet.py @@ -67,20 +67,20 @@ def apply_vlan_config(vlan, config): raise TypeError() # update interface description used e.g. within SNMP - vlan.ifalias = config['description'] + vlan.set_alias(config['description']) # ignore link state changes - vlan.link_detect = config['disable_link_detect'] + vlan.set_link_detect(config['disable_link_detect']) # Maximum Transmission Unit (MTU) - vlan.mtu = config['mtu'] + vlan.set_mtu(config['mtu']) # Change VLAN interface MAC address if config['mac']: - vlan.mac = config['mac'] + vlan.set_mac(config['mac']) # enable/disable VLAN interface if config['disable']: - vlan.state = 'down' + vlan.set_state('down') else: - vlan.state = 'up' + vlan.set_state('up') # Configure interface address(es) # - not longer required addresses get removed first @@ -271,32 +271,32 @@ def apply(eth): e.remove() else: # update interface description used e.g. within SNMP - e.ifalias = eth['description'] + e.set_alias(eth['description']) # # missing DHCP/DHCPv6 options go here # # ignore link state changes - e.link_detect = eth['disable_link_detect'] + e.set_link_detect(eth['disable_link_detect']) # disable ethernet flow control (pause frames) e.set_flow_control(eth['flow_control']) # configure ARP cache timeout in milliseconds - e.arp_cache_tmo = eth['ip_arp_cache_tmo'] + e.set_arp_cache_tmo(eth['ip_arp_cache_tmo']) # Enable proxy-arp on this interface - e.proxy_arp = eth['ip_proxy_arp'] + e.set_proxy_arp(eth['ip_proxy_arp']) # Enable private VLAN proxy ARP on this interface - e.proxy_arp_pvlan = eth['ip_proxy_arp_pvlan'] + e.set_proxy_arp_pvlan(eth['ip_proxy_arp_pvlan']) # Change interface MAC address - re-set to real hardware address (hw-id) # if custom mac is removed if eth['mac']: - e.mac = eth['mac'] + e.set_mac(eth['mac']) else: - e.mac = eth['hw_id'] + e.set_mac(eth['hw_id']) # Maximum Transmission Unit (MTU) - e.mtu = eth['mtu'] + e.set_mtu(eth['mtu']) # GRO (generic receive offload) e.set_gro(eth['offload_gro']) @@ -318,9 +318,9 @@ def apply(eth): # Enable/Disable interface if eth['disable']: - e.state = 'down' + e.set_state('down') else: - e.state = 'up' + e.set_state('up') # Configure interface address(es) # - not longer required addresses get removed first diff --git a/src/conf_mode/interface-loopback.py b/src/conf_mode/interface-loopback.py index a1a807868..10722d137 100755 --- a/src/conf_mode/interface-loopback.py +++ b/src/conf_mode/interface-loopback.py @@ -13,9 +13,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# -from os import environ +import os + from sys import exit from copy import deepcopy @@ -38,7 +38,7 @@ def get_config(): # determine tagNode instance try: - loopback['intf'] = environ['VYOS_TAGNODE_VALUE'] + loopback['intf'] = os.environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -72,21 +72,20 @@ def generate(loopback): return None def apply(loopback): - lo = LoopbackIf(loopback['intf']) - if not loopback['deleted']: + l = LoopbackIf(loopback['intf']) + if loopback['deleted']: + l.remove() + else: # update interface description used e.g. within SNMP - # update interface description used e.g. within SNMP - lo.ifalias = loopback['description'] + l.set_alias(loopback['description']) # Configure interface address(es) # - not longer required addresses get removed first # - newly addresses will be added second + for addr in loopback['address_remove']: + l.del_addr(addr) for addr in loopback['address']: - lo.add_addr(addr) - - # remove interface address(es) - for addr in loopback['address_remove']: - lo.del_addr(addr) + l.add_addr(addr) return None diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py index 35e7928c2..a988e1ab1 100755 --- a/src/conf_mode/interface-openvpn.py +++ b/src/conf_mode/interface-openvpn.py @@ -13,8 +13,6 @@ # # 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 import re @@ -31,8 +29,9 @@ from pwd import getpwnam from subprocess import Popen, PIPE from time import sleep -from vyos.config import Config from vyos import ConfigError +from vyos.config import Config +from vyos.ifconfig import Interface from vyos.validate import is_addr_assigned user = 'openvpn' @@ -226,6 +225,20 @@ auth-retry nointeract client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }} {% endif %} +# DEPRECATED This option will be removed in OpenVPN 2.5 +# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this: +# /C=US/L=Somewhere/CN=John Doe/emailAddress=john@example.com In addition the old +# behaviour was to remap any character other than alphanumeric, underscore ('_'), +# dash ('-'), dot ('.'), and slash ('/') to underscore ('_'). The X.509 Subject +# string as returned by the tls_id environmental variable, could additionally +# contain colon (':') or equal ('='). When using the --compat-names option, this +# old formatting and remapping will be re-enabled again. This is purely implemented +# for compatibility reasons when using older plug-ins or scripts which does not +# handle the new formatting or UTF-8 characters. +# +# See https://phabricator.vyos.net/T1512 +compat-names + {% for option in options -%} {{ option }} {% endfor -%} @@ -580,7 +593,7 @@ def get_config(): # Minimum required TLS version if conf.exists('tls tls-version-min'): openvpn['tls_version_min'] = conf.return_value('tls tls-version-min') - + if conf.exists('shared-secret-key-file'): openvpn['shared_secret_file'] = conf.return_value('shared-secret-key-file') @@ -736,7 +749,7 @@ def verify(openvpn): if openvpn['tls_auth']: if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['tls_auth']): raise ConfigError('Specified auth-file "{}" is invalid'.format(openvpn['tls_auth'])) - + if openvpn['tls_cert']: if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_cert']): raise ConfigError('Specified cert-file "{}" is invalid'.format(openvpn['tls_cert'])) @@ -901,6 +914,29 @@ def apply(openvpn): # execute assembled command subprocess_cmd(cmd) + + # better late then sorry ... but we can only set interface alias after + # OpenVPN has been launched and created the interface + cnt = 0 + while openvpn['intf'] not in interfaces(): + # If VPN tunnel can't be established because the peer/server isn't + # (temporarily) available, the vtun interface never becomes registered + # with the kernel, and the commit would hang if there is no bail out + # condition + cnt += 1 + if cnt == 50: + break + + # sleep 250ms + sleep(0.250) + + try: + # we need to catch the exception if the interface is not up due to + # reason stated above + Interface(openvpn['intf']).set_alias(openvpn['description']) + except: + pass + return None diff --git a/src/conf_mode/interface-vxlan.py b/src/conf_mode/interface-vxlan.py index e97b4bf99..1097ae4d0 100755 --- a/src/conf_mode/interface-vxlan.py +++ b/src/conf_mode/interface-vxlan.py @@ -13,9 +13,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# -from os import environ +import os + from sys import exit from copy import deepcopy @@ -48,7 +48,7 @@ def get_config(): # determine tagNode instance try: - vxlan['intf'] = environ['VYOS_TAGNODE_VALUE'] + vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -127,7 +127,7 @@ def verify(vxlan): if vxlan['link']: # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU # if our configured MTU is at least 50 bytes less - underlay_mtu = int(Interface(vxlan['link']).mtu) + underlay_mtu = int(Interface(vxlan['link']).get_mtu()) if underlay_mtu < (vxlan['mtu'] + 50): raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ 'MTU is to small ({})'.format(underlay_mtu)) @@ -163,14 +163,14 @@ def apply(vxlan): # Finally create the new interface v = VXLANIf(vxlan['intf'], config=conf) # update interface description used e.g. by SNMP - v.ifalias = vxlan['description'] + v.set_alias(vxlan['description']) # Maximum Transfer Unit (MTU) - v.mtu = vxlan['mtu'] + v.set_mtu(vxlan['mtu']) # configure ARP cache timeout in milliseconds - v.arp_cache_tmp = vxlan['ip_arp_cache_tmo'] + v.set_arp_cache_tmo(vxlan['ip_arp_cache_tmo']) # Enable proxy-arp on this interface - v.proxy_arp = vxlan['ip_proxy_arp'] + v.set_proxy_arp(vxlan['ip_proxy_arp']) # Configure interface address(es) - no need to implicitly delete the # old addresses as they have already been removed by deleting the @@ -182,7 +182,7 @@ def apply(vxlan): # parameters we will only re-enable the interface if it is not # administratively disabled if not vxlan['disable']: - v.state='up' + v.set_state('up') return None diff --git a/src/conf_mode/interface-wireguard.py b/src/conf_mode/interface-wireguard.py index 4ae3251fe..0be8b7b0f 100755 --- a/src/conf_mode/interface-wireguard.py +++ b/src/conf_mode/interface-wireguard.py @@ -19,40 +19,35 @@ import sys import os import re -import syslog as sl import subprocess +from copy import deepcopy +from netifaces import interfaces -from vyos.config import Config from vyos import ConfigError +from vyos.config import Config +from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf -try: - ifname = str(os.environ['VYOS_TAGNODE_VALUE']) - intfc = WireGuardIf(ifname) -except KeyError: - print("Interface not specified") - sys.exit(1) - kdir = r'/config/auth/wireguard' + def _check_kmod(): if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") if os.system('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_NOTICE, "modprobe wireguard failed") raise ConfigError("modprobe wireguard failed") def _migrate_default_keys(): if os.path.exists('{}/private.key'.format(kdir)) and not os.path.exists('{}/default/private.key'.format(kdir)): - sl.syslog(sl.LOG_NOTICE, "migrate keypair to default") old_umask = os.umask(0o027) location = '{}/default'.format(kdir) subprocess.call(['sudo mkdir -p ' + location], shell=True) subprocess.call(['sudo chgrp vyattacfg ' + location], shell=True) subprocess.call(['sudo chmod 750 ' + location], shell=True) - os.rename('{}/private.key'.format(kdir),'{}/private.key'.format(location)) - os.rename('{}/public.key'.format(kdir),'{}/public.key'.format(location)) + os.rename('{}/private.key'.format(kdir), + '{}/private.key'.format(location)) + os.rename('{}/public.key'.format(kdir), + '{}/public.key'.format(location)) os.umask(old_umask) @@ -61,47 +56,82 @@ def get_config(): if not c.exists('interfaces wireguard'): return None - config_data = { - ifname: { - 'addr': '', - 'descr': ifname, - 'lport': None, - 'status': 'exists', - 'state': 'enabled', - 'fwmark': 0x00, - 'mtu': 1420, - 'peer': {}, - 'pk' : '{}/default/private.key'.format(kdir) - } + dflt_cnf = { + 'intfc': '', + 'addr': [], + 'addr_remove': [], + 'descr': '', + 'lport': None, + 'delete': False, + 'state': 'up', + 'fwmark': 0x00, + 'mtu': 1420, + 'peer': {}, + 'peer_remove': [], + 'pk': '{}/default/private.key'.format(kdir) } + if os.getenv('VYOS_TAGNODE_VALUE'): + ifname = str(os.environ['VYOS_TAGNODE_VALUE']) + wg = deepcopy(dflt_cnf) + wg['intfc'] = ifname + wg['descr'] = ifname + else: + print("ERROR: VYOS_TAGNODE_VALUE undefined") + sys.exit(1) + c.set_level('interfaces wireguard') - if not c.exists_effective(ifname): - config_data[ifname]['status'] = 'create' + # interface removal state if not c.exists(ifname) and c.exists_effective(ifname): - config_data[ifname]['status'] = 'delete' - - if config_data[ifname]['status'] != 'delete': - if c.exists(ifname + ' address'): - config_data[ifname]['addr'] = c.return_values(ifname + ' address') - if c.exists(ifname + ' disable'): - config_data[ifname]['state'] = 'disable' - if c.exists(ifname + ' port'): - config_data[ifname]['lport'] = c.return_value(ifname + ' port') - if c.exists(ifname + ' fwmark'): - config_data[ifname]['fwmark'] = c.return_value(ifname + ' fwmark') - if c.exists(ifname + ' description'): - config_data[ifname]['descr'] = c.return_value( - ifname + ' description') - if c.exists(ifname + ' mtu'): - config_data[ifname]['mtu'] = c.return_value(ifname + ' mtu') - if c.exists(ifname + ' private-key'): - config_data[ifname]['pk'] = "{0}/{1}/private.key".format(kdir,c.return_value(ifname + ' private-key')) - if c.exists(ifname + ' peer'): - for p in c.list_nodes(ifname + ' peer'): - if not c.exists(ifname + ' peer ' + p + ' disable'): - config_data[ifname]['peer'].update( + wg['delete'] = True + + if not wg['delete']: + c.set_level('interfaces wireguard {}'.format(ifname)) + if c.exists('address'): + wg['addr'] = c.return_values('address') + + # determine addresses which need to be removed + eff_addr = c.return_effective_values('address') + wg['addr_remove'] = list_diff(eff_addr, wg['addr']) + + # ifalias description + if c.exists('description'): + wg['descr'] = c.return_value('description') + + # link state + if c.exists('disable'): + wg['state'] = 'down' + + # local port to listen on + if c.exists('port'): + wg['lport'] = c.return_value('port') + + # fwmark value + if c.exists('fwmark'): + wg['fwmark'] = c.return_value('fwmark') + + # mtu + if c.exists('mtu'): + wg['mtu'] = c.return_value('mtu') + + # private key + if c.exists('private-key'): + wg['pk'] = "{0}/{1}/private.key".format( + kdir, c.return_value('private-key')) + + # peer removal, wg identifies peers by its pubkey + peer_eff = c.list_effective_nodes('peer') + peer_rem = list_diff(peer_eff, c.list_nodes('peer')) + for p in peer_rem: + wg['peer_remove'].append( + c.return_effective_value('peer {} pubkey'.format(p))) + + # peer settings + if c.exists('peer'): + for p in c.list_nodes('peer'): + if not c.exists('peer ' + p + ' disable'): + wg['peer'].update( { p: { 'allowed-ips': [], @@ -110,46 +140,61 @@ def get_config(): } } ) - if c.exists(ifname + ' peer ' + p + ' pubkey'): - config_data[ifname]['peer'][p]['pubkey'] = c.return_value( - ifname + ' peer ' + p + ' pubkey') - if c.exists(ifname + ' peer ' + p + ' allowed-ips'): - config_data[ifname]['peer'][p]['allowed-ips'] = c.return_values( - ifname + ' peer ' + p + ' allowed-ips') - if c.exists(ifname + ' peer ' + p + ' endpoint'): - config_data[ifname]['peer'][p]['endpoint'] = c.return_value( - ifname + ' peer ' + p + ' endpoint') - if c.exists(ifname + ' peer ' + p + ' persistent-keepalive'): - config_data[ifname]['peer'][p]['persistent-keepalive'] = c.return_value( - ifname + ' peer ' + p + ' persistent-keepalive') - if c.exists(ifname + ' peer ' + p + ' preshared-key'): - config_data[ifname]['peer'][p]['psk'] = c.return_value( - ifname + ' peer ' + p + ' preshared-key') - - return config_data + # peer allowed-ips + if c.exists('peer ' + p + ' allowed-ips'): + wg['peer'][p]['allowed-ips'] = c.return_values( + 'peer ' + p + ' allowed-ips') + # peer endpoint + if c.exists('peer ' + p + ' endpoint'): + wg['peer'][p]['endpoint'] = c.return_value( + 'peer ' + p + ' endpoint') + # persistent-keepalive + if c.exists('peer ' + p + ' persistent-keepalive'): + wg['peer'][p]['persistent-keepalive'] = c.return_value( + 'peer ' + p + ' persistent-keepalive') + # preshared-key + if c.exists('peer ' + p + ' preshared-key'): + wg['peer'][p]['psk'] = c.return_value( + 'peer ' + p + ' preshared-key') + # peer pubkeys + key_eff = c.return_effective_value( + 'peer {peer} pubkey'.format(peer=p)) + key_cfg = c.return_value( + 'peer {peer} pubkey'.format(peer=p)) + wg['peer'][p]['pubkey'] = key_cfg + + # on a pubkey change we need to remove the pubkey first + # peers are identified by pubkey, so key update means + # peer removal and re-add + if key_eff != key_cfg and key_eff != None: + wg['peer_remove'].append(key_cfg) + + return wg + def verify(c): if not c: return None - if not os.path.exists(c[ifname]['pk']): + if not os.path.exists(c['pk']): raise ConfigError( "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'") - if c[ifname]['status'] != 'delete': - if not c[ifname]['addr']: + if not c['delete']: + if not c['addr']: raise ConfigError("ERROR: IP address required") - if not c[ifname]['peer']: + if not c['peer']: raise ConfigError("ERROR: peer required") - for p in c[ifname]['peer']: - if not c[ifname]['peer'][p]['allowed-ips']: + for p in c['peer']: + if not c['peer'][p]['allowed-ips']: raise ConfigError("ERROR: allowed-ips required for peer " + p) - if not c[ifname]['peer'][p]['pubkey']: + if not c['peer'][p]['pubkey']: raise ConfigError("peer pubkey required for peer " + p) def apply(c): - # no wg config left, delete all wireguard devices, if any + # no wg configs left, remove all interface from system + # maybe move it into ifconfig.py if not c: net_devs = os.listdir('/sys/class/net/') for dev in net_devs: @@ -158,120 +203,74 @@ def apply(c): if re.search("DEVTYPE=wireguard", buf, re.I | re.M): wg_intf = re.sub("INTERFACE=", "", re.search( "INTERFACE=.*", buf, re.I | re.M).group(0)) - sl.syslog(sl.LOG_NOTICE, "removing interface " + wg_intf) subprocess.call( ['ip l d dev ' + wg_intf + ' >/dev/null'], shell=True) return None - # interface removal - if c[ifname]['status'] == 'delete': - sl.syslog(sl.LOG_NOTICE, "removing interface " + ifname) + # init wg class + intfc = WireGuardIf(c['intfc']) + + # single interface removal + if c['delete']: intfc.remove() return None - c_eff = Config() - c_eff.set_level('interfaces wireguard') + # remove IP addresses + for ip in c['addr_remove']: + intfc.del_addr(ip) - # interface state - if c[ifname]['state'] == 'disable': - sl.syslog(sl.LOG_NOTICE, "disable interface " + ifname) - intfc.state = 'down' - else: - if not intfc.state == 'up': - sl.syslog(sl.LOG_NOTICE, "enable interface " + ifname) - intfc.state = 'up' - - # IP address - if not c_eff.exists_effective(ifname + ' address'): - for ip in c[ifname]['addr']: - intfc.add_addr(ip) - else: - addr_eff = c_eff.return_effective_values(ifname + ' address') - addr_rem = list(set(addr_eff) - set(c[ifname]['addr'])) - addr_add = list(set(c[ifname]['addr']) - set(addr_eff)) - - if len(addr_rem) != 0: - for ip in addr_rem: - sl.syslog( - sl.LOG_NOTICE, "remove IP address {0} from {1}".format(ip, ifname)) - intfc.del_addr(ip) - - if len(addr_add) != 0: - for ip in addr_add: - sl.syslog( - sl.LOG_NOTICE, "add IP address {0} to {1}".format(ip, ifname)) - intfc.add_addr(ip) - - # interface MTU - if c[ifname]['mtu'] != 1420: - intfc.mtu = int(c[ifname]['mtu']) - else: - # default is set to 1420 in config_data - intfc.mtu = int(c[ifname]['mtu']) - - # ifalias for snmp from description - descr_eff = c_eff.return_effective_value(ifname + ' description') - if descr_eff != c[ifname]['descr']: - intfc.ifalias = str(c[ifname]['descr']) + # add IP addresses + for ip in c['addr']: + intfc.add_addr(ip) - # peer deletion - peer_eff = c_eff.list_effective_nodes(ifname + ' peer') - peer_cnf = [] - - try: - for p in c[ifname]['peer']: - peer_cnf.append(p) - except KeyError: - pass - - peer_rem = list(set(peer_eff) - set(peer_cnf)) - for p in peer_rem: - pkey = c_eff.return_effective_value(ifname + ' peer ' + p + ' pubkey') - intfc.remove_peer(pkey) - - # peer key update - for p in peer_eff: - if p in peer_cnf: - ekey = c_eff.return_effective_value( - ifname + ' peer ' + p + ' pubkey') - nkey = c[ifname]['peer'][p]['pubkey'] - if nkey != ekey: - sl.syslog( - sl.LOG_NOTICE, "peer {0} pubkey changed from {1} to {2} on interface {3}".format(p, ekey, nkey, ifname)) - intfc.remove_peer(ekey) - - intfc.config['private-key'] = c[ifname]['pk'] - for p in c[ifname]['peer']: - intfc.config['pubkey'] = str(c[ifname]['peer'][p]['pubkey']) - intfc.config['allowed-ips'] = (c[ifname]['peer'][p]['allowed-ips']) - - # listen-port - if c[ifname]['lport']: - intfc.config['port'] = c[ifname]['lport'] + # interface mtu + intfc.mtu = int(c['mtu']) + # ifalias for snmp from description + intfc.ifalias = str(c['descr']) + + # remove peers + if c['peer_remove']: + for pkey in c['peer_remove']: + intfc.remove_peer(pkey) + + # peer pubkey + # setting up the wg interface + intfc.config['private-key'] = c['pk'] + for p in c['peer']: + # peer pubkey + intfc.config['pubkey'] = str(c['peer'][p]['pubkey']) + # peer allowed-ips + intfc.config['allowed-ips'] = c['peer'][p]['allowed-ips'] + # local listen port + if c['lport']: + intfc.config['port'] = c['lport'] # fwmark - if c[ifname]['fwmark']: - intfc.config['fwmark'] = c[ifname]['fwmark'] - + if c['fwmark']: + intfc.config['fwmark'] = c['fwmark'] # endpoint - if c[ifname]['peer'][p]['endpoint']: - intfc.config['endpoint'] = c[ifname]['peer'][p]['endpoint'] + if c['peer'][p]['endpoint']: + intfc.config['endpoint'] = c['peer'][p]['endpoint'] # persistent-keepalive - if 'persistent-keepalive' in c[ifname]['peer'][p]: - intfc.config['keepalive'] = c[ifname][ - 'peer'][p]['persistent-keepalive'] + if 'persistent-keepalive' in c['peer'][p]: + intfc.config['keepalive'] = c['peer'][p]['persistent-keepalive'] + # maybe move it into ifconfig.py # preshared-key - needs to be read from a file - if 'psk' in c[ifname]['peer'][p]: + if 'psk' in c['peer'][p]: psk_file = '/config/auth/wireguard/psk' old_umask = os.umask(0o077) - open(psk_file, 'w').write(str(c[ifname]['peer'][p]['psk'])) + open(psk_file, 'w').write(str(c['peer'][p]['psk'])) os.umask(old_umask) intfc.config['psk'] = psk_file - intfc.update() + # interface state + intfc.state = c['state'] + + return None + if __name__ == '__main__': try: _check_kmod() diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index a60379760..1662e45e6 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -369,6 +369,9 @@ def verify(c): if c == None or not c: return None + if not c['interfaces']: + raise ConfigError("service ipoe-server interface requires a value") + for intfc in c['interfaces']: if not c['interfaces'][intfc]['range']: raise ConfigError("service ipoe-server interface " + intfc + " client-subnet needs a value") diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index f706d502f..8f32e6e81 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -42,6 +42,8 @@ restrict default noquery nopeer notrap nomodify restrict 127.0.0.1 restrict -6 ::1 +# Do not listen on any interface address by default +interface ignore wildcard # # Configurable section # @@ -63,7 +65,6 @@ restrict {{ n.address }} mask {{ n.netmask }} nomodify notrap nopeer {% if listen_address -%} # NTP should listen on configured addresses only -interface ignore wildcard {% for a in listen_address -%} interface listen {{ a }} {% endfor -%} diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index e7ecd8573..5c2ea71c8 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -166,9 +166,9 @@ def delete_name_servers(data, tag): def set_host_name(state, data): if data['host_name']: state['host_name'] = data['host_name'] - if data['domain_name']: + if 'domain_name' in data: state['domain_name'] = data['domain_name'] - if data['search_domains']: + if 'search_domains' in data: state['search_domains'] = data['search_domains'] def get_name_servers(state, tag): diff --git a/src/tests/test_ntp.py b/src/tests/test_ntp.py deleted file mode 100644 index 1cde490b4..000000000 --- a/src/tests/test_ntp.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 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 -import tempfile -import unittest -from unittest import TestCase, mock -import ipaddress -from contextlib import ExitStack -import textwrap - -from vyos import ConfigError -from vyos.config import Config -try: - from src.conf_mode import ntp -except ModuleNotFoundError: # for unittest.main() - import sys - sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) - from src.conf_mode import ntp - - -class TestNtp(TestCase): - - def test_get_config(self): - tests = [ - { - 'name': 'empty', - 'config': { - 'system ntp': None, - }, - 'expected': None, - }, - { - 'name': 'full-options', - 'config': { - 'system ntp': 'yes', - 'allow-clients address': ['192.0.2.0/24'], - 'listen-address': ['198.51.100.0/24'], - 'server': ['example.com'], - 'server example.com noselect': 'yes', - 'server example.com preempt': 'yes', - 'server example.com prefer': 'yes', - }, - 'expected': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.0'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': ['noselect', 'preempt', 'prefer']} - ] - }, - }, - { - 'name': 'non-options', - 'config': { - 'system ntp': 'yes', - 'allow-clients address': ['192.0.2.0/24'], - 'listen-address': ['198.51.100.0/24'], - 'server': ['example.com'], - }, - 'expected': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.0'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': []} - ] - }, - }, - ] - for case in tests: - def mocked_fn(path): - return case['config'].get(path) - - with self.subTest(msg = case['name']): - m = { - 'return_value': mock.Mock(side_effect = mocked_fn), - 'return_values': mock.Mock(side_effect = mocked_fn), - 'list_nodes': mock.Mock(side_effect = mocked_fn), - 'exists': mock.Mock(side_effect = mocked_fn), - } - with mock.patch.multiple(Config, **m): - actual = ntp.get_config() - self.assertEqual(actual, case['expected']) - - def test_verify(self): - tests = [ - { - 'name': 'none', - 'config': None, - 'expected': None - }, - { - 'name': 'valid', - 'config': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': ['noselect', 'preempt', 'prefer']} - ] - }, - 'expected': None, - }, - { - 'name': 'not configure servers', - 'config': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'servers': [] - }, - 'expected': ConfigError, - }, - { - 'name': 'does not exist in the network', - 'config': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/50', # invalid netmask - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': []} - ] - }, - 'expected': ConfigError, - }, - ] - for case in tests: - with self.subTest(msg = case['name']): - if case['expected'] is not None: - with self.assertRaises(case['expected']): - ntp.verify(case['config']) - else: - ntp.verify(case['config']) - - def test_generate(self): - tests = [ - { - 'name': 'empty', - 'config': None, - 'expected': '', - }, - { - 'name': 'valid', - 'config': { - 'allowed_networks': [ - { - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }, - { - 'address': ipaddress.ip_address('198.51.100.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '198.51.100.0/24', - }, - ], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': '1.example.com', 'options': ['noselect', 'preempt', 'prefer']}, - {'name': '2.example.com', 'options': []}, - ] - }, - 'expected': textwrap.dedent(''' - ### Autogenerated by ntp.py ### - - # - # Non-configurable defaults - # - driftfile /var/lib/ntp/ntp.drift - # By default, only allow ntpd to query time sources, ignore any incoming requests - restrict default noquery nopeer notrap nomodify - # Local users have unrestricted access, allowing reconfiguration via ntpdc - restrict 127.0.0.1 - restrict -6 ::1 - - # - # Configurable section - # - - # Server configuration for: 1.example.com - server 1.example.com iburst noselect preempt prefer - # Server configuration for: 2.example.com - server 2.example.com iburst - - - # Client configuration for network: 192.0.2.0/24 - restrict 192.0.2.1 mask 255.255.255.0 nomodify notrap nopeer - - # Client configuration for network: 198.51.100.0/24 - restrict 198.51.100.1 mask 255.255.255.0 nomodify notrap nopeer - - - - # NTP should listen on configured addresses only - interface ignore wildcard - interface listen 198.51.100.0/24 - - '''), - }, - ] - - for case in tests: - with self.subTest(msg = case['name']): - with tempfile.NamedTemporaryFile() as fp: - ntp.config_file = fp.name - - ntp.generate(case['config']) - actual = fp.file.read().decode('ascii') - print(actual) - self.assertEqual(case['expected'], actual) - - def test_apply(self): - with tempfile.NamedTemporaryFile(delete = False) as fp: - ntp.config_file = fp.name - with mock.patch('os.system') as os_system: - ntp.apply({}) # some configure - os_system.assert_has_calls([ - mock.call('sudo systemctl restart ntp.service'), - ]) - self.assertTrue(os.path.exists(fp.name)) - - ntp.apply(None) # empty configure - os_system.assert_has_calls([ - mock.call('sudo systemctl stop ntp.service'), - ]) - self.assertFalse(os.path.exists(fp.name)) - -if __name__ == "__main__": - unittest.main() |