diff options
21 files changed, 386 insertions, 82 deletions
diff --git a/Jenkinsfile b/Jenkinsfile index bcce16dd0..7b6dc49e3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -74,6 +74,7 @@ pipeline { docker { args '--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006' image 'vyos/vyos-build:current' + alwaysPull true } } options { diff --git a/interface-definitions/interfaces-bonding.xml b/interface-definitions/interfaces-bonding.xml index 88dbab6ab..c71482d9c 100644 --- a/interface-definitions/interfaces-bonding.xml +++ b/interface-definitions/interfaces-bonding.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/interface-bonding.py"> + <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/interfaces-bonding.py"> <properties> <help>Bonding interface name</help> <priority>315</priority> @@ -101,6 +101,11 @@ <help>DHCP client host name (overrides the system host name)</help> </properties> </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> + </properties> + </leafNode> </children> </node> <node name="dhcpv6-options"> @@ -339,6 +344,11 @@ <help>DHCP client host name (overrides the system host name)</help> </properties> </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> + </properties> + </leafNode> </children> </node> <node name="dhcpv6-options"> @@ -480,6 +490,11 @@ <help>DHCP client host name (overrides the system host name)</help> </properties> </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> + </properties> + </leafNode> </children> </node> <node name="dhcpv6-options"> @@ -605,6 +620,11 @@ <help>DHCP client host name (overrides the system host name)</help> </properties> </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> + </properties> + </leafNode> </children> </node> <node name="dhcpv6-options"> diff --git a/interface-definitions/interfaces-bridge.xml b/interface-definitions/interfaces-bridge.xml index 4b82972dc..40505d7de 100644 --- a/interface-definitions/interfaces-bridge.xml +++ b/interface-definitions/interfaces-bridge.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interface-bridge.py"> + <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/interfaces-bridge.py"> <properties> <help>Bridge interface name</help> <priority>470</priority> @@ -85,6 +85,11 @@ <help>DHCP client host name (overrides the system host name)</help> </properties> </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> + </properties> + </leafNode> </children> </node> <node name="dhcpv6-options"> diff --git a/interface-definitions/interfaces-dummy.xml b/interface-definitions/interfaces-dummy.xml index c9860fe3b..3bc4330e4 100644 --- a/interface-definitions/interfaces-dummy.xml +++ b/interface-definitions/interfaces-dummy.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="dummy" owner="${vyos_conf_scripts_dir}/interface-dummy.py"> + <tagNode name="dummy" owner="${vyos_conf_scripts_dir}/interfaces-dummy.py"> <properties> <help>Dummy interface name</help> <priority>300</priority> diff --git a/interface-definitions/interfaces-ethernet.xml b/interface-definitions/interfaces-ethernet.xml index e4a56b216..f51bb3d87 100644 --- a/interface-definitions/interfaces-ethernet.xml +++ b/interface-definitions/interfaces-ethernet.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="ethernet" owner="${vyos_conf_scripts_dir}/interface-ethernet.py"> + <tagNode name="ethernet" owner="${vyos_conf_scripts_dir}/interfaces-ethernet.py"> <properties> <help>Ethernet interface name</help> <priority>318</priority> @@ -70,7 +70,12 @@ </leafNode> <leafNode name="host-name"> <properties> - <help>DHCP client host name (overrides the system host name)</help> + <help>DHCP client host name (overrides system host name)</help> + </properties> + </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> </properties> </leafNode> </children> @@ -454,7 +459,12 @@ </leafNode> <leafNode name="host-name"> <properties> - <help>DHCP client host name (overrides the system host name)</help> + <help>DHCP client host name (overrides system host name)</help> + </properties> + </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> </properties> </leafNode> </children> @@ -615,7 +625,12 @@ </leafNode> <leafNode name="host-name"> <properties> - <help>DHCP client host name (overrides the system host name)</help> + <help>DHCP client host name (overrides system host name)</help> + </properties> + </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> </properties> </leafNode> </children> @@ -760,7 +775,12 @@ </leafNode> <leafNode name="host-name"> <properties> - <help>DHCP client host name (overrides the system host name)</help> + <help>DHCP client host name (overrides system host name)</help> + </properties> + </leafNode> + <leafNode name="vendor-class-id"> + <properties> + <help>DHCP client vendor type</help> </properties> </leafNode> </children> diff --git a/interface-definitions/interfaces-loopback.xml b/interface-definitions/interfaces-loopback.xml index 267731b1c..0f003bc64 100644 --- a/interface-definitions/interfaces-loopback.xml +++ b/interface-definitions/interfaces-loopback.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="loopback" owner="${vyos_conf_scripts_dir}/interface-loopback.py"> + <tagNode name="loopback" owner="${vyos_conf_scripts_dir}/interfaces-loopback.py"> <properties> <help>Loopback interface</help> <priority>300</priority> diff --git a/interface-definitions/interfaces-openvpn.xml b/interface-definitions/interfaces-openvpn.xml index 365d80558..42c953fdc 100644 --- a/interface-definitions/interfaces-openvpn.xml +++ b/interface-definitions/interfaces-openvpn.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="openvpn" owner="${vyos_conf_scripts_dir}/interface-openvpn.py"> + <tagNode name="openvpn" owner="${vyos_conf_scripts_dir}/interfaces-openvpn.py"> <properties> <help>OpenVPN tunnel interface name</help> <priority>460</priority> diff --git a/interface-definitions/interfaces-vxlan.xml b/interface-definitions/interfaces-vxlan.xml index b06c2860c..f93711741 100644 --- a/interface-definitions/interfaces-vxlan.xml +++ b/interface-definitions/interfaces-vxlan.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="vxlan" owner="${vyos_conf_scripts_dir}/interface-vxlan.py"> + <tagNode name="vxlan" owner="${vyos_conf_scripts_dir}/interfaces-vxlan.py"> <properties> <help>Virtual extensible LAN interface (VXLAN)</help> <priority>460</priority> diff --git a/interface-definitions/interfaces-wireguard.xml b/interface-definitions/interfaces-wireguard.xml index f2a7cc316..0c32a3bc1 100644 --- a/interface-definitions/interfaces-wireguard.xml +++ b/interface-definitions/interfaces-wireguard.xml @@ -2,7 +2,7 @@ <interfaceDefinition> <node name="interfaces"> <children> - <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/interface-wireguard.py"> + <tagNode name="wireguard" owner="${vyos_conf_scripts_dir}/interfaces-wireguard.py"> <properties> <help>WireGuard interface name</help> <priority>459</priority> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 1c9cf6897..983906923 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -112,6 +112,7 @@ def vlan_to_dict(conf): 'description': '', 'dhcp_client_id': '', 'dhcp_hostname': '', + 'dhcp_vendor_class_id': '', 'dhcpv6_prm_only': False, 'dhcpv6_temporary': False, 'disable': False, @@ -145,13 +146,17 @@ def vlan_to_dict(conf): if conf.exists('dhcp-options host-name'): vlan['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + # DHCP client vendor identifier + if conf.exists('dhcp-options vendor-class-id'): + vlan['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') + # DHCPv6 only acquire config parameters, no address if conf.exists('dhcpv6-options parameters-only'): - vlan['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + vlan['dhcpv6_prm_only'] = True # DHCPv6 temporary IPv6 address if conf.exists('dhcpv6-options temporary'): - vlan['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + vlan['dhcpv6_temporary'] = True # ignore link state changes if conf.exists('disable-link-detect'): diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index 4ac605b54..0692f77f3 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -23,13 +23,26 @@ from netifaces import ifaddresses, AF_INET, AF_INET6 from subprocess import Popen, PIPE, STDOUT from time import sleep +dhclient_base = r'/var/lib/dhcp/dhclient_' dhcp_cfg = """ # generated by ifconfig.py option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; +timeout 60; +retry 300; + interface "{{ intf }}" { send host-name "{{ hostname }}"; - request subnet-mask, broadcast-address, routers, domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu; + {% if client_id -%} + send dhcp-client-identifier "{{ client_id }}"; + {% endif -%} + {% if vendor_class_id -%} + send vendor-class-identifier "{{ vendor_class_id }}"; + {% endif -%} + request subnet-mask, broadcast-address, routers, domain-name-servers, + rfc3442-classless-static-routes, domain-name, interface-mtu; + require subnet-mask; } + """ dhcpv6_cfg = """ @@ -37,10 +50,8 @@ dhcpv6_cfg = """ interface "{{ intf }}" { request routers, domain-name-servers, domain-name; } -""" - -dhclient_base = r'/var/lib/dhcp/dhclient_' +""" class Interface: def __init__(self, ifname, type=None): @@ -78,6 +89,21 @@ class Interface: self._dhcpv6_pid_file = dhclient_base + self._ifname + '.v6pid' self._dhcpv6_lease_file = dhclient_base + self._ifname + '.v6leases' + # DHCP options + self._dhcp_options = { + 'intf' : self._ifname, + 'hostname' : '', + 'client_id' : '', + 'vendor_class_id' : '' + } + + # DHCPv6 options + self._dhcpv6_options = { + 'intf' : self._ifname, + 'dhcpv6_prm_only' : False, + 'dhcpv6_temporary' : False + } + def _debug_msg(self, msg): if os.path.isfile('/tmp/vyos.ifconfig.debug'): print('DEBUG/{:<6} {}'.format(self._ifname, msg)) @@ -293,21 +319,7 @@ class Interface: # 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) - tmp = self._cmd(cmd) - - # better safe then sorry - wait until the interface is really up - # but only for a given period of time to avoid potential deadlocks! - cnt = 0 - while self.get_state() != state: - cnt += 1 - if cnt == 50: - print('Interface {} could not be brought up in time ...'.format(self._ifname)) - break - - # sleep 250ms - sleep(0.250) - - return tmp + return self._cmd(cmd) def set_proxy_arp(self, enable): """ @@ -449,6 +461,37 @@ class Interface: cmd = 'ip addr del "{}" dev "{}"'.format(addr, self._ifname) return self._cmd(cmd) + + def get_dhcp_options(self): + """ + Return dictionary with supported DHCP options. + + Dictionary should be altered and send back via set_dhcp_options() + so those options are applied when DHCP is run. + """ + return self._dhcp_options + + def set_dhcp_options(self, options): + """ + Store new DHCP options used by next run of DHCP client. + """ + self._dhcp_options = options + + def get_dhcpv6_options(self): + """ + Return dictionary with supported DHCPv6 options. + + Dictionary should be altered and send back via set_dhcp_options() + so those options are applied when DHCP is run. + """ + return self._dhcpv6_options + + def set_dhcpv6_options(self, options): + """ + Store new DHCP options used by next run of DHCP client. + """ + self._dhcpv6_options = options + # replace dhcpv4/v6 with systemd.networkd? def _set_dhcp(self): """ @@ -461,15 +504,13 @@ class Interface: >>> j = Interface('eth0') >>> j.set_dhcp() """ - dhcp = { - 'hostname': 'vyos', - 'intf': self._ifname - } - # read configured system hostname. - # maybe change to vyos hostd client ??? - with open('/etc/hostname', 'r') as f: - dhcp['hostname'] = f.read().rstrip('\n') + dhcp = self.get_dhcp_options() + if not dhcp['hostname']: + # read configured system hostname. + # maybe change to vyos hostd client ??? + with open('/etc/hostname', 'r') as f: + dhcp['hostname'] = f.read().rstrip('\n') # render DHCP configuration tmpl = jinja2.Template(dhcp_cfg) @@ -545,9 +586,14 @@ class Interface: >>> j = Interface('eth0') >>> j.set_dhcpv6() """ - dhcpv6 = { - 'intf': self._ifname - } + dhcpv6 = self.get_dhcpv6_options() + import pprint + pprint.pprint(dhcpv6) + + # better save then sorry .. should be checked in interface script + # but if you missed it we are safe! + if dhcpv6['dhcpv6_prm_only'] and dhcpv6['dhcpv6_temporary']: + raise Exception('DHCPv6 temporary and parameters-only options are mutually exclusive!') # render DHCP configuration tmpl = jinja2.Template(dhcpv6_cfg) @@ -563,16 +609,24 @@ class Interface: sleep(5) # no longer accept router announcements on this interface - cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=0'.format(self._ifname) - self._cmd(cmd) + self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra' + .format(self._ifname), 0) # assemble command-line to start DHCPv6 client (dhclient) cmd = 'start-stop-daemon --start --quiet --pidfile ' + \ self._dhcpv6_pid_file cmd += ' --exec /sbin/dhclient --' # now pass arguments to dhclient binary - cmd += ' -6 -nw -cf {} -pf {} -lf {} {}'.format( - self._dhcpv6_cfg_file, self._dhcpv6_pid_file, self._dhcpv6_lease_file, self._ifname) + cmd += ' -6 -nw -cf {} -pf {} -lf {}'.format( + self._dhcpv6_cfg_file, self._dhcpv6_pid_file, self._dhcpv6_lease_file) + + # add optional arguments + if dhcpv6['dhcpv6_prm_only']: + cmd += ' -S' + if dhcpv6['dhcpv6_temporary']: + cmd += ' -T' + + cmd += ' {}'.format(self._ifname) return self._cmd(cmd) @@ -600,8 +654,8 @@ class Interface: self._cmd(cmd) # accept router announcements on this interface - cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=1'.format(self._ifname) - self._cmd(cmd) + self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra' + .format(self._ifname), 1) # cleanup old config file if os.path.isfile(self._dhcpv6_cfg_file): @@ -1338,15 +1392,16 @@ class WireGuardIf(Interface): def __init__(self, ifname): super().__init__(ifname, type='wireguard') + self.config = { 'port': 0, - 'private-key': None, - 'pubkey': None, - 'psk': '/dev/null', - 'allowed-ips': [], - 'fwmark': 0x00, - 'endpoint': None, - 'keepalive': 0 + 'private-key': None, + 'pubkey': None, + 'psk': '/dev/null', + 'allowed-ips': [], + 'fwmark': 0x00, + 'endpoint': None, + 'keepalive': 0 } def update(self): diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interfaces-bonding.py index 4d5009c73..8a0f9f84d 100755 --- a/src/conf_mode/interface-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -34,6 +34,7 @@ default_config_data = { 'deleted': False, 'dhcp_client_id': '', 'dhcp_hostname': '', + 'dhcp_vendor_class_id': '', 'dhcpv6_prm_only': False, 'dhcpv6_temporary': False, 'disable': False, @@ -83,6 +84,33 @@ def apply_vlan_config(vlan, config): if type(vlan) != type(VLANIf("lo")): raise TypeError() + # get DHCP config dictionary and update values + opt = vlan.get_dhcp_options() + + if config['dhcp_client_id']: + opt['client_id'] = config['dhcp_client_id'] + + if config['dhcp_hostname']: + opt['hostname'] = config['dhcp_hostname'] + + if config['dhcp_vendor_class_id']: + opt['vendor_class_id'] = config['dhcp_vendor_class_id'] + + # store DHCP config dictionary - used later on when addresses are aquired + vlan.set_dhcp_options(opt) + + # get DHCPv6 config dictionary and update values + opt = vlan.get_dhcpv6_options() + + if config['dhcpv6_prm_only']: + opt['dhcpv6_prm_only'] = True + + if config['dhcpv6_temporary']: + opt['dhcpv6_temporary'] = True + + # store DHCPv6 config dictionary - used later on when addresses are aquired + vlan.set_dhcpv6_options(opt) + # update interface description used e.g. within SNMP vlan.set_alias(config['description']) # ignore link state changes @@ -164,13 +192,17 @@ def get_config(): if conf.exists('dhcp-options host-name'): bond['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + # DHCP client vendor identifier + if conf.exists('dhcp-options vendor-class-id'): + bond['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') + # DHCPv6 only acquire config parameters, no address if conf.exists('dhcpv6-options parameters-only'): - bond['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + bond['dhcpv6_prm_only'] = True # DHCPv6 temporary IPv6 address if conf.exists('dhcpv6-options temporary'): - bond['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + bond['dhcpv6_temporary'] = True # ignore link state changes if conf.exists('disable-link-detect'): @@ -216,7 +248,7 @@ def get_config(): if conf.exists('primary'): bond['primary'] = conf.return_value('primary') - # re-set configuration level and retrieve vif-s interfaces + # re-set configuration level to parse new nodes conf.set_level(cfg_base) # get vif-s interfaces (currently effective) - to determine which vif-s # interface is no longer present and needs to be removed @@ -230,7 +262,7 @@ def get_config(): conf.set_level(cfg_base + ' vif-s ' + vif_s) bond['vif_s'].append(vlan_to_dict(conf)) - # re-set configuration level and retrieve vif-s interfaces + # re-set configuration level to parse new nodes conf.set_level(cfg_base) # Determine vif interfaces (currently effective) - to determine which # vif interface is no longer present and needs to be removed @@ -260,6 +292,21 @@ def verify(bond): raise ConfigError('Interface "{}" is not part of the bond' \ .format(bond['primary'])) + + # DHCPv6 parameters-only and temporary address are mutually exclusive + for vif_s in bond['vif_s']: + if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + + for vif_c in vif_s['vif_c']: + if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + + for vif in bond['vif']: + if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + + for vif_s in bond['vif_s']: for vif in bond['vif']: if vif['id'] == vif_s['id']: @@ -368,9 +415,32 @@ def apply(bond): # update interface description used e.g. within SNMP b.set_alias(bond['description']) - # - # missing DHCP/DHCPv6 options go here - # + # get DHCP config dictionary and update values + opt = b.get_dhcp_options() + + if bond['dhcp_client_id']: + opt['client_id'] = bond['dhcp_client_id'] + + if bond['dhcp_hostname']: + opt['hostname'] = bond['dhcp_hostname'] + + if bond['dhcp_vendor_class_id']: + opt['vendor_class_id'] = bond['dhcp_vendor_class_id'] + + # store DHCP config dictionary - used later on when addresses are aquired + b.set_dhcp_options(opt) + + # get DHCPv6 config dictionary and update values + opt = b.get_dhcpv6_options() + + if bond['dhcpv6_prm_only']: + opt['dhcpv6_prm_only'] = True + + if bond['dhcpv6_temporary']: + opt['dhcpv6_temporary'] = True + + # store DHCPv6 config dictionary - used later on when addresses are aquired + b.set_dhcpv6_options(opt) # ignore link state changes b.set_link_detect(bond['disable_link_detect']) diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interfaces-bridge.py index 37b5c4979..70bf4f528 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -32,6 +32,11 @@ default_config_data = { 'arp_cache_tmo': 30, 'description': '', 'deleted': False, + 'dhcp_client_id': '', + 'dhcp_hostname': '', + 'dhcp_vendor_class_id': '', + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, 'disable': False, 'disable_link_detect': 1, 'forwarding_delay': 14, @@ -81,6 +86,26 @@ def get_config(): if conf.exists('description'): bridge['description'] = conf.return_value('description') + # get DHCP client identifier + if conf.exists('dhcp-options client-id'): + bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id') + + # DHCP client host name (overrides the system host name) + if conf.exists('dhcp-options host-name'): + bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + + # DHCP client vendor identifier + if conf.exists('dhcp-options vendor-class-id'): + bridge['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') + + # DHCPv6 only acquire config parameters, no address + if conf.exists('dhcpv6-options parameters-only'): + bridge['dhcpv6_prm_only'] = True + + # DHCPv6 temporary IPv6 address + if conf.exists('dhcpv6-options temporary'): + bridge['dhcpv6_temporary'] = True + # Disable this bridge interface if conf.exists('disable'): bridge['disable'] = True @@ -149,6 +174,9 @@ def get_config(): return bridge def verify(bridge): + if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + conf = Config() for br in conf.list_nodes('interfaces bridge'): # it makes no sense to verify ourself in this case @@ -203,6 +231,33 @@ def apply(bridge): # update interface description used e.g. within SNMP br.set_alias(bridge['description']) + # get DHCP config dictionary and update values + opt = br.get_dhcp_options() + + if bridge['dhcp_client_id']: + opt['client_id'] = bridge['dhcp_client_id'] + + if bridge['dhcp_hostname']: + opt['hostname'] = bridge['dhcp_hostname'] + + if bridge['dhcp_vendor_class_id']: + opt['vendor_class_id'] = bridge['dhcp_vendor_class_id'] + + # store DHCPv6 config dictionary - used later on when addresses are aquired + br.set_dhcp_options(opt) + + # get DHCPv6 config dictionary and update values + opt = br.get_dhcpv6_options() + + if bridge['dhcpv6_prm_only']: + opt['dhcpv6_prm_only'] = True + + if bridge['dhcpv6_temporary']: + opt['dhcpv6_temporary'] = True + + # store DHCPv6 config dictionary - used later on when addresses are aquired + br.set_dhcpv6_options(opt) + # Change interface MAC address if bridge['mac']: br.set_mac(bridge['mac']) diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interfaces-dummy.py index eb0145f65..eb0145f65 100755 --- a/src/conf_mode/interface-dummy.py +++ b/src/conf_mode/interfaces-dummy.py diff --git a/src/conf_mode/interface-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 317da5772..cd40aff3e 100755 --- a/src/conf_mode/interface-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -31,6 +31,7 @@ default_config_data = { 'deleted': False, 'dhcp_client_id': '', 'dhcp_hostname': '', + 'dhcp_vendor_class_id': '', 'dhcpv6_prm_only': False, 'dhcpv6_temporary': False, 'disable': False, @@ -66,6 +67,33 @@ def apply_vlan_config(vlan, config): if type(vlan) != type(VLANIf("lo")): raise TypeError() + # get DHCP config dictionary and update values + opt = vlan.get_dhcp_options() + + if config['dhcp_client_id']: + opt['client_id'] = config['dhcp_client_id'] + + if config['dhcp_hostname']: + opt['hostname'] = config['dhcp_hostname'] + + if config['dhcp_vendor_class_id']: + opt['vendor_class_id'] = config['dhcp_vendor_class_id'] + + # store DHCP config dictionary - used later on when addresses are aquired + vlan.set_dhcp_options(opt) + + # get DHCPv6 config dictionary and update values + opt = vlan.get_dhcpv6_options() + + if config['dhcpv6_prm_only']: + opt['dhcpv6_prm_only'] = True + + if config['dhcpv6_temporary']: + opt['dhcpv6_temporary'] = True + + # store DHCPv6 config dictionary - used later on when addresses are aquired + vlan.set_dhcpv6_options(opt) + # update interface description used e.g. within SNMP vlan.set_alias(config['description']) # ignore link state changes @@ -134,13 +162,17 @@ def get_config(): if conf.exists('dhcp-options host-name'): eth['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + # DHCP client vendor identifier + if conf.exists('dhcp-options vendor-class-id'): + eth['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') + # DHCPv6 only acquire config parameters, no address if conf.exists('dhcpv6-options parameters-only'): - eth['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + eth['dhcpv6_prm_only'] = True # DHCPv6 temporary IPv6 address if conf.exists('dhcpv6-options temporary'): - eth['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + eth['dhcpv6_temporary'] = True # ignore link state changes if conf.exists('disable-link-detect'): @@ -206,7 +238,7 @@ def get_config(): if conf.exists('speed'): eth['speed'] = conf.return_value('speed') - # re-set configuration level and retrieve vif-s interfaces + # re-set configuration level to parse new nodes conf.set_level(cfg_base) # get vif-s interfaces (currently effective) - to determine which vif-s # interface is no longer present and needs to be removed @@ -220,7 +252,7 @@ def get_config(): conf.set_level(cfg_base + ' vif-s ' + vif_s) eth['vif_s'].append(vlan_to_dict(conf)) - # re-set configuration level and retrieve vif-s interfaces + # re-set configuration level to parse new nodes conf.set_level(cfg_base) # Determine vif interfaces (currently effective) - to determine which # vif interface is no longer present and needs to be removed @@ -249,6 +281,9 @@ def verify(eth): if eth['speed'] != 'auto': raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') + if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + conf = Config() # some options can not be changed when interface is enslaved to a bond for bond in conf.list_nodes('interfaces bonding'): @@ -258,6 +293,18 @@ def verify(eth): if eth['address']: raise ConfigError('Can not assign address to interface {} which is a member of {}').format(eth['intf'], bond) + # DHCPv6 parameters-only and temporary address are mutually exclusive + for vif_s in eth['vif_s']: + if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + + for vif_c in vif_s['vif_c']: + if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + + for vif in eth['vif']: + if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: + raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') return None @@ -273,9 +320,32 @@ def apply(eth): # update interface description used e.g. within SNMP e.set_alias(eth['description']) - # - # missing DHCP/DHCPv6 options go here - # + # get DHCP config dictionary and update values + opt = e.get_dhcp_options() + + if eth['dhcp_client_id']: + opt['client_id'] = eth['dhcp_client_id'] + + if eth['dhcp_hostname']: + opt['hostname'] = eth['dhcp_hostname'] + + if eth['dhcp_vendor_class_id']: + opt['vendor_class_id'] = eth['dhcp_vendor_class_id'] + + # store DHCP config dictionary - used later on when addresses are aquired + e.set_dhcp_options(opt) + + # get DHCPv6 config dictionary and update values + opt = e.get_dhcpv6_options() + + if eth['dhcpv6_prm_only']: + opt['dhcpv6_prm_only'] = True + + if eth['dhcpv6_temporary']: + opt['dhcpv6_temporary'] = True + + # store DHCPv6 config dictionary - used later on when addresses are aquired + e.set_dhcpv6_options(opt) # ignore link state changes e.set_link_detect(eth['disable_link_detect']) diff --git a/src/conf_mode/interface-loopback.py b/src/conf_mode/interfaces-loopback.py index 10722d137..10722d137 100755 --- a/src/conf_mode/interface-loopback.py +++ b/src/conf_mode/interfaces-loopback.py diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 5345bf7a2..cdd133904 100755 --- a/src/conf_mode/interface-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -16,11 +16,11 @@ import os import re -import sys -import stat -import jinja2 +from jinja2 import Template from copy import deepcopy +from sys import exit +from stat import S_IRUSR,S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH from grp import getgrnam from ipaddress import ip_address,ip_network,IPv4Interface from netifaces import interfaces @@ -331,12 +331,12 @@ def openvpn_mkdir(directory): os.mkdir(directory) # fix permissions - corresponds to mode 755 - os.chmod(directory, stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH) + os.chmod(directory, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) uid = getpwnam(user).pw_uid gid = getgrnam(group).gr_gid os.chown(directory, uid, gid) -def fixup_permission(filename, permission=stat.S_IRUSR): +def fixup_permission(filename, permission=S_IRUSR): """ Check if the given file exists and change ownershit to root/vyattacfg and appripriate file access permissions - default is user and group readable @@ -737,7 +737,7 @@ def verify(openvpn): if openvpn['shared_secret_file']: if openvpn['encryption'] in ['aes128gcm', 'aes192gcm', 'aes256gcm']: raise ConfigError('GCM encryption with shared-secret-key-file is not supported') - + if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['shared_secret_file']): raise ConfigError('Specified shared-secret-key-file "{}" is not valid'.format(openvpn['shared_secret_file'])) @@ -851,13 +851,13 @@ def generate(openvpn): # Generate client specific configuration for client in openvpn['client']: client_file = directory + '/ccd/' + interface + '/' + client['name'] - tmpl = jinja2.Template(client_tmpl) + tmpl = Template(client_tmpl) client_text = tmpl.render(client) with open(client_file, 'w') as f: f.write(client_text) os.chown(client_file, uid, gid) - tmpl = jinja2.Template(config_tmpl) + tmpl = Template(config_tmpl) config_text = tmpl.render(openvpn) # we need to support quoting of raw parameters from OpenVPN CLI @@ -957,4 +957,4 @@ if __name__ == '__main__': apply(c) except ConfigError as e: print(e) - sys.exit(1) + exit(1) diff --git a/src/conf_mode/interface-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 1097ae4d0..1097ae4d0 100755 --- a/src/conf_mode/interface-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py diff --git a/src/conf_mode/interface-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 0dcce6b1c..7a684bafa 100755 --- a/src/conf_mode/interface-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -190,6 +190,8 @@ def verify(c): raise ConfigError("ERROR: allowed-ips required for peer " + p) if not c['peer'][p]['pubkey']: raise ConfigError("peer pubkey required for peer " + p) + if not c['peer'][p]['endpoint']: + raise ConfigError("peer endpoint required for peer " + p) def apply(c): diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 0ddab2129..cba1fe319 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -224,7 +224,7 @@ init_config_tmpl = """ SNMPDRUN=yes # snmpd options (use syslog, close stdin/out/err). -SNMPDOPTS='-LSed -u snmp -g snmp -p /run/snmpd.pid' +SNMPDOPTS='-LSed -u snmp -g snmp -I -ipCidrRouteTable,inetCidrRouteTable -p /run/snmpd.pid' """ default_config_data = { diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 2a5cba99a..e3b11b537 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -267,7 +267,8 @@ def apply(ssh): else: # SSH access is removed in the commit os.system("sudo systemctl stop ssh.service") - os.unlink(config_file) + if os.path.isfile(config_file): + os.unlink(config_file) return None |