diff options
Diffstat (limited to 'src/ipcpd/common/connmgr.c')
-rw-r--r-- | src/ipcpd/common/connmgr.c | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/src/ipcpd/common/connmgr.c b/src/ipcpd/common/connmgr.c new file mode 100644 index 00000000..9002467c --- /dev/null +++ b/src/ipcpd/common/connmgr.c @@ -0,0 +1,518 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2021 + * + * Handles connections between components + * + * 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 OUROBOROS_PREFIX "connection-manager" + +#include <ouroboros/dev.h> +#include <ouroboros/cacep.h> +#include <ouroboros/errno.h> +#include <ouroboros/list.h> +#include <ouroboros/logs.h> +#include <ouroboros/notifier.h> + +#include "connmgr.h" +#include "ipcp.h" + +#include <pthread.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +enum connmgr_state { + CONNMGR_NULL = 0, + CONNMGR_INIT, + CONNMGR_RUNNING +}; + +struct conn_el { + struct list_head next; + struct conn conn; +}; + +struct comp { + struct conn_info info; + + struct list_head conns; + struct list_head pending; + + pthread_cond_t cond; + pthread_mutex_t lock; +}; + +struct { + struct comp comps[COMPID_MAX]; + enum connmgr_state state; + + pthread_t acceptor; +} connmgr; + +static int get_id_by_name(const char * name) +{ + enum comp_id i; + + for (i = 0; i < COMPID_MAX; ++i) + if (strcmp(name, connmgr.comps[i].info.comp_name) == 0) + return i; + + return -1; +} + +static int get_conn_by_fd(int fd, + enum comp_id id, + struct conn * conn) +{ + struct list_head * p; + + pthread_mutex_lock(&connmgr.comps[id].lock); + + list_for_each(p, &connmgr.comps[id].conns) { + struct conn_el * c = + list_entry(p, struct conn_el, next); + if (c->conn.flow_info.fd == fd) { + *conn = c->conn; + pthread_mutex_unlock(&connmgr.comps[id].lock); + return 0; + } + } + + pthread_mutex_unlock(&connmgr.comps[id].lock); + + return -1; +} + +static int add_comp_conn(enum comp_id id, + int fd, + qosspec_t qs, + struct conn_info * rcv_info) +{ + struct conn_el * el; + + el = malloc(sizeof(*el)); + if (el == NULL) { + log_err("Not enough memory."); + return -1; + } + + el->conn.conn_info = *rcv_info; + el->conn.flow_info.fd = fd; + el->conn.flow_info.qs = qs; + + pthread_mutex_lock(&connmgr.comps[id].lock); + + list_add(&el->next, &connmgr.comps[id].pending); + pthread_cond_signal(&connmgr.comps[id].cond); + + pthread_mutex_unlock(&connmgr.comps[id].lock); + + return 0; +} + +static void * flow_acceptor(void * o) +{ + int fd; + qosspec_t qs; + struct conn_info rcv_info; + struct conn_info fail_info; + + (void) o; + + memset(&fail_info, 0, sizeof(fail_info)); + + while (true) { + int id; + + fd = flow_accept(&qs, NULL); + if (fd < 0) { + if (fd != -EIRMD) + log_warn("Flow accept failed: %d", fd); + continue; + } + + if (cacep_rcv(fd, &rcv_info)) { + log_dbg("Error establishing application connection."); + flow_dealloc(fd); + continue; + } + + id = get_id_by_name(rcv_info.comp_name); + if (id < 0) { + log_dbg("Connection request for unknown component %s.", + rcv_info.comp_name); + cacep_snd(fd, &fail_info); + flow_dealloc(fd); + continue; + } + + assert(id < COMPID_MAX); + + if (cacep_snd(fd, &connmgr.comps[id].info)) { + log_dbg("Failed to respond to request."); + flow_dealloc(fd); + continue; + } + + if (add_comp_conn(id, fd, qs, &rcv_info)) { + log_dbg("Failed to add new connection."); + flow_dealloc(fd); + continue; + } + } + + return (void *) 0; +} + +static void handle_event(void * self, + int event, + const void * o) +{ + struct conn conn; + + (void) self; + + if (!(event == NOTIFY_DT_FLOW_UP || + event == NOTIFY_DT_FLOW_DOWN || + event == NOTIFY_DT_FLOW_DEALLOC)) + return; + + if (get_conn_by_fd(*((int *) o), COMPID_DT, &conn)) + return; + + switch (event) { + case NOTIFY_DT_FLOW_UP: + notifier_event(NOTIFY_DT_CONN_UP, &conn); + break; + case NOTIFY_DT_FLOW_DOWN: + notifier_event(NOTIFY_DT_CONN_DOWN, &conn); + break; + case NOTIFY_DT_FLOW_DEALLOC: + notifier_event(NOTIFY_DT_CONN_DEL, &conn); + break; + default: + break; + } +} + +int connmgr_init(void) +{ + connmgr.state = CONNMGR_INIT; + + if (notifier_reg(handle_event, NULL)) + return -1; + + return 0; +} + +void connmgr_fini(void) +{ + int i; + + notifier_unreg(handle_event); + + if (connmgr.state == CONNMGR_RUNNING) + pthread_join(connmgr.acceptor, NULL); + + for (i = 0; i < COMPID_MAX; ++i) + connmgr_comp_fini(i); +} + +int connmgr_start(void) +{ + if (pthread_create(&connmgr.acceptor, NULL, flow_acceptor, NULL)) + return -1; + + connmgr.state = CONNMGR_RUNNING; + + return 0; +} + +void connmgr_stop(void) +{ + if (connmgr.state == CONNMGR_RUNNING) + pthread_cancel(connmgr.acceptor); +} + +int connmgr_comp_init(enum comp_id id, + const struct conn_info * info) +{ + struct comp * comp; + + assert(id >= 0 && id < COMPID_MAX); + + comp = connmgr.comps + id; + + if (pthread_mutex_init(&comp->lock, NULL)) + return -1; + + if (pthread_cond_init(&comp->cond, NULL)) { + pthread_mutex_destroy(&comp->lock); + return -1; + } + + list_head_init(&comp->conns); + list_head_init(&comp->pending); + + memcpy(&connmgr.comps[id].info, info, sizeof(connmgr.comps[id].info)); + + return 0; +} + +void connmgr_comp_fini(enum comp_id id) +{ + struct list_head * p; + struct list_head * h; + struct comp * comp; + + assert(id >= 0 && id < COMPID_MAX); + + if (strlen(connmgr.comps[id].info.comp_name) == 0) + return; + + comp = connmgr.comps + id; + + pthread_mutex_lock(&comp->lock); + + list_for_each_safe(p, h, &comp->conns) { + struct conn_el * e = list_entry(p, struct conn_el, next); + list_del(&e->next); + free(e); + } + + list_for_each_safe(p, h, &comp->pending) { + struct conn_el * e = list_entry(p, struct conn_el, next); + list_del(&e->next); + free(e); + } + + pthread_mutex_unlock(&comp->lock); + + pthread_cond_destroy(&comp->cond); + pthread_mutex_destroy(&comp->lock); + + memset(&connmgr.comps[id].info, 0, sizeof(connmgr.comps[id].info)); +} + +int connmgr_ipcp_connect(const char * dst, + const char * component, + qosspec_t qs) +{ + struct conn_el * ce; + int id; + + assert(dst); + assert(component); + + ce = malloc(sizeof(*ce)); + if (ce == NULL) { + log_dbg("Out of memory."); + return -1; + } + + id = get_id_by_name(component); + if (id < 0) { + log_dbg("No such component: %s", component); + free(ce); + return -1; + } + + if (connmgr_alloc(id, dst, &qs, &ce->conn)) { + free(ce); + return -1; + } + + if (strlen(dst) > DST_MAX_STRLEN) { + log_warn("Truncating dst length for connection."); + memcpy(ce->conn.flow_info.dst, dst, DST_MAX_STRLEN); + ce->conn.flow_info.dst[DST_MAX_STRLEN] = '\0'; + } else { + strcpy(ce->conn.flow_info.dst, dst); + } + + pthread_mutex_lock(&connmgr.comps[id].lock); + + list_add(&ce->next, &connmgr.comps[id].conns); + + pthread_mutex_unlock(&connmgr.comps[id].lock); + + return 0; +} + +int connmgr_ipcp_disconnect(const char * dst, + const char * component) +{ + struct list_head * p; + struct list_head * h; + int id; + + assert(dst); + assert(component); + + id = get_id_by_name(component); + if (id < 0) + return -1; + + pthread_mutex_lock(&connmgr.comps[id].lock); + + list_for_each_safe(p,h, &connmgr.comps[id].conns) { + struct conn_el * el = list_entry(p, struct conn_el, next); + if (strcmp(el->conn.flow_info.dst, dst) == 0) { + int ret; + pthread_mutex_unlock(&connmgr.comps[id].lock); + list_del(&el->next); + ret = connmgr_dealloc(id, &el->conn); + free(el); + return ret; + } + } + + pthread_mutex_unlock(&connmgr.comps[id].lock); + + return 0; +} + +int connmgr_alloc(enum comp_id id, + const char * dst, + qosspec_t * qs, + struct conn * conn) +{ + assert(id >= 0 && id < COMPID_MAX); + assert(dst); + + conn->flow_info.fd = flow_alloc(dst, qs, NULL); + if (conn->flow_info.fd < 0) { + log_dbg("Failed to allocate flow to %s.", dst); + return -1; + } + + if (qs != NULL) + conn->flow_info.qs = *qs; + else + memset(&conn->flow_info.qs, 0, sizeof(conn->flow_info.qs)); + + log_dbg("Sending cacep info for protocol %s to fd %d.", + connmgr.comps[id].info.protocol, conn->flow_info.fd); + + if (cacep_snd(conn->flow_info.fd, &connmgr.comps[id].info)) { + log_dbg("Failed to create application connection."); + flow_dealloc(conn->flow_info.fd); + return -1; + } + + if (cacep_rcv(conn->flow_info.fd, &conn->conn_info)) { + log_dbg("Failed to connect to application."); + flow_dealloc(conn->flow_info.fd); + return -1; + } + + if (strcmp(connmgr.comps[id].info.protocol, conn->conn_info.protocol)) { + log_dbg("Unknown protocol (requested %s, got %s).", + connmgr.comps[id].info.protocol, + conn->conn_info.protocol); + flow_dealloc(conn->flow_info.fd); + return -1; + } + + if (connmgr.comps[id].info.pref_version != + conn->conn_info.pref_version) { + log_dbg("Unknown protocol version."); + flow_dealloc(conn->flow_info.fd); + return -1; + } + + if (connmgr.comps[id].info.pref_syntax != conn->conn_info.pref_syntax) { + log_dbg("Unknown protocol syntax."); + flow_dealloc(conn->flow_info.fd); + return -1; + } + + switch (id) { + case COMPID_DT: + notifier_event(NOTIFY_DT_CONN_ADD, conn); +#if defined(BUILD_IPCP_UNICAST) && defined(IPCP_CONN_WAIT_DIR) + dir_wait_running(); +#endif + break; + case COMPID_MGMT: + notifier_event(NOTIFY_MGMT_CONN_ADD, conn); + break; + default: + break; + } + + return 0; +} + +int connmgr_dealloc(enum comp_id id, + struct conn * conn) +{ + switch (id) { + case COMPID_DT: + notifier_event(NOTIFY_DT_CONN_DEL, conn); + break; +#if defined(BUILD_IPCP_UNICAST) && defined(IPCP_CONN_WAIT_DIR) + case COMPID_MGMT: + notifier_event(NOTIFY_MGMT_CONN_DEL, conn); + break; +#endif + default: + break; + } + + return flow_dealloc(conn->flow_info.fd); +} + + +int connmgr_wait(enum comp_id id, + struct conn * conn) +{ + struct conn_el * el; + struct comp * comp; + + assert(id >= 0 && id < COMPID_MAX); + assert(conn); + + comp = connmgr.comps + id; + + pthread_mutex_lock(&comp->lock); + + pthread_cleanup_push((void(*)(void *))pthread_mutex_unlock, + (void *) &comp->lock); + + while (list_is_empty(&comp->pending)) + pthread_cond_wait(&comp->cond, &comp->lock); + + pthread_cleanup_pop(false); + + el = list_first_entry((&comp->pending), struct conn_el, next); + if (el == NULL) { + pthread_mutex_unlock(&comp->lock); + return -1; + } + + *conn = el->conn; + + list_del(&el->next); + list_add(&el->next, &connmgr.comps[id].conns); + + pthread_mutex_unlock(&comp->lock); + + return 0; +} |