1
1
mirror of https://github.com/eblot/pybootd.git synced 2024-07-07 10:16:40 +03:00
A minimalist bootp/dhcp/pxe and tftp server
Go to file
2020-04-28 13:57:33 +02:00
.github/workflows Add basic CI rule to build package 2020-04-12 19:18:25 +02:00
pybootd Code cleanup 2020-04-28 13:57:33 +02:00
.gitignore Fix package generation 2019-09-06 18:44:11 +02:00
AUTHORS Update AUTHORS 2019-09-19 10:18:09 +02:00
forward.sh Remove deprecated commands for darwin 2020-04-13 14:43:44 +02:00
LICENSE Initial commit 2011-05-21 00:21:53 +02:00
pybootd.py Start porting to Py3 only and fix old syntax 2019-09-03 18:40:34 +02:00
README.rst Document MAC mask syntax 2020-04-14 17:16:08 +02:00
requirements.txt Deal with the STUPID Intel PXE client implementation 2020-04-10 18:07:15 +02:00
setup.cfg Improve setup script. 2020-04-12 19:10:19 +02:00
setup.py Improve setup script. 2020-04-12 19:10:19 +02:00

+++++++
PyBootd
+++++++

.. image:: https://github.com/eblot/pybootd/workflows/Python%20package/badge.svg
   :alt: Python package build status

Overview
~~~~~~~~

PyBootd is a daemon supporting a subset of the BOOTP, DHCP, PXE, TFTP and HTTP
protocols, with some handy extensions.

One of its main goals is to provide a simple solution to boot up any
PXE-enabled personal computer, with no other tool required but a standard
Python installation.

It is not designed to be feature-complete, but to be used as an easy modifiable
code to develop custom boot solutions

Pybootd can be used for any network boot up, or to install an OS without any
physical support such as a USB key or a CD/DVD.


Requirements
~~~~~~~~~~~~

Python
------

- Python_ 3.5+ or above is required. Python_ 2.x is not longer supported.
- Netifaces_ Python module is required

.. _Python: https://python.org/
.. _Netifaces: https://github.com/al45tair/netifaces

Permissions
-----------

- DHCP protocol requires the daemon to listen on port 67.
- TFTP protocol requires the daemon to listen on port 69.
- HTTP optional daemon may be run on any port.

As these ports are within the server's range (<1024), the superuser privileges
are required on Unix hosts (Linux, macOS, ...) to start up these daemons.


Status
~~~~~~

This project is in beta development stage.


Supported features
~~~~~~~~~~~~~~~~~~

- Access control:

1. None (any remote host can be served)
2. MAC address ACL
3. UUID based ACL - requires PXE protocol
4. HTTP forwarding - authorization is delegated to a remote server using
   simple HTTP GET requests

- Local or remote file serving:

- For example, it is possible to boot up a full Debian system directly from
  the Internet, without storing any file on the pybootd host machine

- Network notification of client requests through UDP messages

- File name translation

  - Files requested from TFTP clients can be filtered and transformed into
    local filenames using filters

- It is possible to use pybootd with only one of the services, either TFTP or
  DHCP

- A very basic HTTP server can be optionally enabled to serve files over HTTP
  for complex hosts that require additional files (such as a root file system)
  after the initial boot sequence.

Warning
~~~~~~~

There is no strong checking of permissions nor robust file path management, so
it is recommended NOT to run this daemon on a host with sensitive content.

Although only read requests are implemented, there is no enforcement or
strong validation of received data and strings from adversary remote clients.


