@sammachin/node-red-dhcp
v0.1.0
Published
Node-RED nodes for building a DHCP server: receive DHCP requests, construct the reply in a flow, and send it
Maintainers
Readme
node-red-contrib-dhcp
Node-RED nodes for building a DHCP server, where the flow decides what every reply contains.
[dhcp request] → [dhcp options] → [dhcp options] → … → [dhcp response]
emits MAC append params append params format + sendBuilt on node-dhcp, used purely as a protocol codec: its own server answers packets from a static, synchronous config, which a Node-RED flow can never drive. Here nothing is answered automatically — a request comes into the flow, the flow builds the reply, and the response node puts it on the wire.
Install
cd ~/.node-red
npm install node-red-contrib-dhcpThe nodes
dhcp request
Listens for DHCP messages and emits one message per packet. Configure the bind address and port (67 by default).
msg.topic // 'DISCOVER' | 'REQUEST' | 'INFORM' | 'RELEASE' | 'DECLINE'
msg.payload // { mac, xid, hostname, requestedIp, clientId, vendorClassId,
// requestedParams, ciaddr, giaddr, broadcastFlag, options }
msg.dhcp // handle for the downstream nodes - keep it on the messagemsg.payload.mac is normalised to aa:bb:cc:dd:ee:ff, so it can be compared against a
reservation table directly.
Several request nodes may bind the same address and port; they share one socket.
dhcp options
Appends parameters to the reply being built. Each node holds a list of rows — a parameter and a value — and you can chain as many nodes as you like; a later one wins for anything already set.
The dropdown lists every option node-dhcp knows about (netmask, router, dns,
leaseTime, domainName, bootFile, …) plus the four packet header fields:
| field | meaning |
| --- | --- |
| yiaddr | the address being offered — required for an OFFER or ACK |
| siaddr | next server / TFTP address |
| sname | server host name |
| file | boot file name |
Values are typed inputs, so they can come from the message, flow or global context, an
environment variable, or a JSONata expression. Each is converted to the parameter's DHCP
type — dns accepts 8.8.8.8, 8.8.4.4, leaseTime accepts 3600, nbNodeType accepts
H-node — and an unconvertible value raises an error a catch node can handle rather than
producing a malformed packet.
There is no address pool and no lease store. What to hand out is the flow's decision.
Vendor sub-options (option 43)
Option 43 is a container rather than a value: it holds a run of sub-options, each one a
code, a length and that many bytes, and what goes in it is the vendor's business. Selecting
vendor reveals a second line on the row for the sub-option code and how to encode the
value — text, an IPv4 address or list, an 8/16/32-bit number, or hex bytes.
| use | sub-option | encode as | value |
| --- | --- | --- | --- |
| UniFi controller | 1 | IPv4 address | 192.168.1.10 |
| PXE discovery control | 6 | 8-bit number | 11 |
| PXE boot server list | 8 | hex bytes | 80 00 01 c0 a8 01 0a |
Unlike other parameters, vendor rows accumulate rather than overwrite, so a block is built from as many rows — and as many nodes — as it needs. Leave the sub-option code blank to drop the encoded bytes into the block verbatim, and a value that is already an array of numbers is taken as raw bytes whatever the encoding, so a function node can build anything the encodings don't cover.
The example above puts 43 = 01 04 c0 a8 01 0a on the wire.
Options by numeric code
Any option, by numeric code sets any option at all — typically something in the 224–254 site-specific range, or one a vendor invented that has no name here. Give the code and the same choice of encoding as vendor sub-options:
| option code | encode as | value | on the wire |
| --- | --- | --- | --- |
| 224 | text | site-specific | e0 0d 73 69 74 65 … |
| 240 | IPv4 address | 10.0.0.1 | f0 04 0a 00 00 01 |
| 251 | hex bytes | de:ad:be:ef | fb 04 de ad be ef |
Codes that already have a name are refused, naming it — option 6 is 'dns' - select it by
name instead — since the value would otherwise be encoded two different ways.
Custom codes are registered with the protocol layer the first time they are sent, which also
means incoming packets carrying them are decoded rather than skipped. Anything else reading
the reply needs to do the same: node-dhcp's parser silently drops codes it doesn't know, as
test/send-discover.js does for the site-specific range.
dhcp response
Formats everything accumulated and sends it from the request node's socket.
- Reply — automatic (DISCOVER → OFFER, REQUEST/INFORM → ACK), or a fixed OFFER, ACK or
NAK. A NAK carries no address; give a reason with the
messageoption. - Server ID — option 54. Defaults to the bind address, or the host's first non-internal IPv4 address.
- Send to — automatic follows RFC 2131 §4.1: the relay agent if
giaddris set, otherwise unicast tociaddrif the client already has an address, otherwise broadcast. Unicasting to the offered address would need a raw socket to prime the ARP cache, which Node cannot do. back to sender replies to the source address and port, which is what a real client sees anyway (clients always send from port 68) and lets a test client on an ephemeral port get the reply without root.
Trying it out
Import DHCP server from the Node-RED examples menu (Import → Examples → node-red-contrib-dhcp). It listens on 127.0.0.1:6767, so no privileges are needed:
node test/send-discover.js --port 6767 --mac aa:bb:cc:dd:ee:ffThe script sends a DISCOVER and prints the parsed OFFER.
For real clients, set the request node to port 67 and the response node's Send to back to automatic. Port 67 is privileged: run Node-RED as root, or grant the capability once:
sudo setcap 'cap_net_bind_service=+ep' $(readlink -f $(which node))Watch the exchange with:
sudo tcpdump -i eth0 -n port 67 or port 68 -vvDo this on an isolated network. A second DHCP server on a live LAN will hand out addresses to anything that asks.
Tests
npm testCovers the option catalogue and coercion, reply construction (including a round trip through node-dhcp's parser), destination selection, and a real DISCOVER → OFFER exchange over loopback.
Licence
MIT
