#include "ac.h" #include "capwap_dfa.h" #include "ac_session.h" #include "ac_discovery.h" #include "ac_backend.h" #include "ac_wlans.h" #include #define AC_RECV_NOERROR_MSGQUEUE -1001 #define AC_RECV_NOERROR_KMODEVENT -1002 /* */ static int ac_recvmsgqueue(int fd, struct ac_session_msgqueue_item_t* item) { int packetsize = -1; do { packetsize = recv(fd, (void*)item, sizeof(struct ac_session_msgqueue_item_t), 0); } while ((packetsize < 0) && ((errno == EAGAIN) || (errno == EINTR))); return ((packetsize == sizeof(struct ac_session_msgqueue_item_t)) ? 1 : 0); } /* */ static void ac_session_msgqueue_parsing_item(struct ac_session_msgqueue_item_t* item) { switch (item->message) { case AC_MESSAGE_QUEUE_CLOSE_THREAD: { struct capwap_list_item* search = g_ac.sessionsthread->first; while (search != NULL) { struct ac_session_thread_t* sessionthread = (struct ac_session_thread_t*)search->item; ASSERT(sessionthread != NULL); if (sessionthread->threadid == item->message_close_thread.threadid) { void* dummy; /* Clean thread resource */ pthread_join(sessionthread->threadid, &dummy); capwap_itemlist_free(capwap_itemlist_remove(g_ac.sessionsthread, search)); break; } /* */ search = search->next; } break; } default: { capwap_logging_debug("Unknown message queue item: %lu", item->message); break; } } } /* */ static void ac_wait_terminate_allsessions(void) { struct ac_session_msgqueue_item_t item; /* Wait that list is empty */ while (g_ac.sessionsthread->count > 0) { capwap_logging_debug("Waiting for %d session terminate", g_ac.sessionsthread->count); /* Receive message queue packet */ if (!ac_recvmsgqueue(g_ac.fdmsgsessions[1], &item)) { capwap_logging_debug("Unable to receive message queue"); break; } /* Parsing message queue packet */ if (item.message == AC_MESSAGE_QUEUE_CLOSE_THREAD) { ac_session_msgqueue_parsing_item(&item); } } capwap_logging_debug("Close all sessions"); } /* Initialize message queue */ int ac_msgqueue_init(void) { if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, g_ac.fdmsgsessions)) { return 0; } return 1; } /* Free sessions message queue */ void ac_msgqueue_free(void) { close(g_ac.fdmsgsessions[1]); close(g_ac.fdmsgsessions[0]); } /* */ void ac_msgqueue_notify_closethread(pthread_t threadid) { struct ac_session_msgqueue_item_t item; /* Send message */ memset(&item, 0, sizeof(struct ac_session_msgqueue_item_t)); item.message = AC_MESSAGE_QUEUE_CLOSE_THREAD; item.message_close_thread.threadid = threadid; send(g_ac.fdmsgsessions[0], (void*)&item, sizeof(struct ac_session_msgqueue_item_t), 0); } /* */ static int ac_recvfrom(struct ac_fds* fds, void* buffer, int* size, union sockaddr_capwap* fromaddr, union sockaddr_capwap* toaddr) { int index; ASSERT(fds); ASSERT(fds->fdspoll != NULL); ASSERT(fds->fdstotalcount > 0); ASSERT(buffer != NULL); ASSERT(size != NULL); ASSERT(*size > 0); ASSERT(fromaddr != NULL); /* Wait packet */ index = capwap_wait_recvready(fds->fdspoll, fds->fdstotalcount, NULL); if (index < 0) { return index; } else if ((fds->kmodeventsstartpos >= 0) && (index >= fds->kmodeventsstartpos)) { int pos = index - fds->kmodeventsstartpos; if (pos < fds->kmodeventscount) { if (!fds->kmodevents[pos].event_handler) { return CAPWAP_RECV_ERROR_SOCKET; } fds->kmodevents[pos].event_handler(fds->fdspoll[index].fd, fds->kmodevents[pos].params, fds->kmodevents[pos].paramscount); } return AC_RECV_NOERROR_KMODEVENT; } else if ((fds->msgqueuestartpos >= 0) && (index >= fds->msgqueuestartpos)) { struct ac_session_msgqueue_item_t item; /* Receive message queue packet */ if (!ac_recvmsgqueue(fds->fdspoll[index].fd, &item)) { return CAPWAP_RECV_ERROR_SOCKET; } /* Parsing message queue packet */ ac_session_msgqueue_parsing_item(&item); return AC_RECV_NOERROR_MSGQUEUE; } /* Receive packet */ if (capwap_recvfrom(fds->fdspoll[index].fd, buffer, size, fromaddr, toaddr)) { return CAPWAP_RECV_ERROR_SOCKET; } return index; } /* Add packet to session */ static void ac_session_add_packet(struct ac_session_t* session, char* buffer, int size, int plainbuffer) { struct capwap_list_item* item; struct ac_packet* packet; ASSERT(session != NULL); ASSERT(buffer != NULL); ASSERT(size > 0); /* Copy packet */ item = capwap_itemlist_create(sizeof(struct ac_packet) + size); packet = (struct ac_packet*)item->item; packet->plainbuffer = plainbuffer; memcpy(packet->buffer, buffer, size); /* Append to packets list */ capwap_lock_enter(&session->sessionlock); capwap_itemlist_insert_after(session->packets, NULL, item); capwap_event_signal(&session->waitpacket); capwap_lock_exit(&session->sessionlock); } /* Add action to session */ void ac_session_send_action(struct ac_session_t* session, long action, long param, const void* data, long length) { struct capwap_list_item* item; struct ac_session_action* actionsession; struct capwap_list_item* search; ASSERT(session != NULL); ASSERT(length >= 0); /* */ item = capwap_itemlist_create(sizeof(struct ac_session_action) + length); actionsession = (struct ac_session_action*)item->item; actionsession->action = action; actionsession->param = param; actionsession->length = length; if (length > 0) { ASSERT(data != NULL); memcpy(actionsession->data, data, length); } /* Validate session before use */ capwap_rwlock_rdlock(&g_ac.sessionslock); search = g_ac.sessions->first; while (search != NULL) { if (session == (struct ac_session_t*)search->item) { /* Append to actions list */ capwap_lock_enter(&session->sessionlock); capwap_itemlist_insert_after(session->action, NULL, item); capwap_event_signal(&session->waitpacket); capwap_lock_exit(&session->sessionlock); break; } /* */ search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); } /* Find AC sessions */ static struct ac_session_t* ac_search_session_from_wtpaddress(union sockaddr_capwap* address) { struct ac_session_t* result = NULL; struct capwap_list_item* search; ASSERT(address != NULL); capwap_rwlock_rdlock(&g_ac.sessionslock); search = g_ac.sessions->first; while (search != NULL) { struct ac_session_t* session = (struct ac_session_t*)search->item; ASSERT(session != NULL); if (!capwap_compare_ip(address, &session->dtls.peeraddr)) { /* Increment session count */ capwap_lock_enter(&session->sessionlock); session->count++; capwap_event_signal(&session->changereference); capwap_lock_exit(&session->sessionlock); /* */ result = session; break; } search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); return result; } /* Find session from wtp id */ struct ac_session_t* ac_search_session_from_wtpid(const char* wtpid) { struct ac_session_t* result = NULL; struct capwap_list_item* search; ASSERT(wtpid != NULL); capwap_rwlock_rdlock(&g_ac.sessionslock); search = g_ac.sessions->first; while (search != NULL) { struct ac_session_t* session = (struct ac_session_t*)search->item; ASSERT(session != NULL); if (session->wtpid && !strcmp(session->wtpid, wtpid)) { /* Increment session count */ capwap_lock_enter(&session->sessionlock); session->count++; capwap_event_signal(&session->changereference); capwap_lock_exit(&session->sessionlock); /* */ result = session; break; } search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); return result; } /* Find session from wtp id */ struct ac_session_t* ac_search_session_from_sessionid(struct capwap_sessionid_element* sessionid) { struct ac_session_t* result = NULL; struct capwap_list_item* search; ASSERT(sessionid != NULL); capwap_rwlock_rdlock(&g_ac.sessionslock); search = g_ac.sessions->first; while (search != NULL) { struct ac_session_t* session = (struct ac_session_t*)search->item; ASSERT(session != NULL); if (!memcmp(sessionid, &session->sessionid, sizeof(struct capwap_sessionid_element))) { /* Increment session count */ capwap_lock_enter(&session->sessionlock); session->count++; capwap_event_signal(&session->changereference); capwap_lock_exit(&session->sessionlock); /* */ result = session; break; } search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); return result; } /* */ int ac_has_sessionid(struct capwap_sessionid_element* sessionid) { int result = 0; struct capwap_list_item* search; ASSERT(sessionid != NULL); capwap_rwlock_rdlock(&g_ac.sessionslock); search = g_ac.sessions->first; while (search != NULL) { struct ac_session_t* session = (struct ac_session_t*)search->item; ASSERT(session != NULL); if (!memcmp(sessionid, &session->sessionid, sizeof(struct capwap_sessionid_element))) { result = 1; break; } search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); return result; } /* */ int ac_has_wtpid(const char* wtpid) { int result = 0; struct capwap_list_item* search; if (!wtpid || !wtpid[0]) { return -1; } capwap_rwlock_rdlock(&g_ac.sessionslock); search = g_ac.sessions->first; while (search != NULL) { struct ac_session_t* session = (struct ac_session_t*)search->item; ASSERT(session != NULL); if (session->wtpid && !strcmp(session->wtpid, wtpid)) { result = 1; break; } search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); return result; } /* */ char* ac_get_printable_wtpid(struct capwap_wtpboarddata_element* wtpboarddata) { char* wtpid = NULL; struct capwap_wtpboarddata_board_subelement* wtpboarddatamacaddress; ASSERT(wtpboarddata != NULL); /* TODO: build printable wtpid depending on the model device */ /* Get macaddress */ wtpboarddatamacaddress = capwap_wtpboarddata_get_subelement(wtpboarddata, CAPWAP_BOARD_SUBELEMENT_MACADDRESS); if (wtpboarddatamacaddress != NULL) { wtpid = capwap_alloc(((wtpboarddatamacaddress->length == MACADDRESS_EUI48_LENGTH) ? CAPWAP_MACADDRESS_EUI48_BUFFER : CAPWAP_MACADDRESS_EUI64_BUFFER)); capwap_printf_macaddress(wtpid, (unsigned char*)wtpboarddatamacaddress->data, wtpboarddatamacaddress->length); } return wtpid; } /* */ void ac_session_close(struct ac_session_t* session) { capwap_lock_enter(&session->sessionlock); session->running = 0; capwap_event_signal(&session->waitpacket); capwap_lock_exit(&session->sessionlock); } /* Close sessions */ static void ac_close_sessions() { struct capwap_list_item* search; capwap_rwlock_rdlock(&g_ac.sessionslock); /* Session */ search = g_ac.sessions->first; while (search != NULL) { struct ac_session_t* session = (struct ac_session_t*)search->item; ASSERT(session != NULL); ac_session_close(session); search = search->next; } capwap_rwlock_exit(&g_ac.sessionslock); } /* Create new session */ static struct ac_session_t* ac_create_session(int sock, union sockaddr_capwap* fromaddr, union sockaddr_capwap* toaddr) { int result; struct capwap_list_item* itemlist; struct ac_session_t* session; ASSERT(sock >= 0); ASSERT(fromaddr != NULL); ASSERT(toaddr != NULL); /* Create new session */ itemlist = capwap_itemlist_create(sizeof(struct ac_session_t)); session = (struct ac_session_t*)itemlist->item; memset(session, 0, sizeof(struct ac_session_t)); session->itemlist = itemlist; session->running = 1; /* */ capwap_crypt_setconnection(&session->dtls, sock, toaddr, fromaddr); /* */ ac_wlans_init(session); /* */ session->count = 2; capwap_event_init(&session->changereference); /* */ session->timeout = capwap_timeout_init(); session->idtimercontrol = capwap_timeout_createtimer(session->timeout); session->idtimerkeepalivedead = capwap_timeout_createtimer(session->timeout); /* Duplicate state for DFA */ memcpy(&session->dfa, &g_ac.dfa, sizeof(struct ac_state)); session->dfa.acipv4list.addresses = capwap_array_clone(g_ac.dfa.acipv4list.addresses); if (!session->dfa.acipv4list.addresses->count && (session->dtls.localaddr.ss.ss_family == AF_INET)) { memcpy(capwap_array_get_item_pointer(session->dfa.acipv4list.addresses, 0), &session->dtls.localaddr.sin.sin_addr, sizeof(struct in_addr)); } session->dfa.acipv6list.addresses = capwap_array_clone(g_ac.dfa.acipv6list.addresses); if (!session->dfa.acipv6list.addresses->count && (session->dtls.localaddr.ss.ss_family == AF_INET6)) { memcpy(capwap_array_get_item_pointer(session->dfa.acipv6list.addresses, 0), &session->dtls.localaddr.sin6.sin6_addr, sizeof(struct in6_addr)); } /* Init */ capwap_event_init(&session->waitpacket); capwap_lock_init(&session->sessionlock); session->action = capwap_list_create(); session->packets = capwap_list_create(); session->requestfragmentpacket = capwap_list_create(); session->responsefragmentpacket = capwap_list_create(); session->notifyevent = capwap_list_create(); session->mtu = g_ac.mtu; session->state = CAPWAP_IDLE_STATE; /* Update session list */ capwap_rwlock_wrlock(&g_ac.sessionslock); capwap_itemlist_insert_after(g_ac.sessions, NULL, itemlist); capwap_rwlock_exit(&g_ac.sessionslock); /* Create thread */ result = pthread_create(&session->threadid, NULL, ac_session_thread, (void*)session); if (!result) { struct ac_session_thread_t* sessionthread; /* Keeps trace of active threads */ itemlist = capwap_itemlist_create(sizeof(struct ac_session_thread_t)); sessionthread = (struct ac_session_thread_t*)itemlist->item; sessionthread->threadid = session->threadid; /* */ capwap_itemlist_insert_after(g_ac.sessionsthread, NULL, itemlist); } else { capwap_logging_fatal("Unable create session thread, error code %d", result); capwap_exit(CAPWAP_OUT_OF_MEMORY); } return session; } /* Release reference of session */ void ac_session_release_reference(struct ac_session_t* session) { ASSERT(session != NULL); capwap_lock_enter(&session->sessionlock); ASSERT(session->count > 0); session->count--; capwap_event_signal(&session->changereference); capwap_lock_exit(&session->sessionlock); } /* Update statistics */ void ac_update_statistics(void) { g_ac.descriptor.stations = 0; /* TODO */ capwap_rwlock_rdlock(&g_ac.sessionslock); g_ac.descriptor.activewtp = g_ac.sessions->count; capwap_rwlock_exit(&g_ac.sessionslock); } /* Handler signal */ static void ac_signal_handler(int signum) { if ((signum == SIGINT) || (signum == SIGTERM)) { g_ac.running = 0; } } /* */ static int ac_execute_init_fdspool(struct ac_fds* fds, struct capwap_network* net, int fdmsgqueue) { ASSERT(fds != NULL); ASSERT(net != NULL); ASSERT(fdmsgqueue > 0); /* */ memset(fds, 0, sizeof(struct ac_fds)); fds->fdsnetworkcount = capwap_network_set_pollfd(net, NULL, 0); fds->msgqueuecount = 1; fds->fdspoll = (struct pollfd*)capwap_alloc(sizeof(struct pollfd) * (fds->fdsnetworkcount + fds->msgqueuecount)); /* Retrive all socket for polling */ fds->fdstotalcount = capwap_network_set_pollfd(net, fds->fdspoll, fds->fdsnetworkcount); if (fds->fdsnetworkcount != fds->fdstotalcount) { capwap_free(fds->fdspoll); return -1; } /* Unix socket message queue */ fds->msgqueuestartpos = fds->fdsnetworkcount; fds->fdspoll[fds->msgqueuestartpos].events = POLLIN | POLLERR | POLLHUP | POLLNVAL; fds->fdspoll[fds->msgqueuestartpos].fd = fdmsgqueue; fds->fdstotalcount += fds->msgqueuecount; return ac_execute_update_fdspool(fds); } /* */ static void ac_execute_free_fdspool(struct ac_fds* fds) { ASSERT(fds != NULL); if (fds->fdspoll) { capwap_free(fds->fdspoll); } if (fds->kmodevents) { capwap_free(fds->kmodevents); } } /* */ int ac_execute_update_fdspool(struct ac_fds* fds) { int totalcount; int kmodcount; struct pollfd* fdsbuffer; ASSERT(fds != NULL); /* Retrieve number of Dynamic File Descriptor Event */ kmodcount = ac_kmod_getfd(NULL, NULL, 0); if (kmodcount < 0) { return -1; } /* Kernel Module Events Callback */ fds->kmodeventsstartpos = -1; if (kmodcount != fds->kmodeventscount) { if (fds->kmodevents) { capwap_free(fds->kmodevents); } /* */ fds->kmodeventscount = kmodcount; fds->kmodevents = (struct ac_kmod_event*)((kmodcount > 0) ? capwap_alloc(sizeof(struct ac_kmod_event) * kmodcount) : NULL); } /* Resize poll */ totalcount = fds->fdsnetworkcount + fds->msgqueuecount + fds->kmodeventscount; if (fds->fdstotalcount != totalcount) { fdsbuffer = (struct pollfd*)capwap_alloc(sizeof(struct pollfd) * totalcount); if (fds->fdspoll) { int count = fds->fdsnetworkcount + fds->msgqueuecount; if (count > 0) { memcpy(fdsbuffer, fds->fdspoll, sizeof(struct pollfd) * count); } capwap_free(fds->fdspoll); } /* */ fds->fdspoll = fdsbuffer; fds->fdstotalcount = totalcount; } /* Retrieve File Descriptor Kernel Module Event */ if (fds->kmodeventscount > 0) { fds->kmodeventsstartpos = fds->fdsnetworkcount + fds->msgqueuecount; ac_kmod_getfd(&fds->fdspoll[fds->kmodeventsstartpos], fds->kmodevents, fds->kmodeventscount); } return fds->fdstotalcount; } /* AC running */ int ac_execute(void) { int result = CAPWAP_SUCCESSFUL; int index; int check; union sockaddr_capwap fromaddr; union sockaddr_capwap toaddr; struct ac_session_t* session; char buffer[CAPWAP_MAX_PACKET_SIZE]; int buffersize; struct ac_fds fds; /* Set file descriptor pool */ if (ac_execute_init_fdspool(&fds, &g_ac.net, g_ac.fdmsgsessions[1]) <= 0) { capwap_logging_debug("Unable to initialize file descriptor pool"); return AC_ERROR_SYSTEM_FAILER; } /* Handler signal */ g_ac.running = 1; signal(SIGPIPE, SIG_IGN); signal(SIGINT, ac_signal_handler); signal(SIGTERM, ac_signal_handler); /* Start discovery thread */ if (!ac_discovery_start()) { ac_execute_free_fdspool(&fds); capwap_logging_debug("Unable to start discovery thread"); return AC_ERROR_SYSTEM_FAILER; } /* Enable Backend Management */ if (!ac_backend_start()) { ac_execute_free_fdspool(&fds); ac_discovery_stop(); capwap_logging_error("Unable start backend management"); return AC_ERROR_SYSTEM_FAILER; } /* */ while (g_ac.running) { /* Receive packet */ buffersize = sizeof(buffer); index = ac_recvfrom(&fds, buffer, &buffersize, &fromaddr, &toaddr); if (!g_ac.running) { capwap_logging_debug("Closing AC"); break; } /* */ if (index >= 0) { /* Search the AC session */ session = ac_search_session_from_wtpaddress(&fromaddr); if (session) { /* Add packet*/ ac_session_add_packet(session, buffer, buffersize, 0); /* Release reference */ ac_session_release_reference(session); } else { unsigned short sessioncount; /* TODO prevent dos attack add filtering ip for multiple error */ /* Get current session number */ capwap_rwlock_rdlock(&g_ac.sessionslock); sessioncount = g_ac.sessions->count; capwap_rwlock_exit(&g_ac.sessionslock); /* */ if (ac_backend_isconnect() && (sessioncount < g_ac.descriptor.maxwtp)) { check = capwap_sanity_check(CAPWAP_UNDEF_STATE, buffer, buffersize, g_ac.enabledtls); if (check == CAPWAP_PLAIN_PACKET) { struct capwap_header* header = (struct capwap_header*)buffer; /* Accepted only packet without fragmentation */ if (!IS_FLAG_F_HEADER(header)) { int headersize = GET_HLEN_HEADER(header) * 4; if (buffersize >= (headersize + sizeof(struct capwap_control_message))) { struct capwap_control_message* control = (struct capwap_control_message*)((char*)buffer + headersize); unsigned long type = ntohl(control->type); if (type == CAPWAP_DISCOVERY_REQUEST) { ac_discovery_add_packet(buffer, buffersize, fds.fdspoll[index].fd, &fromaddr); } else if (!g_ac.enabledtls && (type == CAPWAP_JOIN_REQUEST)) { /* Create a new session */ session = ac_create_session(fds.fdspoll[index].fd, &fromaddr, &toaddr); ac_session_add_packet(session, buffer, buffersize, 1); /* Release reference */ ac_session_release_reference(session); } } } } else if (check == CAPWAP_DTLS_PACKET) { /* Before create new session check if receive DTLS Client Hello */ if (capwap_crypt_has_dtls_clienthello(&((char*)buffer)[sizeof(struct capwap_dtls_header)], buffersize - sizeof(struct capwap_dtls_header))) { /* Create a new session */ session = ac_create_session(fds.fdspoll[index].fd, &fromaddr, &toaddr); ac_session_add_packet(session, buffer, buffersize, 0); /* Release reference */ ac_session_release_reference(session); } } } } } else if ((index == CAPWAP_RECV_ERROR_INTR) || (index == AC_RECV_NOERROR_MSGQUEUE) || (index == AC_RECV_NOERROR_KMODEVENT)) { /* Ignore recv */ continue; } else if (index == CAPWAP_RECV_ERROR_SOCKET) { /* Socket close */ break; } } /* Disable Backend Management */ ac_backend_stop(); /* Terminate discovery thread */ ac_discovery_stop(); /* Close all sessions */ ac_close_sessions(); /* Wait to terminate all sessions */ ac_wait_terminate_allsessions(); /* Free Backend Management */ ac_backend_free(); /* Free file description pool */ ac_execute_free_fdspool(&fds); return result; }