1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-02-12 18:41:51 +00:00
openmptcprouter-feeds/mptcpd/patches/ndiffports.patch
2023-08-09 21:57:52 +02:00

703 lines
22 KiB
Diff

From f127effabdb5aa1114613b637c96b212a603c57a Mon Sep 17 00:00:00 2001
From: Mat Martineau <mathew.j.martineau@linux.intel.com>
Date: Tue, 14 Jun 2022 16:35:18 -0700
Subject: [PATCH] plugins: Add ndiffports plugin
This plugin creates N different subflows for each MPTCP connection, all
using the same single interface on each peer. Only the original local
and remote IP addresses are used, but with unique port numbers for each
subflow.
---
plugins/path_managers/Makefile.am | 17 +-
plugins/path_managers/ndiffports.c | 647 +++++++++++++++++++++++++++++
2 files changed, 663 insertions(+), 1 deletion(-)
create mode 100644 plugins/path_managers/ndiffports.c
diff --git a/plugins/path_managers/Makefile.am b/plugins/path_managers/Makefile.am
index 50719921..8320b750 100644
--- a/plugins/path_managers/Makefile.am
+++ b/plugins/path_managers/Makefile.am
@@ -7,7 +7,7 @@ include $(top_srcdir)/aminclude_static.am
MPTCPD_PLUGIN_CPPFLAGS = \
-I$(top_srcdir)/include -I$(top_builddir)/include
-pkglib_LTLIBRARIES = addr_adv.la sspi.la
+pkglib_LTLIBRARIES = addr_adv.la ndiffports.la sspi.la
sspi_la_SOURCES = sspi.c
sspi_la_CPPFLAGS = $(MPTCPD_PLUGIN_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS)
@@ -24,6 +24,21 @@ sspi_la_LIBADD = \
$(top_builddir)/lib/libmptcpd.la \
$(CODE_COVERAGE_LIBS)
+ndiffports_la_SOURCES = ndiffports.c
+ndiffports_la_CPPFLAGS = $(MPTCPD_PLUGIN_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS)
+ndiffports_la_CFLAGS = \
+ $(ELL_CFLAGS) \
+ $(MPTCPD_PLUGIN_CFLAGS) \
+ $(CODE_COVERAGE_CFLAGS)
+ndiffports_la_LDFLAGS = \
+ -no-undefined \
+ -module \
+ -avoid-version \
+ $(ELL_LIBS)
+ndiffports_la_LIBADD = \
+ $(top_builddir)/lib/libmptcpd.la \
+ $(CODE_COVERAGE_LIBS)
+
addr_adv_la_SOURCES = addr_adv.c
addr_adv_la_CPPFLAGS = $(MPTCPD_PLUGIN_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS)
addr_adv_la_CFLAGS = \
diff --git a/plugins/path_managers/ndiffports.c b/plugins/path_managers/ndiffports.c
new file mode 100644
index 00000000..f217dbbd
--- /dev/null
+++ b/plugins/path_managers/ndiffports.c
@@ -0,0 +1,647 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/**
+ * @file ndiffports.c
+ *
+ * @brief MPTCP n-different-ports path manager plugin.
+ *
+ * This plugin creates N different subflows for each MPTCP connection,
+ * all using the same single interface on each peer. Only the original
+ * local and remote IP addresses are used, but with unique port numbers
+ * for each subflow.
+ *
+ * Copyright (c) 2022, Intel Corporation
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <mptcpd/private/config.h> // For NDEBUG and mptcpd VERSION.
+#endif
+
+#include <assert.h>
+#include <stddef.h> // For NULL.
+#include <limits.h>
+
+#include <netinet/in.h>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+#include <ell/util.h> // For L_STRINGIFY needed by l_error().
+#include <ell/log.h>
+#include <ell/hashmap.h>
+#include <ell/time.h>
+#pragma GCC diagnostic pop
+
+#include <mptcpd/network_monitor.h>
+#include <mptcpd/path_manager.h>
+#include <mptcpd/plugin.h>
+#include <mptcpd/id_manager.h>
+
+/*
+ * Development notes
+ *
+ * - Need config file value for N in Ndiffports
+ * - Track both local and remote port to allow for remote-initiated subflows
+ * - Subflow request timeout (clear pending flag?)
+ *
+ * Questions
+ * - Is subflow close "reason code" available on the genl api?
+ *
+ * Longer-term ideas
+ *
+ * - Accept advertisements for remote ports with the same remote address
+ * as the initial subflow.
+ * - Advertise multiple local listening ports
+ */
+
+/**
+ * @brief Number of subflows to allow.
+ *
+ * Compile-time constant as a temporary measure, until plugins have access
+ * to config file values.
+ */
+#define NDIFFPORTS_LIMIT 2
+
+/**
+ * @brief Time threshold for detecting subflows rejected by the peer
+ *
+ * Subflows with a lifetime under this number of second are considered an
+ * indication of hitting the remote peer's subflow limit.
+ */
+#define NDIFFPORTS_REJECT_TIME 10
+
+/**
+ * @struct ndiffports_subflow_info
+ *
+ * @brief Per-subflow information.
+ *
+ * Each connection tracks this information for each subflow it uses.
+ * With ndiffports, all local and remote addresses are the same so only
+ * port information is required to identify a specific subflow.
+ *
+ * Other fields track the state of the subflow.
+ */
+struct ndiffports_subflow_info
+{
+ /// Local port number for this subflow
+ in_port_t local_port;
+
+ /// Subflow slot is free or in use
+ bool in_use;
+
+ /// Timestamp for tracking last change to subflow state
+ uint64_t timestamp;
+};
+
+/**
+ * @struct ndiffports_connection_info
+ *
+ * @brief Per-connection information.
+ *
+ * Instances of this structure are stored in the ndiffports_connections
+ * hashmap, indexed by token. They are created when a connection is
+ * established and freed when the connection is closed or the plugin
+ * exits.
+ */
+struct ndiffports_connection_info
+{
+ /// Local address for all subflows. Port number ignored.
+ struct sockaddr_storage laddr;
+
+ /// Remote address for all subflows.
+ struct sockaddr_storage raddr;
+
+ /// Server (vs client) side
+ bool server_side;
+
+ /// Active subflow count
+ int active_subflows;
+
+ /// Max subflow count
+ int max_subflows;
+
+ /// Peer rejected subflows
+ int peer_rejected_consecutive;
+
+ /// Subflow was requested, but no new_subflow event yet
+ bool pending_request;
+
+ /// Subflow info
+ struct ndiffports_subflow_info subflow[NDIFFPORTS_LIMIT];
+};
+
+/**
+ * Hashmap of @c ndiffports_connection_info objects that track the
+ * active MPTCP connections and associated state.
+ */
+static struct l_hashmap *ndiffports_connections;
+
+// ----------------------------------------------------------------
+// Helper functions
+// ----------------------------------------------------------------
+
+/**
+ * @brief Extract local port information from a @c sockaddr
+ *
+ * @param[in] addr A @c struct @c sockaddr with an IPv4 or IPv6 address+port
+ *
+ * @return @c sin_port or @c sin6_port from the @a addr, or @c 0 if
+ * an invalid @c sa_family was detected.
+ *
+ */
+static in_port_t get_port(struct sockaddr const *addr)
+{
+ in_port_t port = 0;
+
+ if (addr->sa_family == AF_INET) {
+ port = ((struct sockaddr_in const *) addr)->sin_port;
+ } else if (addr->sa_family == AF_INET6) {
+ port = ((struct sockaddr_in6 const *) addr)->sin6_port;
+ }
+
+ return port;
+}
+
+/**
+ * @brief Set the port number to 0 in a @c struct @c sockaddr.
+ *
+ * @param[in] addr A @c struct @c sockaddr to modify
+ */
+static void clear_port(struct sockaddr *addr)
+{
+ if (addr->sa_family == AF_INET) {
+ ((struct sockaddr_in *) addr)->sin_port = 0;
+ } else if (addr->sa_family == AF_INET6) {
+ ((struct sockaddr_in6 *) addr)->sin6_port = 0;
+ } else {
+ l_error("Unexpected sockaddr family %d", addr->sa_family);
+ }
+}
+
+/**
+ * @brief Locate an unused subflow slot
+ *
+ * Each @c struct @c ndiffports_connection_info contains an array of @c
+ * struct @c ndiffports_subflow_info objects. Find the first subflow
+ * object that is not in use.
+ *
+ * @param[in] info A @c struct @c ndiffports_connection_info
+ *
+ * @return Pointer to the first available object, or NULL if none available
+ */
+static struct ndiffports_subflow_info *
+find_empty_subflow(struct ndiffports_connection_info *info)
+{
+ for (int i = 0; i < NDIFFPORTS_LIMIT && i < info->max_subflows; i++) {
+ if (!info->subflow[i].in_use) {
+ return &info->subflow[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Find an existing subflow matching the provided local endpoint
+ *
+ * Look up a subflow object and return a pointer to it, using the
+ * local port number. Address values are assumed to be validated already.
+ *
+ * @param[in] info A @c struct @c ndiffports_connection_info
+ * @param[in] laddr A @c struct @c sockaddr containing the port number
+ *
+ * @return Pointer to the subflow object if found, or @c NULL if not found.
+ */
+static struct ndiffports_subflow_info *
+find_subflow(struct ndiffports_connection_info *info,
+ struct sockaddr const *laddr)
+{
+ in_port_t lport = get_port(laddr);
+
+ for (int i = 0; i < NDIFFPORTS_LIMIT && i < info->max_subflows; i++) {
+ struct ndiffports_subflow_info *sub = &info->subflow[i];
+
+ if (sub->in_use && sub->local_port == lport) {
+ return &info->subflow[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Initiate a new subflow
+ *
+ * @param[in] pm Path manager object to act on
+ * @param[in] token Token for the connection where the subflow will be added
+ * @param[in] info Connection object where the subflow will be added
+ */
+static void add_new_subflow(struct mptcpd_pm *pm, mptcpd_token_t token,
+ struct ndiffports_connection_info *info)
+{
+ if (info->pending_request) {
+ l_warn("New subflow request while previous request is pending");
+
+ return;
+ }
+
+ info->pending_request = true;
+
+ mptcpd_pm_add_subflow(pm, token, 1, 0,
+ (struct sockaddr *)&info->laddr,
+ (struct sockaddr *)&info->raddr,
+ false);
+
+ /* TODO: start a timer to retry subflow creation */
+}
+
+/**
+ * @brief Check two @c sockaddr objects for equality.
+ *
+ * Addresses are equal if the @c sa_family and @c sin_addr / @c sin6_addr
+ * fields match. Port numbers are ignored.
+ *
+ * @param[in] addr1 Address to compare
+ * @param[in] addr2 Address to compare
+ *
+ * @return @c true if the network addresses match (including address
+ * family). @false if the address family or address values don't match.
+ */
+static bool addrs_equal(struct sockaddr const *addr1,
+ struct sockaddr const *addr2)
+{
+ if (addr1->sa_family != addr2->sa_family) {
+ return false;
+ }
+
+ if (addr1->sa_family == AF_INET) {
+ struct sockaddr_in const *const addr1_in =
+ (struct sockaddr_in const *) addr1;
+ struct sockaddr_in const *const addr2_in =
+ (struct sockaddr_in const *) addr2;
+
+ return addr1_in->sin_addr.s_addr == addr2_in->sin_addr.s_addr;
+ } else {
+ struct sockaddr_in6 const *const addr1_in6 =
+ (struct sockaddr_in6 const *) addr1;
+ struct sockaddr_in6 const *const addr2_in6 =
+ (struct sockaddr_in6 const *) addr2;
+
+ return 0 == memcmp(&addr1_in6->sin6_addr, &addr2_in6->sin6_addr,
+ sizeof(addr1_in6->sin6_addr));
+ }
+}
+
+/**
+ * @brief Validate addresses on a new subflow
+ *
+ * With ndiffports, all subflows are expected to use the same local and
+ * remote addresses as the initial subflow. Valid subflows will have
+ * both addresses match (port numbers are ignored).
+ *
+ * @param[in] info Connection to validate against
+ * @param[in] laddr Local address for new subflow
+ * @param[in] raddr Remote address for new subflow
+ *
+ * @return @c true if local/remote addresses for the connection match
+ * those in the new subflow. @c false if either pair differs.
+ */
+static bool validate_addrs(struct ndiffports_connection_info *info,
+ struct sockaddr const *laddr,
+ struct sockaddr const *raddr)
+{
+ return (addrs_equal((struct sockaddr*)&info->laddr, laddr) &&
+ addrs_equal((struct sockaddr*)&info->raddr, raddr));
+}
+
+/**
+ * @brief Calculate how many seconds ago a timestamp was
+ *
+ * @param[in] timestamp Timestamp in the past
+ *
+ * @return Number of seconds since the @c timestamp. 0 if timestamp is in
+ * the future.
+ */
+static uint64_t seconds_elapsed(uint64_t timestamp)
+{
+ uint64_t now = l_time_now();
+
+ return (now > timestamp) ? l_time_to_secs(now - timestamp) : 0;
+}
+
+// ----------------------------------------------------------------
+// Mptcpd Plugin Operations
+// ----------------------------------------------------------------
+
+/**
+ * @brief Connection established event handler
+ *
+ * When a new connection is created, set up data structures to track
+ * connection-related info and track the initial subflow.
+ *
+ * If additional subflows need to be established and this peer initiated
+ * the connection, initiate one new subflow.
+ *
+ * @param[in] token MPTCP connection token for this new connection
+ * @param[in] laddr Local address and port for the initial subflow
+ * @param[in] raddr Remote address and port for the initial subflow
+ * @param[in] server_side @c true if this peer was the listener (server),
+ * @c false if this peer initiated the connection.
+ * @param[in] pm Path manager object
+ */
+static void ndiffports_connection_established(mptcpd_token_t token,
+ struct sockaddr const *laddr,
+ struct sockaddr const *raddr,
+ bool server_side,
+ struct mptcpd_pm *pm)
+{
+ struct ndiffports_subflow_info *first_subflow;
+ struct ndiffports_connection_info *new_entry;
+ in_port_t local_port = get_port(laddr);
+ void *old_entry;
+
+ if (!local_port) {
+ l_error("Invalid local port");
+ return;
+ }
+
+ new_entry = l_new(struct ndiffports_connection_info, 1);
+
+ memcpy(&new_entry->laddr, laddr, sizeof(new_entry->laddr));
+ memcpy(&new_entry->raddr, raddr, sizeof(new_entry->raddr));
+ new_entry->server_side = server_side;
+ new_entry->active_subflows = 1;
+ new_entry->max_subflows = NDIFFPORTS_LIMIT;
+
+ /* Zero out the port number on the local addr so it's assigned
+ * at connect time for new subflows
+ */
+ clear_port((struct sockaddr *)&new_entry->laddr);
+
+ first_subflow = &new_entry->subflow[0];
+ first_subflow->local_port = local_port;
+ first_subflow->timestamp = l_time_now();
+
+ if (!l_hashmap_replace(ndiffports_connections, L_UINT_TO_PTR(token),
+ new_entry, &old_entry)) {
+ l_error("Unable to store connection info");
+ }
+
+ if (old_entry) {
+ /* Unexpected: token already exists.
+ * The kernel enforces unique tokens so assume it's stale
+ * data from an old connection that no longer exists.
+ */
+ l_warn("Found old entry for token %08x", token);
+ l_free(old_entry);
+ }
+
+ if (!server_side &&
+ new_entry->active_subflows < new_entry->max_subflows) {
+ add_new_subflow(pm, token, new_entry);
+ }
+}
+
+/**
+ * @brief Connection closed event handler
+ *
+ * When a connection is closed, delete the tracking information for
+ * that connection.
+ *
+ * @param[in] token MPTCP connection token for this new connection
+ * @param[in] pm Path manager object (unused)
+ */
+static void ndiffports_connection_closed(mptcpd_token_t token,
+ struct mptcpd_pm *pm)
+{
+ struct ndiffports_connection_info *entry;
+ (void) pm;
+
+ entry = l_hashmap_remove(ndiffports_connections, L_UINT_TO_PTR(token));
+
+ if (!entry) {
+ l_warn("Missing entry for token %08x", token);
+ }
+
+ l_free(entry);
+}
+
+/**
+ * @brief New subflow event handler
+ *
+ * When a new subflow is created, track the subflow information within
+ * the corresponding connection entry.
+ *
+ * If additional subflows need to be established and this peer initiated
+ * the overall MPTCP connection, initiate one new subflow.
+ *
+ * @param[in] token MPTCP connection token for this new connection
+ * @param[in] laddr Local address and port for new subflow
+ * @param[in] raddr Remote address and port for new subflow
+ * @param[in] backup @c true if this is backup subflow, @c false for
+ * regular priority. (not used)
+ * @param[in] pm Path manager object
+ */
+static void ndiffports_new_subflow(mptcpd_token_t token,
+ struct sockaddr const *laddr,
+ struct sockaddr const *raddr,
+ bool backup,
+ struct mptcpd_pm *pm)
+{
+ struct ndiffports_subflow_info *sub;
+ struct ndiffports_connection_info *entry;
+
+ (void) backup;
+
+ entry = l_hashmap_lookup(ndiffports_connections, L_UINT_TO_PTR(token));
+
+ if (!entry) {
+ l_warn("New subflow for unmanaged token %08x", token);
+
+ mptcpd_pm_remove_subflow(pm, token, laddr, raddr);
+ return;
+ }
+
+ if (!validate_addrs(entry, laddr, raddr) ||
+ entry->active_subflows >= entry->max_subflows) {
+ goto reject;
+ }
+
+ sub = find_subflow(entry, laddr);
+
+ if (sub) {
+ l_warn("Unexpected event from established subflow");
+
+ // TODO: stop timer.
+ } else {
+ sub = find_empty_subflow(entry);
+ if (!sub) {
+ goto reject;
+ }
+
+ sub->in_use = true;
+ sub->timestamp = l_time_now();
+
+ entry->active_subflows++;
+
+ sub->local_port = get_port(laddr);
+ }
+
+ sub->timestamp = l_time_now();
+ entry->pending_request = false;
+
+ /* TODO: start timer to clear consecutive_reject counter */
+
+ if (!entry->server_side &&
+ entry->active_subflows < entry->max_subflows) {
+ add_new_subflow(pm, token, entry);
+ }
+
+ return;
+
+reject:
+ mptcpd_pm_remove_subflow(pm, token, laddr, raddr);
+}
+
+/**
+ * @brief Subflow closed event handler
+ *
+ * When a subflow is closed, clear related information. If the number of
+ * subflows is below the expected count and this peer initiated the
+ * original connection, request a new subflow to replace the closed one.
+ *
+ * @param[in] token MPTCP connection token for this new connection
+ * @param[in] laddr Local address and port for new subflow
+ * @param[in] raddr Remote address and port for new subflow
+ * @param[in] backup @c true if this is backup subflow, @c false for
+ * regular priority. (not used)
+ * @param[in] pm Path manager object
+ */
+static void ndiffports_subflow_closed(mptcpd_token_t token,
+ struct sockaddr const *laddr,
+ struct sockaddr const *raddr,
+ bool backup,
+ struct mptcpd_pm *pm)
+{
+ struct ndiffports_connection_info *entry;
+ struct ndiffports_subflow_info *sub;
+
+ (void) backup;
+
+ entry = l_hashmap_lookup(ndiffports_connections, L_UINT_TO_PTR(token));
+
+ if (!entry) {
+ l_warn("Closed subflow for unmanaged token %08x", token);
+ return;
+ }
+
+ if (!validate_addrs(entry, laddr, raddr)) {
+ l_warn("Address/token mismatch for token %08x", token);
+ return;
+ }
+
+ sub = find_subflow(entry, laddr);
+
+ if (sub) {
+ if (seconds_elapsed(sub->timestamp) < NDIFFPORTS_REJECT_TIME) {
+ entry->peer_rejected_consecutive++;
+ }
+
+ sub->in_use = false;
+ sub->timestamp = l_time_now();
+
+ if (entry->active_subflows > 0) {
+ entry->active_subflows--;
+ } else {
+ l_error("Underflow when adjusting subflow count");
+ }
+ } else {
+ l_warn("Untracked subflow was closed");
+ }
+
+ if (!entry->server_side &&
+ entry->active_subflows < entry->max_subflows) {
+ add_new_subflow(pm, token, entry);
+ }
+}
+
+/**
+ * Plugin ops to register with the mptcpd core
+ */
+static struct mptcpd_plugin_ops const pm_ops = {
+ .connection_established = ndiffports_connection_established,
+ .connection_closed = ndiffports_connection_closed,
+ .new_subflow = ndiffports_new_subflow,
+ .subflow_closed = ndiffports_subflow_closed,
+};
+
+/**
+ * @brief Ndiffports plugin init hook
+ *
+ * Set up the hashmap for tracking connections.
+ *
+ * @param[in] pm Path manager object (not used)
+ */
+static int ndiffports_init(struct mptcpd_pm *pm)
+{
+ (void) pm;
+
+ static char const name[] = "ndiffports";
+
+ if (!mptcpd_plugin_register_ops(name, &pm_ops)) {
+ l_error("Failed to initialize "
+ "n-different-ports "
+ "path manager plugin.");
+
+ return -1;
+ }
+
+ ndiffports_connections = l_hashmap_new();
+
+ l_info("MPTCP n-different-ports "
+ "path manager initialized.");
+
+ return 0;
+}
+
+/**
+ * @brief Helper for cleaning up connection info
+ *
+ * Delete one connection entry.
+ *
+ * @param[in] pm Path manager object (not used)
+ */
+static void ndiffports_connection_destroy(void *value)
+{
+ struct ndiffports_connection_info *info = value;
+
+ l_free(info);
+}
+
+/**
+ * @brief Ndiffports plugin exit hook
+ *
+ * Delete the hashmap for tracking connections.
+ *
+ * @param[in] pm Path manager object (not used)
+ */
+static void ndiffports_exit(struct mptcpd_pm *pm)
+{
+ (void) pm;
+
+ l_hashmap_destroy(ndiffports_connections,
+ ndiffports_connection_destroy);
+ ndiffports_connections = NULL;
+ l_info("MPTCP n-different-ports path manager exited.");
+}
+
+MPTCPD_PLUGIN_DEFINE(ndiffports,
+ "N-different-ports path manager",
+ MPTCPD_PLUGIN_PRIORITY_DEFAULT,
+ ndiffports_init,
+ ndiffports_exit)
+
+
+/*
+ Local Variables:
+ c-file-style: "linux"
+ End:
+*/