summaryrefslogtreecommitdiff
path: root/src/ipcpd/udp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ipcpd/udp')
-rw-r--r--src/ipcpd/udp/CMakeLists.txt60
-rw-r--r--src/ipcpd/udp/main.c1167
2 files changed, 1227 insertions, 0 deletions
diff --git a/src/ipcpd/udp/CMakeLists.txt b/src/ipcpd/udp/CMakeLists.txt
new file mode 100644
index 00000000..20c1b58e
--- /dev/null
+++ b/src/ipcpd/udp/CMakeLists.txt
@@ -0,0 +1,60 @@
+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_UDP_TARGET ipcpd-udp CACHE INTERNAL "")
+
+set(UDP_SOURCES
+ # Add source files here
+ ${CMAKE_CURRENT_SOURCE_DIR}/main.c)
+
+add_executable(ipcpd-udp ${UDP_SOURCES} ${IPCP_SOURCES})
+target_link_libraries(ipcpd-udp LINK_PUBLIC ouroboros-dev)
+
+# Find the nsupdate executable
+find_program(NSUPDATE_EXECUTABLE
+ NAMES nsupdate
+ DOC "The nsupdate tool that enables DDNS")
+
+# Find the nslookup executable
+find_program(NSLOOKUP_EXECUTABLE
+ NAMES nslookup
+ DOC "The nslookup tool that resolves DNS names")
+
+mark_as_advanced(NSLOOKUP_EXECUTABLE NSUPDATE_EXECUTABLE)
+
+if (NSLOOKUP_EXECUTABLE AND NSUPDATE_EXECUTABLE)
+ set(DISABLE_DDNS FALSE CACHE BOOL "Disable DDNS support")
+ if (NOT DISABLE_DNS)
+ message(STATUS "DDNS support enabled")
+ set(HAVE_DDNS TRUE CACHE INTERNAL "")
+ else ()
+ message(STATUS "DDNS support disabled by user")
+ unset(HAVE_DDNS CACHE)
+ endif ()
+else ()
+ if (NSLOOKUP_EXECUTABLE)
+ message(STATUS "Install nsupdate to enable DDNS support")
+ elseif (NSUPDATE_EXECUTABLE)
+ message(STATUS "Install nslookup to enable DDNS support")
+ else ()
+ message(STATUS "Install nslookup and nsupdate to enable DDNS support")
+ endif ()
+endif ()
+
+include(AddCompileFlags)
+if (CMAKE_BUILD_TYPE MATCHES "Debug*")
+ add_compile_flags(ipcpd-udp -DCONFIG_OUROBOROS_DEBUG)
+endif ()
+
+install(TARGETS ipcpd-udp RUNTIME DESTINATION sbin)
diff --git a/src/ipcpd/udp/main.c b/src/ipcpd/udp/main.c
new file mode 100644
index 00000000..c54e6624
--- /dev/null
+++ b/src/ipcpd/udp/main.c
@@ -0,0 +1,1167 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2017
+ *
+ * IPC process over UDP
+ *
+ * Dimitri Staessens <[email protected]>
+ * Sander Vrijders <[email protected]>
+ *
+ * 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"
+
+#define OUROBOROS_PREFIX "ipcpd/udp"
+
+#include <ouroboros/hash.h>
+#include <ouroboros/list.h>
+#include <ouroboros/utils.h>
+#include <ouroboros/dev.h>
+#include <ouroboros/ipcp-dev.h>
+#include <ouroboros/fqueue.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/logs.h>
+
+#include "ipcp.h"
+#include "shim-data.h"
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#define FLOW_REQ 1
+#define FLOW_REPLY 2
+
+#define THIS_TYPE IPCP_UDP
+#define LISTEN_PORT htons(0x0D1F)
+#define SHIM_UDP_BUF_SIZE 256
+#define SHIM_UDP_MSG_SIZE 256
+#define SHIM_UDP_MAX_SDU_SIZE 8980
+#define DNS_TTL 86400
+#define FD_UPDATE_TIMEOUT 100 /* microseconds */
+
+#define local_ip (udp_data.s_saddr.sin_addr.s_addr)
+
+#define UDP_MAX_PORTS 0xFFFF
+
+struct mgmt_msg {
+ uint16_t src_udp_port;
+ uint16_t dst_udp_port;
+ uint8_t code;
+ uint8_t qoscube;
+ uint8_t response;
+} __attribute__((packed));
+
+struct uf {
+ int udp;
+ int skfd;
+};
+
+struct {
+ struct shim_data * shim_data;
+
+ uint32_t ip_addr;
+ uint32_t dns_addr;
+ /* listen server */
+ struct sockaddr_in s_saddr;
+ int s_fd;
+
+ fset_t * np1_flows;
+ fqueue_t * fq;
+ fd_set flow_fd_s;
+ /* bidir mappings of (n - 1) file descriptor to (n) flow descriptor */
+ int uf_to_fd[FD_SETSIZE];
+ struct uf fd_to_uf[SYS_MAX_FLOWS];
+ pthread_rwlock_t flows_lock;
+
+ pthread_t sduloop;
+ pthread_t handler;
+ pthread_t sdu_reader;
+
+ bool fd_set_mod;
+ pthread_cond_t fd_set_cond;
+ pthread_mutex_t fd_set_lock;
+} udp_data;
+
+static int udp_data_init(void)
+{
+ int i;
+
+ for (i = 0; i < FD_SETSIZE; ++i)
+ udp_data.uf_to_fd[i] = -1;
+
+ for (i = 0; i < SYS_MAX_FLOWS; ++i) {
+ udp_data.fd_to_uf[i].skfd = -1;
+ udp_data.fd_to_uf[i].udp = -1;
+ }
+
+ FD_ZERO(&udp_data.flow_fd_s);
+
+ udp_data.np1_flows = fset_create();
+ if (udp_data.np1_flows == NULL)
+ return -ENOMEM;
+
+ udp_data.fq = fqueue_create();
+ if (udp_data.fq == NULL) {
+ fset_destroy(udp_data.np1_flows);
+ return -ENOMEM;
+ }
+
+ udp_data.shim_data = shim_data_create();
+ if (udp_data.shim_data == NULL) {
+ fqueue_destroy(udp_data.fq);
+ fset_destroy(udp_data.np1_flows);
+ return -ENOMEM;
+ }
+
+ pthread_rwlock_init(&udp_data.flows_lock, NULL);
+ pthread_cond_init(&udp_data.fd_set_cond, NULL);
+ pthread_mutex_init(&udp_data.fd_set_lock, NULL);
+
+ return 0;
+}
+
+static void udp_data_fini(void)
+{
+ fset_destroy(udp_data.np1_flows);
+ fqueue_destroy(udp_data.fq);
+
+ shim_data_destroy(udp_data.shim_data);
+
+ pthread_rwlock_destroy(&udp_data.flows_lock);
+ pthread_mutex_destroy(&udp_data.fd_set_lock);
+ pthread_cond_destroy(&udp_data.fd_set_cond);
+}
+
+static void set_fd(int fd)
+{
+ pthread_mutex_lock(&udp_data.fd_set_lock);
+
+ udp_data.fd_set_mod = true;
+ FD_SET(fd, &udp_data.flow_fd_s);
+
+ while (udp_data.fd_set_mod)
+ pthread_cond_wait(&udp_data.fd_set_cond, &udp_data.fd_set_lock);
+
+ pthread_mutex_unlock(&udp_data.fd_set_lock);
+}
+
+static void clr_fd(int fd)
+{
+ pthread_mutex_lock(&udp_data.fd_set_lock);
+
+ udp_data.fd_set_mod = true;
+ FD_CLR(fd, &udp_data.flow_fd_s);
+
+ while (udp_data.fd_set_mod)
+ pthread_cond_wait(&udp_data.fd_set_cond, &udp_data.fd_set_lock);
+
+ pthread_mutex_unlock(&udp_data.fd_set_lock);
+}
+
+static int send_shim_udp_msg(uint8_t * buf,
+ size_t len,
+ uint32_t dst_ip_addr)
+{
+ struct sockaddr_in r_saddr;
+
+ memset((char *)&r_saddr, 0, sizeof(r_saddr));
+ r_saddr.sin_family = AF_INET;
+ r_saddr.sin_addr.s_addr = dst_ip_addr;
+ r_saddr.sin_port = LISTEN_PORT;
+
+ if (sendto(udp_data.s_fd, buf, len, 0,
+ (struct sockaddr *) &r_saddr,
+ sizeof(r_saddr)) == -1) {
+ log_err("Failed to send message.");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ipcp_udp_port_alloc(uint32_t dst_ip_addr,
+ uint16_t src_udp_port,
+ const uint8_t * dst,
+ qoscube_t cube)
+{
+ uint8_t * buf;
+ struct mgmt_msg * msg;
+ size_t len;
+ int ret;
+
+ len = sizeof(*msg) + ipcp_dir_hash_len();
+
+ buf = malloc(len);
+ if (buf == NULL)
+ return -1;
+
+ msg = (struct mgmt_msg *) buf;
+ msg->code = FLOW_REQ;
+ msg->src_udp_port = src_udp_port;
+ msg->qoscube = cube;
+
+ memcpy(msg + 1, dst, ipcp_dir_hash_len());
+
+ ret = send_shim_udp_msg(buf, len, dst_ip_addr);
+
+ free(buf);
+
+ return ret;
+}
+
+static int ipcp_udp_port_alloc_resp(uint32_t dst_ip_addr,
+ uint16_t src_udp_port,
+ uint16_t dst_udp_port,
+ int response)
+{
+ uint8_t * buf;
+ struct mgmt_msg * msg;
+ int ret;
+
+ buf = malloc(sizeof(*msg));
+ if (buf == NULL)
+ return -1;
+
+ msg = (struct mgmt_msg *) buf;
+ msg->code = FLOW_REPLY;
+ msg->src_udp_port = src_udp_port;
+ msg->dst_udp_port = dst_udp_port;
+ msg->response = response;
+
+ ret = send_shim_udp_msg(buf, sizeof(*msg), dst_ip_addr);
+
+ free(buf);
+
+ return ret;
+}
+
+static int ipcp_udp_port_req(struct sockaddr_in * c_saddr,
+ const uint8_t * dst,
+ qoscube_t cube)
+{
+ struct timespec ts = {0, FD_UPDATE_TIMEOUT * 1000};
+ struct timespec abstime;
+ struct sockaddr_in f_saddr;
+ socklen_t f_saddr_len = sizeof(f_saddr);
+ int skfd;
+ int fd;
+
+ log_dbg("Port request arrived from UDP port %d",
+ ntohs(c_saddr->sin_port));
+
+ if ((skfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+ log_err("Could not create UDP socket.");
+ return -1;
+ }
+
+ memset((char *) &f_saddr, 0, sizeof(f_saddr));
+ f_saddr.sin_family = AF_INET;
+ f_saddr.sin_addr.s_addr = local_ip;
+ f_saddr.sin_port = 0;
+
+ if (bind(skfd, (struct sockaddr *) &f_saddr, sizeof(f_saddr)) < 0) {
+ log_err("Could not bind to socket.");
+ close(skfd);
+ return -1;
+ }
+
+ if (getsockname(skfd, (struct sockaddr *) &f_saddr, &f_saddr_len) < 0) {
+ log_err("Could not get address from fd.");
+ return -1;
+ }
+
+ /* connect stores the remote address in the file descriptor */
+ if (connect(skfd, (struct sockaddr *) c_saddr, sizeof(*c_saddr)) < 0) {
+ log_err("Could not connect to remote UDP client.");
+ close(skfd);
+ return -1;
+ }
+
+ clock_gettime(PTHREAD_COND_CLOCK, &abstime);
+
+ pthread_mutex_lock(&ipcpi.alloc_lock);
+
+ while (ipcpi.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) {
+ ts_add(&abstime, &ts, &abstime);
+ pthread_cond_timedwait(&ipcpi.alloc_cond,
+ &ipcpi.alloc_lock,
+ &abstime);
+ }
+
+ if (ipcp_get_state() != IPCP_OPERATIONAL) {
+ log_dbg("Won't allocate over non-operational IPCP.");
+ pthread_mutex_unlock(&ipcpi.alloc_lock);
+ return -1;
+ }
+
+ /* reply to IRM */
+ fd = ipcp_flow_req_arr(getpid(), dst, ipcp_dir_hash_len(), cube);
+ if (fd < 0) {
+ pthread_mutex_unlock(&ipcpi.alloc_lock);
+ log_err("Could not get new flow from IRMd.");
+ close(skfd);
+ return -1;
+ }
+
+ pthread_rwlock_wrlock(&udp_data.flows_lock);
+
+ udp_data.uf_to_fd[skfd] = fd;
+ udp_data.fd_to_uf[fd].skfd = skfd;
+ udp_data.fd_to_uf[fd].udp = f_saddr.sin_port;
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ ipcpi.alloc_id = fd;
+ pthread_cond_broadcast(&ipcpi.alloc_cond);
+
+ pthread_mutex_unlock(&ipcpi.alloc_lock);
+
+ log_dbg("Pending allocation request, fd %d, UDP port (%d, %d).",
+ fd, ntohs(f_saddr.sin_port), ntohs(c_saddr->sin_port));
+
+ return 0;
+}
+
+/* returns the n flow descriptor */
+static int udp_port_to_fd(int udp_port)
+{
+ int i;
+
+ for (i = 0; i < SYS_MAX_FLOWS; ++i)
+ if (udp_data.fd_to_uf[i].udp == udp_port)
+ return i;
+
+ return -1;
+}
+
+static int ipcp_udp_port_alloc_reply(uint16_t src_udp_port,
+ uint16_t dst_udp_port,
+ int response)
+{
+ int fd = -1;
+ int ret = 0;
+ int skfd = -1;
+
+ struct sockaddr_in t_saddr;
+ socklen_t t_saddr_len = sizeof(t_saddr);
+
+ log_dbg("Received reply for flow on udp port %d.",
+ ntohs(dst_udp_port));
+
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ fd = udp_port_to_fd(dst_udp_port);
+ skfd = udp_data.fd_to_uf[fd].skfd;
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ /* get the original address with the LISTEN PORT */
+ if (getpeername(skfd, (struct sockaddr *) &t_saddr, &t_saddr_len) < 0) {
+ log_dbg("Flow with fd %d has no peer.", fd);
+ return -1;
+ }
+
+ /* connect to the flow udp port */
+ t_saddr.sin_port = src_udp_port;
+
+ if (connect(skfd, (struct sockaddr *) &t_saddr, sizeof(t_saddr)) < 0) {
+ close(skfd);
+ return -1;
+ }
+
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ set_fd(skfd);
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ if (ipcp_flow_alloc_reply(fd, response) < 0)
+ return -1;
+
+ log_dbg("Flow allocation completed, UDP ports: (%d, %d).",
+ ntohs(dst_udp_port), ntohs(src_udp_port));
+
+ return ret;
+}
+
+static void * ipcp_udp_listener(void * o)
+{
+ uint8_t buf[SHIM_UDP_MSG_SIZE];
+ ssize_t n = 0;
+ struct sockaddr_in c_saddr;
+ int sfd = udp_data.s_fd;
+
+ (void) o;
+
+ while (true) {
+ struct mgmt_msg * msg = NULL;
+
+ memset(&buf, 0, SHIM_UDP_MSG_SIZE);
+ n = sizeof(c_saddr);
+ n = recvfrom(sfd, buf, SHIM_UDP_MSG_SIZE, 0,
+ (struct sockaddr *) &c_saddr, (unsigned *) &n);
+ if (n < 0)
+ continue;
+
+ /* flow alloc request from other host */
+ if (gethostbyaddr((const char *) &c_saddr.sin_addr.s_addr,
+ sizeof(c_saddr.sin_addr.s_addr), AF_INET)
+ == NULL)
+ continue;
+
+ msg = (struct mgmt_msg *) buf;
+
+ switch (msg->code) {
+ case FLOW_REQ:
+ c_saddr.sin_port = msg->src_udp_port;
+ ipcp_udp_port_req(&c_saddr,
+ (uint8_t *) (msg + 1),
+ msg->qoscube);
+ break;
+ case FLOW_REPLY:
+ ipcp_udp_port_alloc_reply(msg->src_udp_port,
+ msg->dst_udp_port,
+ msg->response);
+ break;
+ default:
+ log_err("Unknown message received %d.", msg->code);
+ continue;
+ }
+
+ c_saddr.sin_port = LISTEN_PORT;
+ }
+
+ return 0;
+}
+
+static void * ipcp_udp_sdu_reader(void * o)
+{
+ ssize_t n;
+ int skfd;
+ int fd;
+ /* FIXME: avoid this copy */
+ char buf[SHIM_UDP_MAX_SDU_SIZE];
+ struct sockaddr_in r_saddr;
+ struct timeval tv = {0, FD_UPDATE_TIMEOUT};
+ fd_set read_fds;
+ int flags;
+
+ (void) o;
+
+ while (true) {
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+ pthread_mutex_lock(&udp_data.fd_set_lock);
+
+ read_fds = udp_data.flow_fd_s;
+ udp_data.fd_set_mod = false;
+ pthread_cond_broadcast(&udp_data.fd_set_cond);
+
+ pthread_mutex_unlock(&udp_data.fd_set_lock);
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ if (select(FD_SETSIZE, &read_fds, NULL, NULL, &tv) <= 0)
+ continue;
+
+ for (skfd = 0; skfd < FD_SETSIZE; ++skfd) {
+ if (!FD_ISSET(skfd, &read_fds))
+ continue;
+ flags = fcntl(skfd, F_GETFL, 0);
+ fcntl(skfd, F_SETFL, flags | O_NONBLOCK);
+ n = sizeof(r_saddr);
+ if ((n = recvfrom(skfd,
+ &buf,
+ SHIM_UDP_MAX_SDU_SIZE,
+ 0,
+ (struct sockaddr *) &r_saddr,
+ (unsigned *) &n)) <= 0)
+ continue;
+
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ fd = udp_data.uf_to_fd[skfd];
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ flow_write(fd, buf, n);
+ }
+ }
+
+ return (void *) 0;
+}
+
+static void * ipcp_udp_sdu_loop(void * o)
+{
+ int fd;
+ struct shm_du_buff * sdb;
+
+ (void) o;
+
+ while (true) {
+ fevent(udp_data.np1_flows, udp_data.fq, NULL);
+ while ((fd = fqueue_next(udp_data.fq)) >= 0) {
+ if (ipcp_flow_read(fd, &sdb)) {
+ log_err("Bad read from fd %d.", fd);
+ continue;
+ }
+
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ fd = udp_data.fd_to_uf[fd].skfd;
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ pthread_cleanup_push((void (*)(void *)) ipcp_sdb_release,
+ (void *) sdb);
+
+ if (send(fd, shm_du_buff_head(sdb),
+ shm_du_buff_tail(sdb) - shm_du_buff_head(sdb),
+ 0) < 0)
+ log_err("Failed to send SDU.");
+
+ pthread_cleanup_pop(true);
+ }
+ }
+
+ return (void *) 1;
+}
+
+static int ipcp_udp_bootstrap(const struct ipcp_config * conf)
+{
+ struct sockaddr_in s_saddr;
+ char ipstr[INET_ADDRSTRLEN];
+ char dnsstr[INET_ADDRSTRLEN];
+ int enable = 1;
+ int fd = -1;
+
+ assert(conf);
+ assert(conf->type == THIS_TYPE);
+
+ if (inet_ntop(AF_INET,
+ &conf->ip_addr,
+ ipstr,
+ INET_ADDRSTRLEN) == NULL) {
+ log_err("Failed to convert IP address");
+ return -1;
+ }
+
+ if (conf->dns_addr != 0) {
+ if (inet_ntop(AF_INET,
+ &conf->dns_addr,
+ dnsstr,
+ INET_ADDRSTRLEN) == NULL) {
+ log_err("Failed to convert DNS address");
+ return -1;
+ }
+#ifndef HAVE_DDNS
+ log_warn("DNS disabled at compile time, address ignored");
+#endif
+ } else {
+ strcpy(dnsstr, "not set");
+ }
+
+ /* UDP listen server */
+ if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+ log_err("Can't create socket.");
+ goto fail_socket;
+ }
+
+ if (setsockopt(fd,
+ SOL_SOCKET,
+ SO_REUSEADDR,
+ &enable,
+ sizeof(int)) < 0)
+ log_warn("Failed to set SO_REUSEADDR.");
+
+ memset((char *) &s_saddr, 0, sizeof(s_saddr));
+ udp_data.s_saddr.sin_family = AF_INET;
+ udp_data.s_saddr.sin_addr.s_addr = conf->ip_addr;
+ udp_data.s_saddr.sin_port = LISTEN_PORT;
+
+ if (bind(fd,
+ (struct sockaddr *) &udp_data.s_saddr,
+ sizeof(udp_data.s_saddr)) < 0) {
+ log_err("Couldn't bind to %s.", ipstr);
+ goto fail_bind;
+ }
+
+ udp_data.s_fd = fd;
+ udp_data.ip_addr = conf->ip_addr;
+ udp_data.dns_addr = conf->dns_addr;
+
+ FD_CLR(udp_data.s_fd, &udp_data.flow_fd_s);
+
+ ipcp_set_state(IPCP_OPERATIONAL);
+
+ if (pthread_create(&udp_data.handler,
+ NULL,
+ ipcp_udp_listener,
+ NULL)) {
+ ipcp_set_state(IPCP_INIT);
+ goto fail_bind;
+ }
+
+ if (pthread_create(&udp_data.sdu_reader,
+ NULL,
+ ipcp_udp_sdu_reader,
+ NULL)) {
+ ipcp_set_state(IPCP_INIT);
+ goto fail_sdu_reader;
+ }
+
+ if (pthread_create(&udp_data.sduloop,
+ NULL,
+ ipcp_udp_sdu_loop,
+ NULL)) {
+ ipcp_set_state(IPCP_INIT);
+ goto fail_sduloop;
+ }
+
+ log_dbg("Bootstrapped IPCP over UDP with pid %d.", getpid());
+ log_dbg("Bound to IP address %s.", ipstr);
+ log_dbg("DNS server address is %s.", dnsstr);
+
+ return 0;
+
+ fail_sduloop:
+ pthread_cancel(udp_data.sdu_reader);
+ pthread_join(udp_data.sdu_reader, NULL);
+ fail_sdu_reader:
+ pthread_cancel(udp_data.handler);
+ pthread_join(udp_data.handler, NULL);
+ fail_bind:
+ close(fd);
+ fail_socket:
+ return -1;
+}
+
+#ifdef HAVE_DDNS
+/* FIXME: Dependency on nsupdate to be removed in the end */
+/* NOTE: Disgusted with this crap */
+static int ddns_send(char * cmd)
+{
+ pid_t pid = -1;
+ int wstatus;
+ int pipe_fd[2];
+ char * argv[] = {NSUPDATE_EXEC, 0};
+ char * envp[] = {0};
+
+ if (pipe(pipe_fd)) {
+ log_err("Failed to create pipe.");
+ return -1;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ log_err("Failed to fork.");
+ return -1;
+ }
+
+ if (pid == 0) {
+ close(pipe_fd[1]);
+ dup2(pipe_fd[0], 0);
+ execve(argv[0], &argv[0], envp);
+ }
+
+ close(pipe_fd[0]);
+
+ if (write(pipe_fd[1], cmd, strlen(cmd)) == -1) {
+ log_err("Failed to communicate with nsupdate.");
+ close(pipe_fd[1]);
+ return -1;
+ }
+
+ waitpid(pid, &wstatus, 0);
+ if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)
+ log_dbg("Succesfully communicated with DNS server.");
+ else
+ log_err("Failed to register with DNS server.");
+
+ close(pipe_fd[1]);
+ return 0;
+}
+
+static uint32_t ddns_resolve(char * name,
+ uint32_t dns_addr)
+{
+ pid_t pid = -1;
+ int wstatus;
+ int pipe_fd[2];
+ char dnsstr[INET_ADDRSTRLEN];
+ char buf[SHIM_UDP_BUF_SIZE];
+ ssize_t count = 0;
+ char * substr = NULL;
+ char * substr2 = NULL;
+ char * addr_str = "Address:";
+ uint32_t ip_addr = 0;
+
+ if (inet_ntop(AF_INET, &dns_addr, dnsstr, INET_ADDRSTRLEN) == NULL)
+ return 0;
+
+ if (pipe(pipe_fd)) {
+ log_err("Failed to create pipe.");
+ return 0;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ log_err("Failed to fork.");
+ return 0;
+ }
+
+ if (pid == 0) {
+ char * argv[] = {NSLOOKUP_EXEC, name, dnsstr, 0};
+ char * envp[] = {0};
+
+ close(pipe_fd[0]);
+ dup2(pipe_fd[1], 1);
+ execve(argv[0], &argv[0], envp);
+ }
+
+ close(pipe_fd[1]);
+
+ count = read(pipe_fd[0], buf, SHIM_UDP_BUF_SIZE);
+ if (count <= 0) {
+ log_err("Failed to communicate with nslookup.");
+ close(pipe_fd[0]);
+ return 0;
+ }
+
+ close(pipe_fd[0]);
+
+ waitpid(pid, &wstatus, 0);
+ if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)
+ log_dbg("Succesfully communicated with nslookup.");
+ else
+ log_err("Failed to resolve DNS address.");
+
+ buf[count] = '\0';
+ substr = strtok(buf, "\n");
+ while (substr != NULL) {
+ substr2 = substr;
+ substr = strtok(NULL, "\n");
+ }
+
+ if (substr2 == NULL || strstr(substr2, addr_str) == NULL) {
+ log_err("Failed to resolve DNS address.");
+ return 0;
+ }
+
+ if (inet_pton(AF_INET, substr2 + strlen(addr_str) + 1, &ip_addr) != 1) {
+ log_err("Failed to resolve DNS address.");
+ return 0;
+ }
+
+ return ip_addr;
+}
+#endif
+
+static int ipcp_udp_reg(const uint8_t * hash)
+{
+#ifdef HAVE_DDNS
+ char ipstr[INET_ADDRSTRLEN];
+ char dnsstr[INET_ADDRSTRLEN];
+ char cmd[1000];
+ uint32_t dns_addr;
+ uint32_t ip_addr;
+#endif
+ char hashstr[ipcp_dir_hash_strlen() + 1];
+
+ assert(hash);
+
+ ipcp_hash_str(hashstr, hash);
+
+ if (shim_data_reg_add_entry(udp_data.shim_data, hash)) {
+ log_err("Failed to add " HASH_FMT " to local registry.",
+ HASH_VAL(hash));
+ return -1;
+ }
+
+#ifdef HAVE_DDNS
+ /* register application with DNS server */
+
+ dns_addr = udp_data.dns_addr;
+
+ if (dns_addr != 0) {
+ ip_addr = udp_data.ip_addr;
+
+ if (inet_ntop(AF_INET, &ip_addr,
+ ipstr, INET_ADDRSTRLEN) == NULL) {
+ return -1;
+ }
+
+ if (inet_ntop(AF_INET, &dns_addr,
+ dnsstr, INET_ADDRSTRLEN) == NULL) {
+ return -1;
+ }
+
+ sprintf(cmd, "server %s\nupdate add %s %d A %s\nsend\nquit\n",
+ dnsstr, hashstr, DNS_TTL, ipstr);
+
+ if (ddns_send(cmd)) {
+ shim_data_reg_del_entry(udp_data.shim_data, hash);
+ return -1;
+ }
+ }
+#endif
+ log_dbg("Registered " HASH_FMT ".", HASH_VAL(hash));
+
+ return 0;
+}
+
+static int ipcp_udp_unreg(const uint8_t * hash)
+{
+#ifdef HAVE_DDNS
+ char dnsstr[INET_ADDRSTRLEN];
+ /* max DNS name length + max IP length + max command length */
+ char cmd[100];
+ uint32_t dns_addr;
+#endif
+ char hashstr[ipcp_dir_hash_strlen() + 1];
+
+ assert(hash);
+
+ ipcp_hash_str(hashstr, hash);
+
+#ifdef HAVE_DDNS
+ /* unregister application with DNS server */
+
+ dns_addr = udp_data.dns_addr;
+
+ if (dns_addr != 0) {
+ if (inet_ntop(AF_INET, &dns_addr, dnsstr, INET_ADDRSTRLEN)
+ == NULL) {
+ return -1;
+ }
+ sprintf(cmd, "server %s\nupdate delete %s A\nsend\nquit\n",
+ dnsstr, hashstr);
+
+ ddns_send(cmd);
+ }
+#endif
+
+ shim_data_reg_del_entry(udp_data.shim_data, hash);
+
+ log_dbg("Unregistered " HASH_FMT ".", HASH_VAL(hash));
+
+ return 0;
+}
+
+static int ipcp_udp_query(const uint8_t * hash)
+{
+ uint32_t ip_addr = 0;
+ struct hostent * h;
+#ifdef HAVE_DDNS
+ uint32_t dns_addr = 0;
+#endif
+ char hashstr[ipcp_dir_hash_strlen() + 1];
+
+ assert(hash);
+
+ ipcp_hash_str(hashstr, hash);
+
+ if (shim_data_dir_has(udp_data.shim_data, hash))
+ return 0;
+
+#ifdef HAVE_DDNS
+ dns_addr = udp_data.dns_addr;
+
+ if (dns_addr != 0) {
+ ip_addr = ddns_resolve(hashstr, dns_addr);
+ if (ip_addr == 0) {
+ log_dbg("Could not resolve %s.", hashstr);
+ return -1;
+ }
+ } else {
+#endif
+ h = gethostbyname(hashstr);
+ if (h == NULL) {
+ log_dbg("Could not resolve %s.", hashstr);
+ return -1;
+ }
+
+ ip_addr = *((uint32_t *) (h->h_addr_list[0]));
+#ifdef HAVE_DDNS
+ }
+#endif
+
+ if (shim_data_dir_add_entry(udp_data.shim_data, hash, ip_addr)) {
+ log_err("Failed to add directory entry.");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ipcp_udp_flow_alloc(int fd,
+ const uint8_t * dst,
+ qoscube_t cube)
+{
+ struct sockaddr_in r_saddr; /* server address */
+ struct sockaddr_in f_saddr; /* flow */
+ socklen_t f_saddr_len = sizeof(f_saddr);
+ int skfd;
+ uint32_t ip_addr = 0;
+
+ log_dbg("Allocating flow to " HASH_FMT ".", HASH_VAL(dst));
+
+ assert(dst);
+
+ if (cube != QOS_CUBE_BE) {
+ log_dbg("Unsupported QoS requested.");
+ return -1;
+ }
+
+ skfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ /* this socket is for the flow */
+ memset((char *) &f_saddr, 0, sizeof(f_saddr));
+ f_saddr.sin_family = AF_INET;
+ f_saddr.sin_addr.s_addr = local_ip;
+ f_saddr.sin_port = 0;
+
+ if (bind(skfd, (struct sockaddr *) &f_saddr, sizeof(f_saddr)) < 0) {
+ close(skfd);
+ return -1;
+ }
+
+ if (getsockname(skfd, (struct sockaddr *) &f_saddr, &f_saddr_len) < 0) {
+ log_err("Could not get address from fd.");
+ close(skfd);
+ return -1;
+ }
+
+ if (!shim_data_dir_has(udp_data.shim_data, dst)) {
+ log_dbg("Could not resolve destination.");
+ close(skfd);
+ return -1;
+ }
+ ip_addr = (uint32_t) shim_data_dir_get_addr(udp_data.shim_data, dst);
+
+ /* connect to server (store the remote IP address in the fd) */
+ memset((char *) &r_saddr, 0, sizeof(r_saddr));
+ r_saddr.sin_family = AF_INET;
+ r_saddr.sin_addr.s_addr = ip_addr;
+ r_saddr.sin_port = LISTEN_PORT;
+
+ if (connect(skfd, (struct sockaddr *) &r_saddr, sizeof(r_saddr)) < 0) {
+ close(skfd);
+ return -1;
+ }
+
+ pthread_rwlock_wrlock(&udp_data.flows_lock);
+
+ udp_data.fd_to_uf[fd].udp = f_saddr.sin_port;
+ udp_data.fd_to_uf[fd].skfd = skfd;
+ udp_data.uf_to_fd[skfd] = fd;
+
+ fset_add(udp_data.np1_flows, fd);
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ if (ipcp_udp_port_alloc(ip_addr, f_saddr.sin_port, dst, cube) < 0) {
+ pthread_rwlock_wrlock(&udp_data.flows_lock);
+
+ udp_data.fd_to_uf[fd].udp = -1;
+ udp_data.fd_to_uf[fd].skfd = -1;
+ udp_data.uf_to_fd[skfd] = -1;
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+ close(skfd);
+ return -1;
+ }
+
+ log_dbg("Flow pending on fd %d, UDP port %d.",
+ fd, ntohs(f_saddr.sin_port));
+
+ return 0;
+}
+
+static int ipcp_udp_flow_alloc_resp(int fd,
+ int response)
+{
+ struct timespec ts = {0, FD_UPDATE_TIMEOUT * 1000};
+ struct timespec abstime;
+ int skfd = -1;
+ struct sockaddr_in f_saddr;
+ struct sockaddr_in r_saddr;
+ socklen_t len = sizeof(r_saddr);
+
+ if (response)
+ return 0;
+
+ clock_gettime(PTHREAD_COND_CLOCK, &abstime);
+
+ pthread_mutex_lock(&ipcpi.alloc_lock);
+
+ while (ipcpi.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) {
+ ts_add(&abstime, &ts, &abstime);
+ pthread_cond_timedwait(&ipcpi.alloc_cond,
+ &ipcpi.alloc_lock,
+ &abstime);
+ }
+
+ if (ipcp_get_state() != IPCP_OPERATIONAL) {
+ pthread_mutex_unlock(&ipcpi.alloc_lock);
+ return -1;
+ }
+
+ ipcpi.alloc_id = -1;
+ pthread_cond_broadcast(&ipcpi.alloc_cond);
+
+ pthread_mutex_unlock(&ipcpi.alloc_lock);
+
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ skfd = udp_data.fd_to_uf[fd].skfd;
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ if (getsockname(skfd, (struct sockaddr *) &f_saddr, &len) < 0) {
+ log_dbg("Socket with fd %d has no address.", skfd);
+ return -1;
+ }
+
+ if (getpeername(skfd, (struct sockaddr *) &r_saddr, &len) < 0) {
+ log_dbg("Socket with fd %d has no peer.", skfd);
+ return -1;
+ }
+
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ set_fd(skfd);
+
+ fset_add(udp_data.np1_flows, fd);
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ if (ipcp_udp_port_alloc_resp(r_saddr.sin_addr.s_addr, f_saddr.sin_port,
+ r_saddr.sin_port, response) < 0) {
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+ clr_fd(skfd);
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+ return -1;
+ }
+
+ log_dbg("Accepted flow, fd %d on UDP port %d.",
+ fd, ntohs(f_saddr.sin_port));
+
+ return 0;
+}
+
+static int ipcp_udp_flow_dealloc(int fd)
+{
+ int skfd = -1;
+
+ ipcp_flow_fini(fd);
+
+ pthread_rwlock_wrlock(&udp_data.flows_lock);
+
+ fset_del(udp_data.np1_flows, fd);
+
+ skfd = udp_data.fd_to_uf[fd].skfd;
+
+ udp_data.uf_to_fd[skfd] = -1;
+ udp_data.fd_to_uf[fd].udp = -1;
+ udp_data.fd_to_uf[fd].skfd = -1;
+
+ close(skfd);
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+ pthread_rwlock_rdlock(&udp_data.flows_lock);
+
+ clr_fd(skfd);
+
+ pthread_rwlock_unlock(&udp_data.flows_lock);
+
+ flow_dealloc(fd);
+
+ log_dbg("Flow with fd %d deallocated.", fd);
+
+ return 0;
+}
+
+static struct ipcp_ops udp_ops = {
+ .ipcp_bootstrap = ipcp_udp_bootstrap,
+ .ipcp_enroll = NULL,
+ .ipcp_connect = NULL,
+ .ipcp_disconnect = NULL,
+ .ipcp_reg = ipcp_udp_reg,
+ .ipcp_unreg = ipcp_udp_unreg,
+ .ipcp_query = ipcp_udp_query,
+ .ipcp_flow_alloc = ipcp_udp_flow_alloc,
+ .ipcp_flow_alloc_resp = ipcp_udp_flow_alloc_resp,
+ .ipcp_flow_dealloc = ipcp_udp_flow_dealloc
+};
+
+int main(int argc,
+ char * argv[])
+{
+ if (ipcp_init(argc, argv, &udp_ops) < 0) {
+ ipcp_create_r(getpid(), -1);
+ exit(EXIT_FAILURE);
+ }
+
+ if (udp_data_init() < 0) {
+ log_err("Failed to init udp data.");
+ ipcp_create_r(getpid(), -1);
+ ipcp_fini();
+ exit(EXIT_FAILURE);
+ }
+
+ if (ipcp_boot() < 0) {
+ log_err("Failed to boot IPCP.");
+ ipcp_create_r(getpid(), -1);
+ udp_data_fini();
+ ipcp_fini();
+ exit(EXIT_FAILURE);
+ }
+
+ if (ipcp_create_r(getpid(), 0)) {
+ log_err("Failed to notify IRMd we are initialized.");
+ ipcp_set_state(IPCP_NULL);
+ ipcp_shutdown();
+ udp_data_fini();
+ ipcp_fini();
+ exit(EXIT_FAILURE);
+ }
+
+ ipcp_shutdown();
+
+ if (ipcp_get_state() == IPCP_SHUTDOWN) {
+ pthread_cancel(udp_data.sduloop);
+ pthread_cancel(udp_data.handler);
+ pthread_cancel(udp_data.sdu_reader);
+
+ pthread_join(udp_data.sduloop, NULL);
+ pthread_join(udp_data.handler, NULL);
+ pthread_join(udp_data.sdu_reader, NULL);
+ }
+
+ udp_data_fini();
+
+ ipcp_fini();
+
+ exit(EXIT_SUCCESS);
+}