#undef _GNU_SOURCE #define _GNU_SOURCE #undef _POSIX_C_SOURCE #define _DARWIN_C_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "allocator_thread.h" #include "debug.h" #include "ip_type.h" #include "mutex.h" #include "hash.h" #include "remotedns.h" /* stuff for our internal translation table */ typedef struct { uint32_t hash; char* string; } string_hash_tuple; typedef struct { uint32_t counter; uint32_t capa; string_hash_tuple** list; } internal_ip_lookup_table; static void *dumpstring(char* s, size_t len) { char* p = malloc(len); if(p) memcpy(p, s, len); return p; } static pthread_mutex_t *internal_ips_lock; static internal_ip_lookup_table *internal_ips; uint32_t index_from_internal_ip(ip_type4 internalip) { PFUNC(); ip_type4 tmp = internalip; uint32_t ret; ret = tmp.octet[3] + (tmp.octet[2] << 8) + (tmp.octet[1] << 16); ret -= 1; return ret; } char *string_from_internal_ip(ip_type4 internalip) { PFUNC(); char *res = NULL; uint32_t index = index_from_internal_ip(internalip); if(index < internal_ips->counter) res = internal_ips->list[index]->string; return res; } extern unsigned int remote_dns_subnet; ip_type4 make_internal_ip(uint32_t index) { ip_type4 ret; index++; // so we can start at .0.0.1 if(index > 0xFFFFFF) return IPT4_INVALID; ret.octet[0] = remote_dns_subnet & 0xFF; ret.octet[1] = (index & 0xFF0000) >> 16; ret.octet[2] = (index & 0xFF00) >> 8; ret.octet[3] = index & 0xFF; return ret; } static ip_type4 ip_from_internal_list(char* name, size_t len) { uint32_t hash = dalias_hash((char *) name); size_t i; ip_type4 res; void* new_mem; // see if we already have this dns entry saved. if(internal_ips->counter) { for(i = 0; i < internal_ips->counter; i++) { if(internal_ips->list[i]->hash == hash && !strcmp(name, internal_ips->list[i]->string)) { res = make_internal_ip(i); PDEBUG("got cached ip for %s\n", name); goto have_ip; } } } // grow list if needed. if(internal_ips->capa < internal_ips->counter + 1) { PDEBUG("realloc\n"); new_mem = realloc(internal_ips->list, (internal_ips->capa + 16) * sizeof(void *)); if(new_mem) { internal_ips->capa += 16; internal_ips->list = new_mem; } else { oom: PDEBUG("out of mem\n"); goto err_plus_unlock; } } res = make_internal_ip(internal_ips->counter); if(res.as_int == IPT4_INVALID.as_int) goto err_plus_unlock; string_hash_tuple tmp = { 0 }; new_mem = dumpstring((char*) &tmp, sizeof(string_hash_tuple)); if(!new_mem) goto oom; PDEBUG("creating new entry %d for ip of %s\n", (int) internal_ips->counter, name); internal_ips->list[internal_ips->counter] = new_mem; internal_ips->list[internal_ips->counter]->hash = hash; new_mem = dumpstring((char*) name, len); if(!new_mem) { internal_ips->list[internal_ips->counter] = 0; goto oom; } internal_ips->list[internal_ips->counter]->string = new_mem; internal_ips->counter += 1; have_ip: return res; err_plus_unlock: PDEBUG("return err\n"); return IPT4_INVALID; } /* stuff for communication with the allocator thread */ enum at_direction { ATD_SERVER = 0, ATD_CLIENT, ATD_MAX, }; static pthread_t allocator_thread; int req_pipefd[2]; int resp_pipefd[2]; static int wait_data(int readfd) { PFUNC(); fd_set fds; FD_ZERO(&fds); FD_SET(readfd, &fds); int ret; while((ret = select(readfd+1, &fds, NULL, NULL, NULL)) <= 0) { if(ret < 0) { int e = errno; if(e == EINTR) continue; #ifdef __GLIBC__ char emsg[1024]; char* x = strerror_r(errno, emsg, sizeof emsg); dprintf(2, "select2: %s\n", x); #endif return 0; } } return 1; } static int trywrite(int fd, void* buf, size_t bytes) { ssize_t ret; unsigned char *out = buf; again: ret = write(fd, out, bytes); switch(ret) { case -1: if(errno == EINTR) goto again; case 0: return 0; default: if(ret == bytes || !bytes) return 1; out += ret; bytes -= ret; goto again; } } static int sendmessage(enum at_direction dir, struct at_msg *msg) { static int* destfd[ATD_MAX] = { [ATD_SERVER] = &req_pipefd[1], [ATD_CLIENT] = &resp_pipefd[1] }; assert(msg->h.datalen <= MSG_LEN_MAX); int ret = trywrite(*destfd[dir], msg, sizeof (msg->h)+msg->h.datalen); assert(msg->h.datalen <= MSG_LEN_MAX); return ret; } static int tryread(int fd, void* buf, size_t bytes) { ssize_t ret; unsigned char *out = buf; again: ret = read(fd, out, bytes); switch(ret) { case -1: if(errno == EINTR) goto again; case 0: return 0; default: if(ret == bytes || !bytes) return 1; out += ret; bytes -= ret; goto again; } } static int readmsg(int fd, struct at_msg *msg) { int ret = tryread(fd, msg, sizeof(msg->h)); if(ret != 1) return ret; return tryread(fd, &msg->m, msg->h.datalen); } static int getmessage(enum at_direction dir, struct at_msg *msg) { static int* readfd[ATD_MAX] = { [ATD_SERVER] = &req_pipefd[0], [ATD_CLIENT] = &resp_pipefd[0] }; ssize_t ret; if((ret = wait_data(*readfd[dir]))) { if(!readmsg(*readfd[dir], msg)) return 0; assert(msg->h.datalen <= MSG_LEN_MAX); } return ret; } static void* threadfunc(void* x) { (void) x; int ret; struct at_msg msg; while((ret = getmessage(ATD_SERVER, &msg))) { switch(msg.h.msgtype) { case ATM_GETIP: /* client wants an ip for a DNS name. iterate our list and check if we have an existing entry. * if not, create a new one. */ msg.m.ip = ip_from_internal_list(msg.m.host, msg.h.datalen); msg.h.datalen = sizeof(ip_type4); break; case ATM_GETNAME: { char *host = string_from_internal_ip(msg.m.ip); if(host) { size_t l = strlen(host); assert(l+1 < MSG_LEN_MAX); memcpy(msg.m.host, host, l + 1); msg.h.datalen = l + 1; } else { msg.h.datalen = 0; } break; } case ATM_EXIT: return 0; default: abort(); } ret = sendmessage(ATD_CLIENT, &msg); } return 0; } /* API to access the internal ip mapping */ ip_type4 at_get_ip_for_host(char* host, size_t len) { ip_type4 readbuf; MUTEX_LOCK(internal_ips_lock); if(len > MSG_LEN_MAX) goto inv; struct at_msg msg = {.h.msgtype = ATM_GETIP, .h.datalen = len + 1 }; memcpy(msg.m.host, host, len+1); if(sendmessage(ATD_SERVER, &msg) && getmessage(ATD_CLIENT, &msg)) readbuf = msg.m.ip; else { inv: readbuf = IPT4_INVALID; } assert(msg.h.msgtype == ATM_GETIP); MUTEX_UNLOCK(internal_ips_lock); return readbuf; } size_t at_get_host_for_ip(ip_type4 ip, char* readbuf) { struct at_msg msg = {.h.msgtype = ATM_GETNAME, .h.datalen = sizeof(ip_type4), .m.ip = ip }; size_t res = 0; MUTEX_LOCK(internal_ips_lock); if(sendmessage(ATD_SERVER, &msg) && getmessage(ATD_CLIENT, &msg)) { if((int16_t) msg.h.datalen <= 0) res = 0; else { memcpy(readbuf, msg.m.host, msg.h.datalen); res = msg.h.datalen - 1; } } assert(msg.h.msgtype == ATM_GETNAME); MUTEX_UNLOCK(internal_ips_lock); return res; } static void initpipe(int* fds) { int retval; #ifdef HAVE_PIPE2 retval = pipe2(fds, O_CLOEXEC); #else retval = pipe(fds); if(retval == 0) { fcntl(fds[0], F_SETFD, FD_CLOEXEC); fcntl(fds[1], F_SETFD, FD_CLOEXEC); } #endif if(retval == -1) { perror("pipe"); exit(1); } } #ifndef MAX #define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif #if !defined(PTHREAD_STACK_MIN) || defined(__APPLE__) /* MAC says its min is 8KB, but then crashes in our face. thx hunkOLard */ #define PTHREAD_STACK_MIN 64*1024 #endif /* initialize with pointers to shared memory. these will * be used to place responses and arguments */ void at_init(void) { PFUNC(); void *shm = mmap(0, 4096, PROT_WRITE|PROT_READ, MAP_ANON|MAP_SHARED, -1, 0); assert(shm); internal_ips_lock = shm; internal_ips = (void*)((char*)shm + 2048); MUTEX_INIT(internal_ips_lock); memset(internal_ips, 0, sizeof *internal_ips); initpipe(req_pipefd); initpipe(resp_pipefd); pthread_attr_t allocator_thread_attr; pthread_attr_init(&allocator_thread_attr); pthread_attr_setstacksize(&allocator_thread_attr, MAX(16 * 1024, PTHREAD_STACK_MIN)); pthread_create(&allocator_thread, &allocator_thread_attr, threadfunc, 0); pthread_attr_destroy(&allocator_thread_attr); } void at_close(void) { PFUNC(); const int msg = ATM_EXIT; write(req_pipefd[1], &msg, sizeof(int)); pthread_join(allocator_thread, NULL); close(req_pipefd[0]); close(req_pipefd[1]); close(resp_pipefd[0]); close(resp_pipefd[1]); MUTEX_DESTROY(internal_ips_lock); }