From 7b1a76063b15b238702cc86a71c5f0604c994920 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Fri, 10 Apr 2020 21:20:02 +0100 Subject: dhcp: T2265: refactor DHCP class Break the code between v4 and v6, remove need for getter/setter as they are just exposing the underlying dict. Move FixedDict from tunnel code and expose it to other part so it can be used to prevent accidental change to the dhcp option if no default exists already. --- python/vyos/ifconfig/dhcp.py | 222 +++++++++++++++++++------------------------ 1 file changed, 98 insertions(+), 124 deletions(-) (limited to 'python/vyos/ifconfig/dhcp.py') diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index 8ec8263b5..5b0a0bf6a 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -16,103 +16,55 @@ import os import jinja2 +from vyos.dicts import FixedDict from vyos.ifconfig.control import Control -template_v4 = """ -# 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 }}"; - {% 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; -} - -""" - -template_v6 = """ -# generated by ifconfig.py -interface "{{ intf }}" { - request routers, domain-name-servers, domain-name; -} - -""" - -class DHCP (Control): + +class _DHCP (Control): client_base = r'/var/lib/dhcp/dhclient_' - def __init__ (self, ifname, **kargs): + def __init__(self, ifname, version, **kargs): super().__init__(**kargs) - - # per interface DHCP config files - self._dhcp = { - 4: { - 'ifname': ifname, - 'conf': self.client_base + ifname + '.conf', - 'pid': self.client_base + ifname + '.pid', - 'lease': self.client_base + ifname + '.leases', - 'options': { - 'intf': ifname, - 'hostname': '', - 'client_id': '', - 'vendor_class_id': '' - }, - }, - 6: { - 'ifname': ifname, - 'conf': self.client_base + ifname + '.v6conf', - 'pid': self.client_base + ifname + '.v6pid', - 'lease': self.client_base + ifname + '.v6leases', - 'accept_ra': f'/proc/sys/net/ipv6/conf/{ifname}/accept_ra', - 'options': { - 'intf': ifname, - 'dhcpv6_prm_only': False, - 'dhcpv6_temporary': False - }, - }, + self.version = version + self.file = { + 'ifname': ifname, + 'conf': self.client_base + ifname + '.' + version + 'conf', + 'pid': self.client_base + ifname + '.' + version + 'pid', + 'lease': self.client_base + ifname + '.' + version + 'leases', } - 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[4]['options'] - - def set_dhcp_options(self, options): - """ - Store new DHCP options used by next run of DHCP client. - """ - self._dhcp[4]['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._dhcp[6]['options'] - - def set_dhcpv6_options(self, options): - """ - Store new DHCP options used by next run of DHCP client. - """ - self._dhcp[6]['options'] = options +class _DHCPv4 (_DHCP): + template = """\ + # generated by ifconfig.py + option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; + timeout 60; + retry 300; + + interface "{{ ifname }}" { + send host-name "{{ hostname }}"; + {% 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; + } + """.replace(' ', '') + + def __init__(self, ifname): + super().__init__(ifname, '') + self.options = FixedDict(**{ + 'ifname': ifname, + 'hostname': '', + 'client_id': '', + 'vendor_class_id': '' + }) # replace dhcpv4/v6 with systemd.networkd? - def _set_dhcp(self): + def set(self): """ Configure interface as DHCP client. The dhclient binary is automatically started in background! @@ -121,21 +73,19 @@ class DHCP (Control): >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') - >>> j.set_dhcp() + >>> j.dhcp.v4.set() """ - dhcp = self.get_dhcp_options() - if not dhcp['hostname']: + if not self.options['hostname']: # read configured system hostname. # maybe change to vyos hostd client ??? with open('/etc/hostname', 'r') as f: - dhcp['hostname'] = f.read().rstrip('\n') + self.options['hostname'] = f.read().rstrip('\n') # render DHCP configuration - tmpl = jinja2.Template(template_v4) - dhcp_text = tmpl.render(dhcp) - with open(self._dhcp[4]['conf'], 'w') as f: - f.write(dhcp_text) + tmpl = jinja2.Template(self.template) + with open(self.file['conf'], 'w') as f: + f.write(tmpl.render(self.options)) cmd = 'start-stop-daemon' cmd += ' --start' @@ -146,9 +96,9 @@ class DHCP (Control): cmd += ' --' # now pass arguments to dhclient binary cmd += ' -4 -nw -cf {conf} -pf {pid} -lf {lease} {ifname}' - return self._cmd(cmd.format(**self._dhcp[4])) + return self._cmd(cmd.format(**self.file)) - def _del_dhcp(self): + def delete(self): """ De-configure interface as DHCP clinet. All auto generated files like pid, config and lease will be removed. @@ -157,14 +107,14 @@ class DHCP (Control): >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') - >>> j.del_dhcp() + >>> j.dhcp.v4.delete() """ - if not os.path.isfile(self._dhcp[4]['pid']): + if not os.path.isfile(self.file['pid']): self._debug_msg('No DHCP client PID found') return None - # with open(self._dhcp[4]['pid'], 'r') as f: - # pid = int(f.read()) + # with open(self.file['pid'], 'r') as f: + # pid = int(f.read()) # stop dhclient, we need to call dhclient and tell it should release the # aquired IP address. tcpdump tells me: @@ -178,14 +128,35 @@ class DHCP (Control): # Hostname Option 12, length 10: "vyos" # cmd = '/sbin/dhclient -cf {conf} -pf {pid} -lf {lease} -r {ifname}' - self._cmd(cmd.format(**self._dhcp[4])) + self._cmd(cmd.format(**self.file)) # cleanup old config files for name in ('conf', 'pid', 'lease'): - if os.path.isfile(self._dhcp[4][name]): - os.remove(self._dhcp[4][name]) + if os.path.isfile(self.file[name]): + os.remove(self.file[name]) - def _set_dhcpv6(self): + +class _DHCPv6 (_DHCP): + template = """\ + # generated by ifconfig.py + interface "{{ ifname }}" { + request routers, domain-name-servers, domain-name; + } + + """.replace(' ', '') + + def __init__(self, ifname): + super().__init__(ifname, 'v6') + self.options = FixedDict(**{ + 'ifname': ifname, + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, + }) + self.file.update({ + 'accept_ra': f'/proc/sys/net/ipv6/conf/{ifname}/accept_ra', + }) + + def set(self): """ Configure interface as DHCPv6 client. The dhclient binary is automatically started in background! @@ -196,22 +167,20 @@ class DHCP (Control): >>> j = Interface('eth0') >>> j.set_dhcpv6() """ - dhcpv6 = self.get_dhcpv6_options() # 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']: + if self.options['dhcpv6_prm_only'] and self.options['dhcpv6_temporary']: raise Exception( 'DHCPv6 temporary and parameters-only options are mutually exclusive!') # render DHCP configuration - tmpl = jinja2.Template(template_v6) - dhcpv6_text = tmpl.render(dhcpv6) - with open(self._dhcp[6]['conf'], 'w') as f: - f.write(dhcpv6_text) + tmpl = jinja2.Template(self.template) + with open(self.file['conf'], 'w') as f: + f.write(tmpl.render(self.options)) # no longer accept router announcements on this interface - self._write_sysfs(self._dhcp[6]['accept_ra'], 0) + self._write_sysfs(self.file['accept_ra'], 0) # assemble command-line to start DHCPv6 client (dhclient) cmd = 'start-stop-daemon' @@ -224,15 +193,15 @@ class DHCP (Control): # now pass arguments to dhclient binary cmd += ' -6 -nw -cf {conf} -pf {pid} -lf {lease}' # add optional arguments - if dhcpv6['dhcpv6_prm_only']: + if self.options['dhcpv6_prm_only']: cmd += ' -S' - if dhcpv6['dhcpv6_temporary']: + if self.options['dhcpv6_temporary']: cmd += ' -T' cmd += ' {ifname}' - return self._cmd(cmd.format(**self._dhcp[6])) + return self._cmd(cmd.format(**self.file)) - def _del_dhcpv6(self): + def delete(self): """ De-configure interface as DHCPv6 clinet. All auto generated files like pid, config and lease will be removed. @@ -243,12 +212,12 @@ class DHCP (Control): >>> j = Interface('eth0') >>> j.del_dhcpv6() """ - if not os.path.isfile(self._dhcp[6]['pid']): + if not os.path.isfile(self.file['pid']): self._debug_msg('No DHCPv6 client PID found') return None - # with open(self._dhcp[6]['pid'], 'r') as f: - # pid = int(f.read()) + # with open(self.file['pid'], 'r') as f: + # pid = int(f.read()) # stop dhclient cmd = 'start-stop-daemon' @@ -256,13 +225,18 @@ class DHCP (Control): cmd += ' --oknodo' cmd += ' --quiet' cmd += ' --pidfile {pid}' - self._cmd(cmd.format(**self._dhcp[6])) + self._cmd(cmd.format(**self.file)) # accept router announcements on this interface - self._write_sysfs(self._dhcp[6]['accept_ra'], 1) + self._write_sysfs(self.options['accept_ra'], 1) # cleanup old config files for name in ('conf', 'pid', 'lease'): - if os.path.isfile(self._dhcp[6][name]): - os.remove(self._dhcp[6][name]) + if os.path.isfile(self.file[name]): + os.remove(self.file[name]) + +class DHCP (object): + def __init__(self, ifname): + self.v4 = _DHCPv4(ifname) + self.v6 = _DHCPv6(ifname) -- cgit v1.2.3 From e12a0ce922b67354ecd8e53414cda6b8b3a424fb Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 11 Apr 2020 12:58:52 +0100 Subject: dhcp: T2230: use external template files --- data/templates/dhcp-client/ipv4.tmpl | 17 +++++++++++++++ data/templates/dhcp-client/ipv6.tmpl | 4 ++++ python/vyos/ifconfig/dhcp.py | 40 +++--------------------------------- 3 files changed, 24 insertions(+), 37 deletions(-) create mode 100644 data/templates/dhcp-client/ipv4.tmpl create mode 100644 data/templates/dhcp-client/ipv6.tmpl (limited to 'python/vyos/ifconfig/dhcp.py') diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl new file mode 100644 index 000000000..43f273077 --- /dev/null +++ b/data/templates/dhcp-client/ipv4.tmpl @@ -0,0 +1,17 @@ +# generated by ifconfig.py +option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; +timeout 60; +retry 300; + +interface "{{ ifname }}" { + send host-name "{{ hostname }}"; + {% 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; +} diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl new file mode 100644 index 000000000..83db40c5f --- /dev/null +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -0,0 +1,4 @@ +# generated by ifconfig.py +interface "{{ ifname }}" { + request routers, domain-name-servers, domain-name; +} diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index 5b0a0bf6a..d4ff9c2cd 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -14,10 +14,10 @@ # License along with this library. If not, see . import os -import jinja2 from vyos.dicts import FixedDict from vyos.ifconfig.control import Control +from vyos.template import render class _DHCP (Control): @@ -34,26 +34,6 @@ class _DHCP (Control): } class _DHCPv4 (_DHCP): - template = """\ - # generated by ifconfig.py - option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; - timeout 60; - retry 300; - - interface "{{ ifname }}" { - send host-name "{{ hostname }}"; - {% 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; - } - """.replace(' ', '') - def __init__(self, ifname): super().__init__(ifname, '') self.options = FixedDict(**{ @@ -82,10 +62,7 @@ class _DHCPv4 (_DHCP): with open('/etc/hostname', 'r') as f: self.options['hostname'] = f.read().rstrip('\n') - # render DHCP configuration - tmpl = jinja2.Template(self.template) - with open(self.file['conf'], 'w') as f: - f.write(tmpl.render(self.options)) + render(self.file['conf'], 'dhcp-client/ipv4.tmpl' ,self.options) cmd = 'start-stop-daemon' cmd += ' --start' @@ -137,14 +114,6 @@ class _DHCPv4 (_DHCP): class _DHCPv6 (_DHCP): - template = """\ - # generated by ifconfig.py - interface "{{ ifname }}" { - request routers, domain-name-servers, domain-name; - } - - """.replace(' ', '') - def __init__(self, ifname): super().__init__(ifname, 'v6') self.options = FixedDict(**{ @@ -174,10 +143,7 @@ class _DHCPv6 (_DHCP): raise Exception( 'DHCPv6 temporary and parameters-only options are mutually exclusive!') - # render DHCP configuration - tmpl = jinja2.Template(self.template) - with open(self.file['conf'], 'w') as f: - f.write(tmpl.render(self.options)) + render(self.file['conf'], 'dhcp-client/ipv6.tmpl', self.options) # no longer accept router announcements on this interface self._write_sysfs(self.file['accept_ra'], 0) -- cgit v1.2.3