freewtp/src/dfa.c

477 lines
13 KiB
C

#include "wtp.h"
#include "dfa.h"
#include "array.h"
#include "capwap_dfa.h"
#include "dtls.h"
#include "radio.h"
#include <signal.h>
#include <ev.h>
#define WTP_RECV_NOERROR_RADIO -1001
static const struct dfa_states {
void (*state_enter)(void);
void (*state_execute)(struct capwap_parsed_packet *packet);
} dfa_states[] = {
[CAPWAP_IDLE_STATE] = {
.state_enter = wtp_dfa_state_idle_enter,
},
[CAPWAP_DISCOVERY_STATE] = {
.state_enter = wtp_dfa_state_discovery_enter,
.state_execute = wtp_dfa_state_discovery,
},
[CAPWAP_SULKING_STATE] = {
.state_enter = wtp_dfa_state_sulking_enter,
.state_execute = wtp_dfa_state_sulking,
},
[CAPWAP_DTLS_CONNECT_STATE] = {
.state_enter = wtp_dfa_state_dtlsconnect_enter,
},
[CAPWAP_DTLS_TEARDOWN_STATE] = {
.state_enter = wtp_dfa_state_dtlsteardown_enter,
.state_execute = wtp_dfa_state_dtlsteardown,
},
[CAPWAP_JOIN_STATE] = {
.state_enter = wtp_dfa_state_join_enter,
.state_execute = wtp_dfa_state_join,
},
[CAPWAP_IMAGE_DATA_STATE] = {
},
[CAPWAP_CONFIGURE_STATE] = {
.state_enter = wtp_dfa_state_configure_enter,
.state_execute = wtp_dfa_state_configure,
},
[CAPWAP_RESET_STATE] = {
.state_enter = wtp_dfa_state_reset_enter,
},
[CAPWAP_DATA_CHECK_STATE] = {
.state_enter = wtp_dfa_state_datacheck_enter,
.state_execute = wtp_dfa_state_datacheck,
},
[CAPWAP_RUN_STATE] = {
.state_enter = wtp_dfa_state_run_enter,
.state_execute = wtp_dfa_state_run,
},
[CAPWAP_DEAD_STATE] = {
.state_enter = wtp_dfa_state_dead_enter,
}
};
static inline int is_valid_state(int state)
{
return (state >= 0) && (state < (sizeof(dfa_states) / sizeof(dfa_states[0])));
}
/* libev handler */
static void signal_cb (EV_P_ ev_signal *w, int revents);
static void capwap_control_cb(EV_P_ ev_io *w, int revents);
/* Handler signal */
static void signal_cb (EV_P_ ev_signal *w, int revents)
{
g_wtp.running = 0;
/* Teardown */
wtp_teardown_connection();
}
/* */
static struct capwap_packet_rxmng* wtp_get_packet_rxmng(void) {
if (!g_wtp.rxmngpacket) {
g_wtp.rxmngpacket = capwap_packet_rxmng_create_message();
}
return g_wtp.rxmngpacket;
}
/* */
void wtp_free_packet_rxmng(void) {
if (g_wtp.rxmngpacket) {
capwap_packet_rxmng_free(g_wtp.rxmngpacket);
g_wtp.rxmngpacket = NULL;
}
}
/* */
static void wtp_send_invalid_request(struct capwap_packet_rxmng* rxmngpacket, uint32_t errorcode) {
struct capwap_header_data capwapheader;
struct capwap_packet_txmng* txmngpacket;
struct capwap_list* responsefragmentpacket;
struct capwap_fragment_packet_item* packet;
struct capwap_header* header;
struct capwap_resultcode_element resultcode = { .code = errorcode };
ASSERT(rxmngpacket != NULL);
ASSERT(rxmngpacket->fragmentlist->first != NULL);
/* */
packet = (struct capwap_fragment_packet_item*)rxmngpacket->fragmentlist->first->item;
header = (struct capwap_header*)packet->buffer;
/* Odd message type */
capwap_header_init(&capwapheader, CAPWAP_RADIOID_NONE, GET_WBID_HEADER(header));
txmngpacket = capwap_packet_txmng_create_ctrl_message(&capwapheader, rxmngpacket->ctrlmsg.type + 1, rxmngpacket->ctrlmsg.seq, g_wtp.mtu);
/* Add message element */
capwap_packet_txmng_add_message_element(txmngpacket, CAPWAP_ELEMENT_RESULTCODE, &resultcode);
/* Unknown response complete, get fragment packets */
responsefragmentpacket = capwap_list_create();
capwap_packet_txmng_get_fragment_packets(txmngpacket, responsefragmentpacket, g_wtp.fragmentid);
if (responsefragmentpacket->count > 1)
g_wtp.fragmentid++;
/* Free packets manager */
capwap_packet_txmng_free(txmngpacket);
/* Send unknown response */
capwap_crypt_sendto_fragmentpacket(&g_wtp.dtls, responsefragmentpacket);
/* Don't buffering a packets sent */
capwap_list_free(responsefragmentpacket);
}
/* WTP Execute state */
static void wtp_dfa_execute(struct capwap_parsed_packet* packet)
{
ASSERT(packet != NULL);
if (!is_valid_state(g_wtp.state) ||
!dfa_states[g_wtp.state].state_execute) {
log_printf(LOG_DEBUG, "Got packet in invalid WTP state: %lu", g_wtp.state);
wtp_teardown_connection();
} else
dfa_states[g_wtp.state].state_execute(packet);
}
static void wtp_dfa_process_packet(void *buffer, int buffersize,
union sockaddr_capwap *fromaddr,
union sockaddr_capwap *toaddr)
{
int check, res;
char plain[CAPWAP_MAX_PACKET_SIZE];
struct capwap_packet_rxmng* rxmngpacket;
struct capwap_parsed_packet packet;
/* Check source */
if (g_wtp.state != CAPWAP_DISCOVERY_STATE &&
capwap_compare_ip(&g_wtp.dtls.peeraddr, fromaddr)) {
log_printf(LOG_DEBUG, "CAPWAP packet from unknown WTP when not in DISCOVERY, drop packet");
return; /* Unknown source */
}
/* Check of packet */
check = capwap_sanity_check(g_wtp.state, buffer, buffersize, g_wtp.dtls.enable);
switch (check) {
case CAPWAP_PLAIN_PACKET:
break;
case CAPWAP_DTLS_PACKET: {
int oldaction = g_wtp.dtls.action;
/* Decrypt packet */
buffersize = capwap_decrypt_packet(&g_wtp.dtls, buffer, buffersize, plain, CAPWAP_MAX_PACKET_SIZE);
if (buffersize > 0) {
buffer = plain;
break;
}
if (buffersize == CAPWAP_ERROR_AGAIN) {
/* Check is handshake complete */
if (oldaction == CAPWAP_DTLS_ACTION_HANDSHAKE &&
g_wtp.dtls.action == CAPWAP_DTLS_ACTION_DATA) {
if (g_wtp.state == CAPWAP_DTLS_CONNECT_STATE) {
wtp_dfa_change_state(CAPWAP_JOIN_STATE);
} else
wtp_teardown_connection();
}
} else if (oldaction == CAPWAP_DTLS_ACTION_DATA &&
g_wtp.dtls.action == CAPWAP_DTLS_ACTION_SHUTDOWN) {
wtp_teardown_connection();
} else if (oldaction == CAPWAP_DTLS_ACTION_HANDSHAKE &&
g_wtp.dtls.action == CAPWAP_DTLS_ACTION_ERROR) {
wtp_abort_connecting();
}
return; /* Next packet */
}
case CAPWAP_WRONG_PACKET:
log_printf(LOG_DEBUG, "Warning: sanity check failure");
/* Drop packet */
return;
default:
/* TODO: Really? Previosly, this was hidden in the
* overly deep indention, check if that is correct */
log_printf(LOG_DEBUG, "Warning: wtp_dfa_running took default fall through");
return;
}
/* Defragment management */
rxmngpacket = wtp_get_packet_rxmng();
/* If request, defragmentation packet */
check = capwap_packet_rxmng_add_recv_packet(rxmngpacket, buffer, buffersize);
if (check == CAPWAP_REQUEST_MORE_FRAGMENT)
return;
if (check != CAPWAP_RECEIVE_COMPLETE_PACKET) {
/* Discard fragments */
wtp_free_packet_rxmng();
return;
}
/* Check for already sent response to packet */
if (capwap_is_request_type(rxmngpacket->ctrlmsg.type) &&
g_wtp.remotetype == rxmngpacket->ctrlmsg.type &&
g_wtp.remoteseqnumber == rxmngpacket->ctrlmsg.seq) {
/* Retransmit response */
if (!capwap_crypt_sendto_fragmentpacket(&g_wtp.dtls, g_wtp.responsefragmentpacket)) {
log_printf(LOG_ERR, "Error to resend response packet");
} else {
log_printf(LOG_DEBUG, "Retransmitted control packet");
}
/* Discard fragments */
wtp_free_packet_rxmng();
return;
}
/* Check message type */
res = capwap_check_message_type(rxmngpacket);
if (res != VALID_MESSAGE_TYPE) {
if (res == INVALID_REQUEST_MESSAGE_TYPE) {
log_printf(LOG_WARNING, "Unexpected Unrecognized Request, send Response Packet with error");
wtp_send_invalid_request(rxmngpacket, CAPWAP_RESULTCODE_MSG_UNEXPECTED_UNRECOGNIZED_REQUEST);
}
log_printf(LOG_DEBUG, "Invalid message type");
wtp_free_packet_rxmng();
return;
}
/* Init */
memset(&packet, 0, sizeof(struct capwap_parsed_packet));
/* Parsing packet */
res = capwap_parsing_packet(rxmngpacket, &packet);
if (res != PARSING_COMPLETE) {
if (res == UNRECOGNIZED_MESSAGE_ELEMENT &&
capwap_is_request_type(rxmngpacket->ctrlmsg.type)) {
log_printf(LOG_WARNING, "Unrecognized Message Element, send Response Packet with error");
wtp_send_invalid_request(rxmngpacket, CAPWAP_RESULTCODE_FAILURE_UNRECOGNIZED_MESSAGE_ELEMENT);
/* TODO: add the unrecognized message element */
}
/* */
log_printf(LOG_DEBUG, "Failed parsing packet");
capwap_free_parsed_packet(&packet);
wtp_free_packet_rxmng();
return;
}
/* Validate packet */
if (capwap_validate_parsed_packet(&packet, NULL)) {
if (capwap_is_request_type(rxmngpacket->ctrlmsg.type)) {
log_printf(LOG_WARNING, "Missing Mandatory Message Element, send Response Packet with error");
wtp_send_invalid_request(rxmngpacket, CAPWAP_RESULTCODE_FAILURE_MISSING_MANDATORY_MSG_ELEMENT);
}
/* */
log_printf(LOG_DEBUG, "Failed validation parsed packet");
capwap_free_parsed_packet(&packet);
wtp_free_packet_rxmng();
return;
}
/* Receive a complete packet */
wtp_dfa_execute(&packet);
/* Free packet */
capwap_free_parsed_packet(&packet);
wtp_free_packet_rxmng();
}
/* WTP state machine */
int wtp_dfa_running()
{
int result = CAPWAP_SUCCESSFUL;
/* Handler signal */
g_wtp.running = 1;
ev_signal_init(&g_wtp.sigint_ev, signal_cb, SIGINT);
ev_signal_init(&g_wtp.sigterm_ev, signal_cb, SIGTERM);
ev_signal_start(EV_DEFAULT_UC_ &g_wtp.sigint_ev);
ev_signal_start(EV_DEFAULT_UC_ &g_wtp.sigterm_ev);
/* Init complete, start DFA */
wtp_dfa_change_state(CAPWAP_IDLE_STATE);
ev_run(EV_DEFAULT_UC_ 0);
return result;
}
void wtp_socket_io_start()
{
log_printf(LOG_DEBUG, "Start EV_IO on socket %d", g_wtp.net.socket);
/* Configure libev struct */
ev_io_init (&g_wtp.socket_ev, capwap_control_cb, g_wtp.net.socket, EV_READ);
ev_io_start(EV_DEFAULT_UC_ &g_wtp.socket_ev);
}
void wtp_socket_io_stop()
{
log_printf(LOG_DEBUG, "Stop EV_IO on socket %d", g_wtp.socket_ev.fd);
ev_io_stop(EV_DEFAULT_UC_ &g_wtp.socket_ev);
}
static void capwap_control_cb(EV_P_ ev_io *w, int revents)
{
char buffer[CAPWAP_MAX_PACKET_SIZE];
ssize_t r;
union sockaddr_capwap fromaddr;
union sockaddr_capwap toaddr;
do {
/* If request wait packet from AC */
do {
log_printf(LOG_DEBUG, "Receive CAPWAP Control Channel message");
r = capwap_recvfrom(w->fd, &buffer, sizeof(buffer),
&fromaddr, &toaddr);
} while (r < 0 && errno == EINTR);
log_printf(LOG_DEBUG, "WTP got data: r: %zd", r);
if (!g_wtp.running) {
log_printf(LOG_DEBUG, "Closing WTP, Teardown connection");
ev_io_stop (EV_A_ w);
break;
}
if (r < 0) {
if (errno != EAGAIN) {
log_printf(LOG_DEBUG, "capwap_control_cb I/O error %m, exiting loop");
ev_io_stop (EV_A_ w);
ev_break (EV_A_ EVBREAK_ONE);
}
break;
}
if (g_wtp.teardown) {
log_printf(LOG_DEBUG, "WTP is in teardown, drop packet");
continue; /* Drop packet */
}
wtp_dfa_process_packet(&buffer, r, &fromaddr, &toaddr);
} while (ev_is_active(w));
}
/* Change WTP state machine */
void wtp_dfa_change_state(int state) {
if (state != g_wtp.state) {
log_printf(LOG_DEBUG, "WTP change state from %s to %s",
capwap_dfa_getname(g_wtp.state),
capwap_dfa_getname(state));
g_wtp.state = state;
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timercontrol);
if (is_valid_state(g_wtp.state) &&
dfa_states[g_wtp.state].state_enter)
dfa_states[g_wtp.state].state_enter();
}
}
/* */
void wtp_free_reference_last_request(void)
{
capwap_list_flush(g_wtp.requestfragmentpacket);
}
/* */
void wtp_free_reference_last_response(void) {
capwap_list_flush(g_wtp.responsefragmentpacket);
g_wtp.remotetype = 0;
g_wtp.remoteseqnumber = 0;
}
/* */
static void wtp_dfa_retransmition_timeout_cb(EV_P_ ev_timer *w, int revents)
{
if (!g_wtp.requestfragmentpacket->count) {
log_printf(LOG_WARNING, "Invalid retransmition request packet");
wtp_teardown_connection();
return;
}
g_wtp.retransmitcount++;
if (g_wtp.retransmitcount >= WTP_MAX_RETRANSMIT) {
log_printf(LOG_INFO, "Retransmition request packet timeout");
/* Timeout state */
wtp_free_reference_last_request();
wtp_teardown_connection();
return;
}
/* Retransmit request */
log_printf(LOG_DEBUG, "Retransmition request packet");
if (!capwap_crypt_sendto_fragmentpacket(&g_wtp.dtls, g_wtp.requestfragmentpacket)) {
log_printf(LOG_ERR, "Error to send request packet");
}
/* Update timeout */
ev_timer_again(EV_A_ w);
}
/* */
void wtp_dfa_start_retransmition_timer()
{
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timercontrol);
ev_timer_init(&g_wtp.timercontrol, wtp_dfa_retransmition_timeout_cb,
0., WTP_RETRANSMIT_INTERVAL / 1000.0);
ev_timer_again(EV_DEFAULT_UC_ &g_wtp.timercontrol);
}
/* */
void wtp_dfa_stop_retransmition_timer()
{
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timercontrol);
}
/* */
void wtp_timeout_stop_all()
{
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timercontrol);
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timerecho);
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timerkeepalive);
ev_timer_stop(EV_DEFAULT_UC_ &g_wtp.timerkeepalivedead);
}
void wtp_reset_state(void)
{
/* reset WTP state */
wtp_radio_reset();
wtp_free_reference_last_request();
wtp_free_reference_last_response();
wtp_free_packet_rxmng();
g_wtp.mtu = CAPWAP_MTU_DEFAULT;
g_wtp.remotetype = 0;
g_wtp.remoteseqnumber = WTP_INIT_REMOTE_SEQUENCE;
memset(&g_wtp.dtls.localaddr, 0, sizeof(g_wtp.dtls.localaddr));
memset(&g_wtp.dtls.peeraddr, 0, sizeof(g_wtp.dtls.peeraddr));
CAPWAP_SET_NETWORK_PORT(&g_wtp.net.localaddr, 0);
}