FAQ
~~~

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 ``[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
  serve (*eg.* the address of your local server).

``error: Can't assign requested address``
  This errir is often triggered with an invalid listening address setting.
  Try listening on all IPv4 interfaces with ``address = 0.0.0.0`` and use ACL
  to discard requests from network you do not want to serve.

DHCP client keeps requesting an address but seems to receive none
  Some stupid clients - such as the ones implemented in BIOS/UEFI from Intel
  silently ignore proper network broadcast packets. They only consider global
  broadcast packets. The Ethernet MAC of such clients should be added to
  the ``[buggy_clients]`` section, so that global broadcast packets are
  generated for these clients.


Configuration
-------------

``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 [-h] [-c CONFIG] [-p] [-t] [-d]
   Tiny BOOTP/DHCP/TFTP/PXE server

Options:
  -h, --help            show this help message and exit
  -c CONFIG, --config CONFIG
                        configuration file
  -p, --pxe             only enable BOOTP/DHCP/PXE server
  -t, --tftp            only enable TFTP server
  -H, --http            enable HTTP server (default: disabled)
  -d, --debug           enable debug mode

``pybootd`` daemon uses a configuration file, in ``.ini`` format, for all other
options.

Some options accept a boolean value. The following values are recognized:

- true values: ``on``, ``high``, ``true``, ``enable``, ``enabled``, ``yes``,
               ``1``
- false values: ``off``, ``low``, ``false``, ``disable``, ``disabled``, ``no``,
                ``0``

The BOOTP daemon associates each MAC address to an assigned IP address. As long
as the BOOTP daemon is running, the same IP address is always assigned to the
same client. The address never gets back to the pool, *i.e.* it cannot be
re-assigned to another machine even when the lease expires.

This is especially useful for a full network-based installation, where each
client requests at least an IP address twice:

- when BIOS kicks off, its PXE ROM code requests an IP address, then requests
  an executable to run,
- when the executable runs, it usually boots up an OS (Linux, ...), which in
  turn requests an IP address to resume the installation.

``[logger]`` section
....................

``type``
   The type of logger, if any. ``stderr``, ``file``, ``syslog`` or ``none``.

``level``
   The level of logger verbosity. ``critical``, ``error``, ``info`` or
   ``debug``.

``file``
   The path to the output log file, if ``type`` is set to ``file``.

``[bootpd]`` section
....................

``access``
   Type of access control list. If this option is not defined, all BOOTP
   requests are served, as long as the defined pool is not exhausted. It can be
   one among the following options:

   - ``mac``: incoming BOOTP requests are filtered out based on the MAC address
     of the requester.
   - ``uuid``: incoming PXE requests are filtered out based on the UUID of the
     request. UUIDs are not emitted from simple BOOTP or DHCP clients, so this
     option is only meaningful for PXE-enabled clients.
   - ``http``: incoming requests are forwarded to another host, through simple
     HTTP GET requests. The MAC address and the UUID if it exists, are sent
     to the HTTP server which replies to grant or deny access to the requester.

   A section named after the selected option should exist to define the access
   list.

``address``
   Specifies the network to listen to requesters for receiving incoming BOOTP
   requests. On most hosts, the only valid address is ``0.0.0.0``. Some hosts
   accept subnetworks (such as ``192.168.1.0``). It is recommended not to
   define this option, and use an ACL to reject clients. Hosts will multiple
   network interfaces, it might not be possible to listen to single network.
   Implementing such as feature would require to use RAW sockets, which falls
   out of scope for this simple server.

``allow_simple_dhcp``
   The default behaviour is to expect PXE requests. In order to serve simple
   BOOTP or DHCP requests, this option should be enabled. This option accepts
   a boolean value.

``domain``
   Domain part of the client FQDN, that is the network's domain name.

``dns``
   IP addresses of DNS servers. Multiple addresses are separated with
   semicolon. Specify ``auto`` to re-use DNS addresses used by the
   server. Note that most DHCP clients will only consider the first
   DNS address if multiple are provided.

``gateway``
   Specify gateway address in DHCP reply, default to DHCP server address

``lease_time``
   Validity in seconds of a DHCP lease. Please note that the BOOTP daemon does
   not manage lease expiration; this value has therefore little meaning.

``pool_start``
   First address to allocate for a BOOT client. This has to be an
   address in the local network you want to serve, even if
   ``pool_count`` is set to 0, in which case the address of the DHCP
   server is a good choice.

``pool_count``
   The maximum number of IP addresses that can be dynamically
   allocated from the pool to BOOTP/DHCP clients. Set it to 0 to
   prevent server from dynamically allocating IP addresses from the
   pool and see ``static_dhcp`` below.

``notify``
   When defined, the IP address and port (using a column separator:
   ``a.b.c.d:p``) to which a UDP notification message should be sent whenever
   a client requests an IP address to the BOOTP daemon.

``port``
   Alternative port for incoming BOOTP requests.

``timeout``
   Timeout in seconds for a response from a remote authentication host to be
   received, when ACL is enabled and set to use the HTTP protocol. If no answer
   is received from the remote host, the BOOTP daemon ignores the incoming
   BOOTP/DHCP request.

``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.*::

  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.

It is possible to use a mask syntax to specify a range of MACs addresses, for
example:

  AA-BB-CC/24 = enable

Accepts all Ethernet addresses whose OUI is AA:BB:CC.

``[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.*:

  AA-BB-CC-DD-EE-FF = 192.168.1.2

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.*::

  xxxxxxxx-aaaa-bbbb-cccc-yyyyyyyyyyyy = enable


``[http]`` section
..................

``location``
   The URL prefix to contact the remote server for boot permission.

``pxe``
   The path to append to the URL prefix when the requester emits PXE
   information. A regular PC with PXE capability emits a PXE boot request when
   the BIOS kicks off. The remote HTTP server may therefore identify a BIOS
   boot sequence upon receiving this kind of request from the *pybootd* daemon.

``dhcp``
   The path to append to the URL prefix when the requester emits simple DHCP
   information. A regular OS emits a simple DHCP request at start up. The
   remote HTTP server may therefore identify an OS boot sequence upon receiving
   this kind of request from the *pybootd* daemon.

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.


``[bootfile]`` section
......................

This section contains one entry for each supported architecture.
It defines the name of the initial boot file the client should request,
indexed on the architecture it reports, if any.

It should contain at least one entry, ``default``, which map to the bootfile
for clients that do no expose their architecture.

The bootfile is usually requested over TFTP to boot up after the client has
been assigned a network address.

Each entry is the architecture string, with a filename value.


``[buggy_clients]`` section
...........................

When a BOOTP client requests a network address, the BOOTP/DHCP server should
broadcast on the client's LAN the DHCP offerring. Using the client's network is
recommended, as it avoid broadcasting BOOTP/DHCP packets to other networks.

Some clients, notably the clients based on Intel firmwares, are stupid enough
to ignore DHCP offering which is broadcasted to the network broadcast address.
They do require the DHCP server to broadcast to the global ``255.255.255.255``
address.

This section lists the MAC of the clients that are so stupid they need this
global broadcast address to work. If you use Intel BIOS/UEFI, this option is
likely needed.

Each entry is a MAC address, using the ``-`` byte separator, with a boolean
value.

This section also accepts mask syntax, see ``[mac]`` section for details.


``[tftpd]`` section
...................

``address``
   Address to listen to incoming TFTP requests. When the BOOTP daemon is
   enabled this option is better omitted, as the address is automatically
   received from the BOOTP daemon.

``blocksize``
   Size of each exchanged data block. It is recommended to leave the default
   value, as some clients may not accept other values.

``port``
   Alternative port for incoming TFTP request.

``timeout``
   Timeout in seconds for an acknowledgment from the TFTP client to be
   received. If the timeout expires the TFTP server retransmits the last
   packet. It can be expressed as a real value.

``root``
   Base directory for the TFTP service. This path is automatically prepended
   to the pathname issued from the TFTP client. It can either be:

   - a relative path to the daemon directory, when the ``root`` option starts
     with ``./``,
   - an absolute path, when the ``root`` option starts with ``/``,
   - a URL prefix, to access remote files.


``[httpd]`` section
...................

``address``
   Address to listen to incoming HTTP requests. When the BOOTP daemon is
   enabled this option is better omitted, as the address is automatically
   received from the BOOTP daemon.

``port``
   Alternative port for incoming HTTP request, default to 80

``root``
   Base directory for the HTTP service. This path is automatically prepended
   to the pathname issued from the TFTP client. It can either point to a local
   directory for now.

``check_ip``
   Whether to enforce HTTP client IP or not. When enabled, requests from
   clients that have not obtained an IP address from the BOOTP daemon are
   rejected.


``[filters]`` section
.....................

The ``filters`` section allows on-the-fly pathnames transformation. When a TFTP
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.''
``pxelinux.cfg``) whatever the remote client, thus speeding up the boot
process. This option also enables to access files that are not stored within
the currently configured path (see the ``root`` option).

Each option of the ``filters`` section represents a file pattern to match. It
accepts standard wildcard characters: `*` and `?`. The option's value defines
the translated path.

The *value* part can contain variables. Variables are written with enclosing
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 *tftpd*
server to read the replacement pattern from a file. This special marker should
be written with enclosing brackets, such as ``[file]``.

Examples
........

The following filter::

  pxelinux.cfg/* = pybootd/etc/pxe.cfg

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
back to the simple ``pxelinux.cfg`` file.

The following filter::

  startup = [dir/{filename}.cfg]

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
-------------------------

This option enabled the delegation of the BOOTP authorization to a remote web
server. As *pybootd* emits standard HTTP GET requests and expects standard
HTTP reply codes, any web server may be used to manage authorizations.

This web server receives HTTP GET requests with URLs formatted as follows::

  http://server/path?mac=AA-BB-CC-DD-EE-FF&uuid=xxxxxxxx-aaaa-bbbb-cccc-yyyyyyyyyyyy

where:

- ``http://server`` matches the ``location`` option,
- ``/path`` matches the ``pxe`` or ``dhcp`` options of the ``[http]`` section.

The web server should reply either with:

- ``200 Ok`` result if the requester is to be assigned an IP address, or
- ``401 Unauthorized`` result if it is to be ignored.

The ``pybootd`` package contains a minimalist HTTP server that demonstrates
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
~~~~~~~~~~~~~~~~~~~~~

The ``pybootd.ini`` would contain::

  [logger]
  ; show requests on the standard error output of the daemon
  type = stderr
  ; show informative and error messages only (disable verbose mode)
  level = info

  [bootpd]
  ; do not force a full PXE boot-up cycle to accept the client
  allow_simple_dhcp = enable
  ; First BOOTP/DHCP address to generate
  pool_start = 192.168.1.100
  ; Google DNS
  dns = 8.8.8.8

  [bootfile]
  ; boot-up executable the client should request through TFTP (BIOS)
  default = pxelinux.0
  ; boot-up executable the client should request through TFTP (UEFI x86-64)
  00007 = shimx64.efi

  [tftpd]
  root = http://example.com/installer-amd64/images/netboot

  [filters]
  ; serve a simple configuration file to the linux PXE helper
  pxelinux.cfg/* = pybootd/etc/pxe.cfg

The ``pool_start`` parameter should be a valid address on the host's networks,
and the ``root`` URL may be changed to use alternative mirror and path.

Please note that to complete the network installation, the client should be
able to access the remote resources on its own - as with a network ISO image
installation. There are two ways to achieve this:

- either enable IP forwarding on the *pybootd* host (see ``forward.sh``
  script within the ``pybootd`` package), or
- be sure to connect the network cable of the client to a LAN that has direct
  access to the Internet, once the first installation stage is complete.