/* * Ouroboros - Copyright (C) 2016 - 2021 * * IPC process over UDP * * Dimitri Staessens * Sander Vrijders * * 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 OUROBOROS_PREFIX "ipcpd/udp" #include #include #include #include #include #include #include #include #include #include "ipcp.h" #include "shim-data.h" #include #include #include #include #include #include #include #include #include #include #include #define FLOW_REQ 1 #define FLOW_REPLY 2 #define THIS_TYPE IPCP_UDP #define IPCP_UDP_MAX_PACKET_SIZE 8980 #define OUR_HEADER_LEN sizeof(uint32_t) /* adds eid */ #define IPCP_UDP_BUF_SIZE 8980 #define IPCP_UDP_MSG_SIZE 8980 #define DNS_TTL 86400 #define FD_UPDATE_TIMEOUT 100 /* microseconds */ #define SERV_PORT udp_data.s_saddr.sin_port; #define SERV_SADDR ((struct sockaddr *) &udp_data.s_saddr) #define CLNT_SADDR ((struct sockaddr *) &udp_data.c_saddr) #define SERV_SADDR_SIZE (sizeof(udp_data.s_saddr)) #define LOCAL_IP (udp_data.s_saddr.sin_addr.s_addr) #define MGMT_EID 0 #define MGMT_FRAME_SIZE (sizeof(struct mgmt_msg)) #define MGMT_FRAME_BUF_SIZE 2048 struct ipcp ipcpi; /* Keep order for alignment. */ struct mgmt_msg { uint32_t eid; uint32_t s_eid; uint32_t d_eid; uint8_t code; int8_t response; /* QoS parameters from spec */ uint8_t availability; uint8_t in_order; uint64_t bandwidth; uint32_t delay; uint32_t loss; uint32_t ber; uint32_t max_gap; uint16_t cypher_s; } __attribute__((packed)); struct mgmt_frame { struct list_head next; struct sockaddr_in r_saddr; uint8_t buf[MGMT_FRAME_BUF_SIZE]; size_t len; }; /* UDP flow */ struct uf { int d_eid; /* IP details are stored through connect(). */ int skfd; }; struct { struct shim_data * shim_data; uint32_t dns_addr; /* server socket */ struct sockaddr_in s_saddr; int s_fd; /* client port */ int clt_port; fset_t * np1_flows; struct uf fd_to_uf[SYS_MAX_FLOWS]; pthread_rwlock_t flows_lock; pthread_t packet_writer[IPCP_UDP_WR_THR]; pthread_t packet_reader[IPCP_UDP_RD_THR]; /* Handle mgmt frames in a different thread */ pthread_t mgmt_handler; pthread_mutex_t mgmt_lock; pthread_cond_t mgmt_cond; struct list_head mgmt_frames; } udp_data; static int udp_data_init(void) { int i; if (pthread_rwlock_init(&udp_data.flows_lock, NULL)) goto fail_rwlock_init; if (pthread_cond_init(&udp_data.mgmt_cond, NULL)) goto fail_mgmt_cond; if (pthread_mutex_init(&udp_data.mgmt_lock, NULL)) goto fail_mgmt_lock; for (i = 0; i < SYS_MAX_FLOWS; ++i) { udp_data.fd_to_uf[i].skfd = -1; udp_data.fd_to_uf[i].d_eid = -1; } udp_data.np1_flows = fset_create(); if (udp_data.np1_flows == NULL) goto fail_fset; udp_data.shim_data = shim_data_create(); if (udp_data.shim_data == NULL) goto fail_data; list_head_init(&udp_data.mgmt_frames); return 0; fail_data: fset_destroy(udp_data.np1_flows); fail_fset: pthread_mutex_destroy(&udp_data.mgmt_lock); fail_mgmt_lock: pthread_cond_destroy(&udp_data.mgmt_cond); fail_mgmt_cond: pthread_rwlock_destroy(&udp_data.flows_lock); fail_rwlock_init: return -1; } static void udp_data_fini(void) { shim_data_destroy(udp_data.shim_data); fset_destroy(udp_data.np1_flows); pthread_rwlock_destroy(&udp_data.flows_lock); pthread_cond_destroy(&udp_data.mgmt_cond); pthread_mutex_destroy(&udp_data.mgmt_lock); } static int ipcp_udp_port_alloc(int skfd, uint32_t s_eid, const uint8_t * dst, qosspec_t qs, const void * data, size_t dlen) { uint8_t * buf; struct mgmt_msg * msg; size_t len; assert(dlen > 0 ? data != NULL : data == NULL); len = sizeof(*msg) + ipcp_dir_hash_len(); buf = malloc(len + dlen); if (buf == NULL) return -1; msg = (struct mgmt_msg *) buf; msg->eid = hton32(MGMT_EID); msg->code = FLOW_REQ; msg->s_eid = hton32(s_eid); msg->delay = hton32(qs.delay); msg->bandwidth = hton64(qs.bandwidth); msg->availability = qs.availability; msg->loss = hton32(qs.loss); msg->ber = hton32(qs.ber); msg->in_order = qs.in_order; msg->max_gap = hton32(qs.max_gap); msg->cypher_s = hton16(qs.cypher_s); memcpy(msg + 1, dst, ipcp_dir_hash_len()); memcpy(buf + len, data, dlen); if (write(skfd, msg, len + dlen) < 0) { free(buf); return -1; } free(buf); return 0; } static int ipcp_udp_port_alloc_resp(int skfd, uint32_t s_eid, uint32_t d_eid, int8_t response, const void * data, size_t len) { struct mgmt_msg * msg; msg = malloc(sizeof(*msg) + len); if (msg == NULL) return -1; msg->eid = hton32(MGMT_EID); msg->code = FLOW_REPLY; msg->s_eid = hton32(s_eid); msg->d_eid = hton32(d_eid); msg->response = response; memcpy(msg + 1, data, len); if (write(skfd, msg, sizeof(*msg) + len) < 0) { free(msg); return -1; } free(msg); return 0; } static int ipcp_udp_port_req(struct sockaddr_in * c_saddr, int d_eid, const uint8_t * dst, qosspec_t qs, const void * data, size_t len) { struct timespec ts = {0, FD_UPDATE_TIMEOUT * 1000}; struct timespec abstime; int skfd; int fd; skfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (skfd < 0) { log_err("Could not create UDP socket."); return -1; } /* Remote listens on server port. Mod of c_saddr allowed. */ c_saddr->sin_port = udp_data.s_saddr.sin_port; /* 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); close(skfd); return -1; } /* reply to IRM */ fd = ipcp_flow_req_arr(dst, ipcp_dir_hash_len(), qs, data, len); 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.fd_to_uf[fd].skfd = skfd; udp_data.fd_to_uf[fd].d_eid = d_eid; 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, remote eid %d.", fd, d_eid); return 0; } static int ipcp_udp_port_alloc_reply(uint32_t s_eid, uint32_t d_eid, int8_t response, const void * data, size_t len) { struct sockaddr_in t_saddr; socklen_t t_saddr_len; int ret = 0; int skfd = -1; t_saddr_len = sizeof(t_saddr); pthread_rwlock_wrlock(&udp_data.flows_lock); skfd = udp_data.fd_to_uf[s_eid].skfd; if (skfd < 0) { pthread_rwlock_unlock(&udp_data.flows_lock); log_err("Got reply for unknown UDP eid: %u.", s_eid); return -1; } udp_data.fd_to_uf[s_eid].d_eid = d_eid; pthread_rwlock_unlock(&udp_data.flows_lock); if (getpeername(skfd, (struct sockaddr *) &t_saddr, &t_saddr_len) < 0) { log_dbg("Flow with fd %d has no peer.", s_eid); close(skfd); return -1; } if (connect(skfd, (struct sockaddr *) &t_saddr, sizeof(t_saddr)) < 0) { log_dbg("Could not connect flow to remote."); close(skfd); return -1; } if (ipcp_flow_alloc_reply(s_eid, response, data, len) < 0) { log_dbg("Failed to reply to flow allocation."); return -1; } log_dbg("Flow allocation completed on eids (%d, %d).", s_eid, d_eid); return ret; } static int ipcp_udp_mgmt_frame(const uint8_t * buf, size_t len, struct sockaddr_in c_saddr) { struct mgmt_msg * msg; size_t msg_len; qosspec_t qs; msg = (struct mgmt_msg *) buf; switch (msg->code) { case FLOW_REQ: msg_len = sizeof(*msg) + ipcp_dir_hash_len(); assert(len >= msg_len); qs.delay = ntoh32(msg->delay); qs.bandwidth = ntoh64(msg->bandwidth); qs.availability = msg->availability; qs.loss = ntoh32(msg->loss); qs.ber = ntoh32(msg->ber); qs.in_order = msg->in_order; qs.max_gap = ntoh32(msg->max_gap); qs.cypher_s = ntoh16(msg->cypher_s); return ipcp_udp_port_req(&c_saddr, ntoh32(msg->s_eid), (uint8_t *) (msg + 1), qs, buf + msg_len, len - msg_len); case FLOW_REPLY: assert(len >= sizeof(*msg)); return ipcp_udp_port_alloc_reply(ntoh32(msg->s_eid), ntoh32(msg->d_eid), msg->response, buf + sizeof(*msg), len - sizeof(*msg)); default: log_err("Unknown message received %d.", msg->code); return -1; } } static void * ipcp_udp_mgmt_handler(void * o) { (void) o; pthread_cleanup_push((void (*)(void *)) pthread_mutex_unlock, (void *) &udp_data.mgmt_lock); while (true) { struct mgmt_frame * frame; pthread_mutex_lock(&udp_data.mgmt_lock); while (list_is_empty(&udp_data.mgmt_frames)) pthread_cond_wait(&udp_data.mgmt_cond, &udp_data.mgmt_lock); frame = list_first_entry((&udp_data.mgmt_frames), struct mgmt_frame, next); assert(frame != NULL); list_del(&frame->next); pthread_mutex_unlock(&udp_data.mgmt_lock); ipcp_udp_mgmt_frame(frame->buf, frame->len, frame->r_saddr); free(frame); } pthread_cleanup_pop(false); return (void *) 0; } static void * ipcp_udp_packet_reader(void * o) { uint8_t buf[IPCP_UDP_MAX_PACKET_SIZE]; uint8_t * data; ssize_t n; uint32_t eid; uint32_t * eid_p; (void) o; data = buf + sizeof(uint32_t); eid_p = (uint32_t *) buf; while (true) { struct mgmt_frame * frame; struct sockaddr_in r_saddr; socklen_t len; len = sizeof(r_saddr); n = recvfrom(udp_data.s_fd, buf, IPCP_UDP_MAX_PACKET_SIZE, 0, (struct sockaddr *) &r_saddr, &len); if (n < 0) continue; if (n == 0) log_dbg("Got a 0 frame."); if ((size_t) n < sizeof(eid)) { log_dbg("Dropped bad frame."); continue; } eid = ntoh32(*eid_p); /* pass onto mgmt queue */ if (eid == MGMT_EID) { if ((size_t) n < MGMT_FRAME_SIZE) { log_warn("Dropped runt mgmt frame."); continue; } frame = malloc(sizeof(*frame)); if (frame == NULL) continue; memcpy(frame->buf, buf, n); memcpy(&frame->r_saddr, &r_saddr, sizeof(r_saddr)); frame->len = n; pthread_mutex_lock(&udp_data.mgmt_lock); list_add(&frame->next, &udp_data.mgmt_frames); pthread_cond_signal(&udp_data.mgmt_cond); pthread_mutex_unlock(&udp_data.mgmt_lock); continue; } flow_write(eid, data, n - sizeof(eid)); } return 0; } static void cleanup_writer(void * o) { fqueue_destroy((fqueue_t *) o); } static void * ipcp_udp_packet_writer(void * o) { fqueue_t * fq; fq = fqueue_create(); if (fq == NULL) return (void *) -1; (void) o; ipcp_lock_to_core(); pthread_cleanup_push(cleanup_writer, fq); while (true) { int fd; int eid; fevent(udp_data.np1_flows, fq, NULL); while ((fd = fqueue_next(fq)) >= 0) { struct shm_du_buff * sdb; uint8_t * buf; uint16_t len; if (fqueue_type(fq) != FLOW_PKT) continue; if (ipcp_flow_read(fd, &sdb)) { log_dbg("Bad read from fd %d.", fd); continue; } len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); if (len > IPCP_UDP_MAX_PACKET_SIZE) { log_dbg("Packet length exceeds MTU."); ipcp_sdb_release(sdb); continue; } buf = shm_du_buff_head_alloc(sdb, OUR_HEADER_LEN); if (buf == NULL) { log_dbg("Failed to allocate header."); ipcp_sdb_release(sdb); continue; } pthread_rwlock_rdlock(&udp_data.flows_lock); eid = hton32(udp_data.fd_to_uf[fd].d_eid); fd = udp_data.fd_to_uf[fd].skfd; pthread_rwlock_unlock(&udp_data.flows_lock); memcpy(buf, &eid, sizeof(eid)); pthread_cleanup_push((void (*)(void *)) ipcp_sdb_release, (void *) sdb); if (write(fd, buf, len + OUR_HEADER_LEN) < 0) log_err("Failed to send packet."); pthread_cleanup_pop(true); } } pthread_cleanup_pop(true); return (void *) 1; } static int ipcp_udp_bootstrap(const struct ipcp_config * conf) { char ipstr[INET_ADDRSTRLEN]; char dnsstr[INET_ADDRSTRLEN]; char portstr[128]; /* port is max 64535 = 5 chars */ int i = 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 */ udp_data.s_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (udp_data.s_fd < 0) { log_err("Can't create socket: %s", strerror(errno)); goto fail_socket; } if (setsockopt(udp_data.s_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) log_warn("Failed to set SO_REUSEADDR."); memset((char *) &udp_data.s_saddr, 0, sizeof(udp_data.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 = htons(conf->srv_port); if (bind(udp_data.s_fd, SERV_SADDR, SERV_SADDR_SIZE) < 0) { log_err("Couldn't bind to %s.", ipstr); goto fail_bind; } udp_data.dns_addr = conf->dns_addr; udp_data.clt_port = htons(conf->clt_port); ipcp_set_state(IPCP_OPERATIONAL); if (pthread_create(&udp_data.mgmt_handler, NULL, ipcp_udp_mgmt_handler, NULL)) { ipcp_set_state(IPCP_INIT); goto fail_bind; } for (i = 0; i < IPCP_UDP_RD_THR; ++i) { if (pthread_create(&udp_data.packet_reader[i], NULL, ipcp_udp_packet_reader, NULL)) { ipcp_set_state(IPCP_INIT); goto fail_packet_reader; } } for (i = 0; i < IPCP_UDP_WR_THR; ++i) { if (pthread_create(&udp_data.packet_writer[i], NULL, ipcp_udp_packet_writer, NULL)) { ipcp_set_state(IPCP_INIT); goto fail_packet_writer; } } sprintf(portstr, "%d", conf->clt_port); log_dbg("Bootstrapped IPCP over UDP with pid %d.", getpid()); log_dbg("Bound to IP address %s.", ipstr); log_dbg("Client port is %s.", conf->clt_port == 0 ? "random" : portstr); log_dbg("Server port is %u.", conf->srv_port); log_dbg("DNS server address is %s.", dnsstr); return 0; fail_packet_writer: while (i > 0) { pthread_cancel(udp_data.packet_writer[--i]); pthread_join(udp_data.packet_writer[i], NULL); } i = IPCP_UDP_RD_THR; fail_packet_reader: while (i > 0) { pthread_cancel(udp_data.packet_reader[--i]); pthread_join(udp_data.packet_reader[i], NULL); } pthread_cancel(udp_data.mgmt_handler); pthread_join(udp_data.mgmt_handler, NULL); fail_bind: close(udp_data.s_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[IPCP_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, IPCP_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 && count != IPCP_UDP_BUF_SIZE) 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; hashstr = malloc(ipcp_dir_hash_strlen() + 1); if (hashstr == NULL) return -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)); free(hashstr); return -1; } #ifdef HAVE_DDNS /* register application with DNS server */ dns_addr = udp_data.dns_addr; if (dns_addr != 0) { ip_addr = udp_data.s_saddr.sin_addr.s_addr; if (inet_ntop(AF_INET, &ip_addr, ipstr, INET_ADDRSTRLEN) == NULL) { free(hashstr); return -1; } if (inet_ntop(AF_INET, &dns_addr, dnsstr, INET_ADDRSTRLEN) == NULL) { free(hashstr); 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); free(hashstr); return -1; } } #endif log_dbg("Registered " HASH_FMT ".", HASH_VAL(hash)); free(hashstr); 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; assert(hash); hashstr = malloc(ipcp_dir_hash_strlen() + 1); if (hashstr == NULL) return -1; 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) { free(hashstr); 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)); free(hashstr); return 0; } static int ipcp_udp_query(const uint8_t * hash) { uint32_t ip_addr = 0; char * hashstr; struct hostent * h; #ifdef HAVE_DDNS uint32_t dns_addr = 0; #endif assert(hash); hashstr = malloc(ipcp_dir_hash_strlen() + 1); if (hashstr == NULL) return -ENOMEM; ipcp_hash_str(hashstr, hash); if (shim_data_dir_has(udp_data.shim_data, hash)) { free(hashstr); 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); free(hashstr); return -1; } } else { #endif h = gethostbyname(hashstr); if (h == NULL) { log_dbg("Could not resolve %s.", hashstr); free(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."); free(hashstr); return -1; } free(hashstr); return 0; } static int ipcp_udp_flow_alloc(int fd, const uint8_t * dst, qosspec_t qs, const void * data, size_t len) { struct sockaddr_in r_saddr; /* Server address */ struct sockaddr_in c_saddr; /* Client address */ socklen_t c_saddr_len; int skfd; uint32_t ip_addr = 0; char ip_str[INET_ADDRSTRLEN]; c_saddr_len = sizeof(c_saddr); log_dbg("Allocating flow to " HASH_FMT ".", HASH_VAL(dst)); (void) qs; assert(dst); skfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (skfd < 0) { log_err("Could not create socket."); return -1; } /* This socket is for the flow. */ memset((char *) &c_saddr, 0, sizeof(c_saddr)); c_saddr.sin_family = AF_INET; c_saddr.sin_addr.s_addr = LOCAL_IP; c_saddr.sin_port = udp_data.clt_port; if (bind(skfd, (struct sockaddr *) &c_saddr, sizeof(c_saddr)) < 0) { log_dbg("Could not bind socket to client address."); close(skfd); return -1; } if (getsockname(skfd, (struct sockaddr *) &c_saddr, &c_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); inet_ntop(AF_INET, &ip_addr, ip_str, INET_ADDRSTRLEN); log_dbg("Destination UDP ipcp resolved at %s.", ip_str); /* Connect to server and store the remote IP address in the skfd. */ 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 = udp_data.s_saddr.sin_port; if (connect(skfd, (struct sockaddr *) &r_saddr, sizeof(r_saddr)) < 0) { log_dbg("Could not connect socket to remote."); close(skfd); return -1; } if (ipcp_udp_port_alloc(skfd, fd, dst, qs, data, len) < 0) { log_err("Could not allocate port."); close(skfd); return -1; } pthread_rwlock_wrlock(&udp_data.flows_lock); udp_data.fd_to_uf[fd].d_eid = -1; udp_data.fd_to_uf[fd].skfd = skfd; pthread_rwlock_unlock(&udp_data.flows_lock); fset_add(udp_data.np1_flows, fd); log_dbg("Flow pending on fd %d, UDP src port %d, dst port %d.", fd, ntohs(c_saddr.sin_port), ntohs(r_saddr.sin_port)); return 0; } static int ipcp_udp_flow_alloc_resp(int fd, int resp, const void * data, size_t len) { struct timespec ts = {0, FD_UPDATE_TIMEOUT * 1000}; struct timespec abstime; int skfd; int d_eid; if (resp) 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; d_eid = udp_data.fd_to_uf[fd].d_eid; pthread_rwlock_unlock(&udp_data.flows_lock); if (ipcp_udp_port_alloc_resp(skfd, d_eid, fd, resp, data, len) < 0) { log_err("Failed to respond to flow request."); return -1; } fset_add(udp_data.np1_flows, fd); log_dbg("Accepted flow, fd %d on eid %d.", fd, d_eid); return 0; } static int ipcp_udp_flow_dealloc(int fd) { int skfd = -1; ipcp_flow_fini(fd); fset_del(udp_data.np1_flows, fd); pthread_rwlock_wrlock(&udp_data.flows_lock); skfd = udp_data.fd_to_uf[fd].skfd; udp_data.fd_to_uf[fd].d_eid = -1; udp_data.fd_to_uf[fd].skfd = -1; pthread_rwlock_unlock(&udp_data.flows_lock); close(skfd); 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_join = NULL, .ipcp_flow_alloc_resp = ipcp_udp_flow_alloc_resp, .ipcp_flow_dealloc = ipcp_udp_flow_dealloc }; int main(int argc, char * argv[]) { int i; if (ipcp_init(argc, argv, &udp_ops) < 0) goto fail_init; if (udp_data_init() < 0) { log_err("Failed to init udp data."); goto fail_data_init; } if (ipcp_boot() < 0) { log_err("Failed to boot IPCP."); goto fail_boot; } if (ipcp_create_r(0)) { log_err("Failed to notify IRMd we are initialized."); goto fail_create_r; } ipcp_shutdown(); if (ipcp_get_state() == IPCP_SHUTDOWN) { for (i = 0; i < IPCP_UDP_RD_THR; ++i) pthread_cancel(udp_data.packet_reader[i]); for (i = 0; i < IPCP_UDP_WR_THR; ++i) pthread_cancel(udp_data.packet_writer[i]); pthread_cancel(udp_data.mgmt_handler); for (i = 0; i < IPCP_UDP_RD_THR; ++i) pthread_join(udp_data.packet_reader[i], NULL); for (i = 0; i < IPCP_UDP_WR_THR; ++i) pthread_join(udp_data.packet_writer[i], NULL); pthread_join(udp_data.mgmt_handler, NULL); } udp_data_fini(); ipcp_fini(); exit(EXIT_SUCCESS); fail_create_r: ipcp_set_state(IPCP_NULL); ipcp_shutdown(); fail_boot: udp_data_fini(); fail_data_init: ipcp_fini(); fail_init: ipcp_create_r(-1); exit(EXIT_FAILURE); }