diff options
Diffstat (limited to 'src/ipcpd/udp')
| -rw-r--r-- | src/ipcpd/udp/CMakeLists.txt | 60 | ||||
| -rw-r--r-- | src/ipcpd/udp/main.c | 1167 | 
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 <dimitri.staessens@ugent.be> + *    Sander Vrijders   <sander.vrijders@ugent.be> + * + * 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); +}  | 
