Skip to content

Commit

Permalink
More settings related to DNS
Browse files Browse the repository at this point in the history
Include DNS in the web UI
  • Loading branch information
f18m committed Dec 10, 2024
1 parent 5a078e4 commit 4b0f05f
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 25 deletions.
7 changes: 7 additions & 0 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ non-informative, so Dnsmasq-DHCP allow users to override that by specifying a hu
name for a particular DHCP client (using its MAC address as identifier).


### HomeAssistant mDNS

HomeAssistant runs an [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) server on port 5353.
This is not impacted in any way by the DNS server functionality offered by this addon.



## Links

- [dnsmasq manual page](https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html)
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ test-docker-image:
${DOCKER_RUN_OPTIONS} \
${IMAGETAG}:localtest


# NOTE: in the HTTP link below the port is actually the one in test-options.json, and currently it's 8976
test-docker-image-live:
docker build -f Dockerfile.live -t debug-image-live .
@echo
@echo "Starting container of image debug-image-live"
@echo "Point your browser at http://localhost:8976"
@echo
docker run \
--rm \
--name $(TEST_CONTAINER_NAME) \
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This addon also implements a UI webpage to view the list of DHCP clients with al

## About

This addon setups and manages a Dnsmasq instance configured to run as a DNS and DHCP server (despite the name 'dnsmasq' also provides DHCP server functionalities, not only DNS).
This addon setups and manages a Dnsmasq instance configured to run both as a DNS and DHCP server (despite the name 'dnsmasq' also provides DHCP server functionalities, not only DNS).

