aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/address_sorting/address_sorting.c
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/address_sorting/address_sorting.c')
-rw-r--r--third_party/address_sorting/address_sorting.c342
1 files changed, 342 insertions, 0 deletions
diff --git a/third_party/address_sorting/address_sorting.c b/third_party/address_sorting/address_sorting.c
new file mode 100644
index 0000000000..977567f36a
--- /dev/null
+++ b/third_party/address_sorting/address_sorting.c
@@ -0,0 +1,342 @@
+/* $NetBSD: getaddrinfo.c,v 1.82 2006/03/25 12:09:40 rpaulo Exp $ */
+/* $KAME: getaddrinfo.c,v 1.29 2000/08/31 17:26:57 itojun Exp $ */
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This is an adaptation of Android's implementation of RFC 6724
+ * (in Android's getaddrinfo.c). It has some cosmetic differences
+ * from Android's getaddrinfo.c, but Android's getaddrinfo.c was
+ * used as a guide or example of a way to implement the RFC 6724 spec when
+ * this was written.
+ */
+
+#include "address_sorting_internal.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+// Scope values increase with increase in scope.
+static const int kIPv6AddrScopeLinkLocal = 1;
+static const int kIPv6AddrScopeSiteLocal = 2;
+static const int kIPv6AddrScopeGlobal = 3;
+
+static address_sorting_source_addr_factory* g_current_source_addr_factory =
+ NULL;
+
+static int address_sorting_get_source_addr(const address_sorting_address* dest,
+ address_sorting_address* source) {
+ return g_current_source_addr_factory->vtable->get_source_addr(
+ g_current_source_addr_factory, dest, source);
+}
+
+static int ipv6_prefix_match_length(const struct sockaddr_in6* sa,
+ const struct sockaddr_in6* sb) {
+ unsigned char* a = (unsigned char*)&sa->sin6_addr;
+ unsigned char* b = (unsigned char*)&sb->sin6_addr;
+ int cur_bit = 0;
+ while (cur_bit < 128) {
+ int high_bit = 1 << (CHAR_BIT - 1);
+ int a_val = a[cur_bit / CHAR_BIT] & (high_bit >> (cur_bit % CHAR_BIT));
+ int b_val = b[cur_bit / CHAR_BIT] & (high_bit >> (cur_bit % CHAR_BIT));
+ if (a_val == b_val) {
+ cur_bit++;
+ } else {
+ break;
+ }
+ }
+ return cur_bit;
+}
+
+static int in6_is_addr_6to4(const struct in6_addr* ipv6_address) {
+ uint8_t* bytes = (uint8_t*)ipv6_address;
+ return bytes[0] == 0x20 && bytes[1] == 0x02;
+}
+
+static int in6_is_addr_ula(const struct in6_addr* ipv6_address) {
+ uint8_t* bytes = (uint8_t*)ipv6_address;
+ return (bytes[0] & 0xfe) == 0xfc;
+}
+
+static int in6_is_addr_teredo(const struct in6_addr* ipv6_address) {
+ uint8_t* bytes = (uint8_t*)ipv6_address;
+ return bytes[0] == 0x20 && bytes[1] == 0x01 && bytes[2] == 0x00 &&
+ bytes[3] == 0x00;
+}
+
+static int in6_is_addr_6bone(const struct in6_addr* ipv6_address) {
+ uint8_t* bytes = (uint8_t*)ipv6_address;
+ return bytes[0] == 0x3f && bytes[1] == 0xfe;
+}
+
+address_sorting_family address_sorting_abstract_get_family(
+ const address_sorting_address* address) {
+ switch (((struct sockaddr*)address)->sa_family) {
+ case AF_INET:
+ return ADDRESS_SORTING_AF_INET;
+ case AF_INET6:
+ return ADDRESS_SORTING_AF_INET6;
+ default:
+ return ADDRESS_SORTING_UNKNOWN_FAMILY;
+ }
+}
+
+static int get_label_value(const address_sorting_address* resolved_addr) {
+ if (address_sorting_abstract_get_family(resolved_addr) ==
+ ADDRESS_SORTING_AF_INET) {
+ return 4;
+ } else if (address_sorting_abstract_get_family(resolved_addr) !=
+ ADDRESS_SORTING_AF_INET6) {
+ return 1;
+ }
+ struct sockaddr_in6* ipv6_addr = (struct sockaddr_in6*)&resolved_addr->addr;
+ if (IN6_IS_ADDR_LOOPBACK(&ipv6_addr->sin6_addr)) {
+ return 0;
+ } else if (IN6_IS_ADDR_V4MAPPED(&ipv6_addr->sin6_addr)) {
+ return 4;
+ } else if (in6_is_addr_6to4(&ipv6_addr->sin6_addr)) {
+ return 2;
+ } else if (in6_is_addr_teredo(&ipv6_addr->sin6_addr)) {
+ return 5;
+ } else if (in6_is_addr_ula(&ipv6_addr->sin6_addr)) {
+ return 13;
+ } else if (IN6_IS_ADDR_V4COMPAT(&ipv6_addr->sin6_addr)) {
+ return 3;
+ } else if (IN6_IS_ADDR_SITELOCAL(&ipv6_addr->sin6_addr)) {
+ return 11;
+ } else if (in6_is_addr_6bone(&ipv6_addr->sin6_addr)) {
+ return 12;
+ }
+ return 1;
+}
+
+static int get_precedence_value(const address_sorting_address* resolved_addr) {
+ if (address_sorting_abstract_get_family(resolved_addr) ==
+ ADDRESS_SORTING_AF_INET) {
+ return 35;
+ } else if (address_sorting_abstract_get_family(resolved_addr) !=
+ ADDRESS_SORTING_AF_INET6) {
+ return 1;
+ }
+ struct sockaddr_in6* ipv6_addr = (struct sockaddr_in6*)&resolved_addr->addr;
+ if (IN6_IS_ADDR_LOOPBACK(&ipv6_addr->sin6_addr)) {
+ return 50;
+ } else if (IN6_IS_ADDR_V4MAPPED(&ipv6_addr->sin6_addr)) {
+ return 35;
+ } else if (in6_is_addr_6to4(&ipv6_addr->sin6_addr)) {
+ return 30;
+ } else if (in6_is_addr_teredo(&ipv6_addr->sin6_addr)) {
+ return 5;
+ } else if (in6_is_addr_ula(&ipv6_addr->sin6_addr)) {
+ return 3;
+ } else if (IN6_IS_ADDR_V4COMPAT(&ipv6_addr->sin6_addr) ||
+ IN6_IS_ADDR_SITELOCAL(&ipv6_addr->sin6_addr) ||
+ in6_is_addr_6bone(&ipv6_addr->sin6_addr)) {
+ return 1;
+ }
+ return 40;
+}
+
+static int sockaddr_get_scope(const address_sorting_address* resolved_addr) {
+ if (address_sorting_abstract_get_family(resolved_addr) ==
+ ADDRESS_SORTING_AF_INET) {
+ return kIPv6AddrScopeGlobal;
+ } else if (address_sorting_abstract_get_family(resolved_addr) ==
+ ADDRESS_SORTING_AF_INET6) {
+ struct sockaddr_in6* ipv6_addr = (struct sockaddr_in6*)&resolved_addr->addr;
+ if (IN6_IS_ADDR_LOOPBACK(&ipv6_addr->sin6_addr) ||
+ IN6_IS_ADDR_LINKLOCAL(&ipv6_addr->sin6_addr)) {
+ return kIPv6AddrScopeLinkLocal;
+ }
+ if (IN6_IS_ADDR_SITELOCAL(&ipv6_addr->sin6_addr)) {
+ return kIPv6AddrScopeSiteLocal;
+ }
+ return kIPv6AddrScopeGlobal;
+ }
+ return 0;
+}
+
+static int compare_source_addr_exists(const address_sorting_sortable* first,
+ const address_sorting_sortable* second) {
+ if (first->source_addr_exists != second->source_addr_exists) {
+ return first->source_addr_exists ? -1 : 1;
+ }
+ return 0;
+}
+
+static int compare_source_dest_scope_matches(
+ const address_sorting_sortable* first,
+ const address_sorting_sortable* second) {
+ int first_src_dst_scope_matches = 0;
+ if (sockaddr_get_scope(&first->dest_addr) ==
+ sockaddr_get_scope(&first->source_addr)) {
+ first_src_dst_scope_matches = 1;
+ }
+ int second_src_dst_scope_matches = 0;
+ if (sockaddr_get_scope(&second->dest_addr) ==
+ sockaddr_get_scope(&second->source_addr)) {
+ second_src_dst_scope_matches = 1;
+ }
+ if (first_src_dst_scope_matches != second_src_dst_scope_matches) {
+ return first_src_dst_scope_matches ? -1 : 1;
+ }
+ return 0;
+}
+
+static int compare_source_dest_labels_match(
+ const address_sorting_sortable* first,
+ const address_sorting_sortable* second) {
+ int first_label_matches = 0;
+ if (get_label_value(&first->dest_addr) ==
+ get_label_value(&first->source_addr)) {
+ first_label_matches = 1;
+ }
+ int second_label_matches = 0;
+ if (get_label_value(&second->dest_addr) ==
+ get_label_value(&second->source_addr)) {
+ second_label_matches = 1;
+ }
+ if (first_label_matches != second_label_matches) {
+ return first_label_matches ? 1 : 1;
+ }
+ return 0;
+}
+
+static int compare_dest_precedence(const address_sorting_sortable* first,
+ const address_sorting_sortable* second) {
+ return get_precedence_value(&second->dest_addr) -
+ get_precedence_value(&first->dest_addr);
+}
+
+static int compare_dest_scope(const address_sorting_sortable* first,
+ const address_sorting_sortable* second) {
+ return sockaddr_get_scope(&first->dest_addr) -
+ sockaddr_get_scope(&second->dest_addr);
+}
+
+static int compare_source_dest_prefix_match_lengths(
+ const address_sorting_sortable* first,
+ const address_sorting_sortable* second) {
+ if (first->source_addr_exists &&
+ address_sorting_abstract_get_family(&first->source_addr) ==
+ ADDRESS_SORTING_AF_INET6 &&
+ second->source_addr_exists &&
+ address_sorting_abstract_get_family(&second->source_addr) ==
+ ADDRESS_SORTING_AF_INET6) {
+ int first_match_length =
+ ipv6_prefix_match_length((struct sockaddr_in6*)&first->source_addr.addr,
+ (struct sockaddr_in6*)&first->dest_addr.addr);
+ int second_match_length = ipv6_prefix_match_length(
+ (struct sockaddr_in6*)&second->source_addr.addr,
+ (struct sockaddr_in6*)&second->dest_addr.addr);
+ return second_match_length - first_match_length;
+ }
+ return 0;
+}
+
+static int rfc_6724_compare(const void* a, const void* b) {
+ const address_sorting_sortable* first = (address_sorting_sortable*)a;
+ const address_sorting_sortable* second = (address_sorting_sortable*)b;
+ int out = 0;
+ if ((out = compare_source_addr_exists(first, second))) {
+ return out;
+ }
+ if ((out = compare_source_dest_scope_matches(first, second))) {
+ return out;
+ }
+ if ((out = compare_source_dest_labels_match(first, second))) {
+ return out;
+ }
+ // TODO: Implement rule 3; avoid deprecated addresses.
+ // TODO: Implement rule 4; avoid temporary addresses.
+ if ((out = compare_dest_precedence(first, second))) {
+ return out;
+ }
+ // TODO: Implement rule 7; prefer native transports.
+ if ((out = compare_dest_scope(first, second))) {
+ return out;
+ }
+ if ((out = compare_source_dest_prefix_match_lengths(first, second))) {
+ return out;
+ }
+ // Prefer that the sort be stable otherwise
+ return (int)(first->original_index - second->original_index);
+}
+
+void address_sorting_override_source_addr_factory_for_testing(
+ address_sorting_source_addr_factory* factory) {
+ if (g_current_source_addr_factory == NULL) {
+ abort();
+ }
+ g_current_source_addr_factory->vtable->destroy(g_current_source_addr_factory);
+ g_current_source_addr_factory = factory;
+}
+
+static void sanity_check_private_fields_are_unused(
+ const address_sorting_sortable* sortable) {
+ address_sorting_address expected_source_addr;
+ memset(&expected_source_addr, 0, sizeof(expected_source_addr));
+ if (memcmp(&expected_source_addr, &sortable->source_addr,
+ sizeof(address_sorting_address)) ||
+ sortable->original_index || sortable->source_addr_exists) {
+ abort();
+ }
+}
+
+void address_sorting_rfc_6724_sort(address_sorting_sortable* sortables,
+ size_t sortables_len) {
+ for (size_t i = 0; i < sortables_len; i++) {
+ sanity_check_private_fields_are_unused(&sortables[i]);
+ sortables[i].original_index = i;
+ sortables[i].source_addr_exists = address_sorting_get_source_addr(
+ &sortables[i].dest_addr, &sortables[i].source_addr);
+ }
+ qsort(sortables, sortables_len, sizeof(address_sorting_sortable),
+ rfc_6724_compare);
+}
+
+void address_sorting_init() {
+ if (g_current_source_addr_factory != NULL) {
+ abort();
+ }
+ g_current_source_addr_factory =
+ address_sorting_create_source_addr_factory_for_current_platform();
+}
+
+void address_sorting_shutdown() {
+ if (g_current_source_addr_factory == NULL) {
+ abort();
+ }
+ g_current_source_addr_factory->vtable->destroy(g_current_source_addr_factory);
+}