From 3a2148537adeb73e96b066e4f9b3d246f989f39a Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Thu, 5 Sep 2019 17:44:14 +0200 Subject: [PATCH] Update doc, and use `d` suffix for local daemons/servers --- README.rst | 79 ++++++++++++++++++++++------------------- pybootd/daemons.py | 4 ++- pybootd/etc/pybootd.ini | 16 ++++----- pybootd/pxed.py | 37 +++++++++---------- pybootd/tftpd.py | 17 +++++---- 5 files changed, 82 insertions(+), 71 deletions(-) diff --git a/README.rst b/README.rst index 19d9ebf..9145f52 100644 --- a/README.rst +++ b/README.rst @@ -21,17 +21,15 @@ Requirements Python ------ -- Python_ 2.7 or above is required. Python_ 3.x is not yet supported. -- Six_ compatibility module +- Python_ 3.5+ or above is required. Python_ 2.x is not longer supported. - Netifaces_ Python module is required on OS X; on Linux only, iproute2_ can be used as an alternative -- Optional: python-pkg-resources_ Python module +- Optional: python_pkg_resources_ Python module .. _Python: http://python.org/ .. _Netifaces: http://alastairs-place.net/netifaces/ .. _iproute2: http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2 -.. _python-pkg-resources: http://pythonhosted.org/distribute/pkg_resources.html -.. _Six: http://pythonhosted.org/six +.. _python_pkg_resources: http://pythonhosted.org/distribute/pkg_resources.html Permissions ----------- @@ -81,7 +79,7 @@ Common errors ``pybootd.pxed.BootpError: Unable to detect network configuration`` This error is often triggered when the ``pool_start`` address is not part of a valid network. Double check the network configuration and - fix up the ``[bootp]`` section so that it matches the actual + fix up the ``[bootpd]`` section so that it matches the actual network. If you don't want to allocate addresses dynamically from the pool (with ``pool_count = 0``), you still need to specify ``pool_start`` to some address in the local network you want to @@ -95,8 +93,8 @@ Common errors Configuration ------------- -``pybootd`` has a few option switches. The server offers two services: bootp -(which supports Dhcp and PXE extensions) and tftp. It is possible to disable +``pybootd`` has a few option switches. The server offers two services: *bootpd* +(which supports DHCP and PXE extensions) and *tftpd*. It is possible to disable either services. Usage: pybootd.py [options] @@ -145,8 +143,8 @@ client requests at least an IP address twice: ``file`` The path to the output log file, if ``type`` is set to ``file``. -``[bootp]`` section -................... +``[bootpd]`` section +.................... ``access`` Type of access control list. If this option is not defined, all BOOTP @@ -228,36 +226,40 @@ client requests at least an IP address twice: ``servername`` Name of the BOOTP server. + ``[mac]`` section ................. - The ``[mac]`` section contains one entry for each MAC address to allow or - block. The value for each entry is a boolean, *i.e.*:: +The ``[mac]`` section contains one entry for each MAC address to allow or +block. The value for each entry is a boolean, *i.e.*:: - AA-BB-CC-DD-EE-FF = enable + ``AA-BB-CC-DD-EE-FF = enable`` + +Note that due to a limitation of the configuration parser, ':' byte separator +in MAC addresses is not allowed, please use '-' separator. - Note that due to a limitation of the configuration parser, ':' byte separator - in MAC addresses is not allowed, please use '-' separator. ``[static_dhcp]`` section ......................... - The ``[static_dhcp]`` section contains one entry for each MAC - address to associate with a specific IP address. The IP address can be - any IPv4 address in dotted notation, *i.e.*: +The ``[static_dhcp]`` section contains one entry for each MAC +address to associate with a specific IP address. The IP address can be +any IPv4 address in dotted notation, *i.e.*: - AA-BB-CC-DD-EE-FF = 192.168.1.2 + ``AA-BB-CC-DD-EE-FF = 192.168.1.2`` + +The MAC addresses specified here will automatically be allowed, +unless ``[mac]`` section specifies otherwise. - The MAC addresses specified here will automatically be allowed, - unless ``[mac]`` section specifies otherwise. ``[uuid]`` section .................. - The ``[uuid]`` section contains one entry for each UUID to allow or block. - The value for each entry is a boolean, *i.e.*:: +The ``[uuid]`` section contains one entry for each UUID to allow or block. +The value for each entry is a boolean, *i.e.*:: + + ``xxxxxxxx-aaaa-bbbb-cccc-yyyyyyyyyyyy = enable`` - xxxxxxxx-aaaa-bbbb-cccc-yyyyyyyyyyyy = enable ``[http]`` section .................. @@ -281,8 +283,9 @@ The ``pxe``/``dhcp`` option pair enables the remote HTTP server to identify the boot phase: either a BIOS initialization or an OS boot sequence. When such differentiation is useless, both options may refer to the same path. -``[tftp]`` section -.................. + +``[tftpd]`` section +................... ``address`` Address to listen to incoming TFTP requests. When the BOOTP daemon is @@ -310,11 +313,12 @@ differentiation is useless, both options may refer to the same path. - an absolute path, when the ``root`` option starts with ``/``, - a URL prefix, to access remote files. + ``[filters]`` section ..................... The ``filters`` section allows on-the-fly pathnames transformation. When a TFTP -client requests some specific filenames, the *tftp* server can translate them +client requests some specific filenames, the *tftpd* server can translate them to other ones. This option is useful to serve the very same configuration file (''e.g.'' @@ -332,8 +336,8 @@ braces, such as ``{varname}``. For now, the only supported variable is ``filename``, which is replaced with the actual requested filename. -The *value* part can also contain a special marker, that tells the *tftp* -daemon to read the replacement pattern from a file. This special marker should +The *value* part can also contain a special marker, that tells the *tftpd* +server to read the replacement pattern from a file. This special marker should be written with enclosing brackets, such as ``[file]``. Examples @@ -343,7 +347,7 @@ The following filter:: pxelinux.cfg/* = pybootd/etc/pxe.cfg -tells the *tftp* server that all client requests matching the +tells the *tftpd* server that all client requests matching the ``pxelinux.cfg/*`` pattern should be served the ``pybootd/etc/pxe.cfg`` file instead. This prevents the client to perform the usual time-costing fallback requests using UUID, MAC, and suffix addresses before eventually falling @@ -353,7 +357,7 @@ The following filter:: startup = [dir/{filename}.cfg] -tells the *tftp* server that when the ``startup`` file is requested, it should +tells the *tftpd* server that when the ``startup`` file is requested, it should read out the actual filename from the ``dir/startup.cfg`` file. HTTP-based authentication @@ -382,15 +386,16 @@ this feature. It can be found within the ``tests/`` subdirectory. See the ``config.ini`` file for this test daemon. The test daemon expects the ``pxe`` path to be set to ``/boot`` and the ``dhcp`` path to ``/linux``. + Sample configurations ~~~~~~~~~~~~~~~~~~~~~ Installing a Debian 6.0 machine from the official archive --------------------------------------------------------- -As the *tftp* daemon is able to retrieve remote files using the HTTP protocol, -there is no need to manually download any file from a Debian mirror. The daemon -will forward all file requests to the mirror on behalf of the client being -installed. +As pybootd's *tftpd* server is able to retrieve remote files using the HTTP +protocol, there is no need to manually download any file from a Debian mirror. +The daemon will forward all file requests to the mirror on behalf of the client +being installed. The ``pybootd.ini`` would contain:: @@ -400,7 +405,7 @@ The ``pybootd.ini`` would contain:: ; show informative and error messages only (disable verbose mode) level = info - [bootp] + [bootpd] ; do not force a full PXE boot-up cycle to accept the client allow_simple_dhcp = enable ; First BOOTP/DHCP address to generate @@ -410,7 +415,7 @@ The ``pybootd.ini`` would contain:: ; boot-up executable the client should request through TFTP boot_file = pxelinux.0 - [tftp] + [tftpd] ; URL to install a Debian 6.0 Intel/AMD 64-bit network installation root = http://http.us.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot diff --git a/pybootd/daemons.py b/pybootd/daemons.py index a573610..735f1fd 100755 --- a/pybootd/daemons.py +++ b/pybootd/daemons.py @@ -31,7 +31,9 @@ from .pxed import BootpServer from .tftpd import TftpServer from .util import logger_factory, EasyConfigParser -#pybootd: disable-msg=broad-except +#pylint: disable-msg=broad-except +#pylint: disable-msg=missing-docstring +#pylint: disable-msg=invalid-name class BootpDaemon(Thread): diff --git a/pybootd/etc/pybootd.ini b/pybootd/etc/pybootd.ini index 912f1e5..1fbbe77 100644 --- a/pybootd/etc/pybootd.ini +++ b/pybootd/etc/pybootd.ini @@ -2,11 +2,10 @@ type = stderr level = info -[bootp] +[bootpd] address = 0.0.0.0 ; pool_start should be in a valid subnet -; pool_start = 192.168.25.100 -pool_start = 10.113.116.245 +pool_start = 192.168.25.100 pool_count = 5 domain = localdomain server_name = debug @@ -14,13 +13,14 @@ boot_file = pxelinux.0 lease_time = 86400 access = mac allow_simple_dhcp = enable -dns = 10.130.0.2 +dns = 8.8.8.8 set_gateway = true ; use "nc -l -u 127.0.0.1 -p 12345" to debug ; notify = 192.168.26.201:12345;192.168.26.200:12345 [mac] -00-1E-4F-C4-95-EE = enable +; see doc: byte separator should be defined with dash, not column +00-AA-55-12-34-56 = enable [uuid] 12345678-abcd-ef00-1111-abcdefabcdef = enable @@ -32,10 +32,10 @@ pxe = boot dhcp = linux always_check = disable -[tftp] +[tftpd] ;address = (use address from bootpd) -;root = ./images -root = http://http.us.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot +root = ./images +;root = http://http.us.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot [filters] ;pxelinux.cfg/* = pybootd/etc/pxe.cfg diff --git a/pybootd/pxed.py b/pybootd/pxed.py index a573e29..ad4f5e9 100644 --- a/pybootd/pxed.py +++ b/pybootd/pxed.py @@ -182,6 +182,8 @@ class BootpServer: ACCESS_REMOTE = ['http'] # Access modes, remotely retrieved (ST_IDLE, ST_PXE, ST_DHCP) = range(3) # Current state + BOOTP_SECTION = 'bootpd' + def __init__(self, logger, config): self.sock = [] self.log = logger @@ -190,16 +192,15 @@ class BootpServer: self.ippool = {} # key MAC address string, value assigned IP string self.filepool = {} # key IP string, value pathname self.states = {} # key MAC address string, value client state - self.bootp_section = 'bootp' - self.pool_start = self.config.get(self.bootp_section, 'pool_start') + self.pool_start = self.config.get(self.BOOTP_SECTION, 'pool_start') if not self.pool_start: raise BootpError('Missing pool_start definition') - self.pool_count = int(self.config.get(self.bootp_section, + self.pool_count = int(self.config.get(self.BOOTP_SECTION, 'pool_count', '10')) self.netconfig = get_iface_config(self.pool_start) if not self.netconfig: - host = self.config.get(self.bootp_section, 'address', '0.0.0.0') + host = self.config.get(self.BOOTP_SECTION, 'address', '0.0.0.0') self.netconfig = get_iface_config(host) if not self.netconfig: # the available networks on the host may not match the config... @@ -208,7 +209,7 @@ class BootpServer: keys = sorted(self.netconfig.keys()) self.log.info('Using %s' % ', '.join(map( ':'.join, zip(keys, [self.netconfig[k] for k in keys])))) - nlist = self.config.get(self.bootp_section, 'notify') + nlist = self.config.get(self.BOOTP_SECTION, 'notify') self.notify = [] if nlist: try: @@ -218,7 +219,7 @@ class BootpServer: self.notify.append((n[0], int(n[1]))) except Exception as exc: raise BootpError('Invalid notification URL: %s' % exc) - access = self.config.get(self.bootp_section, 'access') + access = self.config.get(self.BOOTP_SECTION, 'access') if not access: self.acl = None else: @@ -257,8 +258,8 @@ class BootpServer: return self.netconfig def bind(self): - host = self.config.get(self.bootp_section, 'address', '0.0.0.0') - port = self.config.get(self.bootp_section, 'port', + host = self.config.get(self.BOOTP_SECTION, 'address', '0.0.0.0') + port = self.config.get(self.BOOTP_SECTION, 'port', str(BOOTP_PORT_REQUEST)) sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) @@ -398,8 +399,8 @@ class BootpServer: if newstate == self.ST_IDLE: sdhcp = 'allow_simple_dhcp' simple_dhcp = \ - self.config.has_option(self.bootp_section, sdhcp) and \ - to_bool(self.config.get(self.bootp_section, sdhcp)) + self.config.has_option(self.BOOTP_SECTION, sdhcp) and \ + to_bool(self.config.get(self.BOOTP_SECTION, sdhcp)) if not simple_dhcp: self.log.info('Request from %s ignored (idle state)' % mac_str) return @@ -497,7 +498,7 @@ class BootpServer: raise BootpError('No more IP available in definined pool') mask = iptoint(self.config.get( - self.bootp_section, 'netmask', self.netconfig['mask'])) + self.BOOTP_SECTION, 'netmask', self.netconfig['mask'])) reply_broadcast = iptoint(ip) & mask reply_broadcast |= (~mask) & ((1 << 32)-1) buf[BOOTP_YIADDR] = inet_aton(ip) @@ -517,12 +518,12 @@ class BootpServer: buf[BOOTP_SIADDR] = inet_aton(server_addr) # sname buf[BOOTP_SNAME] = \ - '.'.join([self.config.get(self.bootp_section, + '.'.join([self.config.get(self.BOOTP_SECTION, 'servername', 'unknown'), - self.config.get(self.bootp_section, + self.config.get(self.BOOTP_SECTION, 'domain', 'localdomain')]).encode() # file - buf[BOOTP_FILE] = self.config.get(self.bootp_section, + buf[BOOTP_FILE] = self.config.get(self.BOOTP_SECTION, 'boot_file', '\x00').encode() if not dhcp_msg_type: @@ -573,18 +574,18 @@ class BootpServer: pkt += spack('!BB4s', DHCP_SERVER, 4, server) mask = inet_aton(self.config.get( - self.bootp_section, 'netmask', self.netconfig['mask'])) + self.BOOTP_SECTION, 'netmask', self.netconfig['mask'])) pkt += spack('!BB4s', DHCP_IP_MASK, 4, mask) - gateway_addr = self.config.get(self.bootp_section, 'gateway', '') + gateway_addr = self.config.get(self.BOOTP_SECTION, 'gateway', '') if gateway_addr: gateway = inet_aton(gateway_addr) else: gateway = server pkt += spack('!BB4s', DHCP_IP_GATEWAY, 4, gateway) - dns = self.config.get(self.bootp_section, + dns = self.config.get(self.BOOTP_SECTION, 'dns', None) if dns: if dns.lower() == 'auto': @@ -595,7 +596,7 @@ class BootpServer: dns_ip = inet_aton(dns_str) pkt += spack('!BB4s', DHCP_IP_DNS, 4, dns_ip) pkt += spack('!BBI', DHCP_LEASE_TIME, 4, - int(self.config.get(self.bootp_section, + int(self.config.get(self.BOOTP_SECTION, 'lease_time', str(24*3600)))) pkt += spack('!BB', DHCP_END, 0) diff --git a/pybootd/tftpd.py b/pybootd/tftpd.py index c548526..fd189d5 100644 --- a/pybootd/tftpd.py +++ b/pybootd/tftpd.py @@ -404,25 +404,28 @@ class TftpServer: Each request is handled in its own thread """ + TFTP_SECTION = 'tftpd' + def __init__(self, logger, config, bootpd=None): self.log = logger self.config = config self.sock = [] self.bootpd = bootpd - self.blocksize = int(self.config.get('tftp', 'blocksize', '512')) - self.timeout = float(self.config.get('tftp', 'timeout', '2.0')) - self.retry = int(self.config.get('tftp', 'blocksize', '5')) - self.root = self.config.get('tftp', 'root', os.getcwd()) + self.blocksize = int(self.config.get(self.TFTP_SECTION, 'blocksize', + '512')) + self.timeout = float(self.config.get(self.TFTP_SECTION, 'timeout', '2.0')) + self.retry = int(self.config.get(self.TFTP_SECTION, 'blocksize', '5')) + self.root = self.config.get(self.TFTP_SECTION, 'root', os.getcwd()) self.fcre, self.filepatterns = self.get_file_filters() self.genfilecre = recompile(r'\[(?P[\w\.\-]+)\]') def bind(self): netconfig = self.bootpd and self.bootpd.get_netconfig() - host = self.config.get('tftp', 'address', + host = self.config.get(self.TFTP_SECTION, 'address', netconfig and netconfig['server']) if not host: raise TftpError(TftpError.NO_SUCH_USER, 'TFTP address no defined') - port = int(self.config.get('tftp', 'port', str(TFTP_PORT))) + port = int(self.config.get(self.TFTP_SECTION, 'port', str(TFTP_PORT))) sock = socket(AF_INET, SOCK_DGRAM) self.sock.append(sock) sock.bind((host, port)) @@ -433,7 +436,7 @@ class TftpServer: if not self.bootpd.is_alive(): self.log.info('Bootp daemon is dead, exiting') break - r, w, e = select(self.sock, [], self.sock) + r = select(self.sock, [], self.sock)[0] for sock in r: data, addr = sock.recvfrom(516) tc = TftpConnection(self)