Python3 script which using YAML file as host config to offer DHCP/IP for device.
System sniff DHCP packets and answer using own build DHCP responses (based on scapy library)
Built-in TFTP/HTTP servers allow host download config/firmware which was pointed in dhcp option.
No more installing Isc/Kea DHCP and configuring DHCP scopes/TFTP/HTTP...
[root@server ztp]# ./ztp.py -h
usage: ztp.py [-h] [-i INTERFACE] [-l LIMIT] [-p PATH] [--port_tftp PORT_TFTP]
[--port_http PORT_HTTP] [-d PCAP]
optional arguments:
-h, --help show this help message and exit
-i INTERFACE, --interface INTERFACE
Interface to service requests
-p PATH, --path PATH TFTP folder path. Set `None` to disable TFTP
--port_tftp PORT_TFTP
TFTP port
--port_http PORT_HTTP
HTTP port
-d PCAP, --pcap PCAP collect PCAP file name for debug
[root@server ztp]# pmap 3068 | grep total
total 410900K
VM0021210000:
mac: 50:00:21:21:00:00
hostname: Spine-21
domain: juniper.lab
ip: 10.240.40.21
subnet_mask: 255.255.252.0
router: 10.240.40.1
name_server: 8.8.8.8 // (only SINGLE server supported)
lease_time: 60
tftp_server_name: 10.240.40.254
boot-file-name: juniper.config
tftp_server_address: 10.240.40.254
vendor_specific: 0:junos.tgz,1:VM0021210000/juniper.sh,3:http
Config needs to be store in tftp folder in single / or multiple yaml files.
Configs are checked/read everytime when new dhcp Discovery pacet would be capture.
This consuming some resorces (utlize disk) but allow you 'in fly' modyfie config without restarting ztp instance.
YAML Config format:
<hostname/name>:
mac: <mac - allow format with seperators like .:->
<parameter>: <value>
In some cases new unboxed device report as hostname their serial number so in this situation, if script discovery that hostname inside Discovery message match this <hostname/name> - it will use this config section to send Offer.
If hostname/name will not be found in config files - it will check in 2nd step - mac match - for DHCP request in all configs.
parameters are related to scapy dhcp layer lib which can be found under this Link -> DHCPOptions
Please note that parameters tftp_server_name (option 66) and tftp_server_address (option 150) are missing in scapy layer lib so it needs to be added manually:
sed -i 's/ 67: StrField/ 66: "tftp_server_name",\n 67: StrField/g' /usr/local/lib/python3.6/site-packages/scapy/layers/dhcp.py
sed -i 's/ 255: "end"/ 150: IPField("tftp_server_address", "0.0.0.0"),\n 255: "end"/g' /usr/local/lib/python3.6/site-packages/scapy/layers/dhcp.py
Scapy issie 2747
Please remember that this docker need to be exposed to net=host as we need to get access to broadcast packages (scapy promisc mode). Use IFACE env on which interface docker should be listening.
Download from docker.hub
[user@localhost ~]# docker pull nchekwa/python_ztp
[user@localhost ~]# docker run --name ztp --net=host -e IFACE="eth1" -v /tftp:/opt/ztp/tftp --rm -ti nchekwa/python_ztp
[user@localhost ~]# docker create \
--name=ztp \
--net=host \
-e IFACE="eth1" \
-v /tftp:/opt/ztp/tftp \
--restart unless-stopped \
nchekwa/python_ztp:latest
[user@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
22cf97689207 nchekwa/python_ztp:latest "/bin/sh -c '\"python…" About a minute ago Created ztp
[user@localhost ~]# docker start ztp
ztp
[user@localhost ~]# docker logs -f --tail 10 ztp
Running. Press CTRL-C to exit.
[facts] DHCP/TFTP interface: eth1
[facts] eth1 IP: 10.240.40.254
[facts] eth1 MAC: 50:00:00:33:00:01
[facts] File folder status: exist
[facts] File Path: /opt/ztp/tftp
[OK] TFTP starting on 10.240.40.254 - port 69
[Warning] HTTP 10.240.40.254:80 port in use
[OK] DHCP starting sniffer eth1 - udp and (port 67 or 68)
eth1|<- [Rcv][Discover][0x61ec0f9e] Host VM0033330000 (50:00:33:33:00:00) asked for an IP
eth1|<- [Rcv][Discover][0x7c78d728] Host VM0033330000 (50:00:33:33:00:00) asked for an IP
curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose version 1.26.2, build eefe0d31
---
version: "3"
services:
python_ztp:
image: nchekwa/python_ztp
container_name: ztp
network_mode: host
environment:
- IFACE=eth1
volumes:
- /tftp:/opt/ztp/tftp
restart: unless-stopped
[user@localhost ~]# docker-compose -f docker-compose.yaml up -d
[user@localhost ~]# docker-compose -f docker-compose.yaml down