/* * Ouroboros - Copyright (C) 2016 * * Shim IPC process over UDP * * Dimitri Staessens <dimitri.staessens@intec.ugent.be> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <ouroboros/config.h> #include "ipcp.h" #include "flow.h" #include <ouroboros/shm_du_map.h> #include <ouroboros/list.h> #include <ouroboros/utils.h> #include <ouroboros/ipcp.h> #include <ouroboros/dif_config.h> #include <ouroboros/sockets.h> #define OUROBOROS_PREFIX "ipcpd/shim-udp" #include <ouroboros/logs.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> #define THIS_TYPE IPCP_SHIM_UDP #define LISTEN_PORT htons(0x0D1F) #define SHIM_UDP_BUF_SIZE 256 #define SHIM_UDP_MAX_SDU_SIZE 8980 #define shim_data(type) ((struct ipcp_udp_data *) type->data) #define local_ip (((struct ipcp_udp_data *) \ _ipcp->data)->s_saddr.sin_addr.s_addr) /* global for trapping signal */ int irmd_pid; /* this IPCP's data */ #ifdef MAKE_CHECK extern struct ipcp * _ipcp; /* defined in test */ #else struct ipcp * _ipcp; #endif struct ipcp_udp_data { /* keep ipcp_data first for polymorphism */ struct ipcp_data ipcp_data; uint32_t ip_addr; uint32_t dns_addr; /* listen server */ struct sockaddr_in s_saddr; int s_fd; fd_set flow_fd_s; flow_t * fd_to_flow_ptr[FD_SETSIZE]; pthread_mutex_t lock; }; struct udp_flow { /* keep flow first for polymorphism */ flow_t flow; int fd; }; void ipcp_sig_handler(int sig, siginfo_t * info, void * c) { switch(sig) { case SIGINT: case SIGTERM: case SIGHUP: LOG_DBG("Terminating by order of %d. Bye.", info->si_pid); if (info->si_pid == irmd_pid) { /* shm_du_map_close(_ipcp->data->dum); */ exit(0); } default: return; } } struct ipcp_udp_data * ipcp_udp_data_create(char * ap_name) { struct ipcp_udp_data * udp_data; struct ipcp_data * data; enum ipcp_type ipcp_type; int n; udp_data = malloc(sizeof *udp_data); if (udp_data == NULL) { LOG_DBGF("Failed to allocate."); return NULL; } ipcp_type = THIS_TYPE; data = (struct ipcp_data *) udp_data; if (ipcp_data_init(data, ap_name, ipcp_type) == NULL) { free(udp_data); return NULL; } FD_ZERO(&udp_data->flow_fd_s); for (n = 0; n < FD_SETSIZE; ++n) udp_data->fd_to_flow_ptr[n] = NULL; return udp_data; } static void * ipcp_udp_listener() { char buf[SHIM_UDP_BUF_SIZE]; int n = 0; struct sockaddr_in f_saddr; struct sockaddr_in c_saddr; struct hostent * hostp; struct udp_flow * flow; int sfd = shim_data(_ipcp)->s_fd; irm_msg_t msg = IRM_MSG__INIT; irm_msg_t * ret_msg ; while (true) { n = sizeof c_saddr; n = recvfrom(sfd, buf, SHIM_UDP_BUF_SIZE, 0, (struct sockaddr *) &c_saddr, (unsigned *) &n); if (n < 0) continue; /* flow alloc request from other host */ hostp = gethostbyaddr((const char *) &c_saddr.sin_addr.s_addr, sizeof(c_saddr.sin_addr.s_addr), AF_INET); if (hostp == NULL) continue; /* create a new socket for the server */ flow = malloc(sizeof *flow); if (flow == NULL) continue; flow->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (flow->fd == -1) { free(flow); continue; } memset((char *) &f_saddr, 0, sizeof f_saddr); f_saddr.sin_family = AF_INET; f_saddr.sin_addr.s_addr = local_ip; /* * FIXME: we could have a port dedicated per registered AP * Not that critical for UDP, but will be for LLC */ f_saddr.sin_port = 0; /* * store the remote address in the file descriptor * this avoids having to store the sockaddr_in in * the flow structure */ if (connect(flow->fd, (struct sockaddr *) &c_saddr, sizeof c_saddr) < 0) { close(flow->fd); free(flow); continue; } /* reply to IRM */ msg.code = IRM_MSG_CODE__IPCP_FLOW_REQ_ARR; msg.ap_name = ANONYMOUS_AP; msg.ae_name = ""; /* no AE */ msg.dst_name = buf; ret_msg = send_recv_irm_msg(&msg); if (ret_msg == NULL) { LOG_ERR("Could not send message to IRM."); close(flow->fd); free(flow); continue; } if (!ret_msg->has_port_id) { LOG_ERR("Didn't get port_id."); free(ret_msg); close(flow->fd); free(flow); continue; } flow->flow.port_id = ret_msg->port_id; flow->flow.oflags = FLOW_O_DEFAULT; flow->flow.state = FLOW_PENDING; if(ipcp_data_add_flow(_ipcp->data, (flow_t *) flow)) { LOG_DBGF("Could not add flow."); free(ret_msg); close(flow->fd); free(flow); continue; } FD_SET(flow->fd, &shim_data(_ipcp)->flow_fd_s); shim_data(_ipcp)->fd_to_flow_ptr[flow->fd] = &flow->flow; } return 0; } static void * ipcp_udp_sdu_reader() { int n; int fd; char buf[SHIM_UDP_MAX_SDU_SIZE]; struct sockaddr_in r_saddr; while (true) { flow_t * flow; if (select(FD_SETSIZE, &shim_data(_ipcp)->flow_fd_s, NULL, NULL, NULL) < 0) continue; for (fd = 0; fd < FD_SETSIZE; ++fd) { if (!FD_ISSET(fd, &shim_data(_ipcp)->flow_fd_s)) continue; n = sizeof r_saddr; n = recvfrom(fd, buf, SHIM_UDP_MAX_SDU_SIZE, 0, (struct sockaddr *) &r_saddr, (unsigned *) &n); flow = shim_data(_ipcp)->fd_to_flow_ptr[fd]; if (flow->state == FLOW_PENDING) { if (connect(fd, (struct sockaddr *) &r_saddr, sizeof r_saddr) < 0) continue; flow->state = FLOW_ALLOCATED; } /* send the sdu to the correct port_id */ LOG_MISSING; } } return (void *) 0; } int ipcp_udp_bootstrap(struct dif_config * conf) { char ipstr[INET_ADDRSTRLEN]; char dnsstr[INET_ADDRSTRLEN]; pthread_t handler; pthread_t sdu_reader; if (conf->type != THIS_TYPE) { LOG_ERR("Config doesn't match IPCP type."); return -1; } if (_ipcp->state != IPCP_INIT) { LOG_ERR("IPCP in wrong state."); return -1; } inet_ntop(AF_INET, &conf->ip_addr, ipstr, INET_ADDRSTRLEN); if (conf->dns_addr != 0) inet_ntop(AF_INET, &conf->dns_addr, dnsstr, INET_ADDRSTRLEN); else strcpy(dnsstr, "not set.\n"); shim_data(_ipcp)->ip_addr = conf->ip_addr; shim_data(_ipcp)->dns_addr = conf->dns_addr; /* UDP listen server */ if ((shim_data(_ipcp)->s_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { LOG_DBGF("Can't create socket."); return -1; } shim_data(_ipcp)->s_saddr.sin_family = AF_INET; shim_data(_ipcp)->s_saddr.sin_addr.s_addr = conf->ip_addr; shim_data(_ipcp)->s_saddr.sin_port = LISTEN_PORT; if (bind(shim_data(_ipcp)->s_fd, (struct sockaddr *) &shim_data(_ipcp)->s_saddr, sizeof shim_data(_ipcp)->s_saddr ) < 0) { LOG_ERR("Couldn't bind to %s.", ipstr); return -1; } pthread_create(&handler, NULL, ipcp_udp_listener, NULL); pthread_create(&sdu_reader, NULL, ipcp_udp_sdu_reader, NULL); _ipcp->state = IPCP_ENROLLED; LOG_DBG("Bootstrapped shim IPCP over UDP %s-%d.", _ipcp->data->iname->name, _ipcp->data->iname->id); LOG_DBG("Bound to IP address %s.", ipstr); LOG_DBG("DNS server address is %s.", dnsstr); return 0; } int ipcp_udp_name_reg(char * name) { if (_ipcp->state != IPCP_ENROLLED) { LOG_DBGF("Won't register with non-enrolled IPCP."); return -1; } if (ipcp_data_add_reg_entry(_ipcp->data, name)) { LOG_ERR("Failed to add %s to local registry.", name); return -1; } LOG_DBG("Registered %s", name); /* FIXME: register application with DNS server */ LOG_MISSING; return 0; } int ipcp_udp_name_unreg(char * name) { ipcp_data_del_reg_entry(_ipcp->data, name); LOG_DBG("Unregistered %s.", name); /* FIXME: unregister application from DNS server */ LOG_MISSING; return 0; } int ipcp_udp_flow_alloc(uint32_t port_id, char * dst_name, char * src_ap_name, char * src_ae_name, struct qos_spec * qos) { struct udp_flow * flow = NULL; struct sockaddr_in l_saddr; struct sockaddr_in r_saddr; struct hostent * h; irm_msg_t msg = IRM_MSG__INIT; irm_msg_t * ret_msg = NULL; if (dst_name == NULL || src_ap_name == NULL || src_ae_name == NULL) return -1; LOG_DBG("Received flow allocation request from %s to %s.", src_ap_name, dst_name); if (strlen(dst_name) > 255 || strlen(src_ap_name) > 255 || strlen(src_ae_name) > 255) { LOG_ERR("Name too long for this shim."); return -1; } if (qos != NULL) LOG_DBGF("QoS requested. UDP/IP can't do that."); flow = malloc(sizeof *flow); if (flow == NULL) return -1; flow->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (flow->fd == -1) { free(flow); return -1; } /* this socket is for the flow */ memset((char *) &l_saddr, 0, sizeof l_saddr); l_saddr.sin_family = AF_INET; l_saddr.sin_addr.s_addr = local_ip; l_saddr.sin_port = 0; if (bind(flow->fd, (struct sockaddr *) &l_saddr, sizeof l_saddr) < 0) { char ipstr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &l_saddr.sin_addr.s_addr, ipstr, INET_ADDRSTRLEN); close(flow->fd); free(flow); return -1; } h = gethostbyname(dst_name); if (h == NULL) { close(flow->fd); free(flow); return -1; } memset((char *) &r_saddr, 0, sizeof r_saddr); r_saddr.sin_family = AF_INET; r_saddr.sin_addr.s_addr = (uint32_t) *(h->h_addr_list[0]); r_saddr.sin_port = LISTEN_PORT; /* at least try to get the packet on the wire */ while (sendto(flow->fd, dst_name, strlen(dst_name), 0, (struct sockaddr *) &r_saddr, sizeof r_saddr) < 0) flow->flow.port_id = port_id; flow->flow.oflags = FLOW_O_DEFAULT; flow->flow.state = FLOW_PENDING; /* add flow to the list */ pthread_mutex_lock(&_ipcp->data->flow_lock); if(ipcp_data_add_flow(_ipcp->data, (flow_t *) flow)) { LOG_DBGF("Could not add flow."); pthread_mutex_unlock(&_ipcp->data->flow_lock); close(flow->fd); free(flow); return -1; } pthread_mutex_unlock(&_ipcp->data->flow_lock); /* tell IRMd that flow allocation "worked" */ msg.code = IRM_MSG_CODE__IPCP_FLOW_ALLOC_REPLY; msg.has_port_id = true; msg.port_id = flow->flow.port_id; msg.has_response = true; msg.response = 0; ret_msg = send_recv_irm_msg(&msg); if (ret_msg == NULL) { close(flow->fd); ipcp_data_del_flow(_ipcp->data, flow->flow.port_id); return -1; } FD_SET(flow->fd, &shim_data(_ipcp)->flow_fd_s); shim_data(_ipcp)->fd_to_flow_ptr[flow->fd] = &flow->flow; return 0; } int ipcp_udp_flow_alloc_resp(uint32_t port_id, int response) { struct udp_flow * flow = (struct udp_flow *) ipcp_data_find_flow(_ipcp->data, port_id); if (flow == NULL) { return -1; } if (response) { ipcp_data_del_flow(_ipcp->data, port_id); return 0; } /* awaken pending flow */ if (flow->flow.state != FLOW_PENDING) return -1; flow->flow.state = FLOW_ALLOCATED; return 0; } int ipcp_udp_flow_dealloc(uint32_t port_id) { return 0; } int ipcp_udp_du_write(uint32_t port_id, size_t map_index) { return 0; } int ipcp_udp_du_read(uint32_t port_id, size_t map_index) { return 0; } struct ipcp * ipcp_udp_create(char * ap_name) { struct ipcp * i; struct ipcp_udp_data * data; struct ipcp_ops * ops; i = malloc(sizeof *i); if (i == NULL) return NULL; data = ipcp_udp_data_create(ap_name); if (data == NULL) { free(i); return NULL; } ops = malloc(sizeof *ops); if (ops == NULL) { free(data); free(i); return NULL; } ops->ipcp_bootstrap = ipcp_udp_bootstrap; ops->ipcp_enroll = NULL; /* shim */ ops->ipcp_reg = NULL; /* shim */ ops->ipcp_unreg = NULL; /* shim */ ops->ipcp_name_reg = ipcp_udp_name_reg; ops->ipcp_name_unreg = ipcp_udp_name_unreg; ops->ipcp_flow_alloc = ipcp_udp_flow_alloc; ops->ipcp_flow_alloc_resp = ipcp_udp_flow_alloc_resp; ops->ipcp_flow_dealloc = ipcp_udp_flow_dealloc; ops->ipcp_du_read = ipcp_udp_du_read; ops->ipcp_du_write = ipcp_udp_du_write; i->data = (struct ipcp_data *) data; i->ops = ops; i->state = IPCP_INIT; return i; } #ifndef MAKE_CHECK int main (int argc, char * argv[]) { /* argument 1: pid of irmd ? */ /* argument 2: ap name */ struct sigaction sig_act; if (ipcp_arg_check(argc, argv)) { LOG_ERR("Wrong arguments."); exit(1); } /* store the process id of the irmd */ irmd_pid = atoi(argv[1]); /* init sig_act */ memset(&sig_act, 0, sizeof sig_act); /* install signal traps */ sig_act.sa_sigaction = &ipcp_sig_handler; sig_act.sa_flags = SA_SIGINFO; sigaction(SIGINT, &sig_act, NULL); sigaction(SIGTERM, &sig_act, NULL); sigaction(SIGHUP, &sig_act, NULL); _ipcp = ipcp_udp_create(argv[2]); if (_ipcp == NULL) { LOG_ERR("Won't."); exit(1); } ipcp_main_loop(_ipcp); exit(0); } #endif /* MAKE_CHECK */