1
1
mirror of https://github.com/eblot/pybootd.git synced 2024-09-11 22:17:44 +03:00

Update doc, and use d suffix for local daemons/servers

This commit is contained in:
Emmanuel Blot 2019-09-05 17:44:14 +02:00
parent 6d8a7111cb
commit 3a2148537a
5 changed files with 82 additions and 71 deletions

View File

@ -21,17 +21,15 @@ Requirements
Python Python
------ ------
- Python_ 2.7 or above is required. Python_ 3.x is not yet supported. - Python_ 3.5+ or above is required. Python_ 2.x is not longer supported.
- Six_ compatibility module
- Netifaces_ Python module is required on OS X; on Linux only, iproute2_ can be - Netifaces_ Python module is required on OS X; on Linux only, iproute2_ can be
used as an alternative used as an alternative
- Optional: python-pkg-resources_ Python module - Optional: python_pkg_resources_ Python module
.. _Python: http://python.org/ .. _Python: http://python.org/
.. _Netifaces: http://alastairs-place.net/netifaces/ .. _Netifaces: http://alastairs-place.net/netifaces/
.. _iproute2: http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2 .. _iproute2: http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2
.. _python-pkg-resources: http://pythonhosted.org/distribute/pkg_resources.html .. _python_pkg_resources: http://pythonhosted.org/distribute/pkg_resources.html
.. _Six: http://pythonhosted.org/six
Permissions Permissions
----------- -----------
@ -81,7 +79,7 @@ Common errors
``pybootd.pxed.BootpError: Unable to detect network configuration`` ``pybootd.pxed.BootpError: Unable to detect network configuration``
This error is often triggered when the ``pool_start`` address is not This error is often triggered when the ``pool_start`` address is not
part of a valid network. Double check the network configuration and 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 network. If you don't want to allocate addresses dynamically from
the pool (with ``pool_count = 0``), you still need to specify the pool (with ``pool_count = 0``), you still need to specify
``pool_start`` to some address in the local network you want to ``pool_start`` to some address in the local network you want to
@ -95,8 +93,8 @@ Common errors
Configuration Configuration
------------- -------------
``pybootd`` has a few option switches. The server offers two services: bootp ``pybootd`` has a few option switches. The server offers two services: *bootpd*
(which supports Dhcp and PXE extensions) and tftp. It is possible to disable (which supports DHCP and PXE extensions) and *tftpd*. It is possible to disable
either services. either services.
Usage: pybootd.py [options] Usage: pybootd.py [options]
@ -145,8 +143,8 @@ client requests at least an IP address twice:
``file`` ``file``
The path to the output log file, if ``type`` is set to ``file``. The path to the output log file, if ``type`` is set to ``file``.
``[bootp]`` section ``[bootpd]`` section
................... ....................
``access`` ``access``
Type of access control list. If this option is not defined, all BOOTP 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`` ``servername``
Name of the BOOTP server. Name of the BOOTP server.
``[mac]`` section ``[mac]`` section
................. .................
The ``[mac]`` section contains one entry for each MAC address to allow or The ``[mac]`` section contains one entry for each MAC address to allow or
block. The value for each entry is a boolean, *i.e.*:: 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 ``[static_dhcp]`` section
......................... .........................
The ``[static_dhcp]`` section contains one entry for each MAC The ``[static_dhcp]`` section contains one entry for each MAC
address to associate with a specific IP address. The IP address can be address to associate with a specific IP address. The IP address can be
any IPv4 address in dotted notation, *i.e.*: 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 ``[uuid]`` section
.................. ..................
The ``[uuid]`` section contains one entry for each UUID to allow or block. The ``[uuid]`` section contains one entry for each UUID to allow or block.
The value for each entry is a boolean, *i.e.*:: The value for each entry is a boolean, *i.e.*::
``xxxxxxxx-aaaa-bbbb-cccc-yyyyyyyyyyyy = enable``
xxxxxxxx-aaaa-bbbb-cccc-yyyyyyyyyyyy = enable
``[http]`` section ``[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 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. differentiation is useless, both options may refer to the same path.
``[tftp]`` section
.................. ``[tftpd]`` section
...................
``address`` ``address``
Address to listen to incoming TFTP requests. When the BOOTP daemon is 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 ``/``, - an absolute path, when the ``root`` option starts with ``/``,
- a URL prefix, to access remote files. - a URL prefix, to access remote files.
``[filters]`` section ``[filters]`` section
..................... .....................
The ``filters`` section allows on-the-fly pathnames transformation. When a TFTP 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. to other ones.
This option is useful to serve the very same configuration file (''e.g.'' 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 For now, the only supported variable is ``filename``, which is replaced with
the actual requested filename. the actual requested filename.
The *value* part can also contain a special marker, that tells the *tftp* The *value* part can also contain a special marker, that tells the *tftpd*
daemon to read the replacement pattern from a file. This special marker should server to read the replacement pattern from a file. This special marker should
be written with enclosing brackets, such as ``[file]``. be written with enclosing brackets, such as ``[file]``.
Examples Examples
@ -343,7 +347,7 @@ The following filter::
pxelinux.cfg/* = pybootd/etc/pxe.cfg 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 ``pxelinux.cfg/*`` pattern should be served the ``pybootd/etc/pxe.cfg`` file
instead. This prevents the client to perform the usual time-costing fallback instead. This prevents the client to perform the usual time-costing fallback
requests using UUID, MAC, and suffix addresses before eventually falling requests using UUID, MAC, and suffix addresses before eventually falling
@ -353,7 +357,7 @@ The following filter::
startup = [dir/{filename}.cfg] 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. read out the actual filename from the ``dir/startup.cfg`` file.
HTTP-based authentication 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`` ``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``. path to be set to ``/boot`` and the ``dhcp`` path to ``/linux``.
Sample configurations Sample configurations
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
Installing a Debian 6.0 machine from the official archive Installing a Debian 6.0 machine from the official archive
--------------------------------------------------------- ---------------------------------------------------------
As the *tftp* daemon is able to retrieve remote files using the HTTP protocol, As pybootd's *tftpd* server is able to retrieve remote files using the HTTP
there is no need to manually download any file from a Debian mirror. The daemon protocol, there is no need to manually download any file from a Debian mirror.
will forward all file requests to the mirror on behalf of the client being The daemon will forward all file requests to the mirror on behalf of the client
installed. being installed.
The ``pybootd.ini`` would contain:: The ``pybootd.ini`` would contain::
@ -400,7 +405,7 @@ The ``pybootd.ini`` would contain::
; show informative and error messages only (disable verbose mode) ; show informative and error messages only (disable verbose mode)
level = info level = info
[bootp] [bootpd]
; do not force a full PXE boot-up cycle to accept the client ; do not force a full PXE boot-up cycle to accept the client
allow_simple_dhcp = enable allow_simple_dhcp = enable
; First BOOTP/DHCP address to generate ; 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-up executable the client should request through TFTP
boot_file = pxelinux.0 boot_file = pxelinux.0
[tftp] [tftpd]
; URL to install a Debian 6.0 Intel/AMD 64-bit network installation ; 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 root = http://http.us.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot

View File

@ -31,7 +31,9 @@ from .pxed import BootpServer
from .tftpd import TftpServer from .tftpd import TftpServer
from .util import logger_factory, EasyConfigParser 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): class BootpDaemon(Thread):

View File

@ -2,11 +2,10 @@
type = stderr type = stderr
level = info level = info
[bootp] [bootpd]
address = 0.0.0.0 address = 0.0.0.0
; pool_start should be in a valid subnet ; pool_start should be in a valid subnet
; pool_start = 192.168.25.100 pool_start = 192.168.25.100
pool_start = 10.113.116.245
pool_count = 5 pool_count = 5
domain = localdomain domain = localdomain
server_name = debug server_name = debug
@ -14,13 +13,14 @@ boot_file = pxelinux.0
lease_time = 86400 lease_time = 86400
access = mac access = mac
allow_simple_dhcp = enable allow_simple_dhcp = enable
dns = 10.130.0.2 dns = 8.8.8.8
set_gateway = true set_gateway = true
; use "nc -l -u 127.0.0.1 -p 12345" to debug ; use "nc -l -u 127.0.0.1 -p 12345" to debug
; notify = 192.168.26.201:12345;192.168.26.200:12345 ; notify = 192.168.26.201:12345;192.168.26.200:12345
[mac] [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] [uuid]
12345678-abcd-ef00-1111-abcdefabcdef = enable 12345678-abcd-ef00-1111-abcdefabcdef = enable
@ -32,10 +32,10 @@ pxe = boot
dhcp = linux dhcp = linux
always_check = disable always_check = disable
[tftp] [tftpd]
;address = (use address from bootpd) ;address = (use address from bootpd)
;root = ./images root = ./images
root = http://http.us.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot ;root = http://http.us.debian.org/debian/dists/squeeze/main/installer-amd64/current/images/netboot
[filters] [filters]
;pxelinux.cfg/* = pybootd/etc/pxe.cfg ;pxelinux.cfg/* = pybootd/etc/pxe.cfg

View File

@ -182,6 +182,8 @@ class BootpServer:
ACCESS_REMOTE = ['http'] # Access modes, remotely retrieved ACCESS_REMOTE = ['http'] # Access modes, remotely retrieved
(ST_IDLE, ST_PXE, ST_DHCP) = range(3) # Current state (ST_IDLE, ST_PXE, ST_DHCP) = range(3) # Current state
BOOTP_SECTION = 'bootpd'
def __init__(self, logger, config): def __init__(self, logger, config):
self.sock = [] self.sock = []
self.log = logger self.log = logger
@ -190,16 +192,15 @@ class BootpServer:
self.ippool = {} # key MAC address string, value assigned IP string self.ippool = {} # key MAC address string, value assigned IP string
self.filepool = {} # key IP string, value pathname self.filepool = {} # key IP string, value pathname
self.states = {} # key MAC address string, value client state 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: if not self.pool_start:
raise BootpError('Missing pool_start definition') 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')) 'pool_count', '10'))
self.netconfig = get_iface_config(self.pool_start) self.netconfig = get_iface_config(self.pool_start)
if not self.netconfig: 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) self.netconfig = get_iface_config(host)
if not self.netconfig: if not self.netconfig:
# the available networks on the host may not match the config... # the available networks on the host may not match the config...
@ -208,7 +209,7 @@ class BootpServer:
keys = sorted(self.netconfig.keys()) keys = sorted(self.netconfig.keys())
self.log.info('Using %s' % ', '.join(map( self.log.info('Using %s' % ', '.join(map(
':'.join, zip(keys, [self.netconfig[k] for k in keys])))) ':'.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 = [] self.notify = []
if nlist: if nlist:
try: try:
@ -218,7 +219,7 @@ class BootpServer:
self.notify.append((n[0], int(n[1]))) self.notify.append((n[0], int(n[1])))
except Exception as exc: except Exception as exc:
raise BootpError('Invalid notification URL: %s' % 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: if not access:
self.acl = None self.acl = None
else: else:
@ -257,8 +258,8 @@ class BootpServer:
return self.netconfig return self.netconfig
def bind(self): def bind(self):
host = self.config.get(self.bootp_section, 'address', '0.0.0.0') host = self.config.get(self.BOOTP_SECTION, 'address', '0.0.0.0')
port = self.config.get(self.bootp_section, 'port', port = self.config.get(self.BOOTP_SECTION, 'port',
str(BOOTP_PORT_REQUEST)) str(BOOTP_PORT_REQUEST))
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
@ -398,8 +399,8 @@ class BootpServer:
if newstate == self.ST_IDLE: if newstate == self.ST_IDLE:
sdhcp = 'allow_simple_dhcp' sdhcp = 'allow_simple_dhcp'
simple_dhcp = \ simple_dhcp = \
self.config.has_option(self.bootp_section, sdhcp) and \ self.config.has_option(self.BOOTP_SECTION, sdhcp) and \
to_bool(self.config.get(self.bootp_section, sdhcp)) to_bool(self.config.get(self.BOOTP_SECTION, sdhcp))
if not simple_dhcp: if not simple_dhcp:
self.log.info('Request from %s ignored (idle state)' % mac_str) self.log.info('Request from %s ignored (idle state)' % mac_str)
return return
@ -497,7 +498,7 @@ class BootpServer:
raise BootpError('No more IP available in definined pool') raise BootpError('No more IP available in definined pool')
mask = iptoint(self.config.get( 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 = iptoint(ip) & mask
reply_broadcast |= (~mask) & ((1 << 32)-1) reply_broadcast |= (~mask) & ((1 << 32)-1)
buf[BOOTP_YIADDR] = inet_aton(ip) buf[BOOTP_YIADDR] = inet_aton(ip)
@ -517,12 +518,12 @@ class BootpServer:
buf[BOOTP_SIADDR] = inet_aton(server_addr) buf[BOOTP_SIADDR] = inet_aton(server_addr)
# sname # sname
buf[BOOTP_SNAME] = \ buf[BOOTP_SNAME] = \
'.'.join([self.config.get(self.bootp_section, '.'.join([self.config.get(self.BOOTP_SECTION,
'servername', 'unknown'), 'servername', 'unknown'),
self.config.get(self.bootp_section, self.config.get(self.BOOTP_SECTION,
'domain', 'localdomain')]).encode() 'domain', 'localdomain')]).encode()
# file # file
buf[BOOTP_FILE] = self.config.get(self.bootp_section, buf[BOOTP_FILE] = self.config.get(self.BOOTP_SECTION,
'boot_file', '\x00').encode() 'boot_file', '\x00').encode()
if not dhcp_msg_type: if not dhcp_msg_type:
@ -573,18 +574,18 @@ class BootpServer:
pkt += spack('!BB4s', DHCP_SERVER, 4, server) pkt += spack('!BB4s', DHCP_SERVER, 4, server)
mask = inet_aton(self.config.get( 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) 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: if gateway_addr:
gateway = inet_aton(gateway_addr) gateway = inet_aton(gateway_addr)
else: else:
gateway = server gateway = server
pkt += spack('!BB4s', DHCP_IP_GATEWAY, 4, gateway) pkt += spack('!BB4s', DHCP_IP_GATEWAY, 4, gateway)
dns = self.config.get(self.bootp_section, dns = self.config.get(self.BOOTP_SECTION,
'dns', None) 'dns', None)
if dns: if dns:
if dns.lower() == 'auto': if dns.lower() == 'auto':
@ -595,7 +596,7 @@ class BootpServer:
dns_ip = inet_aton(dns_str) dns_ip = inet_aton(dns_str)
pkt += spack('!BB4s', DHCP_IP_DNS, 4, dns_ip) pkt += spack('!BB4s', DHCP_IP_DNS, 4, dns_ip)
pkt += spack('!BBI', DHCP_LEASE_TIME, 4, pkt += spack('!BBI', DHCP_LEASE_TIME, 4,
int(self.config.get(self.bootp_section, int(self.config.get(self.BOOTP_SECTION,
'lease_time', 'lease_time',
str(24*3600)))) str(24*3600))))
pkt += spack('!BB', DHCP_END, 0) pkt += spack('!BB', DHCP_END, 0)

View File

@ -404,25 +404,28 @@ class TftpServer:
Each request is handled in its own thread Each request is handled in its own thread
""" """
TFTP_SECTION = 'tftpd'
def __init__(self, logger, config, bootpd=None): def __init__(self, logger, config, bootpd=None):
self.log = logger self.log = logger
self.config = config self.config = config
self.sock = [] self.sock = []
self.bootpd = bootpd self.bootpd = bootpd
self.blocksize = int(self.config.get('tftp', 'blocksize', '512')) self.blocksize = int(self.config.get(self.TFTP_SECTION, 'blocksize',
self.timeout = float(self.config.get('tftp', 'timeout', '2.0')) '512'))
self.retry = int(self.config.get('tftp', 'blocksize', '5')) self.timeout = float(self.config.get(self.TFTP_SECTION, 'timeout', '2.0'))
self.root = self.config.get('tftp', 'root', os.getcwd()) 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.fcre, self.filepatterns = self.get_file_filters()
self.genfilecre = recompile(r'\[(?P<name>[\w\.\-]+)\]') self.genfilecre = recompile(r'\[(?P<name>[\w\.\-]+)\]')
def bind(self): def bind(self):
netconfig = self.bootpd and self.bootpd.get_netconfig() 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']) netconfig and netconfig['server'])
if not host: if not host:
raise TftpError(TftpError.NO_SUCH_USER, 'TFTP address no defined') 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) sock = socket(AF_INET, SOCK_DGRAM)
self.sock.append(sock) self.sock.append(sock)
sock.bind((host, port)) sock.bind((host, port))
@ -433,7 +436,7 @@ class TftpServer:
if not self.bootpd.is_alive(): if not self.bootpd.is_alive():
self.log.info('Bootp daemon is dead, exiting') self.log.info('Bootp daemon is dead, exiting')
break break
r, w, e = select(self.sock, [], self.sock) r = select(self.sock, [], self.sock)[0]
for sock in r: for sock in r:
data, addr = sock.recvfrom(516) data, addr = sock.recvfrom(516)
tc = TftpConnection(self) tc = TftpConnection(self)