]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 1683] Interface binding does not seem to work as intended.
authorDave Hart <hart@ntp.org>
Tue, 9 Nov 2010 03:14:33 +0000 (03:14 +0000)
committerDave Hart <hart@ntp.org>
Tue, 9 Nov 2010 03:14:33 +0000 (03:14 +0000)
bk: 4cd8bc99yO-4KLsr_T3zINTNAOvVWw

ChangeLog
include/ntp_machine.h
ntpd/ntp_io.c

index 805affdec75782d96b01a3ca6aeb92f230e843ef..5bf07306e672a1a8572643911a7f963365e1a4b5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+---
+
+* [Bug 1683] Interface binding does not seem to work as intended.
+
 ---
 (4.2.6p3-RC8) 2010/10/29 Released by Harlan Stenn <stenn@ntp.org>
 
index 73fd172a2ef628d8b4fd0b93d8e298fe77e37a61..69dca68170f221a9b5781ccd4cb36263238dbfd6 100644 (file)
@@ -230,7 +230,10 @@ typedef unsigned long u_long;
 typedef int SOCKET;
 # define INVALID_SOCKET        -1
 # define SOCKET_ERROR  -1
+# define socket_errno()                (errno)
 # define closesocket(fd)       close(fd)
+#else  /* SYS_WINNT follows */
+# define socket_errno()                (errno = WSAGetLastError())
 #endif
 
 int ntp_set_tod (struct timeval *tvp, void *tzp);
index 6a0bb483f3723dba1a47bfd1822c79dd4c44a001..b8674fc51f31b31bbc2063a5453ef800dafa55b0 100644 (file)
@@ -275,7 +275,6 @@ static struct interface *wildipv6 = NULL;
 static void            add_fd_to_list          (SOCKET, 
                                                 enum desc_type);
 static struct interface *find_addr_in_list     (sockaddr_u *);
-static struct interface *find_samenet_addr_in_list(sockaddr_u *);
 static struct interface *find_flagged_addr_in_list(sockaddr_u *, int);
 static void            delete_addr_from_list   (sockaddr_u *);
 static void            delete_interface_from_list(struct interface *);
@@ -290,9 +289,14 @@ static nic_rule_action     interface_action(char *, isc_netaddr_t *,
                                         isc_uint32_t);
 static void            convert_isc_if  (isc_interface_t *,
                                         struct interface *, u_short);
+static void            calc_addr_distance(sockaddr_u *,
+                                          const sockaddr_u *,
+                                          const sockaddr_u *);
+static int             cmp_addr_distance(const sockaddr_u *,
+                                         const sockaddr_u *);
 static struct interface *getinterface  (sockaddr_u *, int);
-static struct interface *getsamenetinterface   (sockaddr_u *, int);
 static struct interface *findlocalinterface    (sockaddr_u *, int, int);
+static struct interface *findclosestinterface  (sockaddr_u *, int);
 static struct interface *findlocalcastinterface        (sockaddr_u *);
 
 /*
@@ -1273,6 +1277,9 @@ convert_isc_if(
        u_short port
        )
 {
+       const u_char v6loop[16] = {0, 0, 0, 0, 0, 0, 0, 0,
+                                  0, 0, 0, 0, 0, 0, 0, 1};
+
        strncpy(itf->name, isc_if->name, sizeof(itf->name));
        itf->name[sizeof(itf->name) - 1] = 0; /* strncpy may not */
        itf->family = (u_short)isc_if->af;
@@ -1316,6 +1323,21 @@ convert_isc_if(
                | ((INTERFACE_F_MULTICAST & isc_if->flags) 
                         ? INT_MULTICAST : 0)
                ;
+
+       /*
+        * Clear the loopback flag if the address is not localhost.
+        * http://bugs.ntp.org/1691
+        */
+       if (INT_LOOPBACK & itf->flags) {
+               if (AF_INET == itf->family) {
+                       if (127 != (SRCADR(&itf->sin) >> 24))
+                               itf->flags &= ~INT_LOOPBACK;
+               } else {
+                       if (memcmp(v6loop, NSRCADR6(&itf->sin),
+                                  sizeof(NSRCADR6(&itf->sin))))
+                               itf->flags &= ~INT_LOOPBACK;
+               }
+       }
 }
 
 
