Skip to content

Commit ec2dd11

Browse files
committed
netlink: add netlink monitoring for real-time iface/addr changes
Subscribe to RTMGRP_LINK, RTMGRP_IPV4_IFADDR, and RTMGRP_IPV6_IFADDR events so interface and address changes are detected immediately instead of waiting for the 10-second polling cycle. The netlink socket fd is added to the main select() loop alongside the per-interface mDNS sockets. When an RTM_NEWLINK, RTM_DELLINK, RTM_NEWADDR, or RTM_DELADDR event arrives, sys_init() is called to rescan interfaces right away. ENOBUFS (kernel event overflow) is handled by treating it as an implicit change, ensuring no update is ever silently missed. The 10-second poll is retained as a safety net on all platforms. Platform portability is handled entirely in netlink.h: Linux gets real implementations in netlink.c, while other platforms get static inline no-op stubs so mdnsd.c requires no preprocessor guards. Closes #48 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent f3a4280 commit ec2dd11

4 files changed

Lines changed: 188 additions & 2 deletions

File tree

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ if ENABLE_MQUERY
88
bin_PROGRAMS = mquery
99
endif
1010

11-
mdnsd_SOURCES = mdnsd.c mdnsd.h addr.c conf.c queue.h mcsock.c mcsock.h
11+
mdnsd_SOURCES = mdnsd.c mdnsd.h addr.c conf.c queue.h mcsock.c mcsock.h netlink.c netlink.h
1212
mdnsd_LDADD = ../libmdnsd/libmdnsd.la $(LIBS) $(LIBOBJS)
1313

1414
mquery_SOURCES = mquery.c mcsock.c mcsock.h

src/mdnsd.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@
4444
#include "config.h"
4545
#include "mcsock.h"
4646
#include "mdnsd.h"
47+
#include "netlink.h"
4748

48-
#define SYS_INTERVAL 10 /* System interface poll interval */
49+
#define SYS_INTERVAL 10 /* System interface poll interval, safety net */
4950

5051
static volatile sig_atomic_t running = 1;
5152
static volatile sig_atomic_t reload = 0;
@@ -270,6 +271,7 @@ int main(int argc, char *argv[])
270271
fd_set fds;
271272
int timeout = 0;
272273
int c, rc;
274+
int nl_sd = -1;
273275

