]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkd: address - add hash helpers for Address objects
authorTom Gundersen <teg@jklm.no>
Tue, 22 Sep 2015 15:54:27 +0000 (17:54 +0200)
committerTom Gundersen <teg@jklm.no>
Sun, 11 Oct 2015 13:03:04 +0000 (15:03 +0200)
Add compare_func and hash_func for the Address object. The notion of
address equality is the same as in the kernel, and hashing preserves
preserves equality.

Two addresses are considered equal if:
   - they have the same address family, and
     - they are neither IPv4 nor IPv6 addresses, or
     - the local addresses are identical, and
       - they are IPv6 addresses, or
         - they have the same prefixlength, and
         - their peer prefixes are identical

This fixes a bug in the old equality check, which got the local address
and the peer prefix mixed up.

src/network/networkd-address.c
src/network/test-network.c

index b2f24ec305a1d1c4591dd8f85e4fa1252466b046..efbdda593980c62e57bfdc03e0719e15bcc8a9e9 100644 (file)
 
 #include <net/if.h>
 
-#include "utf8.h"
-#include "util.h"
 #include "conf-parser.h"
 #include "firewall-util.h"
 #include "netlink-util.h"
+#include "set.h"
+#include "utf8.h"
+#include "util.h"
 
 #include "networkd.h"
 #include "networkd-address.h"
@@ -97,6 +98,103 @@ void address_free(Address *address) {
         free(address);
 }
 
+static void address_hash_func(const void *b, struct siphash *state) {
+        const Address *a = b;
+
+        assert(a);
+
+        siphash24_compress(&a->family, sizeof(a->family), state);
+
+        switch (a->family) {
+        case AF_INET:
+                siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state);
+
+                /* peer prefix */
+                if (a->prefixlen != 0) {
+                        uint32_t prefix;
+
+                        if (a->in_addr_peer.in.s_addr != 0)
+                                prefix = be32toh(a->in_addr_peer.in.s_addr) >> (32 - a->prefixlen);
+                        else
+                                prefix = be32toh(a->in_addr.in.s_addr) >> (32 - a->prefixlen);
+
+                        siphash24_compress(&prefix, sizeof(prefix), state);
+                }
+
+                /* fallthrough */
+        case AF_INET6:
+                /* local address */
+                siphash24_compress(&a->in_addr, FAMILY_ADDRESS_SIZE(a->family), state);
+
+                break;
+        default:
+                /* treat any other address family as AF_UNSPEC */
+                break;
+        }
+}
+
+static int address_compare_func(const void *c1, const void *c2) {
+        const Address *a1 = c1, *a2 = c2;
+
+        if (a1->family < a2->family)
+                return -1;
+        if (a1->family > a2->family)
+                return 1;
+
+        switch (a1->family) {
+        /* use the same notion of equality as the kernel does */
+        case AF_INET:
+                if (a1->prefixlen < a2->prefixlen)
+                        return -1;
+                if (a1->prefixlen > a2->prefixlen)
+                        return 1;
+
+                /* compare the peer prefixes */
+                if (a1->prefixlen != 0) {
+                        /* make sure we don't try to shift by 32.
+                         * See ISO/IEC 9899:TC3 § 6.5.7.3. */
+                        uint32_t b1, b2;
+
+                        if (a1->in_addr_peer.in.s_addr != 0)
+                                b1 = be32toh(a1->in_addr_peer.in.s_addr) >> (32 - a1->prefixlen);
+                        else
+                                b1 = be32toh(a1->in_addr.in.s_addr) >> (32 - a1->prefixlen);
+
+                        if (a2->in_addr_peer.in.s_addr != 0)
+                                b2 = be32toh(a2->in_addr_peer.in.s_addr) >> (32 - a1->prefixlen);
+                        else
+                                b2 = be32toh(a2->in_addr.in.s_addr) >> (32 - a1->prefixlen);
+
+                        if (b1 < b2)
+                                return -1;
+                        if (b1 > b2)
+                                return 1;
+                }
+
+                /* fall-through */
+        case AF_INET6:
+                return memcmp(&a1->in_addr, &a2->in_addr, FAMILY_ADDRESS_SIZE(a1->family));
+        default:
+                /* treat any other address family as AF_UNSPEC */
+                return 0;
+        }
+}
+
+static const struct hash_ops address_hash_ops = {
+        .hash = address_hash_func,
+        .compare = address_compare_func
+};
+
+bool address_equal(Address *a1, Address *a2) {
+        if (a1 == a2)
+                return true;
+
+        if (!a1 || !a2)
+                return false;
+
+        return address_compare_func(a1, a2) == 0;
+}
+
 int address_establish(Address *address, Link *link) {
         bool masq;
         int r;
@@ -572,50 +670,3 @@ int config_parse_label(const char *unit,
 
         return 0;
 }
-
-bool address_equal(Address *a1, Address *a2) {
-        /* same object */
-        if (a1 == a2)
-                return true;
-
-        /* one, but not both, is NULL */
-        if (!a1 || !a2)
-                return false;
-
-        if (a1->family != a2->family)
-                return false;
-
-        switch (a1->family) {
-        /* use the same notion of equality as the kernel does */
-        case AF_UNSPEC:
-                return true;
-
-        case AF_INET:
-                if (a1->prefixlen != a2->prefixlen)
-                        return false;
-                else if (a1->prefixlen == 0)
-                        /* make sure we don't try to shift by 32.
-                         * See ISO/IEC 9899:TC3 § 6.5.7.3. */
-                        return true;
-                else {
-                        uint32_t b1, b2;
-
-                        b1 = be32toh(a1->in_addr.in.s_addr);
-                        b2 = be32toh(a2->in_addr.in.s_addr);
-
-                        return (b1 >> (32 - a1->prefixlen)) == (b2 >> (32 - a1->prefixlen));
-                }
-
-        case AF_INET6: {
-                uint64_t *b1, *b2;
-
-                b1 = (uint64_t*)&a1->in_addr.in6;
-                b2 = (uint64_t*)&a2->in_addr.in6;
-
-                return (((b1[0] ^ b2[0]) | (b1[1] ^ b2[1])) == 0UL);
-        }
-
-        default:
-                assert_not_reached("Invalid address family");
-        }
-}
index 80676651a97edc7d8574ad6ca87b7c285711e633..bac1d6781ddb83bc73ecb9c9482dfe4cec840e53 100644 (file)
@@ -158,17 +158,18 @@ static void test_address_equality(void) {
         assert_se(address_equal(a1, a2));
 
         assert_se(inet_pton(AF_INET, "192.168.3.9", &a1->in_addr.in));
-        assert_se(address_equal(a1, a2));
+        assert_se(!address_equal(a1, a2));
         assert_se(inet_pton(AF_INET, "192.168.3.9", &a2->in_addr.in));
         assert_se(address_equal(a1, a2));
+        assert_se(inet_pton(AF_INET, "192.168.3.10", &a1->in_addr_peer.in));
+        assert_se(address_equal(a1, a2));
+        assert_se(inet_pton(AF_INET, "192.168.3.11", &a2->in_addr_peer.in));
+        assert_se(address_equal(a1, a2));
         a1->prefixlen = 10;
         assert_se(!address_equal(a1, a2));
         a2->prefixlen = 10;
         assert_se(address_equal(a1, a2));
 
-        assert_se(inet_pton(AF_INET, "192.168.3.10", &a2->in_addr.in));
-        assert_se(address_equal(a1, a2));
-
         a1->family = AF_INET6;
         assert_se(!address_equal(a1, a2));