An efficient programmable BOOTP/DHCP client/server/relay (re-)written in Go.
pdhcp
is a DHCP toolbox, which can be used to implement client, server or relay roles in a standard
DHCP infrastructure. Contrary to most DHCP packages, pdhcp
does not directly implement the business
logic of a standard DHCP system, but relies on external helpers (or backends) for that.
The communication protocol between pdhcp
and these external helpers is based on JSONL:
each DHCP request or response is translated into its JSON object equivalent, serialized, and sent to/received from
the external backends. For instance in client mode (see documentation below), this backend is basically the shell
script starting pdhcp
, and DHCP responses from servers are simply emitted on the standard output
(gron makes JSON grepable):
$ pdhcp -i eth0 |gron |gron -u
{
"address-lease-time": 604800,
"bootp-assigned-address": "192.168.40.150",
"bootp-broadcast": true,
"bootp-filename": "undionly.kpxe",
"bootp-hardware-length": 6,
"bootp-hardware-type": "ethernet",
"bootp-opcode": "reply",
"bootp-relay-hops": 0,
"bootp-server-address": "192.168.40.1",
"bootp-start-time": 0,
"bootp-transaction-id": "9acb0442",
"client-hardware-address": "aa:ff:19:93:8d:a6",
"dhcp-message-type": "offer",
"domain-name": "domain.com",
"domain-name-servers": [
"192.168.40.1"
],
"hostname": "server",
"routers": [
"192.168.40.1"
],
"server-identifier": "192.168.40.1",
"subnet-mask": "255.255.255.0"
}
The wrapper script calling pdhcp
is then responsible for chaining the necessary calls, for instance to perform
a DHCP lease allocation (known as a DORA
cycle). It may seem like a lot of extra work compared to existing DHCP programs, but it's quite easy to implement
in practice and brings a lot of flexibility in your DHCP infrastructure workflows (especially for large datacenters
servers provisioning scenarii, which is one of the cases pdhcp
was originally designed for).
pdhcp
implements RFC2131 / RFC2132 and most of their subsequent updates.
You may need to install a recent version of the Golang compiler (>= 1.13) and the GNU make
utility to build the pdhcp
binaries,. Once these requirements are fulfilled, clone the pdhcp
Github repository locally:
$ git clone https://github.com/pyke369/pdhcp
and type:
$ make
This will take care of building everything. You may optionally produce a Debian package by typing:
$ make deb
(the devscripts package needs to be installed for this last command)
A basic help screen is displayed by using the -h
command-line parameter:
$ pdhcp -h
usage: pdhcp [OPTIONS...]
options are:
-6 run in IPv6 mode
-R string
add/modify DHCP attributes in the default client request
-a string
use an alternate listen address (default "0.0.0.0")
-b string
specify the backend command path or URL
-f string
provide an alternate logging configuration
-h show this help screen
-i string
specify a comma-separated list of interfaces to use
-j list all available DHCP options (JSON format)
-l list all available DHCP options (human format)
-p int
use an alternate DHCP port (default 67)
-r string
specify the remote DHCP server address in relay mode
-s string
use an alternate local relay address
-v show the program version
-w int
change the backend workers count (default 1)
The command-line options not specific to a particular running mode are described here:
-v
: show the program version and exit:
$ pdhcp -v
pdhcp/v2.0.0
-f
: change the logging configuration string, according to the ulog syntax:
$ pdhcp -f 'file(path=/var/log/pdhcp-%Y%m%d.log) syslog(facility=daemon,name=dhcp-server)'
-l
: list DHCP options used in the JSONL-based communication protocol, in human-readable format:
$ pdhcp -l
option type id
--------------------------------------- --------------------------------------- ---
bootp-opcode BOOTP opcode -
bootp-hardware-type hardware address type -
bootp-hardware-length 8bits integer -
bootp-relay-hops 8bits integer -
bootp-transaction-id hex-encoded blob -
bootp-start-time 16bits integer -
bootp-broadcast boolean -
bootp-client-address IPv4 address -
bootp-assigned-address IPv4 address -
bootp-server-address IPv4 address -
bootp-relay-address IPv4 address -
client-hardware-address colon-separated hex-encoded blob -
bootp-server-name string -
bootp-filename string -
subnet-mask IPv4 address 1
time-offset 32bits integer 2
routers IPv4 addresses list 3
time-servers IPv4 addresses list 4
...
private-27 hex-encoded blob 250
private-28 hex-encoded blob 251
private-29 hex-encoded blob 252
private-30 hex-encoded blob 253
private-31 hex-encoded blob 254
-j
: list DHCP options used in the JSONL-based communication protocol, in machine-parseable format (JSON):
$ pdhcp -j |gron |gron -u
{
"address-lease-time": {
"id": 51,
"list": false,
"mode": "integer"
},
"all-subnets-local": {
"id": 27,
"list": false,
"mode": "boolean"
},
"arp-cache-timeout": {
"id": 35,
"list": false,
"mode": "integer"
},
"associated-addresses": {
"id": 92,
"list": true,
"mode": "inet4"
},
"authentication": {
"id": 90,
"list": false,
"mode": "binary"
},
"auto-configuration": {
"id": 116,
"list": false,
"mode": "integer"
},
...
}
-p
: use the specified DHCP server port (default is 67 for DHCPv4, 547 for DHCPv6); the client port is automatically adjusted against this value (+1=68 for DHCPv4, -1=546 for DHCPv6).
$ pdhcp ... -p 6767 ...
-6
: switch to DHCPv6.
$ pdhcp ... -6 ...
The following options can be used in client mode (in addition to the general options above):
-i
: specify the network interface name thepdhcp
client will be using to send DHCP requests; this parameter is mandatory in client mode:
$ pdhcp ... -i eth0 ...
-R
: add or modify options in the client DHCP request (in JSON format); the client will send the following request by default:
{
"bootp-transaction-id": "<random-transaction-id>",
"bootp-broadcast": true,
"dhcp-message-type": "discover",
"client-hardware-address": "<interface-MAC-address>",
"parameters-request-list": [ "hostname", "subnet-mask", "routers", "domain-name", "domain-name-servers", "time-offset", "ntp-servers" ],
"requested-ip-address": "<interface-IP-address>", (if available)
"hostname": "<FQDN>" (if available)
}
For instance, the DHCP message type may be changed in the DHCP request above by using the following syntax:
$i pdhcp ... -R '{"dhcp-message-type":"request"}' ...
The following options can be used in server mode (in addition to the general options above):
-i
: specify the network interfaces thepdhcp
server will listen on to receive DHCP requests from clients; any number of interfaces may be specified; if this parameter is not used at all, thepdhcp
server will then only listen for requests from remote DHCP relays:
$ pdhcp ... -i eth0,eth1,eth3.456,br0 ...
-b
: specify the executable path (local CGI mode) or URL (remote HTTP mode) used as backend:
$ pdhcp ... -b /usr/share/pdhcp/local-backend.py ...
$ pdhcp ... -b https://user:password@server.domain.com/dhcp ...
-w
: when the associated backend is a local executable (CGI mode), specify the number of forked processes (a.k.a "workers"); DHCP requests from clients will be load-balanced among all available workers:
$ pdhcp ... -w 8 ...
The following options can be used in relay mode (in addition to the general options above):
-i
: specify the network interfaces thepdhcp
relay will listen on to receive DHCP requests from clients; any number of interfaces may be specified, and this parameter is mandatory in relay mode.pdhcp
is capable of listening on non-broadcast interfaces in relay mode, which may prove useful to receive remote DHCP server responses on tunnel or virtual interfaces:
$ pdhcp ... -i eth3,br2,eth0.321 ...
-r
: specify the remote server to relay DHCP requests to; this parameter is mandatory in relay mode and a custom port may be specified if needed (67 by default):
$ pdhcp ... -r dhcp-server.domain.com:6767 ...
-s
: the giaddr field (gateway IP address) of the relayed BOOTP packets contains by default the first IP address detected on the interface receiving the DHCP client requests; using this parameter provide a way to overload this value (note it should never be used in a regular production setup):
$ pdhcp ... -s 192.168.40.10 ...
Some backend examples are provided in the support
sub-folder, and briefly described here:
local-backend.py
: a very basic (but fully functionnal) backend written in Python, designed to run along thepdhcp
server process, with the following minimalist command-line:
$ pdhcp -b support/local-backend.py -w 4
2020-02-27 16:16:38.312 INFO {"event":"start","mode":"backend","pid":21263,"version":"2.0.0"}
2020-02-27 16:16:38.313 INFO {"event":"listen","listen":"-@0.0.0.0:67"}
2020-02-27 16:16:38.313 INFO {"backend":"support/local-backend.py","event":"worker-start","worker":21269}
2020-02-27 16:16:38.313 INFO {"backend":"support/local-backend.py","event":"worker-start","worker":21270}
2020-02-27 16:16:38.314 INFO {"backend":"support/local-backend.py","event":"worker-start","worker":21271}
2020-02-27 16:16:38.315 INFO {"backend":"support/local-backend.py","event":"worker-start","worker":21272}
remote-backend.php
: the same backend as above, but written in PHP and supposed to be hosted behind a local or remote HTTP tiers; an example of invocation frompdhcp
:
$ pdhcp -b https://some-php-server.com/remote-backend.php
2020-02-27 16:17:48.997 INFO {"event":"start","mode":"backend","pid":21333,"version":"2.0.0"}
2020-02-27 16:17:48.998 INFO {"event":"listen","listen":"-@0.0.0.0:67"}
http-backend
: a full-featured HTTP backend written in Go, with static and dynamic (leases management) support; the configuration is read from a tree of files, which allows for a clean and potentially complex setup:
$ support/http-backend support/http-backend.conf
2020-02-27 16:18:24 INFO {"config":"http-backend.conf","event":"start","pid":21379,"version":"2.0.0"}
2020-02-27 16:18:24 INFO {"event":"listen","listen":"*:8000"}
- DHCPv6 is not supported yet.
- *BSD (incl. Darwin/MacOS) platform-specific code (BPF-based) is also not there yet.