summaryrefslogtreecommitdiff
path: root/src/acd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/acd.c')
-rw-r--r--src/acd.c596
1 files changed, 596 insertions, 0 deletions
diff --git a/src/acd.c b/src/acd.c
new file mode 100644
index 00000000..7ace1554
--- /dev/null
+++ b/src/acd.c
@@ -0,0 +1,596 @@
+/*
+ *
+ * Connection Manager
+ *
+ * Copyright (C) 2018 Commend International GmbH. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Address Conflict Detection (RFC 5227)
+ *
+ * based on DHCP client library with GLib integration,
+ * Copyright (C) 2009-2014 Intel Corporation. All rights reserved.
+ *
+ */
+
+#include <netinet/if_ether.h>
+#include <net/if_arp.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "connman.h"
+#include <connman/acd.h>
+#include <connman/log.h>
+#include <connman/inet.h>
+#include <connman/dbus.h>
+#include <glib.h>
+#include "src/shared/arp.h"
+
+enum acd_state {
+ ACD_STATE_PROBE,
+ ACD_STATE_ANNOUNCE,
+ ACD_STATE_MONITOR,
+ ACD_STATE_DEFEND,
+};
+
+static const char* acd_state_texts[] = {
+ "PROBE",
+ "ANNOUNCE",
+ "MONITOR",
+ "DEFEND"
+};
+
+struct acd_host {
+ enum acd_state state;
+ int ifindex;
+ char *interface;
+ uint8_t mac_address[6];
+ uint32_t requested_ip; /* host byte order */
+
+ /* address conflict fields */
+ uint32_t ac_ip; /* host byte order */
+ uint8_t ac_mac[6];
+ gint64 ac_timestamp;
+ bool ac_resolved;
+ const char *path;
+
+ bool listen_on;
+ int listener_sockfd;
+ unsigned int retry_times;
+ unsigned int conflicts;
+ guint timeout;
+ guint listener_watch;
+
+ acd_host_cb_t ipv4_available_cb;
+ gpointer ipv4_available_data;
+ acd_host_cb_t ipv4_lost_cb;
+ gpointer ipv4_lost_data;
+ acd_host_cb_t ipv4_conflict_cb;
+ gpointer ipv4_conflict_data;
+ acd_host_cb_t ipv4_max_conflicts_cb;
+ gpointer ipv4_max_conflicts_data;
+};
+
+static int start_listening(struct acd_host *acd);
+static void stop_listening(struct acd_host *acd);
+static gboolean acd_listener_event(GIOChannel *channel, GIOCondition condition,
+ gpointer acd_data);
+static int acd_recv_arp_packet(struct acd_host *acd);
+static void send_probe_packet(gpointer acd_data);
+static gboolean acd_probe_timeout(gpointer acd_data);
+static gboolean send_announce_packet(gpointer acd_data);
+static gboolean acd_announce_timeout(gpointer acd_data);
+static gboolean acd_defend_timeout(gpointer acd_data);
+
+/* for D-Bus property */
+static void report_conflict(struct acd_host *acd, const struct ether_arp* arp);
+
+static void debug(struct acd_host *acd, const char *format, ...)
+{
+ char str[256];
+ va_list ap;
+
+ va_start(ap, format);
+
+ if (vsnprintf(str, sizeof(str), format, ap) > 0)
+ connman_info("ACD index %d: %s", acd->ifindex, str);
+
+ va_end(ap);
+}
+
+void acd_host_free(struct acd_host *acd)
+{
+ if (!acd)
+ return;
+
+ g_free(acd->interface);
+ g_free(acd);
+}
+
+struct acd_host *acd_host_new(int ifindex, const char *path)
+{
+ struct acd_host *acd;
+
+ if (ifindex < 0) {
+ connman_error("Invalid interface index %d", ifindex);
+ return NULL;
+ }
+
+ acd = g_try_new0(struct acd_host, 1);
+ if (!acd) {
+ connman_error("Could not allocate ACD data structure");
+ return NULL;
+ }
+
+ acd->interface = connman_inet_ifname(ifindex);
+ if (!acd->interface) {
+ connman_error("Interface with index %d is not available", ifindex);
+ goto error;
+ }
+
+ if (!connman_inet_is_ifup(ifindex)) {
+ connman_error("Interface with index %d and name %s is down", ifindex,
+ acd->interface);
+ goto error;
+ }
+
+ __connman_inet_get_interface_mac_address(ifindex, acd->mac_address);
+
+ acd->listener_sockfd = -1;
+ acd->listen_on = false;
+ acd->ifindex = ifindex;
+ acd->listener_watch = 0;
+ acd->retry_times = 0;
+
+ acd->ipv4_available_cb = NULL;
+ acd->ipv4_lost_cb = NULL;
+ acd->ipv4_conflict_cb = NULL;
+ acd->ipv4_max_conflicts_cb = NULL;
+
+ acd->ac_ip = 0;
+ memset(acd->ac_mac, 0, sizeof(acd->ac_mac));
+ acd->ac_timestamp = 0;
+ acd->ac_resolved = true;
+ acd->path = path;
+
+ return acd;
+
+error:
+ acd_host_free(acd);
+ return NULL;
+}
+
+static void remove_timeout(struct acd_host *acd)
+{
+ if (acd->timeout > 0)
+ g_source_remove(acd->timeout);
+
+ acd->timeout = 0;
+}
+
+static int start_listening(struct acd_host *acd)
+{
+ GIOChannel *listener_channel;
+ int listener_sockfd;
+
+ if (acd->listen_on)
+ return 0;
+
+ debug(acd, "start listening");
+
+ listener_sockfd = arp_socket(acd->ifindex);
+ if (listener_sockfd < 0)
+ return -EIO;
+
+ listener_channel = g_io_channel_unix_new(listener_sockfd);
+ if (!listener_channel) {
+ /* Failed to create listener channel */
+ close(listener_sockfd);
+ return -EIO;
+ }
+
+ acd->listen_on = true;
+ acd->listener_sockfd = listener_sockfd;
+
+ g_io_channel_set_close_on_unref(listener_channel, TRUE);
+ acd->listener_watch =
+ g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH,
+ G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
+ acd_listener_event, acd,
+ NULL);
+ g_io_channel_unref(listener_channel);
+
+ return 0;
+}
+
+static void stop_listening(struct acd_host *acd)
+{
+ if (!acd->listen_on)
+ return;
+
+ if (acd->listener_watch > 0)
+ g_source_remove(acd->listener_watch);
+ acd->listen_on = FALSE;
+ acd->listener_sockfd = -1;
+ acd->listener_watch = 0;
+}
+
+static gboolean acd_listener_event(GIOChannel *channel, GIOCondition condition,
+ gpointer acd_data)
+{
+ struct acd_host *acd = acd_data;
+
+ if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ acd->listener_watch = 0;
+ return FALSE;
+ }
+
+ if (!acd->listen_on)
+ return FALSE;
+
+ acd_recv_arp_packet(acd);
+
+ return TRUE;
+}
+
+static bool is_link_local(uint32_t ip)
+{
+ return (ip & LINKLOCAL_ADDR) == LINKLOCAL_ADDR;
+}
+
+static int acd_recv_arp_packet(struct acd_host *acd)
+{
+ ssize_t cnt;
+ struct ether_arp arp;
+ uint32_t ip_n; /* network byte order */
+ struct in_addr addr;
+ int source_conflict;
+ int target_conflict;
+ bool probe;
+ char* confltxt;
+ uint8_t* mac;
+ uint8_t* omac;
+
+ memset(&arp, 0, sizeof(arp));
+ cnt = read(acd->listener_sockfd, &arp, sizeof(arp));
+ if (cnt != sizeof(arp))
+ return -EINVAL;
+
+ if (arp.arp_op != htons(ARPOP_REPLY) &&
+ arp.arp_op != htons(ARPOP_REQUEST))
+ return -EINVAL;
+
+ if (memcmp(arp.arp_sha, acd->mac_address, ETH_ALEN) == 0)
+ return 0;
+
+ ip_n = htonl(acd->requested_ip);
+ source_conflict = !memcmp(arp.arp_spa, &ip_n, sizeof(uint32_t));
+ probe = !memcmp(arp.arp_spa, "\0\0\0\0", sizeof(uint32_t));
+ target_conflict = probe &&
+ !memcmp(arp.arp_tpa, &ip_n, sizeof(uint32_t));
+
+ if (!source_conflict && !target_conflict)
+ return 0;
+
+ acd->conflicts++;
+
+ confltxt = target_conflict ? "target" : "source";
+
+ addr.s_addr = ip_n;
+ debug(acd, "IPv4 %d %s conflicts detected for address %s. "
+ "State=%s", acd->conflicts, confltxt, inet_ntoa(addr),
+ acd_state_texts[acd->state]);
+ mac = acd->mac_address;
+ omac = arp.arp_sha;
+ debug(acd, "Our MAC: %02x:%02x:%02x:%02x:%02x:%02x"
+ " other MAC: %02x:%02x:%02x:%02x:%02x:%02x",
+ mac[0], mac[1], mac[2],mac[3], mac[4], mac[5],
+ omac[0], omac[1], omac[2],omac[3], omac[4], omac[5]);
+
+ if (acd->state == ACD_STATE_MONITOR) {
+ if (!source_conflict)
+ return 0;
+
+ acd->state = ACD_STATE_DEFEND;
+ debug(acd, "DEFEND mode conflicts: %d", acd->conflicts);
+ /* Try to defend with a single announce. */
+ send_announce_packet(acd);
+ return 0;
+ } else if (acd->state == ACD_STATE_DEFEND) {
+ if (!source_conflict)
+ return 0;
+
+ debug(acd, "LOST IPv4 address %s", inet_ntoa(addr));
+ if (!is_link_local(acd->requested_ip))
+ report_conflict(acd, &arp);
+
+ if (acd->ipv4_lost_cb)
+ acd->ipv4_lost_cb(acd, acd->ipv4_lost_data);
+ return 0;
+ }
+
+ if (acd->conflicts < MAX_CONFLICTS) {
+ if (!is_link_local(acd->requested_ip))
+ report_conflict(acd, &arp);
+
+ acd_host_stop(acd);
+
+ /* we need a new request_ip */
+ if (acd->ipv4_conflict_cb)
+ acd->ipv4_conflict_cb(acd, acd->ipv4_conflict_data);
+ } else {
+ acd_host_stop(acd);
+
+ /*
+ * Here we got a lot of conflicts, RFC3927 and RFC5227 state that we
+ * have to wait RATE_LIMIT_INTERVAL before retrying.
+ */
+ if (acd->ipv4_max_conflicts_cb)
+ acd->ipv4_max_conflicts_cb(acd, acd->ipv4_max_conflicts_data);
+ }
+
+ return 0;
+}
+
+int acd_host_start(struct acd_host *acd, uint32_t ip)
+{
+ guint timeout;
+ int err;
+
+ remove_timeout(acd);
+
+ err = start_listening(acd);
+ if (err)
+ return err;
+
+ acd->retry_times = 0;
+ acd->requested_ip = ip;
+
+ /* First wait a random delay to avoid storm of ARP requests on boot */
+ timeout = __connman_util_random_delay_ms(PROBE_WAIT);
+ acd->state = ACD_STATE_PROBE;
+
+ acd->timeout = g_timeout_add_full(G_PRIORITY_HIGH,
+ timeout,
+ acd_probe_timeout,
+ acd,
+ NULL);
+
+ return 0;
+}
+
+void acd_host_stop(struct acd_host *acd)
+{
+ stop_listening(acd);
+
+ remove_timeout(acd);
+
+ if (acd->listener_watch > 0) {
+ g_source_remove(acd->listener_watch);
+ acd->listener_watch = 0;
+ }
+
+ acd->state = ACD_STATE_PROBE;
+ acd->retry_times = 0;
+ acd->requested_ip = 0;
+}
+
+static void send_probe_packet(gpointer acd_data)
+{
+ guint timeout;
+ struct acd_host *acd = acd_data;
+
+ debug(acd, "sending ARP probe request");
+ remove_timeout(acd);
+ if (acd->retry_times == 1) {
+ acd->state = ACD_STATE_PROBE;
+ start_listening(acd);
+ }
+ arp_send_packet(acd->mac_address, 0,
+ acd->requested_ip, acd->ifindex);
+
+ if (acd->retry_times < PROBE_NUM) {
+ /* Add a random timeout in range of PROBE_MIN to PROBE_MAX. */
+ timeout = __connman_util_random_delay_ms(PROBE_MAX-PROBE_MIN);
+ timeout += PROBE_MIN * 1000;
+ } else
+ timeout = ANNOUNCE_WAIT * 1000;
+
+ acd->timeout = g_timeout_add_full(G_PRIORITY_HIGH,
+ timeout,
+ acd_probe_timeout,
+ acd,
+ NULL);
+}
+
+static gboolean acd_probe_timeout(gpointer acd_data)
+{
+ struct acd_host *acd = acd_data;
+
+ acd->timeout = 0;
+
+ debug(acd, "acd probe timeout (retries %d)", acd->retry_times);
+ if (acd->retry_times == PROBE_NUM) {
+ acd->state = ACD_STATE_ANNOUNCE;
+ acd->retry_times = 1;
+
+ send_announce_packet(acd);
+ return FALSE;
+ }
+
+ acd->retry_times++;
+ send_probe_packet(acd);
+
+ return FALSE;
+}
+
+static gboolean send_announce_packet(gpointer acd_data)
+{
+ struct acd_host *acd = acd_data;
+
+ debug(acd, "sending ACD announce request");
+
+ arp_send_packet(acd->mac_address,
+ acd->requested_ip,
+ acd->requested_ip,
+ acd->ifindex);
+
+ remove_timeout(acd);
+
+ if (acd->state == ACD_STATE_DEFEND)
+ acd->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ DEFEND_INTERVAL,
+ acd_defend_timeout,
+ acd,
+ NULL);
+ else
+ acd->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ ANNOUNCE_INTERVAL,
+ acd_announce_timeout,
+ acd,
+ NULL);
+ return TRUE;
+}
+
+static gboolean acd_announce_timeout(gpointer acd_data)
+{
+ struct acd_host *acd = acd_data;
+
+ acd->timeout = 0;
+
+ debug(acd, "acd announce timeout (retries %d)", acd->retry_times);
+ if (acd->retry_times != ANNOUNCE_NUM) {
+ acd->retry_times++;
+ send_announce_packet(acd);
+ return FALSE;
+ }
+
+ debug(acd, "switching to monitor mode");
+ acd->state = ACD_STATE_MONITOR;
+
+ if (!acd->ac_resolved && !is_link_local(acd->requested_ip))
+ report_conflict(acd, NULL);
+
+ if (acd->ipv4_available_cb)
+ acd->ipv4_available_cb(acd,
+ acd->ipv4_available_data);
+ acd->conflicts = 0;
+
+ return FALSE;
+}
+
+static gboolean acd_defend_timeout(gpointer acd_data)
+{
+ struct acd_host *acd = acd_data;
+
+ debug(acd, "back to MONITOR mode");
+ acd->timeout = 0;
+ acd->conflicts = 0;
+ acd->state = ACD_STATE_MONITOR;
+
+ return FALSE;
+}
+
+void acd_host_register_event(struct acd_host *acd,
+ enum acd_host_event event,
+ acd_host_cb_t func,
+ gpointer user_data)
+{
+ switch (event) {
+ case ACD_HOST_EVENT_IPV4_AVAILABLE:
+ acd->ipv4_available_cb = func;
+ acd->ipv4_available_data = user_data;
+ break;
+ case ACD_HOST_EVENT_IPV4_LOST:
+ acd->ipv4_lost_cb = func;
+ acd->ipv4_lost_data = user_data;
+ break;
+ case ACD_HOST_EVENT_IPV4_CONFLICT:
+ acd->ipv4_conflict_cb = func;
+ acd->ipv4_conflict_data = user_data;
+ break;
+ case ACD_HOST_EVENT_IPV4_MAXCONFLICT:
+ acd->ipv4_max_conflicts_cb = func;
+ acd->ipv4_max_conflicts_data = user_data;
+ break;
+ default:
+ connman_warn("%s unknown event %d.", __FUNCTION__, event);
+ break;
+ }
+}
+
+static void append_ac_mac(DBusMessageIter *iter, void *user_data)
+{
+ struct acd_host *acd = user_data;
+ char mac[32];
+ uint8_t *m = acd->ac_mac;
+ const char *str = mac;
+
+ snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
+ m[0], m[1], m[2], m[3], m[4], m[5]);
+ connman_dbus_dict_append_basic(iter, "Address", DBUS_TYPE_STRING, &str);
+}
+
+static void append_ac_ipv4(DBusMessageIter *iter, void *user_data)
+{
+ struct acd_host *acd = user_data;
+ struct in_addr addr;
+ char *a;
+
+ addr.s_addr = htonl(acd->ac_ip);
+ a = inet_ntoa(addr);
+ if (!a)
+ a = "";
+ connman_dbus_dict_append_basic(iter, "Address", DBUS_TYPE_STRING, &a);
+}
+
+static void append_ac_property(DBusMessageIter *iter, void *user_data)
+{
+ struct acd_host *acd = user_data;
+
+ connman_dbus_dict_append_dict(iter, "IPv4", append_ac_ipv4, acd);
+ connman_dbus_dict_append_dict(iter, "Ethernet", append_ac_mac, acd);
+ connman_dbus_dict_append_basic(iter, "Timestamp", DBUS_TYPE_INT64,
+ &acd->ac_timestamp);
+ connman_dbus_dict_append_basic(iter, "Resolved", DBUS_TYPE_BOOLEAN,
+ &acd->ac_resolved);
+}
+
+void acd_host_append_dbus_property(struct acd_host *acd, DBusMessageIter *dict)
+{
+ connman_dbus_dict_append_dict(dict, "LastAddressConflict",
+ append_ac_property, acd);
+}
+
+static void report_conflict(struct acd_host *acd, const struct ether_arp* arp)
+{
+ if (arp) {
+ acd->ac_ip = acd->requested_ip;
+ memcpy(acd->ac_mac, arp->arp_sha, sizeof(acd->ac_mac));
+ acd->ac_timestamp = g_get_real_time();
+ acd->ac_resolved = false;
+ } else {
+ acd->ac_resolved = true;
+ }
+
+ connman_dbus_property_changed_dict(acd->path, CONNMAN_SERVICE_INTERFACE,
+ "LastAddressConflict", append_ac_property, acd);
+}
+
+unsigned int acd_host_get_conflicts_count(struct acd_host *acd)
+{
+ return acd->conflicts;
+}