Skip to content

Instantly share code, notes, and snippets.

@pletch
Last active February 6, 2026 08:10
Show Gist options
  • Select an option

  • Save pletch/c9b208c1bbc1bd33de19d0ad03f96f82 to your computer and use it in GitHub Desktop.

Select an option

Save pletch/c9b208c1bbc1bd33de19d0ad03f96f82 to your computer and use it in GitHub Desktop.
Scrape OpnSense DHCP Leases Status Page and Export Results as List
#!/usr/bin/env python3
#
# This python script provides a function to query the opnSense (v21.3 - v23.1.x) dhcp leases status page and
# return a list of tuples including ip, hostname, and mac address. This script will not work with version 23.7.x+
# due to changes in the DHCP lease page.
# See comment from dheldt on modified version that works using api searchLeases page.
# To use the original script, ensure LXML is installed via package manager or via pip.
#
# 27-Mar-2021 - Original release
# 17-Jul-2022 - Fix url in scrape function. Add error trapping for case where user/pass are not set up correctly.
# 24-Sep-2023 - Add note about 23.7.x incompatibility.
#
import sys
import requests
from lxml import html
import re
url = "http://192.168.1.1/status_dhcp_leases.php" #change url to match your opnSense machine address. Note http or https!
user = 'your_username' #Username for opnSense login (default is 'root')
password = 'your_password' #Password for opnSense login
def scrape_opnsense_dhcp(url, user, password):
ip = []
mac = []
hostname = []
s = requests.session()
r = s.get(url,verify = False)
matchme = '"X-CSRFToken", "(.*)" \);'
csrf = re.search(matchme,str(r.text))
payload = {
'login' : 'Login',
'usernamefld' : user,
'passwordfld' : password
}
r = s.post(url,data=payload,verify = False,headers={"X-CSRFToken":csrf.group(1)})
r = s.get(url,verify = False)
tree = html.fromstring(r.content)
tr_elements = tree.xpath('//tr')
try:
headers = [header.text for header in tr_elements[0]]
except IndexError:
print("Error retrieving lease list. Are you sure username and password were set up in script?")
quit()
ip.extend(tree.xpath('//table[@class="table table-striped"]//tbody//tr//td[' + str(headers.index('IP address') + 1) +']//text()'))
for node in tree.xpath('//table[@class="table table-striped"]//tbody//tr//td['+ str(headers.index('MAC address') + 1) +']'):
if bool(re.search(r'([0-9a-f]{2}(?::[0-9a-f]{2}){5})', node.text)):
mac.append(node.text)
for node in tree.xpath('//table[@class="table table-striped"]//tbody//tr//td['+ str(headers.index('Hostname') + 1) +']'):
if node.text is None:
hostname.append('no_hostname')
else:
hostname.append(node.text)
for i in range(len(mac)):
mac[i] = mac[i].strip()
return(list(zip(ip, mac, hostname)))
if __name__ == "__main__":
dhcp_list = scrape_opnsense_dhcp(url, user, password)
for entry in dhcp_list:
print(entry)
@f-vicente
Copy link

Thanks for the info in the comments.

I used the last idea (using the URL for the api) to implement an easy way to migrate to Kea DHCP just if anybody needs, is here https://gist.github.com/f-vicente/9967d3d69bc885601647ff4d6528b29b (tested on OpnSense 25.7)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment