summaryrefslogtreecommitdiff
path: root/src/ipcpd/unicast/pff/alternate.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ipcpd/unicast/pff/alternate.c')
-rw-r--r--src/ipcpd/unicast/pff/alternate.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/src/ipcpd/unicast/pff/alternate.c b/src/ipcpd/unicast/pff/alternate.c
new file mode 100644
index 00000000..85e85914
--- /dev/null
+++ b/src/ipcpd/unicast/pff/alternate.c
@@ -0,0 +1,400 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2024
+ *
+ * Policy for PFF with alternate next hops
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200112L
+
+#include "config.h"
+
+#include <ouroboros/errno.h>
+#include <ouroboros/list.h>
+
+#include "pft.h"
+#include "alternate.h"
+
+#include <string.h>
+#include <assert.h>
+#include <pthread.h>
+
+struct nhop {
+ struct list_head next;
+ int fd;
+};
+
+struct addr {
+ struct list_head next;
+ uint64_t addr;
+};
+
+struct pff_i {
+ struct pft * pft;
+
+ struct list_head addrs;
+
+ struct list_head nhops_down;
+
+ pthread_rwlock_t lock;
+};
+
+struct pff_ops alternate_pff_ops = {
+ .create = alternate_pff_create,
+ .destroy = alternate_pff_destroy,
+ .lock = alternate_pff_lock,
+ .unlock = alternate_pff_unlock,
+ .add = alternate_pff_add,
+ .update = alternate_pff_update,
+ .del = alternate_pff_del,
+ .flush = alternate_pff_flush,
+ .nhop = alternate_pff_nhop,
+ .flow_state_change = alternate_flow_state_change
+};
+
+static int add_addr(struct pff_i * pff_i,
+ uint64_t addr)
+{
+ struct addr * a;
+
+ a = malloc(sizeof(*a));
+ if (a == NULL)
+ return -1;
+
+ a->addr = addr;
+
+ list_add(&a->next, &(pff_i->addrs));
+
+ return 0;
+}
+
+static void del_addr(struct pff_i * pff_i,
+ uint64_t addr)
+{
+ struct list_head * p;
+ struct list_head * h;
+
+ list_for_each_safe(p, h, &(pff_i->addrs)) {
+ struct addr * e = list_entry(p, struct addr, next);
+ if (e->addr == addr) {
+ list_del(&e->next);
+ free(e);
+ return;
+ }
+ }
+}
+
+static void del_addrs(struct pff_i * pff_i)
+{
+ struct list_head * p;
+ struct list_head * h;
+
+ list_for_each_safe(p, h, &(pff_i->addrs)) {
+ struct addr * e = list_entry(p, struct addr, next);
+ list_del(&e->next);
+ free(e);
+ }
+}
+
+static void del_nhops_down(struct pff_i * pff_i)
+{
+ struct list_head * p;
+ struct list_head * h;
+
+ list_for_each_safe(p, h, &(pff_i->nhops_down)) {
+ struct nhop * e = list_entry(p, struct nhop, next);
+ list_del(&e->next);
+ free(e);
+ }
+}
+
+static int del_nhop_down(struct pff_i * pff_i,
+ int fd)
+{
+ struct list_head * p;
+ struct list_head * h;
+
+ list_for_each_safe(p, h, &(pff_i->nhops_down)) {
+ struct nhop * e = list_entry(p, struct nhop, next);
+ if (e->fd == fd) {
+ list_del(&e->next);
+ free(e);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int add_nhop_down(struct pff_i * pff_i,
+ int fd)
+{
+ struct nhop * nhop;
+
+ nhop = malloc(sizeof(*nhop));
+ if (nhop == NULL)
+ return -1;
+
+ nhop->fd = fd;
+
+ list_add(&nhop->next, &(pff_i->nhops_down));
+
+ return 0;
+}
+
+static bool nhops_down_has(struct pff_i * pff_i,
+ int fd)
+{
+ struct list_head * pos = NULL;
+
+ list_for_each(pos, &pff_i->nhops_down) {
+ struct nhop * e = list_entry(pos, struct nhop, next);
+ if (e->fd == fd)
+ return true;
+ }
+
+ return false;
+}
+
+static int add_to_pft(struct pff_i * pff_i,
+ uint64_t addr,
+ int * fd,
+ size_t len)
+{
+ int * fds;
+
+ assert(pff_i);
+ assert(len > 0);
+
+ fds = malloc(sizeof(*fds) * (len + 1));
+ if (fds == NULL)
+ goto fail_malloc;
+
+ memcpy(fds, fd, len * sizeof(*fds));
+ /* Put primary hop again at the end */
+ fds[len] = fds[0];
+
+ if (pft_insert(pff_i->pft, addr, fds, len))
+ goto fail_insert;
+
+ return 0;
+
+ fail_insert:
+ free(fds);
+ fail_malloc:
+ return -1;
+}
+
+struct pff_i * alternate_pff_create(void)
+{
+ struct pff_i * tmp;
+
+ tmp = malloc(sizeof(*tmp));
+ if (tmp == NULL)
+ goto fail_malloc;
+
+ if (pthread_rwlock_init(&tmp->lock, NULL))
+ goto fail_lock;
+
+ tmp->pft = pft_create(PFT_SIZE, false);
+ if (tmp->pft == NULL)
+ goto fail_pft;
+
+ list_head_init(&tmp->nhops_down);
+ list_head_init(&tmp->addrs);
+
+ return tmp;
+
+ fail_pft:
+ pthread_rwlock_destroy(&tmp->lock);
+ fail_lock:
+ free(tmp);
+ fail_malloc:
+ return NULL;
+}
+
+void alternate_pff_destroy(struct pff_i * pff_i)
+{
+ assert(pff_i);
+
+ pft_destroy(pff_i->pft);
+ del_nhops_down(pff_i);
+ del_addrs(pff_i);
+ pthread_rwlock_destroy(&pff_i->lock);
+ free(pff_i);
+}
+
+void alternate_pff_lock(struct pff_i * pff_i)
+{
+ pthread_rwlock_wrlock(&pff_i->lock);
+}
+
+void alternate_pff_unlock(struct pff_i * pff_i)
+{
+ pthread_rwlock_unlock(&pff_i->lock);
+}
+
+int alternate_pff_add(struct pff_i * pff_i,
+ uint64_t addr,
+ int * fd,
+ size_t len)
+{
+ assert(pff_i);
+ assert(len > 0);
+
+ if (add_to_pft(pff_i, addr, fd, len))
+ return -1;
+
+ if (add_addr(pff_i, addr)) {
+ pft_delete(pff_i->pft, addr);
+ return -1;
+ }
+
+ return 0;
+}
+
+int alternate_pff_update(struct pff_i * pff_i,
+ uint64_t addr,
+ int * fd,
+ size_t len)
+{
+ assert(pff_i);
+ assert(len > 0);
+
+ if (pft_delete(pff_i->pft, addr))
+ return -1;
+
+ if (add_to_pft(pff_i, addr, fd, len))
+ return -1;
+
+ return 0;
+}
+
+int alternate_pff_del(struct pff_i * pff_i,
+ uint64_t addr)
+{
+ assert(pff_i);
+
+ del_addr(pff_i, addr);
+
+ if (pft_delete(pff_i->pft, addr))
+ return -1;
+
+ return 0;
+}
+
+void alternate_pff_flush(struct pff_i * pff_i)
+{
+ assert(pff_i);
+
+ pft_flush(pff_i->pft);
+
+ del_nhops_down(pff_i);
+
+ del_addrs(pff_i);
+}
+
+int alternate_pff_nhop(struct pff_i * pff_i,
+ uint64_t addr)
+{
+ int fd;
+ size_t len;
+ int * fds;
+
+ assert(pff_i);
+
+ pthread_rwlock_rdlock(&pff_i->lock);
+
+ if (pft_lookup(pff_i->pft, addr, &fds, &len)) {
+ pthread_rwlock_unlock(&pff_i->lock);
+ return -1;
+ }
+
+ fd = *fds;
+
+ pthread_rwlock_unlock(&pff_i->lock);
+
+ return fd;
+}
+
+int alternate_flow_state_change(struct pff_i * pff_i,
+ int fd,
+ bool up)
+{
+ struct list_head * p;
+ size_t len;
+ int * fds;
+ size_t i;
+ int tmp;
+
+ assert(pff_i);
+
+ pthread_rwlock_wrlock(&pff_i->lock);
+
+ if (up) {
+ if (del_nhop_down(pff_i, fd)) {
+ pthread_rwlock_unlock(&pff_i->lock);
+ return -1;
+ }
+ } else {
+ if (add_nhop_down(pff_i, fd)) {
+ pthread_rwlock_unlock(&pff_i->lock);
+ return -1;
+ }
+ }
+
+ list_for_each(p, &pff_i->addrs) {
+ struct addr * e = list_entry(p, struct addr, next);
+ if (pft_lookup(pff_i->pft, e->addr, &fds, &len)) {
+ pthread_rwlock_unlock(&pff_i->lock);
+ return -1;
+ }
+
+ if (up) {
+ /* It is using an alternate */
+ if (fds[len] == fd && fds[0] != fd) {
+ for (i = 0 ; i < len; i++) {
+ /* Found the primary */
+ if (fds[i] == fd) {
+ tmp = fds[0];
+ fds[0] = fds[i];
+ fds[i] = tmp;
+ break;
+ }
+ }
+ }
+ } else {
+ /* Need to switch to a (different) alternate */
+ if (fds[0] == fd) {
+ for (i = 1; i < len; i++) {
+ /* Usable alternate */
+ if (!nhops_down_has(pff_i, fds[i])) {
+ tmp = fds[0];
+ fds[0] = fds[i];
+ fds[i] = tmp;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ pthread_rwlock_unlock(&pff_i->lock);
+
+ return 0;
+}