274276
prognm = progname(argv[0]);
275277
while ((c = getopt(argc, argv, "H:h"
@@ -342,11 +344,17 @@ int main(int argc, char *argv[])
342344
sig_init();
343345
sys_init();
344346
pidfile(PACKAGE_NAME);
347+
nl_sd = netlink_init();
345348

346349
while (running) {
347350
int nfds = 0;
348351

349352
FD_ZERO(&fds);
353+
if (nl_sd >= 0) {
354+
FD_SET(nl_sd, &fds);
355+
if (nl_sd > nfds)
356+
nfds = nl_sd;
357+
}
350358
for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) {
351359
if (iface->sd < 0 || iface->unused)
352360
continue;
@@ -377,6 +385,11 @@ int main(int argc, char *argv[])
377385
continue;
378386
}
379387

388+
if (nl_sd >= 0 && FD_ISSET(nl_sd, &fds)) {
389+
if (netlink_read(nl_sd) > 0)
390+
sys_init();
391+
}
392+
380393
if (sys_timeout(&timeout))
381394
sys_init();
382395

@@ -408,6 +421,7 @@ int main(int argc, char *argv[])
408421
for (iface = iface_iterator(1); iface; iface = iface_iterator(0))
409422
free_iface(iface);
410423
iface_exit();
424+
netlink_exit(nl_sd);
411425

412426
return 0;
413427
}

src/netlink.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* Netlink interface monitor for Linux
2+
*
3+
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are met:
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the distribution.
13+
* * Neither the name of the copyright holders nor the names of its
14+
* contributors may be used to endorse or promote products derived from
15+
* this software without specific prior written permission.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
*/
29+
30+
#ifdef __linux__
31+
32+
#include <errno.h>
33+
#include <string.h>
34+
#include <unistd.h>
35+
#include <sys/socket.h>
36+
#include <linux/netlink.h>
37+
#include <linux/rtnetlink.h>
38+
39+
#include "mdnsd.h"
40+
#include "netlink.h"
41+
42+
#define NL_BUFSZ 4096
43+
44+
/*
45+
* Open a netlink socket subscribed to interface link and address events.
46+
* Returns the socket fd on success, -1 on failure.
47+
*/
48+
int netlink_init(void)
49+
{
50+
struct sockaddr_nl sa = { 0 };
51+
int sd;
52+
53+
sd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_ROUTE);
54+
if (sd < 0) {
55+
ERR("Failed creating netlink socket: %s", strerror(errno));
56+
return -1;
57+
}
58+
59+
sa.nl_family = AF_NETLINK;
60+
sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
61+
if (bind(sd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
62+
ERR("Failed binding netlink socket: %s", strerror(errno));
63+
close(sd);
64+
return -1;
65+
}
66+
67+
return sd;
68+
}
69+
70+
/*
71+
* Drain all pending netlink messages. Returns 1 if any link or address
72+
* change event was seen (caller should re-run interface discovery), 0 if
73+
* nothing relevant arrived, or -1 on a fatal socket error.
74+
*
75+
* ENOBUFS means the kernel dropped events because we were too slow; treat
76+
* that as an implicit change so the caller rescans to catch up.
77+
*/
78+
int netlink_read(int sd)
79+
{
80+
char buf[NL_BUFSZ];
81+
struct nlmsghdr *nh;
82+
ssize_t len;
83+
size_t l;
84+
int changed = 0;
85+
86+
while (1) {
87+
len = recv(sd, buf, sizeof(buf), 0);
88+
if (len < 0) {
89+
if (errno == EAGAIN || errno == EWOULDBLOCK)
90+
break;
91+
if (errno == EINTR)
92+
continue;
93+
if (errno == ENOBUFS) {
94+
WARN("Netlink buffer overflow, interface events may have been lost");
95+
changed = 1;
96+
break;
97+
}
98+
ERR("Netlink recv error: %s", strerror(errno));
99+
return -1;
100+
}
101+
102+
l = (size_t)len;
103+
for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, l); nh = NLMSG_NEXT(nh, l)) {
104+
switch (nh->nlmsg_type) {
105+
case RTM_NEWLINK:
106+
case RTM_DELLINK:
107+
case RTM_NEWADDR:
108+
case RTM_DELADDR:
109+
changed = 1;
110+
break;
111+
default:
112+
break;
113+
}
114+
}
115+
}
116+
117+
return changed;
118+
}
119+
120+
void netlink_exit(int sd)
121+
{
122+
if (sd >= 0)
123+
close(sd);
124+
}
125+
126+
#endif /* __linux__ */

src/netlink.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Netlink interface monitor for Linux
2+
*
3+
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are met:
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the distribution.
13+
* * Neither the name of the copyright holders nor the names of its
14+
* contributors may be used to endorse or promote products derived from
15+
* this software without specific prior written permission.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
*/
29+
30+
#ifndef MDNSD_NETLINK_H_
31+
#define MDNSD_NETLINK_H_
32+
33+
#ifdef __linux__
34+
35+
int netlink_init(void);
36+
int netlink_read(int sd);
37+
void netlink_exit(int sd);
38+
39+
#else /* non-Linux stubs */
40+
41+
static inline int netlink_init(void) { return -1; }
42+
static inline int netlink_read(int sd) { (void)sd; return 0; }
43+
static inline void netlink_exit(int sd) { (void)sd; }
44+
45+
#endif /* __linux__ */
46+
#endif /* MDNSD_NETLINK_H_ */

0 commit comments

Comments
 (0)