/********************************************************************* PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved. See LICENSE and COPYING for usage. Authors: Jelle De Vleeschouwer *********************************************************************/ /* * For testing purposes. pico_dev_radio_manager allows simulating a mesh * network for smoke tests. I previously used geomess, but that's another * dependency to add then. Then @danielinux wrote the pico_dev_radiotest but * that required adding a multicast route on the host which in its turn * required 'sudo'. So I wrote a small simulator which doesn't require sudo. * - Jelle */ #include "pico_dev_radiotest.h" #include "pico_addressing.h" #include "pico_dev_tap.h" #include "pico_802154.h" #include "pico_device.h" #include "pico_config.h" #include "pico_stack.h" #include "pico_dev_radio_mgr.h" #include #include #include #include #include #include #include #include #ifdef DEBUG_RADIOTEST #define RADIO_DBG dbg #else #define RADIO_DBG(...) do { } while (0) #endif #define LISTENING_PORT 7777 #define MESSAGE_MTU 150 #define EVER (;;) struct socket { int s; uint8_t mgr; uint8_t id; uint8_t area0; uint8_t area1; }; /* Compare two application sockets */ static int pico_radio_mgr_sock_cmp(void *a, void *b) { struct socket *sa = a, *sb = b; return (int)(sa->id - sb->id); } PICO_TREE_DECLARE(Sockets, pico_radio_mgr_sock_cmp); /* Insert a new socket in the tree */ static int pico_radio_mgr_socket_insert(int socket, uint8_t id, uint8_t area0, uint8_t area1, uint8_t mgr) { struct socket *s = PICO_ZALLOC(sizeof(struct socket)); if (s) { s->area0 = area0; s->area1 = area1; s->s = socket; s->mgr = mgr; s->id = id; if (!pico_tree_insert(&Sockets, s)) return 0; PICO_FREE(s); } return -1; } /* Gather an array of poll descriptors with all sockets */ static struct pollfd * pico_radio_mgr_socket_all(int *n) { struct pico_tree_node *i = NULL; struct socket *key = NULL; struct pollfd *fds = NULL; int j = 1; *n = 0; /* Retrieve all sockets */ pico_tree_foreach(i, &Sockets) { (*n)++; } /* Create array from tree */ fds = PICO_ZALLOC(sizeof(struct pollfd) * (size_t)*n); if (fds) { /* Put every socket in array */ pico_tree_foreach(i, &Sockets) { if (i && (key = i->keyValue)) { if (!key->id) { fds[0].fd = key->s; fds[0].events = POLLIN; } else { fds[j].fd = key->s; fds[j].events = POLLIN | POLLHUP; j++; } } } } return fds; } /* Get connection socket that belongs to a particular node */ static struct socket * pico_radio_mgr_socket_node(uint8_t id) { struct socket test = { 0, 0, id }; return pico_tree_findKey(&Sockets, &test); } /* Handle POLLHUP event */ static int pico_radio_mgr_socket_hup(int socket) { struct pico_tree_node *i = NULL; struct socket *key = NULL; pico_tree_foreach(i, &Sockets) { key = i->keyValue; if (key && key->s == socket) { pico_tree_delete(&Sockets, key); RADIO_DBG("Radio %d detached from network\n", key->id); PICO_FREE(key); close(socket); return 0; } } return -1; } /* Receive's an 'Hello'-message from the node that contains the id, the inserts * an entry in the Sockets-tree */ static int pico_radio_mgr_welcome(int socket) { int ret_len = sizeof(uint8_t); uint8_t id = 0, area0, area1; errno = 0; while ((ret_len = recv(socket, &id, (size_t)ret_len, 0)) != 1) { if (errno && EINTR != errno) goto hup; } while ((ret_len = recv(socket, &area0, (size_t)ret_len, 0)) != 1) { if (errno && EINTR != errno) goto hup; } while ((ret_len = recv(socket, &area1, (size_t)ret_len, 0)) != 1) { if (errno && EINTR != errno) goto hup; } if (id <= 0) { // Node's can't have ID '0'. RADIO_DBG("Invalid socket\n"); close(socket); return -1; } RADIO_DBG("Connected to node %u in area %u and %u on socket %d.\n", id, area0, area1, socket); if (pico_radio_mgr_socket_insert(socket, id, area0, area1, 0)) { RADIO_DBG("Failed inserting new socket\n"); close(socket); return -1; } return 0; hup: RADIO_DBG("recv() failed with error: %s\n", strerror(errno)); close(socket); return -1; } /* Accepts a new TCP connection request */ static int pico_radio_mgr_accept(int socket) { unsigned int len = sizeof(struct sockaddr_in); struct sockaddr_in addr; int s = accept(socket, (struct sockaddr *)&addr, &len); if (s < 0) { RADIO_DBG("Failed accepting connection\n"); return s; } else if (!s) { RADIO_DBG("accept() returned file descriptor '%d'\n", s); return s; } return pico_radio_mgr_welcome(s); } /* Start listening for TCP connection requests on 'LISTENING_PORT' */ static int pico_radio_mgr_listen(void) { struct sockaddr_in addr; int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); int ret = 0, yes = 1; memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(LISTENING_PORT); addr.sin_addr.s_addr = INADDR_ANY; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); ret = bind(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); if (ret < 0) { RADIO_DBG("Failed binding socket to address: %s\n", strerror(ret)); return -1; } ret = listen(s, 5); if (ret < 0) { RADIO_DBG("Failed start listening\n"); return -1; } /* Also insert server socket in tree for polling */ if (pico_radio_mgr_socket_insert(s, 0, 0, 0, 1)) { close(s); return -1; } dbg("Started listening on port %d\n", LISTENING_PORT); return s; } /* Distribute received frame over all the areas where the node is attached to */ static void pico_radio_mgr_distribute(uint8_t *buf, int len, uint8_t id) { struct socket *node = pico_radio_mgr_socket_node(id); uint8_t area0 = 0, area1 = 0, ar0 = 0, ar1 = 0, phy = (uint8_t)len; struct pico_tree_node *i = NULL; struct socket *key = NULL; if (node) { RADIO_DBG("Received frame from node '%d' of '%d' bytes\n", id, len); area0 = node->area0; area1 = node->area1; } else { RADIO_DBG("Received frame from node not connected to network, weird..\n"); return; } pico_tree_foreach(i, &Sockets) { key = i->keyValue; if (key && key->id != id && key->id) { // Do not sent to ourselves or manager ar0 = key->area0; ar1 = key->area1; if (area0 == ar0 || area0 == ar1 || (area1 && (area1 == ar0 || area1 == ar1))) { len = (int)sendto(key->s, &phy, (size_t)1, 0, NULL, 0); if (len != 1) return; len = (int)sendto(key->s, buf, (size_t)phy, 0, NULL, 0); if (len == (int)phy) RADIO_DBG("Forwarded from '%u' of %d bytes sent to '%u'\n", id, len, key->id); } } } } /* Process poll-events */ static void pico_radio_mgr_process(struct pollfd *fds, int n) { uint8_t buf[MESSAGE_MTU] = { 0 }, node = 0, phy = 0; int i = 0, ret_len = 0; short event = 0; for (i = 0; i < n; i++) { event = fds[i].revents; if (event && (event & POLLIN)) { // POLLIN if (!i) { /* Accept a new connection */ pico_radio_mgr_accept(fds[i].fd); continue; } /* Read from node */ ret_len = (int)recv(fds[i].fd, &phy, (size_t)1, 0); if (ret_len <= 0) goto hup; ret_len = (int)recv(fds[i].fd, buf, (size_t)phy, 0); if (ret_len <= 0 || ret_len != phy) goto hup; node = buf[ret_len - 2]; pico_radio_mgr_distribute(buf, ret_len, node); } else if (event && (event & POLLHUP)) { goto hup; } } return; hup: pico_radio_mgr_socket_hup(fds[i].fd); } static void pico_radio_mgr_quit(int signum) { struct pico_tree_node *i = NULL, *tmp = NULL; struct socket *key = NULL; IGNORE_PARAMETER(signum); dbg("Closing all sockets..."); pico_tree_foreach_safe(i, &Sockets, tmp) { key = i->keyValue; if (key) { pico_tree_delete(&Sockets, key); shutdown(key->s, SHUT_RDWR); PICO_FREE(key); } } dbg("done.\n"); exit(0); } /* Create and start a radio-manager instance */ int pico_radio_mgr_start(void) { int server = pico_radio_mgr_listen(); struct pollfd *fds = NULL; nfds_t n = 0; int ret = 0; if (server < 0) return -1; signal(SIGQUIT, pico_radio_mgr_quit); for EVER { if (fds) PICO_FREE(fds); fds = pico_radio_mgr_socket_all((int *)&n); errno = 0; ret = poll(fds, n, 1); if (errno != EINTR && ret < 0) { RADIO_DBG("Socket error: %s\n", strerror(ret)); return ret; } else if (!ret) { continue; } pico_radio_mgr_process(fds, (int)n); } }