1From 00289e89cccb9567d6ea6bd2a394fd14b61e5ad1 Mon Sep 17 00:00:00 2001
2Message-ID: <00289e89cccb9567d6ea6bd2a394fd14b61e5ad1.1687508149.git.stefan@agner.ch>
3In-Reply-To: <e136dcdcdd93ef32ada981e89c195905eb809eea.1687508149.git.stefan@agner.ch>
4References: <e136dcdcdd93ef32ada981e89c195905eb809eea.1687508149.git.stefan@agner.ch>
5From: Nate Karstens <[email protected]>
6Date: Mon, 24 Jul 2017 09:38:55 -0500
7Subject: [PATCH] Handle noisy netlink sockets
8
9The POSIX implementation currently clears all network interfaces
10when netlink indicates that there has been a change. This causes
11the following problems:
12
13  1) Applications are informed that all of the services they are
14     tracking have been removed.
15  2) Increases network load because the client must re-query for
16     all records it is interested in.
17
18This changes netlink notification handling by:
19
20  1) Always comparing with the latest interface list returned
21     by the OS.
22  2) Confirming that the interface has been changed in a way
23     that we care about.
24
25Upstream-Status: Submitted [[email protected]]
26
27Signed-off-by: Nate Karstens <[email protected]>
28Signed-off-by: Alex Kiernan <[email protected]>
29---
30 mDNSPosix/mDNSPosix.c | 182 +++++++++++++++++++++++++++++++++++++++---
31 1 file changed, 172 insertions(+), 10 deletions(-)
32
33diff --git a/mDNSPosix/mDNSPosix.c b/mDNSPosix/mDNSPosix.c
34index 9867881..ad7000d 100644
35--- a/mDNSPosix/mDNSPosix.c
36+++ b/mDNSPosix/mDNSPosix.c
37@@ -1788,14 +1788,43 @@ mDNSlocal void          ProcessRoutingNotification(int sd, GenLinkedList *change
38
39 #endif // USES_NETLINK
40
41+// Test whether the given PosixNetworkInterface matches the given struct ifaddrs
42+mDNSlocal mDNSBool InterfacesMatch(PosixNetworkInterface *intf, struct ifaddrs *ifi)
43+{
44+    mDNSBool match = mDNSfalse;
45+    mDNSAddr ip, mask;
46+    int if_index;
47+
48+    if_index = if_nametoindex(ifi->ifa_name);
49+    if (if_index == 0)
50+        return mDNSfalse;
51+
52+    if((intf->index == if_index) &&
53+       (intf->sa_family == ifi->ifa_addr->sa_family) &&
54+       (strcmp(intf->coreIntf.ifname, ifi->ifa_name) == 0))
55+        {
56+        SockAddrTomDNSAddr(ifi->ifa_addr,    &ip,   NULL);
57+        SockAddrTomDNSAddr(ifi->ifa_netmask, &mask, NULL);
58+
59+        match = mDNSSameAddress(&intf->coreIntf.ip, &ip) &&
60+                mDNSSameAddress(&intf->coreIntf.mask, &mask);
61+        }
62+
63+    return match;
64+}
65+
66 // Called when data appears on interface change notification socket
67 mDNSlocal void InterfaceChangeCallback(int fd, void *context)
68 {
69     IfChangeRec     *pChgRec = (IfChangeRec*) context;
70+    mDNS            *m = pChgRec->mDNS;
71     fd_set readFDs;
72     GenLinkedList changedInterfaces;
73     NetworkInterfaceIndex *changedInterface;
74     struct timeval zeroTimeout = { 0, 0 };
75+    struct ifaddrs *ifa_list, **ifi, *ifa_loop4 = NULL;
76+    PosixNetworkInterface *intf, *intfNext;
77+    mDNSBool found, foundav4;
78
79     (void)fd; // Unused
80
81@@ -1810,12 +1839,149 @@ mDNSlocal void InterfaceChangeCallback(int fd, void *context)
82     }
83     while (0 < select(pChgRec->NotifySD + 1, &readFDs, (fd_set*) NULL, (fd_set*) NULL, &zeroTimeout));
84
85-    // Currently we rebuild the entire interface list whenever any interface change is
86-    // detected. If this ever proves to be a performance issue in a multi-homed
87-    // configuration, more care should be paid to changedInterfaces.
88-    if (changedInterfaces.Head != NULL)
89-        mDNSPlatformPosixRefreshInterfaceList(pChgRec->mDNS);
90+    CleanRecentInterfaces();
91+
92+    if (changedInterfaces.Head == NULL) goto cleanup;
93+
94+    if (getifaddrs(&ifa_list) < 0) goto cleanup;
95+
96+    for (intf = (PosixNetworkInterface*)(m->HostInterfaces); intf != NULL; intf = intfNext)
97+    {
98+        intfNext = (PosixNetworkInterface*)(intf->coreIntf.next);
99+
100+        // Loopback interface(s) are handled later
101+        if (intf->coreIntf.Loopback) continue;
102+
103+        found = mDNSfalse;
104+        for (ifi = &ifa_list; *ifi != NULL; ifi = &(*ifi)->ifa_next)
105+        {
106+            if (InterfacesMatch(intf, *ifi))
107+            {
108+                found = mDNStrue;
109+                break;
110+            }
111+        }
112+
113+        // Removes changed and old interfaces from m->HostInterfaces
114+        if (!found) TearDownInterface(m, intf);
115+    }
116+
117+    // Add new and changed interfaces in ifa_list
118+    // Save off loopback interface in case it is needed later
119+    for (ifi = &ifa_list; *ifi != NULL; ifi = &(*ifi)->ifa_next)
120+    {
121+        found = mDNSfalse;
122+        for (intf = (PosixNetworkInterface*)(m->HostInterfaces); intf != NULL; intf = intfNext)
123+        {
124+            intfNext = (PosixNetworkInterface*)(intf->coreIntf.next);
125+
126+            // Loopback interface(s) are handled later
127+            if (intf->coreIntf.Loopback) continue;
128+
129+            if (InterfacesMatch(intf, *ifi))
130+            {
131+                found = mDNStrue;
132+                break;
133+            }
134+
135+            // Removes changed and old interfaces from m->HostInterfaces
136+        }
137+        if (found)
138+	    continue;
139+
140+        if ((ifa_loop4 == NULL) &&
141+            ((*ifi)->ifa_addr->sa_family == AF_INET) &&
142+            ((*ifi)->ifa_flags & IFF_UP) &&
143+            ((*ifi)->ifa_flags & IFF_LOOPBACK))
144+        {
145+            ifa_loop4 = *ifi;
146+            continue;
147+        }
148+
149+        if (     (((*ifi)->ifa_addr->sa_family == AF_INET)
150+#if HAVE_IPV6
151+                  || ((*ifi)->ifa_addr->sa_family == AF_INET6)
152+#endif
153+                  ) && ((*ifi)->ifa_flags & IFF_UP)
154+                    && !((*ifi)->ifa_flags & IFF_POINTOPOINT)
155+                    && !((*ifi)->ifa_flags & IFF_LOOPBACK))
156+        {
157+            struct ifaddrs *i = *ifi;
158+
159+#define ethernet_addr_len 6
160+            uint8_t hwaddr[ethernet_addr_len];
161+            int hwaddr_len = 0;
162+
163+#if defined(TARGET_OS_LINUX) && TARGET_OS_LINUX
164+            struct ifreq ifr;
165+            int sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
166+            if (sockfd >= 0)
167+            {
168+                /* Add hardware address */
169+                memcpy(ifr.ifr_name, i->ifa_name, IFNAMSIZ);
170+                if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) != -1)
171+                {
172+                    if (ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER)
173+                    {
174+                        memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, ethernet_addr_len);
175+                        hwaddr_len = ethernet_addr_len;
176+                    }
177+                }
178+                close(sockfd);
179+            }
180+            else
181+            {
182+                memset(hwaddr, 0, sizeof(hwaddr));
183+            }
184+#endif // TARGET_OS_LINUX
185+            SetupOneInterface(m, i->ifa_addr, i->ifa_netmask,
186+                              hwaddr, hwaddr_len, i->ifa_name, if_nametoindex(i->ifa_name), i->ifa_flags);
187+        }
188+    }
189+
190+    // Determine if there is at least one non-loopback IPv4 interface. This is to work around issues
191+    // with multicast loopback on IPv6 interfaces -- see corresponding logic in SetupInterfaceList().
192+    foundav4 = mDNSfalse;
193+    for (intf = (PosixNetworkInterface*)(m->HostInterfaces); intf != NULL; intf = (PosixNetworkInterface*)(intf->coreIntf.next))
194+    {
195+        if (intf->sa_family == AF_INET && !intf->coreIntf.Loopback)
196+        {
197+            foundav4 = mDNStrue;
198+            break;
199+        }
200+    }
201+
202+    if (foundav4)
203+    {
204+        for (intf = (PosixNetworkInterface*)(m->HostInterfaces); intf != NULL; intf = intfNext)
205+        {
206+            intfNext = (PosixNetworkInterface*)(intf->coreIntf.next);
207+            if (intf->coreIntf.Loopback) TearDownInterface(m, intf);
208+        }
209+    }
210+    else
211+    {
212+        found = mDNSfalse;
213+
214+        for (intf = (PosixNetworkInterface*)(m->HostInterfaces); intf != NULL; intf = (PosixNetworkInterface*)(intf->coreIntf.next))
215+        {
216+            if (intf->coreIntf.Loopback)
217+            {
218+                found = mDNStrue;
219+                break;
220+            }
221+        }
222+
223+        if (!found && (ifa_loop4 != NULL))
224+        {
225+            SetupOneInterface(m, ifa_loop4->ifa_addr, ifa_loop4->ifa_netmask,
226+                              NULL, 0, ifa_loop4->ifa_name, if_nametoindex(ifa_loop4->ifa_name), ifa_loop4->ifa_flags);
227+        }
228+    }
229+
230+    if (ifa_list != NULL) freeifaddrs(ifa_list);
231
232+cleanup:
233     while ((changedInterface = (NetworkInterfaceIndex*)changedInterfaces.Head) != NULL)
234     {
235         RemoveFromList(&changedInterfaces, changedInterface);
236@@ -1947,15 +2113,11 @@ mDNSexport void mDNSPlatformClose(mDNS *const m)
237 #endif
238 }
239
240-// This is used internally by InterfaceChangeCallback.
241-// It's also exported so that the Standalone Responder (mDNSResponderPosix)
242+// This is exported so that the Standalone Responder (mDNSResponderPosix)
243 // can call it in response to a SIGHUP (mainly for debugging purposes).
244 mDNSexport mStatus mDNSPlatformPosixRefreshInterfaceList(mDNS *const m)
245 {
246     int err;
247-    // This is a pretty heavyweight way to process interface changes --
248-    // destroying the entire interface list and then making fresh one from scratch.
249-    // We should make it like the OS X version, which leaves unchanged interfaces alone.
250     ClearInterfaceList(m);
251     err = SetupInterfaceList(m);
252     return PosixErrorToStatus(err);
253--
2542.41.0
255
256