[PATCH] ipcpd: Add broadcast IPCP

  • From: Dimitri Staessens <dimitri.staessens@xxxxxxxx>
  • To: ouroboros@xxxxxxxxxxxxx
  • Date: Wed, 24 Oct 2018 09:26:04 +0200

This adds a broadcast IPCP that allows us to easily create multicast
applications. The broadcast IPCP accepts flows for "<layer_name>.mc".
A tool, obc (Ouroboros broadcast), is added that sends and reads a
message to a broadcast layer.

Signed-off-by: Dimitri Staessens <dimitri.staessens@xxxxxxxx>
---
 include/ouroboros/ipcp.h           |   1 +
 src/ipcpd/CMakeLists.txt           |   1 +
 src/ipcpd/broadcast/CMakeLists.txt |  34 ++
 src/ipcpd/broadcast/comp.h         |  46 +++
 src/ipcpd/broadcast/connmgr.c      | 521 +++++++++++++++++++++++++++++
 src/ipcpd/broadcast/connmgr.h      |  74 ++++
 src/ipcpd/broadcast/dt.c           | 318 ++++++++++++++++++
 src/ipcpd/broadcast/dt.h           |  27 ++
 src/ipcpd/broadcast/enroll.c       | 363 ++++++++++++++++++++
 src/ipcpd/broadcast/enroll.h       |  47 +++
 src/ipcpd/broadcast/main.c         | 359 ++++++++++++++++++++
 src/ipcpd/ipcp.c                   |  10 +-
 src/irmd/config.h.in               |   1 +
 src/irmd/ipcp.c                    |   3 +
 src/irmd/main.c                    |   2 +-
 src/lib/irm.c                      |   1 +
 src/tools/CMakeLists.txt           |   1 +
 src/tools/irm/irm_ipcp_bootstrap.c |  16 +-
 src/tools/irm/irm_ipcp_create.c    |  17 +-
 src/tools/irm/irm_ipcp_enroll.c    |  37 +-
 src/tools/obc/CMakeLists.txt       |  16 +
 src/tools/obc/obc.c                | 153 +++++++++
 22 files changed, 2023 insertions(+), 25 deletions(-)
 create mode 100644 src/ipcpd/broadcast/CMakeLists.txt
 create mode 100644 src/ipcpd/broadcast/comp.h
 create mode 100644 src/ipcpd/broadcast/connmgr.c
 create mode 100644 src/ipcpd/broadcast/connmgr.h
 create mode 100644 src/ipcpd/broadcast/dt.c
 create mode 100644 src/ipcpd/broadcast/dt.h
 create mode 100644 src/ipcpd/broadcast/enroll.c
 create mode 100644 src/ipcpd/broadcast/enroll.h
 create mode 100644 src/ipcpd/broadcast/main.c
 create mode 100644 src/tools/obc/CMakeLists.txt
 create mode 100644 src/tools/obc/obc.c