[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
Expand Down Expand Up @@ -64,3 +64,16 @@ The UI nginx reverse-proxy configuration has been adapted from:
## How to Install

Check out the [addon docs](DOCS.md). Open an issue if you hit any problem.

## Similar Addons

* [dnsmasq](https://github.com/home-assistant/addons/tree/master/dnsmasq): a simple DNS server addon (no DHCP).
* [AdGuard Home](https://github.com/hassio-addons/addon-adguard-home): network-wide ads & trackers blocking DNS server. It also includes an embedded DHCP server.

## Other Noteworthy Projects

* [pihole](https://pi-hole.net/): pi-hole embeds a modified dnsmasq variant (they named it FTL, Faster Than Light) which provides a bunch of DNS metrics that are missing from the regular dnsmasq binary.

## Future Developments

It might be interesting to
4 changes: 4 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,12 @@ options:
dns_server:
# should this addon provide also a DNS server?
enable: true
# on which port the dnsmasq DNS server must listen to?
port: 53
# how many entries should be cached on the DNS server to reduce traffic to upstream DNS servers?
# the max size for this cache is 10k entries according to dnsmasq docs
cache_size: 10000
# log_requests will enable logging all DNS requests... which results in a very verbose log!!
log_requests: false
# DNS domain to resolve locally
dns_domain: lan
Expand Down Expand Up @@ -136,6 +139,7 @@ schema:
link: "str?"
dns_server:
enable: bool
port: int
cache_size: int
log_requests: bool
dns_domain: str
Expand Down
20 changes: 17 additions & 3 deletions dhcp-clients-webapp-backend/pkg/uibackend/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,11 @@ type IpAddressReservation struct {
Link *template.Template // maybe nil
}

// AddonConfig is used to unmarshal HomeAssistant option file correctly
// This must be updated every time the config.yaml of the addon is changed
// AddonConfig is used to unmarshal HomeAssistant option file correctly.
// This must be updated every time the config.yaml of the addon is changed;
// however this structure contains only fields that are relevant to the
// UI backend behavior. In other words the addon config.yaml might contain
// more settings than those listed here.
type AddonConfig struct {
// Static IP addresses, as read from the configuration
ipAddressReservationsByIP map[netip.Addr]IpAddressReservation
Expand All @@ -124,9 +127,13 @@ type AddonConfig struct {
// Lease times
defaultLease string
addressReservationLease string

// DNS
dnsEnable bool
dnsDomain string
}

// readAddonConfig reads the configuration of this Home Assistant addon and converts it
// UnmarshalJSON reads the configuration of this Home Assistant addon and converts it
// into maps and slices that get stored into the UIBackend instance
func (b *AddonConfig) UnmarshalJSON(data []byte) error {

Expand All @@ -153,6 +160,11 @@ func (b *AddonConfig) UnmarshalJSON(data []byte) error {
AddressReservationLease string `json:"address_reservation_lease"`
} `json:"dhcp_server"`

DnsServer struct {
Enable bool `json:"enable"`
DnsDomain string `json:"dns_domain"`
} `json:"dns_server"`

WebUI struct {
Log bool `json:"log_activity"`
Port int `json:"port"`
Expand Down Expand Up @@ -237,6 +249,8 @@ func (b *AddonConfig) UnmarshalJSON(data []byte) error {
b.webUIPort = cfg.WebUI.Port
b.defaultLease = cfg.DhcpServer.DefaultLease
b.addressReservationLease = cfg.DhcpServer.AddressReservationLease
b.dnsEnable = cfg.DnsServer.Enable
b.dnsDomain = cfg.DnsServer.DnsDomain

return nil
}
Expand Down
26 changes: 21 additions & 5 deletions dhcp-clients-webapp-backend/pkg/uibackend/uibackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (b *UIBackend) logRequestMiddleware(next http.Handler) http.Handler {
})
}

func (b *UIBackend) getWebSocketMessage() WebSocketMessage {
func (b *UIBackend) generateWebSocketMessage() WebSocketMessage {

// get a copy of latest status -- lock it during the copy, to avoid race conditions
// with the dnsmasq.leases watcher goroutine:
Expand Down Expand Up @@ -239,7 +239,7 @@ func (b *UIBackend) handleWebSocketConn(w http.ResponseWriter, r *http.Request)
}
defer ws.Close()

msg := b.getWebSocketMessage()
msg := b.generateWebSocketMessage()
b.logger.Infof("Received new websocket client: pushing %d/%d current/past DHCP clients to it",
len(msg.CurrentClients), len(msg.PastClients))

Expand Down Expand Up @@ -273,7 +273,7 @@ func (b *UIBackend) broadcastUpdatesToClients() {

ticker := time.NewTicker(10 * time.Second)

msg := b.getWebSocketMessage()
msg := b.generateWebSocketMessage()
for {
select {
case <-b.broadcastCh:
Expand All @@ -291,7 +291,7 @@ func (b *UIBackend) broadcastUpdatesToClients() {

if len(b.clients) > 0 {
// regen message
msg = b.getWebSocketMessage()
msg = b.generateWebSocketMessage()

// loop over all clients
numSuccess := 0
Expand Down Expand Up @@ -388,18 +388,28 @@ func (b *UIBackend) renderPage(w http.ResponseWriter, r *http.Request) {
dhcpPoolSize = int(iprange.New(b.cfg.dhcpStartIP, b.cfg.dhcpEndIP).Size().Int64())
}

// DNS
dnsEnableString := "disabled"
if b.cfg.dnsEnable {
dnsEnableString = "enabled"
}

templateData := struct {
// websockets
WebSocketURI string

// config info that are handy to have in the UI page
// DHCP config info that are handy to have in the UI page
DhcpStartIP string
DhcpEndIP string
DhcpPoolSize int
DefaultLease string
AddressReservationLease string
DHCPServerStartTime int64

// DNS config info
DnsEnabled string
DnsDomain string

// embedded contents
CssFileContent template.CSS
JavascriptFileContent template.JS
Expand All @@ -420,6 +430,11 @@ func (b *UIBackend) renderPage(w http.ResponseWriter, r *http.Request) {
// time of this app
DHCPServerStartTime: b.startTimestamp.Unix(),

// DNS config info
DnsEnabled: dnsEnableString,
DnsDomain: b.cfg.dnsDomain,

// embedded contents
CssFileContent: template.CSS(b.cssContents),
JavascriptFileContent: template.JS(b.jsContents),
}
Expand Down Expand Up @@ -522,6 +537,7 @@ func (b *UIBackend) evaluateLink(hostname string, ip netip.Addr, mac net.Hardwar
"ip": ip.String(),
"hostname": hostname,
"friendly_name": friendlyName,
"dns_domain": b.cfg.dnsDomain,
})
if err != nil {
b.logger.Warnf("failed to render the link template [%v]", theTemplate)
Expand Down
6 changes: 3 additions & 3 deletions dhcp-clients-webapp-backend/templates/dnsmasq-dhcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function formatTimeSince(unixPastTimestamp) {
const seconds = Math.floor((timeDifference % msecsInMinute) / 1000);

// Format the time as a string
const dayPart = days > 0 ? `${days} day${days > 1 ? 's' : ''}, ` : '';
const dayPart = days > 0 ? `${days}d, ` : '';
const timePart = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

return dayPart + timePart;
Expand Down Expand Up @@ -175,7 +175,7 @@ function processWebSocketEvent(event) {
console.error('Error while parsing JSON:', error);
}

var message = document.getElementById("message");
var message = document.getElementById("dhcp_stats_message");

if (data === null) {
console.log("Websocket connection: received an empty JSON");
Expand Down Expand Up @@ -267,7 +267,7 @@ function processWebSocketEvent(event) {
message.innerHTML = "<span class='boldText'>" + data.current_clients.length + " DHCP current clients</span> hold a DHCP lease.<br/>" +
dhcp_static_ip + " have a static IP address configuration.<br/>" +
dhcp_addresses_used + " are within the DHCP pool. DHCP pool usage is at " + usagePerc + "%.<br/>" +
"<span class='boldText'>" + data.past_clients.length + " DHCP past clients</span> contacted the server some while ago but failed to do so since last DHCP server restart, " +
"<span class='boldText'>" + data.past_clients.length + " DHCP past clients</span> contacted the server some time ago but failed to do so since last DHCP server restart, " +
uptime_str + " hh:mm:ss ago.<br/>";
}
}
Expand Down
40 changes: 28 additions & 12 deletions dhcp-clients-webapp-backend/templates/index.templ.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,29 @@
</script>
</head>
<body>
<h1 class="topLevel">DHCP Clients</h1>

<p class="topLevel" id="addon_cfg">
The configured DHCP range is: <span class="monoText">{{ .DhcpStartIP }} - {{ .DhcpEndIP }}</span>.
The default lease time is <span class="monoText">{{ .DefaultLease }}</span>.
The lease time for clients with IP address reservations is <span class="monoText">{{ .AddressReservationLease }}</span>.<br/>
</p>
<p class="topLevel" id="message"></p>
<h1 class="topLevel">Dnsmasq-DHCP addon</h1>

<div class="container">
<div class="tabs">
<div class="tabs__pills">
<button class="btn active" data-id="current_clients">Current DHCP Clients</button>
<button class="btn" data-id="past_clients">Past DHCP Clients</button>
<button class="btn active" data-id="dhcp_summary">DHCP Summary</button>
<button class="btn" data-id="dhcp_current_clients">Current DHCP Clients</button>
<button class="btn" data-id="dhcp_past_clients">Past DHCP Clients</button>
<button class="btn" data-id="dns_summary">DNS Summary</button>
</div>

<div class="tabs__panels">
<div id="current_clients" class="active">
<div id="dhcp_summary" class="active">
<h2>DHCP Server Summary</h2>

<p class="topLevel" id="dhcp_addon_cfg">
The configured DHCP range is: <span class="monoText">{{ .DhcpStartIP }} - {{ .DhcpEndIP }}</span>.<br/>
The default lease time is <span class="monoText">{{ .DefaultLease }}</span>.
The lease time for clients with IP address reservations is <span class="monoText">{{ .AddressReservationLease }}</span>.<br/>
</p>
<p class="topLevel" id="dhcp_stats_message"></p>
</div>
<div id="dhcp_current_clients">

<!-- the Datatables.net table will be attached to this TABLE element -->
<table id="current_table" class="display" width="100%"></table>
Expand All @@ -67,12 +72,23 @@ <h1 class="topLevel">DHCP Clients</h1>
<span class="monoText">HH:MM:SS</span>.</li>
</ul>
</div>
<div id="past_clients">
<div id="dhcp_past_clients">

<!-- the Datatables.net table will be attached to this TABLE element -->
<table id="past_table" class="display" width="100%"></table>

</div>
<div id="dns_summary">
<h2>DNS Server Summary</h2>

<p class="topLevel" id="dns_addon_cfg">
DNS server is: <span class="monoText">{{ .DnsEnabled }}</span><br/>
DNS domain: <span class="monoText">{{ .DnsDomain }}</span>
</p>

<p class="topLevel" id="dns_stats_message"></p>

</div>
</div>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions rootfs/usr/share/tempio/dnsmasq.config
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ user=root
{{ if not .dns_server.enable }}
# port=0 disables dnsmasq's DNS server functionality.
port=0
{{ else }}
port={{ .dns_server.port }}
{{ end }}

# do not use the DNS servers specified in /etc/resolv.conf:
Expand Down

0 comments on commit 4b0f05f

Please sign in to comment.