@@ -2708,11 +2730,7 @@ open_socket(
        /* create a datagram (UDP) socket */
        fd = socket(AF(addr), SOCK_DGRAM, 0);
        if (INVALID_SOCKET == fd) {
-#ifndef SYS_WINNT
-               errval = errno;
-#else
-               errval = WSAGetLastError();
-#endif
+               errval = socket_errno();
                msyslog(LOG_ERR, 
                        "socket(AF_INET%s, SOCK_DGRAM, 0) failed on address %s: %m",
                        IS_IPV6(addr) ? "6" : "", stoa(addr));
@@ -3514,7 +3532,6 @@ findlocalinterface(
        sockaddrlen = sizeof(saddr);
        rtn = getsockname(s, &saddr.sa, &sockaddrlen);
        closesocket(s);
-
        if (SOCKET_ERROR == rtn)
                return NULL;
 
@@ -3524,15 +3541,16 @@ findlocalinterface(
        iface = getinterface(&saddr, flags);
 
        /* 
-        * if we didn't find an exact match on saddr check for an
-        * interface on the same subnet as saddr.  This handles the
-        * case of the address suggested by the kernel being
-        * excluded by the user's -I and -L options to ntpd, when
-        * another address is enabled on the same subnet.
-        * See http://bugs.ntp.org/1184 for more detail.
+        * if we didn't find an exact match on saddr, find the closest
+        * available local address.  This handles the case of the
+        * address suggested by the kernel being excluded by nic rules
+        * or the user's -I and -L options to ntpd.
+        * See http://bugs.ntp.org/1184 and http://bugs.ntp.org/1683
+        * for more background.
         */
        if (NULL == iface || iface->ignore_packets)
-               iface = getsamenetinterface(&saddr, flags);
+               iface = findclosestinterface(&saddr, 
+                                            flags | INT_LOOPBACK);
 
        /* Don't use an interface which will ignore replies */
        if (iface != NULL && iface->ignore_packets)
@@ -3543,43 +3561,167 @@ findlocalinterface(
 
 
 /*
- * fetch an interface structure the matches the
- * address and has the given flags NOT set
+ * findclosestinterface
+ *
+ * If there are -I/--interface or -L/novirtualips command-line options,
+ * or "nic" or "interface" rules in ntp.conf, findlocalinterface() may
+ * find the kernel's preferred local address for a given peer address is
+ * administratively unavailable to ntpd, and punt to this routine's more
+ * expensive search.
+ *
+ * Find the numerically closest local address to the one connect()
+ * suggested.  This matches an address on the same subnet first, as
+ * needed by Bug 1184, and provides a consistent choice if there are
+ * multiple feasible local addresses, regardless of the order ntpd
+ * enumerated them.
  */
 static struct interface *
-getinterface(
-       sockaddr_u *    addr, 
+findclosestinterface(
+       sockaddr_u *    addr,
        int             flags
        )
 {
-       struct interface *iface;
-       
-       iface = find_addr_in_list(addr);
+       struct interface *      endpt;
+       struct interface *      winner;
+       sockaddr_u              addr_dist;
+       sockaddr_u              min_dist;
 
-       if (iface != NULL && (iface->flags & flags))
-               iface = NULL;
+       winner = NULL;
        
-       return iface;
+       for (endpt = inter_list; endpt != NULL; endpt = endpt->link) {
+               if (endpt->ignore_packets ||
+                   AF(addr) != endpt->family || 
+                   flags & endpt->flags)
+                       continue;
+               
+               calc_addr_distance(&addr_dist, addr, &endpt->sin);
+               if (NULL == winner ||
+                   -1 == cmp_addr_distance(&addr_dist, &min_dist)) {
+                       min_dist = addr_dist;
+                       winner = endpt;
+               }
+       }
+       if (NULL == winner)
+               DPRINTF(4, ("findclosestinterface(%s) failed\n", 
+                           stoa(addr)));
+       else
+               DPRINTF(4, ("findclosestinterface(%s) -> %s\n", 
+                           stoa(addr), stoa(&winner->sin)));
+
+       return winner;
+}
+
+
+/*
+ * calc_addr_distance - calculate the distance between two addresses,
+ *                     the absolute value of the difference between
+ *                     the addresses numerically, stored as an address.
+ */
+static void
+calc_addr_distance(
+       sockaddr_u *            dist,
+       const sockaddr_u *      a1,
+       const sockaddr_u *      a2
+       )
+{
+       u_int32 a1val;
+       u_int32 a2val;
+       u_int32 v4dist;
+       int     found_greater;
+       int     a1_greater;
+       int     i;
+
+       NTP_REQUIRE(AF(a1) == AF(a2));
+
+       memset(dist, 0, sizeof(*dist));
+       AF(dist) = AF(a1);
+
+       /* v4 can be done a bit simpler */
+       if (IS_IPV4(a1)) {
+               a1val = SRCADR(a1);
+               a2val = SRCADR(a2);
+               v4dist = (a1val > a2val)
+                            ? a1val - a2val
+                            : a2val - a1val;
+               SET_ADDR4(dist, v4dist);
+
+               return;
+       }
+
+       found_greater = FALSE;
+       a1_greater = FALSE;     /* suppress pot. uninit. warning */
+       for (i = 0; i < sizeof(NSRCADR6(a1)); i++) {
+               if (!found_greater &&
+                   NSRCADR6(a1)[i] != NSRCADR6(a2)[i]) {
+                       found_greater = TRUE;
+                       a1_greater = (NSRCADR6(a1)[i] > NSRCADR6(a2)[i]);
+               }
+               if (!found_greater) {
+                       NSRCADR6(dist)[i] = 0;
+               } else {
+                       if (a1_greater)
+                               NSRCADR6(dist)[i] = NSRCADR6(a1)[i] -
+                                                   NSRCADR6(a2)[i];
+                       else
+                               NSRCADR6(dist)[i] = NSRCADR6(a2)[i] -
+                                                   NSRCADR6(a1)[i];
+               }
+       }
+}
+
+
+/*
+ * cmp_addr_distance - compare two address distances, returning -1, 0,
+ *                    1 to indicate their relationship.
+ */
+static int
+cmp_addr_distance(
+       const sockaddr_u *      d1,
+       const sockaddr_u *      d2
+       )
+{
+       int     i;
+
+       NTP_REQUIRE(AF(d1) == AF(d2));
+
+       if (IS_IPV4(d1)) {
+               if (SRCADR(d1) < SRCADR(d2))
+                       return -1;
+               else if (SRCADR(d1) == SRCADR(d2))
+                       return 0;
+               else
+                       return 1;
+       }
+
+       for (i = 0; i < sizeof(NSRCADR6(d1)); i++) {
+               if (NSRCADR6(d1)[i] < NSRCADR6(d2)[i])
+                       return -1;
+               else if (NSRCADR6(d1)[i] > NSRCADR6(d2)[i])
+                       return 1;
+       }
+
+       return 0;
 }
 
 
+
 /*
- * fetch an interface structure with a local address on the same subnet
- * as addr which has the given flags NOT set
+ * fetch an interface structure the matches the
+ * address and has the given flags NOT set
  */
 static struct interface *
-getsamenetinterface(
-       sockaddr_u *    addr,
+getinterface(
+       sockaddr_u *    addr, 
        int             flags
        )
 {
        struct interface *iface;
-
-       iface = find_samenet_addr_in_list(addr);
+       
+       iface = find_addr_in_list(addr);
 
        if (iface != NULL && (iface->flags & flags))
                iface = NULL;
-
+       
        return iface;
 }
 
@@ -4071,98 +4213,6 @@ find_addr_in_list(
        return NULL;
 }
 
-static inline isc_boolean_t
-same_network_v4(
-       struct sockaddr_in *addr1,
-       struct sockaddr_in *mask,
-       struct sockaddr_in *addr2
-       )
-{
-       return (addr1->sin_addr.s_addr & mask->sin_addr.s_addr)
-              == (addr2->sin_addr.s_addr & mask->sin_addr.s_addr);
-}
-
-#ifdef INCLUDE_IPV6_SUPPORT
-static inline isc_boolean_t
-same_network_v6(
-       struct sockaddr_in6 *addr1,
-       struct sockaddr_in6 *mask,
-       struct sockaddr_in6 *addr2
-       )
-{
-       int i;
-
-       for (i = 0; 
-            i < sizeof(addr1->sin6_addr.s6_addr) / 
-                sizeof(addr1->sin6_addr.s6_addr[0]);
-            i++)
-
-               if ((addr1->sin6_addr.s6_addr[i] &
-                    mask->sin6_addr.s6_addr[i]) 
-                   !=
-                   (addr2->sin6_addr.s6_addr[i] &
-                    mask->sin6_addr.s6_addr[i]))
-
-                       return ISC_FALSE;
-
-       return ISC_TRUE;
-}
-#endif /* INCLUDE_IPV6_SUPPORT */
-
-
-static isc_boolean_t
-same_network(
-       sockaddr_u *a1,
-       sockaddr_u *mask,
-       sockaddr_u *a2
-       )
-{
-       isc_boolean_t sn;
-
-       if (AF(a1) != AF(a2))
-               sn = ISC_FALSE;
-       else if (IS_IPV4(a1))
-               sn = same_network_v4(&a1->sa4, &mask->sa4, &a2->sa4);
-#ifdef INCLUDE_IPV6_SUPPORT
-       else if (IS_IPV6(a1))
-               sn = same_network_v6(&a1->sa6, &mask->sa6, &a2->sa6);
-#endif
-       else
-               sn = ISC_FALSE;
-
-       return sn;
-}
-
-/*
- * Find an address in the list on the same network as addr which is not
- * addr.
- */
-static struct interface *
-find_samenet_addr_in_list(
-       sockaddr_u *addr
-       ) 
-{
-       remaddr_t *entry;
-
-       DPRINTF(4, ("Searching for addr with same subnet as %s in list of addresses - ",
-                   stoa(addr)));
-
-       for (entry = remoteaddr_list;
-            entry != NULL;
-            entry = entry->link)
-
-               if (!SOCK_EQ(addr, &entry->addr)
-                   && same_network(&entry->addr, 
-                                   &entry->interface->mask,
-                                   addr)) {
-                       DPRINTF(4, ("FOUND\n"));
-                       return entry->interface;
-               }
-
-       DPRINTF(4, ("NOT FOUND\n"));
-       return NULL;
-}
-
 
 /*
  * Find the given address with the all given flags set in the list