diff --git a/include/ouroboros/ipcp.h b/include/ouroboros/ipcp.h
index e423a01..4445c2c 100644
--- a/include/ouroboros/ipcp.h
+++ b/include/ouroboros/ipcp.h
@@ -36,6 +36,7 @@
 enum ipcp_type {
         IPCP_LOCAL = 0,
         IPCP_NORMAL,
+        IPCP_BROADCAST,
         IPCP_RAPTOR,
         IPCP_ETH_LLC,
         IPCP_ETH_DIX,
diff --git a/src/ipcpd/CMakeLists.txt b/src/ipcpd/CMakeLists.txt
index 1c6f6ae..5c109b4 100644
--- a/src/ipcpd/CMakeLists.txt
+++ b/src/ipcpd/CMakeLists.txt
@@ -40,6 +40,7 @@ set(IPCP_SOURCES
   ${CMAKE_CURRENT_SOURCE_DIR}/shim-data.c
   )
 
+add_subdirectory(broadcast)
 add_subdirectory(local)
 add_subdirectory(normal)
 add_subdirectory(raptor)
diff --git a/src/ipcpd/broadcast/CMakeLists.txt 
b/src/ipcpd/broadcast/CMakeLists.txt
new file mode 100644
index 0000000..afcc869
--- /dev/null
+++ b/src/ipcpd/broadcast/CMakeLists.txt
@@ -0,0 +1,34 @@
+get_filename_component(CURRENT_SOURCE_PARENT_DIR
+  ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
+get_filename_component(CURRENT_BINARY_PARENT_DIR
+  ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+include_directories(${CURRENT_SOURCE_PARENT_DIR})
+include_directories(${CURRENT_BINARY_PARENT_DIR})
+
+include_directories(${CMAKE_SOURCE_DIR}/include)
+include_directories(${CMAKE_BINARY_DIR}/include)
+
+set(IPCP_BROADCAST_TARGET ipcpd-broadcast CACHE INTERNAL "")
+
+set(SOURCE_FILES
+  # Add source files here
+  connmgr.c
+  dt.c
+  enroll.c
+  main.c
+  )
+
+add_executable(ipcpd-broadcast ${SOURCE_FILES} ${IPCP_SOURCES}
+  ${LAYER_CONFIG_PROTO_SRCS})
+target_link_libraries(ipcpd-broadcast LINK_PUBLIC ouroboros-dev)
+
+include(AddCompileFlags)
+if (CMAKE_BUILD_TYPE MATCHES "Debug*")
+  add_compile_flags(ipcpd-broadcast -DCONFIG_OUROBOROS_DEBUG)
+endif ()
+
+install(TARGETS ipcpd-broadcast RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
diff --git a/src/ipcpd/broadcast/comp.h b/src/ipcpd/broadcast/comp.h
new file mode 100644
index 0000000..9c883e7
--- /dev/null
+++ b/src/ipcpd/broadcast/comp.h
@@ -0,0 +1,46 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Components for the normal IPC process
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#ifndef OUROBOROS_IPCPD_NORMAL_COMP_H
+#define OUROBOROS_IPCPD_NORMAL_COMP_H
+
+#include <ouroboros/cacep.h>
+
+#define DST_MAX_STRLEN 64
+
+enum comp_id {
+        COMPID_DT = 0,
+        COMPID_ENROLL,
+        COMPID_MGMT,
+        COMPID_MAX
+};
+
+struct conn {
+        struct conn_info conn_info;
+        struct {
+                char      dst[DST_MAX_STRLEN + 1];
+                int       fd;
+                qosspec_t qs;
+        } flow_info;
+};
+
+#endif /* OUROBOROS_IPCPD_NORMAL_COMP_H */
diff --git a/src/ipcpd/broadcast/connmgr.c b/src/ipcpd/broadcast/connmgr.c
new file mode 100644
index 0000000..dee88e0
--- /dev/null
+++ b/src/ipcpd/broadcast/connmgr.c
@@ -0,0 +1,521 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Handles connections between components
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#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 "comp.h"
+#include "connmgr.h"
+#include "enroll.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);
+                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;
+        case COMPID_MGMT:
+                notifier_event(NOTIFY_MGMT_CONN_DEL, conn);
+                break;
+        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;
+}
diff --git a/src/ipcpd/broadcast/connmgr.h b/src/ipcpd/broadcast/connmgr.h
new file mode 100644
index 0000000..f767e72
--- /dev/null
+++ b/src/ipcpd/broadcast/connmgr.h
@@ -0,0 +1,74 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Handles the different AP connections
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#ifndef OUROBOROS_IPCPD_NORMAL_CONNMGR_H
+#define OUROBOROS_IPCPD_NORMAL_CONNMGR_H
+
+#include <ouroboros/cacep.h>
+#include <ouroboros/qos.h>
+
+#include "comp.h"
+
+#define NOTIFY_DT_CONN_ADD     0x00D0
+#define NOTIFY_DT_CONN_DEL     0x00D1
+#define NOTIFY_DT_CONN_QOS     0x00D2
+#define NOTIFY_DT_CONN_UP      0x00D3
+#define NOTIFY_DT_CONN_DOWN    0x00D4
+#define NOTIFY_DT_FLOW_UP      0x00D5
+#define NOTIFY_DT_FLOW_DOWN    0x00D6
+#define NOTIFY_DT_FLOW_DEALLOC 0x00D7
+
+#define NOTIFY_MGMT_CONN_ADD   0x00F0
+#define NOTIFY_MGMT_CONN_DEL   0x00F1
+
+int         connmgr_init(void);
+
+void        connmgr_fini(void);
+
+int         connmgr_start(void);
+
+void        connmgr_stop(void);
+
+int         connmgr_comp_init(enum comp_id             id,
+                              const struct conn_info * info);
+
+void        connmgr_comp_fini(enum comp_id id);
+
+int         connmgr_ipcp_connect(const char * dst,
+                                 const char * component,
+                                 qosspec_t    qs);
+
+int         connmgr_ipcp_disconnect(const char * dst,
+                                    const char * component);
+
+int         connmgr_alloc(enum comp_id  id,
+                          const char *  dst,
+                          qosspec_t *   qs,
+                          struct conn * conn);
+
+int         connmgr_dealloc(enum comp_id  id,
+                            struct conn * conn);
+
+int         connmgr_wait(enum comp_id  id,
+                         struct conn * conn);
+
+#endif /* OUROBOROS_IPCPD_NORMAL_CONNMGR_H */
diff --git a/src/ipcpd/broadcast/dt.c b/src/ipcpd/broadcast/dt.c
new file mode 100644
index 0000000..948d66e
--- /dev/null
+++ b/src/ipcpd/broadcast/dt.c
@@ -0,0 +1,318 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Forward loop for broadcast
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include "config.h"
+
+#define BROADCAST_MTU     1400 /* FIXME: avoid packet copy. */
+
+#define DT               "dt"
+#define OUROBOROS_PREFIX DT
+
+#include <ouroboros/endian.h>
+#include <ouroboros/dev.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/fqueue.h>
+#include <ouroboros/list.h>
+#include <ouroboros/logs.h>
+#include <ouroboros/notifier.h>
+#include <ouroboros/utils.h>
+
+#include "comp.h"
+#include "connmgr.h"
+#include "dt.h"
+#include "ipcp.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <pthread.h>
+
+struct nb {
+        struct list_head next;
+
+        int              fd;
+};
+
+struct {
+        struct list_head  nbs;
+        size_t            nbs_len;
+        pthread_rwlock_t  nbs_lock;
+
+        fset_t *          set;
+
+        pthread_t         reader;
+        pthread_t         listener;
+} fwd;
+
+static int dt_add_nb(int fd)
+{
+        struct list_head * p;
+        struct nb *        nb;
+
+        pthread_rwlock_wrlock(&fwd.nbs_lock);
+
+        list_for_each(p, &fwd.nbs) {
+                struct nb * el = list_entry(p, struct nb, next);
+                if (el->fd == fd) {
+                        log_dbg("Already know neighbor.");
+                        pthread_rwlock_unlock(&fwd.nbs_lock);
+                        return -EPERM;
+                }
+        }
+
+        nb = malloc(sizeof(*nb));
+        if (nb == NULL) {
+                pthread_rwlock_unlock(&fwd.nbs_lock);
+                return -ENOMEM;
+        }
+
+        nb->fd    = fd;
+
+        list_add_tail(&nb->next, p);
+
+        ++fwd.nbs_len;
+
+        log_dbg("Neighbor %d added.", fd);
+
+        pthread_rwlock_unlock(&fwd.nbs_lock);
+
+        return 0;
+}
+
+static int dt_del_nb(int fd)
+{
+        struct list_head * p;
+        struct list_head * h;
+
+        pthread_rwlock_wrlock(&fwd.nbs_lock);
+
+        list_for_each_safe(p, h, &fwd.nbs) {
+                struct nb * nb = list_entry(p, struct nb, next);
+                if (nb->fd == fd) {
+                        list_del(&nb->next);
+                        --fwd.nbs_len;
+                        pthread_rwlock_unlock(&fwd.nbs_lock);
+                        log_dbg("Neighbor %d deleted.", nb->fd);
+                        free(nb);
+                        return 0;
+                }
+        }
+
+        pthread_rwlock_unlock(&fwd.nbs_lock);
+
+        return -EPERM;
+}
+
+static void * dt_conn_handle(void * o)
+{
+        struct conn conn;
+
+        (void) o;
+
+        while (true) {
+                if (connmgr_wait(COMPID_DT, &conn)) {
+                        log_err("Failed to get next DT connection.");
+                        continue;
+                }
+
+                /* NOTE: connection acceptance policy could be here. */
+
+                notifier_event(NOTIFY_DT_CONN_ADD, &conn);
+        }
+
+        return 0;
+}
+
+
+static void dt_packet(uint8_t * buf,
+                      size_t    len,
+                      int       in_fd)
+{
+        struct list_head * p;
+
+        pthread_rwlock_rdlock(&fwd.nbs_lock);
+
+        pthread_cleanup_push((void (*))(void *) pthread_rwlock_unlock,
+                             &fwd.nbs_lock);
+
+        list_for_each(p, &fwd.nbs) {
+                struct nb * nb = list_entry(p, struct nb, next);
+                if (nb->fd != in_fd)
+                        flow_write(nb->fd, buf, len); /* FIXME: avoid copy. */
+        }
+
+        pthread_cleanup_pop(true);
+}
+
+static void * dt_reader(void * o)
+{
+        fqueue_t *   fq;
+        int          ret;
+        uint8_t      buf[BROADCAST_MTU];
+        int          fd;
+        ssize_t      len;
+
+        (void) o;
+
+        fq = fqueue_create();
+        if (fq == NULL)
+                return (void *) -1;
+
+        pthread_cleanup_push((void (*) (void *)) fqueue_destroy,
+                             (void *) fq);
+
+        while (true) {
+                ret = fevent(fwd.set, fq, NULL);
+                if (ret < 0) {
+                        log_warn("Event error: %d.", ret);
+                        continue;
+                }
+
+                while ((fd = fqueue_next(fq)) >= 0) {
+                        if (fqueue_type(fq) != FLOW_PKT)
+                                continue;
+
+                        /* FIXME: avoid copy. */
+                        len = flow_read(fd, buf, BROADCAST_MTU);
+                        if (len < 0)
+                                continue;
+
+                        dt_packet(buf, len, fd);
+                }
+        }
+
+        pthread_cleanup_pop(true);
+
+        return (void *) 0;
+}
+
+static void handle_event(void *       self,
+                         int          event,
+                         const void * o)
+{
+        /* FIXME: Apply correct QoS on graph */
+        struct conn *      c;
+
+        (void) self;
+
+        c = (struct conn *) o;
+
+        switch (event) {
+        case NOTIFY_DT_CONN_ADD:
+                if (dt_add_nb(c->flow_info.fd))
+                        log_dbg("Failed to add neighbor.");
+                fset_add(fwd.set, c->flow_info.fd);
+                break;
+        case NOTIFY_DT_CONN_DEL:
+                if (dt_del_nb(c->flow_info.fd))
+                        log_dbg("Failed to delete neighbor.");
+                fset_del(fwd.set, c->flow_info.fd);
+                break;
+        default:
+                break;
+        }
+}
+
+int dt_init(void)
+{
+        struct conn_info info;
+
+        memset(&info, 0, sizeof(info));
+
+        strcpy(info.comp_name, DT);
+        strcpy(info.comp_name, DT_COMP);
+
+        list_head_init(&fwd.nbs);
+
+        if (notifier_reg(handle_event, NULL))
+                goto fail_notifier_reg;
+
+        if (pthread_rwlock_init(&fwd.nbs_lock, NULL))
+                goto fail_lock_init;
+
+        fwd.set = fset_create();
+        if (fwd.set == NULL)
+                goto fail_fset_create;
+
+        if (pthread_create(&fwd.reader, NULL, dt_reader, NULL))
+                goto fail_pthread_create_reader;
+
+        if (pthread_create(&fwd.listener, NULL, dt_conn_handle, NULL))
+                goto fail_pthread_create_listener;
+
+        if (connmgr_comp_init(COMPID_DT, &info))
+                goto fail_connmgr_comp_init;
+
+        fwd.nbs_len     = 0;
+
+        return 0;
+
+ fail_connmgr_comp_init:
+        pthread_cancel(fwd.listener);
+        pthread_join(fwd.listener, NULL);
+ fail_pthread_create_listener:
+        pthread_cancel(fwd.reader);
+        pthread_join(fwd.reader, NULL);
+ fail_pthread_create_reader:
+        fset_destroy(fwd.set);
+ fail_fset_create:
+        pthread_rwlock_destroy(&fwd.nbs_lock);
+ fail_lock_init:
+        notifier_unreg(handle_event);
+ fail_notifier_reg:
+        return -1;
+}
+
+void dt_fini(void)
+{
+        struct list_head * p;
+        struct list_head * h;
+
+        notifier_unreg(handle_event);
+
+        pthread_cancel(fwd.reader);
+        pthread_cancel(fwd.listener);
+
+        pthread_join(fwd.reader, NULL);
+        pthread_join(fwd.listener, NULL);
+
+        fset_destroy(fwd.set);
+
+        pthread_rwlock_wrlock(&fwd.nbs_lock);
+
+        list_for_each_safe(p, h, &fwd.nbs) {
+                struct nb * n = list_entry(p, struct nb, next);
+                list_del(&n->next);
+                free(n);
+        }
+
+        pthread_rwlock_unlock(&fwd.nbs_lock);
+
+        pthread_rwlock_destroy(&fwd.nbs_lock);
+}
diff --git a/src/ipcpd/broadcast/dt.h b/src/ipcpd/broadcast/dt.h
new file mode 100644
index 0000000..853ce03
--- /dev/null
+++ b/src/ipcpd/broadcast/dt.h
@@ -0,0 +1,27 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Forward loop for broadcast
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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 DT_COMP "Data Transfer"
+
+int  dt_init(void);
+
+void dt_fini(void);
diff --git a/src/ipcpd/broadcast/enroll.c b/src/ipcpd/broadcast/enroll.c
new file mode 100644
index 0000000..6671949
--- /dev/null
+++ b/src/ipcpd/broadcast/enroll.c
@@ -0,0 +1,363 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Enrollment Task
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 199309L
+#endif
+
+#define OUROBOROS_PREFIX "enrollment"
+
+#include <ouroboros/endian.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/time_utils.h>
+#include <ouroboros/dev.h>
+#include <ouroboros/logs.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/sockets.h>
+
+#include "connmgr.h"
+#include "enroll.h"
+#include "ipcp.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "ipcp_config.pb-c.h"
+typedef EnrollMsg enroll_msg_t;
+
+#define ENROLL_COMP             "Enrollment"
+#define ENROLL_PROTO            "OEP" /* Ouroboros enrollment protocol */
+#define ENROLL_WARN_TIME_OFFSET 20
+#define ENROLL_BUF_LEN          1024
+
+enum enroll_state {
+        ENROLL_NULL = 0,
+        ENROLL_INIT,
+        ENROLL_RUNNING
+};
+
+struct {
+        struct ipcp_config conf;
+        enum enroll_state  state;
+        pthread_t          listener;
+} enroll;
+
+static int send_rcv_enroll_msg(int fd)
+{
+        enroll_msg_t    req = ENROLL_MSG__INIT;
+        enroll_msg_t *  reply;
+        uint8_t         buf[ENROLL_BUF_LEN];
+        ssize_t         len;
+        ssize_t         delta_t;
+        struct timespec t0;
+        struct timespec rtt;
+
+        req.code = ENROLL_CODE__ENROLL_REQ;
+
+        len = enroll_msg__get_packed_size(&req);
+        if (len < 0) {
+                log_dbg("Failed pack request message.");
+                return -1;
+        }
+
+        enroll_msg__pack(&req, buf);
+
+        clock_gettime(CLOCK_REALTIME, &t0);
+
+        if (flow_write(fd, buf, len)) {
+                log_dbg("Failed to send request message.");
+                return -1;
+        }
+
+        len = flow_read(fd, buf, ENROLL_BUF_LEN);
+        if (len < 0) {
+                log_dbg("No enrollment reply received.");
+                return -1;
+        }
+
+        log_dbg("Received enrollment info (%zd bytes).", len);
+
+        reply = enroll_msg__unpack(NULL, len, buf);
+        if (reply == NULL) {
+                log_dbg("No enrollment response.");
+                return -1;
+        }
+
+        if (reply->code != ENROLL_CODE__ENROLL_BOOT) {
+                log_dbg("Failed to unpack enrollment response.");
+                enroll_msg__free_unpacked(reply, NULL);
+                return -1;
+        }
+
+        if (!(reply->has_t_sec && reply->has_t_nsec)) {
+                log_dbg("No time in response message.");
+                enroll_msg__free_unpacked(reply, NULL);
+                return -1;
+        }
+
+        clock_gettime(CLOCK_REALTIME, &rtt);
+
+        delta_t = ts_diff_ms(&t0, &rtt);
+
+        rtt.tv_sec  = reply->t_sec;
+        rtt.tv_nsec = reply->t_nsec;
+
+        if (labs(ts_diff_ms(&t0, &rtt)) - delta_t > ENROLL_WARN_TIME_OFFSET)
+                log_warn("Clock offset above threshold.");
+
+        strcpy(enroll.conf.layer_info.layer_name,
+               reply->conf->layer_info->layer_name);
+        enroll.conf.layer_info.dir_hash_algo
+                = reply->conf->layer_info->dir_hash_algo;
+
+        enroll_msg__free_unpacked(reply, NULL);
+
+        return 0;
+}
+
+static ssize_t enroll_pack(uint8_t ** buf)
+{
+        enroll_msg_t      msg        = ENROLL_MSG__INIT;
+        ipcp_config_msg_t config     = IPCP_CONFIG_MSG__INIT;
+        layer_info_msg_t  layer_info = LAYER_INFO_MSG__INIT;
+        struct timespec   now;
+        ssize_t           len;
+
+        clock_gettime(CLOCK_REALTIME, &now);
+
+        msg.code       = ENROLL_CODE__ENROLL_BOOT;
+        msg.has_t_sec  = true;
+        msg.t_sec      = now.tv_sec;
+        msg.has_t_nsec = true;
+        msg.t_nsec     = now.tv_nsec;
+        msg.conf       = &config;
+
+        config.ipcp_type          = enroll.conf.type;
+        config.layer_info         = &layer_info;
+
+        layer_info.layer_name     = (char *) enroll.conf.layer_info.layer_name;
+        layer_info.dir_hash_algo  = enroll.conf.layer_info.dir_hash_algo;
+
+        len = enroll_msg__get_packed_size(&msg);
+
+        *buf = malloc(len);
+        if (*buf == NULL)
+                return -1;
+
+        enroll_msg__pack(&msg, *buf);
+
+        return len;
+}
+
+static void * enroll_handle(void * o)
+{
+        struct conn    conn;
+        uint8_t        buf[ENROLL_BUF_LEN];
+        uint8_t *      reply;
+        ssize_t        len;
+        enroll_msg_t * msg;
+
+        (void) o;
+
+        while (true) {
+                if (connmgr_wait(COMPID_ENROLL, &conn)) {
+                        log_err("Failed to get next connection.");
+                        continue;
+                }
+
+                len = flow_read(conn.flow_info.fd, buf, ENROLL_BUF_LEN);
+                if (len < 0) {
+                        log_err("Failed to read from flow.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        continue;
+                }
+
+                msg = enroll_msg__unpack(NULL, len, buf);
+                if (msg == NULL) {
+                        log_err("Failed to unpack message.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        continue;
+                }
+
+                if (msg->code != ENROLL_CODE__ENROLL_REQ) {
+                        log_err("Wrong message type.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        enroll_msg__free_unpacked(msg, NULL);
+                        continue;
+                }
+
+                log_dbg("Enrolling a new neighbor.");
+
+                enroll_msg__free_unpacked(msg, NULL);
+
+                len = enroll_pack(&reply);
+                if (reply == NULL) {
+                        log_err("Failed to pack enrollment message.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        continue;
+                }
+
+                log_dbg("Sending enrollment info (%zd bytes).", len);
+
+                if (flow_write(conn.flow_info.fd, reply, len)) {
+                        log_err("Failed respond to enrollment request.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        free(reply);
+                        continue;
+                }
+
+                free(reply);
+
+                len = flow_read(conn.flow_info.fd, buf, ENROLL_BUF_LEN);
+                if (len < 0) {
+                        log_err("Failed to read from flow.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        continue;
+                }
+
+                msg = enroll_msg__unpack(NULL, len, buf);
+                if (msg == NULL) {
+                        log_err("Failed to unpack message.");
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        continue;
+                }
+
+                if (msg->code != ENROLL_CODE__ENROLL_DONE || !msg->has_result) 
{
+                        log_err("Wrong message type.");
+                        enroll_msg__free_unpacked(msg, NULL);
+                        connmgr_dealloc(COMPID_ENROLL, &conn);
+                        continue;
+                }
+
+                if (msg->result == 0)
+                        log_dbg("Neighbor enrollment successful.");
+                else
+                        log_dbg("Neigbor reported failed enrollment.");
+
+                enroll_msg__free_unpacked(msg, NULL);
+
+                connmgr_dealloc(COMPID_ENROLL, &conn);
+        }
+
+        return 0;
+}
+
+int enroll_boot(struct conn * conn)
+{
+        log_dbg("Getting boot information.");
+
+        if (send_rcv_enroll_msg(conn->flow_info.fd)) {
+                log_err("Failed to enroll.");
+                return -1;
+        }
+
+        return 0;
+}
+
+int enroll_done(struct conn * conn,
+                int           result)
+{
+        enroll_msg_t msg = ENROLL_MSG__INIT;
+        uint8_t      buf[ENROLL_BUF_LEN];
+        ssize_t       len;
+
+        msg.code       = ENROLL_CODE__ENROLL_DONE;
+        msg.has_result = true;
+        msg.result     = result;
+
+        len = enroll_msg__get_packed_size(&msg);
+        if (len < 0) {
+                log_dbg("Failed pack request message.");
+                return -1;
+        }
+
+        enroll_msg__pack(&msg, buf);
+
+        if (flow_write(conn->flow_info.fd, buf, len)) {
+                log_dbg("Failed to send acknowledgment.");
+                return -1;
+        }
+
+        return 0;
+}
+
+void enroll_bootstrap(const struct ipcp_config * conf)
+{
+        assert(conf);
+
+        memcpy(&enroll.conf, conf, sizeof(enroll.conf));
+}
+
+struct ipcp_config * enroll_get_conf(void)
+{
+        return &enroll.conf;
+}
+
+int enroll_init(void)
+{
+        struct conn_info info;
+
+        memset(&info, 0, sizeof(info));
+
+        strcpy(info.comp_name, ENROLL_COMP);
+        strcpy(info.protocol, ENROLL_PROTO);
+        info.pref_version = 1;
+        info.pref_syntax  = PROTO_GPB;
+        info.addr         = 0;
+
+        if (connmgr_comp_init(COMPID_ENROLL, &info)) {
+                log_err("Failed to register with connmgr.");
+                return -1;
+        }
+
+        enroll.state = ENROLL_INIT;
+
+        return 0;
+}
+
+void enroll_fini(void)
+{
+        if (enroll.state == ENROLL_RUNNING)
+                pthread_join(enroll.listener, NULL);
+
+        connmgr_comp_fini(COMPID_ENROLL);
+}
+
+int enroll_start(void)
+{
+        if (pthread_create(&enroll.listener, NULL, enroll_handle, NULL))
+                return -1;
+
+        enroll.state = ENROLL_RUNNING;
+
+        return 0;
+}
+
+void enroll_stop(void)
+{
+        if (enroll.state == ENROLL_RUNNING)
+                pthread_cancel(enroll.listener);
+}
diff --git a/src/ipcpd/broadcast/enroll.h b/src/ipcpd/broadcast/enroll.h
new file mode 100644
index 0000000..615b702
--- /dev/null
+++ b/src/ipcpd/broadcast/enroll.h
@@ -0,0 +1,47 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Enrollment Task
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#ifndef OUROBOROS_IPCPD_NORMAL_ENROLL_H
+#define OUROBOROS_IPCPD_NORMAL_ENROLL_H
+
+#include <ouroboros/ipcp.h>
+
+#include "comp.h"
+
+int                  enroll_init(void);
+
+void                 enroll_fini(void);
+
+int                  enroll_start(void);
+
+void                 enroll_stop(void);
+
+void                 enroll_bootstrap(const struct ipcp_config * conf);
+
+int                  enroll_boot(struct conn * conn);
+
+int                  enroll_done(struct conn * conn,
+                                 int           result);
+
+struct ipcp_config * enroll_get_conf(void);
+
+#endif /* OUROBOROS_IPCPD_NORMAL_ENROLL_H */
diff --git a/src/ipcpd/broadcast/main.c b/src/ipcpd/broadcast/main.c
new file mode 100644
index 0000000..8c6bfa7
--- /dev/null
+++ b/src/ipcpd/broadcast/main.c
@@ -0,0 +1,359 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * Broadcast IPC Process
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include "config.h"
+
+#define OUROBOROS_PREFIX "broadcast-ipcp"
+
+#include <ouroboros/errno.h>
+#include <ouroboros/hash.h>
+#include <ouroboros/dev.h>
+#include <ouroboros/ipcp-dev.h>
+#include <ouroboros/logs.h>
+#include <ouroboros/notifier.h>
+#include <ouroboros/rib.h>
+#include <ouroboros/time_utils.h>
+
+#include "connmgr.h"
+#include "dt.h"
+#include "enroll.h"
+#include "ipcp.h"
+
+#include <stdbool.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#define THIS_TYPE IPCP_BROADCAST
+
+static int initialize_components(const struct ipcp_config * conf)
+{
+        ipcpi.layer_name = strdup(conf->layer_info.layer_name);
+        if (ipcpi.layer_name == NULL) {
+                log_err("Failed to set layer name.");
+                goto fail_layer_name;
+        }
+
+        ipcpi.dir_hash_algo = conf->layer_info.dir_hash_algo;
+
+        assert(ipcp_dir_hash_len() != 0);
+
+        if (dt_init()) {
+                log_err("Failed to initialize forwarding component.");
+                goto fail_dt;
+        }
+
+        ipcp_set_state(IPCP_INIT);
+
+        return 0;
+
+ fail_dt:
+        free(ipcpi.layer_name);
+ fail_layer_name:
+        return -1;
+}
+
+static void finalize_components(void)
+{
+        dt_fini();
+
+        free(ipcpi.layer_name);
+}
+
+static int start_components(void)
+{
+        assert(ipcp_get_state() == IPCP_INIT);
+
+        ipcp_set_state(IPCP_OPERATIONAL);
+
+        if (enroll_start()) {
+                log_err("Failed to start enrollment.");
+                goto fail_enroll_start;
+        }
+
+        if (connmgr_start()) {
+                log_err("Failed to start AP connection manager.");
+                goto fail_connmgr_start;
+        }
+
+        return 0;
+
+ fail_connmgr_start:
+        enroll_stop();
+ fail_enroll_start:
+        ipcp_set_state(IPCP_INIT);
+        return -1;
+}
+
+static void stop_components(void)
+{
+        assert(ipcp_get_state() == IPCP_OPERATIONAL ||
+               ipcp_get_state() == IPCP_SHUTDOWN);
+
+        connmgr_stop();
+
+        enroll_stop();
+
+        ipcp_set_state(IPCP_INIT);
+}
+
+static int broadcast_ipcp_enroll(const char *        dst,
+                                 struct layer_info * info)
+{
+        struct conn conn;
+
+        if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn)) {
+                log_err("Failed to get connection.");
+                goto fail_er_flow;
+        }
+
+        /* Get boot state from peer. */
+        if (enroll_boot(&conn)) {
+                log_err("Failed to get boot information.");
+                goto fail_enroll_boot;
+        }
+
+        if (initialize_components(enroll_get_conf())) {
+                log_err("Failed to initialize IPCP components.");
+                goto fail_enroll_boot;
+        }
+
+        if (start_components()) {
+                log_err("Failed to start components.");
+                goto fail_start_comp;
+        }
+
+        if (enroll_done(&conn, 0))
+                log_warn("Failed to confirm enrollment with peer.");
+
+        if (connmgr_dealloc(COMPID_ENROLL, &conn))
+                log_warn("Failed to deallocate enrollment flow.");
+
+        log_info("Enrolled with %s.", dst);
+
+        info->dir_hash_algo = ipcpi.dir_hash_algo;
+        strcpy(info->layer_name, ipcpi.layer_name);
+
+        return 0;
+
+ fail_start_comp:
+        finalize_components();
+ fail_enroll_boot:
+        connmgr_dealloc(COMPID_ENROLL, &conn);
+ fail_er_flow:
+        return -1;
+}
+
+static int broadcast_ipcp_bootstrap(const struct ipcp_config * conf)
+{
+        assert(conf);
+        assert(conf->type == THIS_TYPE);
+
+        enroll_bootstrap(conf);
+
+        if (initialize_components(conf)) {
+                log_err("Failed to init IPCP components.");
+                goto fail_init;
+        }
+
+        if (start_components()) {
+                log_err("Failed to init IPCP components.");
+                goto fail_start;
+        }
+
+        log_dbg("Bootstrapped in layer %s.", conf->layer_info.layer_name);
+
+        return 0;
+
+ fail_start:
+        finalize_components();
+ fail_init:
+        return -1;
+}
+
+static int broadcast_ipcp_query(const uint8_t * dst)
+{
+        uint8_t * buf;
+        size_t    len;
+        int       ret;
+        char *    multicast_name;
+        char *    suffix = ".mc";
+
+        len = hash_len(ipcpi.dir_hash_algo);
+        buf =  malloc(len);
+        if (buf == NULL)
+                return -ENOMEM;
+
+        multicast_name = malloc(strlen(ipcpi.layer_name) + strlen(suffix) + 1);
+        if (multicast_name == NULL) {
+                free(buf);
+                return -ENOMEM;
+        }
+
+        strcpy(multicast_name, ipcpi.layer_name);
+        strcat(multicast_name, suffix);
+
+        str_hash(ipcpi.dir_hash_algo, buf, multicast_name);
+
+        free(multicast_name);
+
+        ret = memcmp(buf, dst, len);
+
+        free(buf);
+
+        return ret;
+}
+
+static int broadcast_ipcp_alloc(int             fd,
+                                const uint8_t * dst,
+                                qosspec_t       qs)
+{
+        struct conn conn;
+
+        (void) qs;
+
+        memset(&conn, 0, sizeof(conn));
+
+        conn.flow_info.fd = fd;
+
+        if (broadcast_ipcp_query(dst) != 0)
+                return -1;
+
+        notifier_event(NOTIFY_DT_CONN_ADD, &conn);
+
+        ipcp_flow_alloc_reply(fd, 0);
+
+        return 0;
+}
+
+int broadcast_ipcp_dealloc(int fd)
+{
+        struct conn conn;
+
+        memset(&conn, 0, sizeof(conn));
+
+        conn.flow_info.fd = fd;
+
+        notifier_event(NOTIFY_DT_CONN_DEL, &conn);
+
+        flow_dealloc(fd);
+
+        return 0;
+}
+
+
+static struct ipcp_ops broadcast_ops = {
+        .ipcp_bootstrap       = broadcast_ipcp_bootstrap,
+        .ipcp_enroll          = broadcast_ipcp_enroll,
+        .ipcp_connect         = connmgr_ipcp_connect,
+        .ipcp_disconnect      = connmgr_ipcp_disconnect,
+        .ipcp_reg             = NULL,
+        .ipcp_unreg           = NULL,
+        .ipcp_query           = broadcast_ipcp_query,
+        .ipcp_flow_alloc      = broadcast_ipcp_alloc,
+        .ipcp_flow_alloc_resp = NULL,
+        .ipcp_flow_dealloc    = broadcast_ipcp_dealloc
+};
+
+int main(int    argc,
+         char * argv[])
+{
+        if (ipcp_init(argc, argv, &broadcast_ops) < 0) {
+                log_err("Failed to init IPCP.");
+                goto fail_init;
+        }
+
+        /* These components must be init at creation. */
+        if (rib_init(ipcpi.name)) {
+                log_err("Failed to initialize RIB.");
+                goto fail_rib_init;
+        }
+
+        if (notifier_init()) {
+                log_err("Failed to initialize notifier component.");
+                goto fail_notifier_init;
+        }
+
+        if (connmgr_init()) {
+                log_err("Failed to initialize connection manager.");
+                goto fail_connmgr_init;
+        }
+
+        if (enroll_init()) {
+                log_err("Failed to initialize enrollment component.");
+                goto fail_enroll_init;
+        }
+
+        if (ipcp_boot() < 0) {
+                log_err("Failed to boot IPCP.");
+                goto fail_boot;
+        }
+
+        if (ipcp_create_r(getpid(), 0)) {
+                log_err("Failed to notify IRMd we are initialized.");
+                ipcp_set_state(IPCP_NULL);
+                goto fail_create_r;
+        }
+
+        ipcp_shutdown();
+
+        if (ipcp_get_state() == IPCP_SHUTDOWN) {
+                stop_components();
+                finalize_components();
+        }
+
+        enroll_fini();
+
+        connmgr_fini();
+
+        notifier_fini();
+
+        rib_fini();
+
+        ipcp_fini();
+
+        exit(EXIT_SUCCESS);
+
+ fail_create_r:
+        ipcp_shutdown();
+ fail_boot:
+        enroll_fini();
+ fail_enroll_init:
+        connmgr_fini();
+ fail_connmgr_init:
+        notifier_fini();
+ fail_notifier_init:
+        rib_fini();
+ fail_rib_init:
+        ipcp_fini();
+ fail_init:
+        ipcp_create_r(getpid(), -1);
+        exit(EXIT_FAILURE);
+}
diff --git a/src/ipcpd/ipcp.c b/src/ipcpd/ipcp.c
index 862b346..60a15c1 100644
--- a/src/ipcpd/ipcp.c
+++ b/src/ipcpd/ipcp.c
@@ -256,8 +256,14 @@ static void * mainloop(void * o)
                                 layer_info.dir_hash_algo      = HASH_MD5;
                         }
 
-                        /* Only udp needs a fixed hash algorithm */
-                        if (conf_msg->ipcp_type != IPCP_UDP) {
+                        if (conf_msg->ipcp_type == IPCP_BROADCAST) {
+                                conf.layer_info.dir_hash_algo = HASH_SHA3_256;
+                                layer_info.dir_hash_algo      = HASH_SHA3_256;
+                        }
+
+                        /* Only udp and broadcast have a fixed hash algorithm 
*/
+                        if (conf_msg->ipcp_type != IPCP_UDP &&
+                            conf_msg->ipcp_type != IPCP_BROADCAST) {
                                 switch(conf_msg->layer_info->dir_hash_algo) {
                                 case DIR_HASH_SHA3_224:
                                         conf.layer_info.dir_hash_algo =
diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in
index a4c7128..ad8a552 100644
--- a/src/irmd/config.h.in
+++ b/src/irmd/config.h.in
@@ -24,6 +24,7 @@
 #define IPCP_ETH_LLC_EXEC       "@IPCP_ETH_LLC_TARGET@"
 #define IPCP_ETH_DIX_EXEC       "@IPCP_ETH_DIX_TARGET@"
 #define IPCP_NORMAL_EXEC        "@IPCP_NORMAL_TARGET@"
+#define IPCP_BROADCAST_EXEC     "@IPCP_BROADCAST_TARGET@"
 #define IPCP_LOCAL_EXEC         "@IPCP_LOCAL_TARGET@"
 #define IPCP_RAPTOR_EXEC        "@IPCP_RAPTOR_TARGET@"
 
diff --git a/src/irmd/ipcp.c b/src/irmd/ipcp.c
index 19e68ee..7f3f480 100644
--- a/src/irmd/ipcp.c
+++ b/src/irmd/ipcp.c
@@ -140,6 +140,9 @@ pid_t ipcp_create(const char *   name,
         case IPCP_NORMAL:
                 exec_name = IPCP_NORMAL_EXEC;
                 break;
+        case IPCP_BROADCAST:
+                exec_name = IPCP_BROADCAST_EXEC;
+                break;
         case IPCP_UDP:
                 exec_name = IPCP_UDP_EXEC;
                 break;
diff --git a/src/irmd/main.c b/src/irmd/main.c
index 9ddcbbb..67e16de 100644
--- a/src/irmd/main.c
+++ b/src/irmd/main.c
@@ -618,7 +618,7 @@ static int connect_ipcp(pid_t        pid,
                 return -EIPCP;
         }
 
-        if (entry->type != IPCP_NORMAL) {
+        if (entry->type != IPCP_NORMAL && entry->type != IPCP_BROADCAST) {
                 pthread_rwlock_unlock(&irmd.reg_lock);
                 log_err("Cannot establish connections for this IPCP type.");
                 return -EIPCP;
diff --git a/src/lib/irm.c b/src/lib/irm.c
index bf10ae3..dcc315d 100644
--- a/src/lib/irm.c
+++ b/src/lib/irm.c
@@ -142,6 +142,7 @@ int irm_bootstrap_ipcp(pid_t                      pid,
                 config.dns_addr     = conf->dns_addr;
                 break;
         case IPCP_LOCAL:
+        case IPCP_BROADCAST:
         case IPCP_RAPTOR:
                 break;
         case IPCP_ETH_LLC:
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index d7a4d17..7c40d9a 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_subdirectory(irm)
 add_subdirectory(ocbr)
 add_subdirectory(oecho)
+add_subdirectory(obc)
 add_subdirectory(oping)
 add_subdirectory(operf)
 if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
diff --git a/src/tools/irm/irm_ipcp_bootstrap.c 
b/src/tools/irm/irm_ipcp_bootstrap.c
index 3d9386a..861b152 100644
--- a/src/tools/irm/irm_ipcp_bootstrap.c
+++ b/src/tools/irm/irm_ipcp_bootstrap.c
@@ -51,6 +51,7 @@
 #endif
 
 #define NORMAL                 "normal"
+#define BROADCAST              "broadcast"
 #define UDP                    "udp"
 #define ETH_LLC                "eth-llc"
 #define ETH_DIX                "eth-dix"
@@ -86,7 +87,7 @@ static void usage(void)
                "                name <ipcp name>\n"
                "                layer <layer name>\n"
                "                [type [TYPE]]\n"
-               "where TYPE = {" NORMAL " " LOCAL " "
+               "where TYPE = {" NORMAL " " BROADCAST " " LOCAL " "
                UDP " " ETH_LLC " " ETH_DIX " " RAPTOR "},\n\n"
                "if TYPE == " NORMAL "\n"
                "                [addr <address size> (default: %d)]\n"
@@ -125,7 +126,9 @@ static void usage(void)
                "if TYPE == " RAPTOR "\n"
                "                [hash [ALGORITHM] (default: %s)]\n"
                "where ALGORITHM = {" SHA3_224 " " SHA3_256 " "
-               SHA3_384 " " SHA3_512 "}\n\n",
+               SHA3_384 " " SHA3_512 "}\n"
+               "if TYPE == " BROADCAST "\n"
+               "                [autobind]\n\n",
                DEFAULT_ADDR_SIZE, DEFAULT_EID_SIZE, DEFAULT_TTL,
                FLAT_RANDOM_ADDR_AUTH, LINK_STATE_ROUTING, SIMPLE_PFF,
                SHA3_256, SHA3_256, 0xA000, SHA3_256, SHA3_256, SHA3_256);
@@ -250,6 +253,8 @@ int do_bootstrap_ipcp(int     argc,
         if (ipcp_type != NULL) {
                 if (strcmp(ipcp_type, NORMAL) == 0)
                         type = IPCP_NORMAL;
+                else if (strcmp(ipcp_type, BROADCAST) == 0)
+                        type = IPCP_BROADCAST;
                 else if (strcmp(ipcp_type, UDP) == 0)
                         type = IPCP_UDP;
                 else if (strcmp(ipcp_type, ETH_LLC) == 0)
@@ -285,8 +290,9 @@ int do_bootstrap_ipcp(int     argc,
                         }
                         conf.type = ipcps[i].type;
 
-                        if (autobind && conf.type != IPCP_NORMAL) {
-                                printf("Can only bind normal IPCPs, "
+                        if (autobind && (conf.type != IPCP_NORMAL &&
+                                         conf.type != IPCP_BROADCAST)) {
+                                printf("Can not bind this IPCP type,"
                                        "autobind disabled.\n\n");
                                 autobind = false;
                         }
@@ -326,6 +332,8 @@ int do_bootstrap_ipcp(int     argc,
                                 conf.dev = dev;
                                 conf.ethertype = ethertype;
                                 break;
+                        case IPCP_BROADCAST:
+                                /* FALLTHRU */
                         case IPCP_LOCAL:
                                 /* FALLTHRU */
                         case IPCP_RAPTOR:
diff --git a/src/tools/irm/irm_ipcp_create.c b/src/tools/irm/irm_ipcp_create.c
index c886696..5694ecc 100644
--- a/src/tools/irm/irm_ipcp_create.c
+++ b/src/tools/irm/irm_ipcp_create.c
@@ -44,19 +44,20 @@
 #include "irm_ops.h"
 #include "irm_utils.h"
 
-#define NORMAL  "normal"
-#define UDP     "udp"
-#define ETH_LLC "eth-llc"
-#define ETH_DIX "eth-dix"
-#define LOCAL   "local"
-#define RAPTOR  "raptor"
+#define NORMAL    "normal"
+#define BROADCAST "broadcast"
+#define UDP       "udp"
+#define ETH_LLC   "eth-llc"
+#define ETH_DIX   "eth-dix"
+#define LOCAL     "local"
+#define RAPTOR    "raptor"
 
 static void usage(void)
 {
         printf("Usage: irm ipcp create\n"
                "                name <ipcp name>\n"
                "                type [TYPE]\n\n"
-               "where TYPE = {" NORMAL " " LOCAL " "
+               "where TYPE = {" NORMAL " " BROADCAST " " LOCAL " "
                UDP " " ETH_LLC " " RAPTOR "}\n");
 }
 
@@ -90,6 +91,8 @@ int do_create_ipcp(int     argc,
 
         if (strcmp(ipcp_type, NORMAL) == 0)
                 type = IPCP_NORMAL;
+        else if (strcmp(ipcp_type, BROADCAST) == 0)
+                type = IPCP_BROADCAST;
         else if (strcmp(ipcp_type, UDP) == 0)
                 type = IPCP_UDP;
         else if (strcmp(ipcp_type, LOCAL) == 0)
diff --git a/src/tools/irm/irm_ipcp_enroll.c b/src/tools/irm/irm_ipcp_enroll.c
index c1628af..5b6caf5 100644
--- a/src/tools/irm/irm_ipcp_enroll.c
+++ b/src/tools/irm/irm_ipcp_enroll.c
@@ -46,30 +46,39 @@
 
 #include <string.h>
 
+#define NORMAL    "normal"
+#define BROADCAST "broadcast"
+
 static void usage(void)
 {
         printf("Usage: irm ipcp enroll\n"
                "                name <ipcp name>\n"
                "                layer <layer to enroll in>\n"
-               "                [autobind]\n");
+               "                [type [TYPE], default = normal]\n"
+               "                [autobind]\n"
+               "where TYPE = {" NORMAL " " BROADCAST "}\n");
 }
 
 int do_enroll_ipcp(int     argc,
                    char ** argv)
 {
-        char *             ipcp     = NULL;
-        char *             layer    = NULL;
+        char *             ipcp      = NULL;
+        char *             layer     = NULL;
         struct ipcp_info * ipcps;
-        pid_t              pid      = -1;
-        ssize_t            len      = 0;
-        int                i        = 0;
-        bool               autobind = false;
+        pid_t              pid       = -1;
+        ssize_t            len       = 0;
+        int                i         = 0;
+        bool               autobind  = false;
         int                cargs;
+        char *             ipcp_type = NORMAL;
+        enum ipcp_type     type      = IPCP_INVALID;
 
         while (argc > 0) {
                 cargs = 2;
                 if (matches(*argv, "name") == 0) {
                         ipcp = *(argv + 1);
+                } else if (matches(*argv, "type") == 0) {
+                        ipcp_type = *(argv + 1);
                 } else if (matches(*argv, "layer") == 0) {
                         layer = *(argv + 1);
                 } else if (matches(*argv, "autobind") == 0) {
@@ -90,14 +99,19 @@ int do_enroll_ipcp(int     argc,
                 return -1;
         }
 
+        if (strcmp(ipcp_type, NORMAL) == 0)
+                type = IPCP_NORMAL;
+        else if (strcmp(ipcp_type, BROADCAST) == 0)
+                type = IPCP_BROADCAST;
+
         len = irm_list_ipcps(&ipcps);
         for (i = 0; i < len; i++)
                 if (wildcard_match(ipcps[i].name, ipcp) == 0 &&
-                    ipcps[i].type == IPCP_NORMAL)
+                    ipcps[i].type == type)
                         pid = ipcps[i].pid;
 
         if (pid < 0) {
-                pid = irm_create_ipcp(ipcp, IPCP_NORMAL);
+                pid = irm_create_ipcp(ipcp, type);
                 if (pid < 0)
                         goto fail;
                 free(ipcps);
@@ -105,7 +119,7 @@ int do_enroll_ipcp(int     argc,
         }
 
         for (i = 0; i < len; i++) {
-                if (ipcps[i].type != IPCP_NORMAL)
+                if (ipcps[i].type != type)
                         continue;
                 if (wildcard_match(ipcps[i].name, ipcp) == 0) {
                         pid = ipcps[i].pid;
@@ -121,7 +135,8 @@ int do_enroll_ipcp(int     argc,
                         }
 
                         if (autobind && irm_bind_process(pid, layer)) {
-                                printf("Failed to bind %d to %s.\n", pid, 
layer);
+                                printf("Failed to bind %d to %s.\n",
+                                       pid, layer);
                                 goto fail;
                         }
                 }
diff --git a/src/tools/obc/CMakeLists.txt b/src/tools/obc/CMakeLists.txt
new file mode 100644
index 0000000..db5e999
--- /dev/null
+++ b/src/tools/obc/CMakeLists.txt
@@ -0,0 +1,16 @@
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+include_directories(${CMAKE_SOURCE_DIR}/include)
+include_directories(${CMAKE_BINARY_DIR}/include)
+
+set(SOURCE_FILES
+  # Add source files here
+  obc.c
+  )
+
+add_executable(obc ${SOURCE_FILES})
+
+target_link_libraries(obc LINK_PUBLIC ouroboros-dev)
+
+install(TARGETS obc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/obc/obc.c b/src/tools/obc/obc.c
new file mode 100644
index 0000000..9f27643
--- /dev/null
+++ b/src/tools/obc/obc.c
@@ -0,0 +1,153 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2018
+ *
+ * A simple broadcast application
+ *
+ *    Dimitri Staessens <dimitri.staessens@xxxxxxxx>
+ *    Sander Vrijders   <sander.vrijders@xxxxxxxx>
+ *
+ * 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 copyright holder 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 COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDER 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.
+ */
+
+#define _POSIX_C_SOURCE 199309L
+
+#include <ouroboros/dev.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define BUF_SIZE 256
+
+static void usage(void)
+{
+        printf("Usage: obc [OPTION]...\n"
+               "Sends a message to a broadcast network\n\n"
+               "  -n --name                 Name of the broadcast layer\n"
+               "  -m --message              A message to send\n"
+               "  [-l, --listen             Listen mode]\n"
+               "      --help                Display this help text and 
exit\n");
+}
+
+static int reader_main(const char * dst)
+{
+        int    fd;
+        char   buf[BUF_SIZE];
+
+        printf("Starting a reader.\n");
+
+        fd = flow_alloc(dst, NULL, NULL);
+        if (fd < 0) {
+                printf("Failed to allocate multicast flow.\n");
+                return -1;
+        }
+
+        printf("New flow.\n");
+
+        while (true) {
+                ssize_t count = flow_read(fd, &buf, BUF_SIZE);
+                if (count < 0) {
+                        printf("Failed to read.\n");
+                        flow_dealloc(fd);
+                        break;
+                }
+
+                printf("Message is %.*s.\n", (int) count, buf);
+        }
+
+        return 0;
+}
+
+static int writer_main(const char * dst,
+                       const char * message)
+{
+        int     fd      = 0;
+
+        fd = flow_alloc(dst, NULL, NULL);
+        if (fd < 0) {
+                printf("Failed to allocate multicast flow.\n");
+                return -1;
+        }
+
+        if (flow_write(fd, message, strlen(message) + 1) < 0) {
+                printf("Failed to write packet.\n");
+                flow_dealloc(fd);
+                return -1;
+        }
+
+        flow_dealloc(fd);
+
+        return 0;
+}
+
+int main(int     argc,
+         char ** argv)
+{
+        int    ret    = -1;
+        bool   reader = false;
+        char * name   = NULL;
+        char * msg    = "Ouroboros multicast rocks!";
+
+        argc--;
+        argv++;
+        while (argc > 0) {
+                if (strcmp(*argv, "-l") == 0 ||
+                    strcmp(*argv, "--listen") == 0) {
+                        reader = true;
+                } else if (strcmp(*argv, "-n") == 0 ||
+                           strcmp(*argv, "--name") == 0) {
+                        name = *(argv + 1);
+                        argc--;
+                        argv++;
+                } else if (strcmp(*argv, "-m") == 0 ||
+                           strcmp(*argv, "--message") == 0) {
+                        msg = *(argv + 1);
+                        argc--;
+                        argv++;
+                } else {
+                        usage();
+                        return 0;
+                }
+                argc--;
+                argv++;
+        }
+
+        if (name == NULL) {
+                usage();
+                exit(EXIT_FAILURE);
+        }
+
+        if (reader)
+                ret = reader_main(name);
+        else
+                ret = writer_main(name, msg);
+
+        return ret;
+}
-- 
2.19.1


Other related posts: