#include #include "session.h" #include "tcp_utils.h" #define EX_KEY_MAX_LEN 64 struct ex_schema { char key[EX_KEY_MAX_LEN]; session_ex_free_cb *free_cb; void *args; }; struct ex_manager { struct ex_schema schemas[EX_DATA_MAX_COUNT]; uint8_t count; }; static struct ex_manager g_ex_manager = {0}; /****************************************************************************** * session set/get ******************************************************************************/ void session_init(struct session *sess) { memset(sess, 0, sizeof(struct session)); } void session_set_id(struct session *sess, uint64_t id) { sess->id = id; } uint64_t session_get_id(const struct session *sess) { return sess->id; } void session_set_tuple(struct session *sess, const struct tuple6 *tuple) { memcpy(&sess->tuple, tuple, sizeof(struct tuple6)); } const struct tuple6 *session_get_tuple(const struct session *sess) { return &sess->tuple; } void session_set_tuple_dir(struct session *sess, enum session_dir dir) { sess->tuple_dir = dir; } enum session_dir session_get_tuple_dir(const struct session *sess) { return sess->tuple_dir; } void session_set_cur_dir(struct session *sess, enum session_dir dir) { sess->cur_dir = dir; } enum session_dir session_get_cur_dir(const struct session *sess) { return sess->cur_dir; } void session_set_state(struct session *sess, enum session_state state) { sess->state = state; } enum session_state session_get_state(const struct session *sess) { return sess->state; } void session_set_type(struct session *sess, enum session_type type) { sess->type = type; } enum session_type session_get_type(const struct session *sess) { return sess->type; } void session_set_dup_traffic(struct session *sess) { sess->dup = 1; } int session_has_dup_traffic(const struct session *sess) { return sess->dup; } void session_set_closing_reason(struct session *sess, enum closing_reason reason) { sess->reason = reason; } enum closing_reason session_get_closing_reason(const struct session *sess) { return sess->reason; } void session_inc_metric(struct session *sess, enum session_metric_index idx, uint64_t val) { sess->metrics[idx] += val; } void session_set_metric(struct session *sess, enum session_metric_index idx, uint64_t val) { sess->metrics[idx] = val; } uint64_t session_get_metric(const struct session *sess, enum session_metric_index idx) { return sess->metrics[idx]; } void session_set_timestamp(struct session *sess, enum session_timestamp_index idx, uint64_t timestamp) { sess->timestamps[idx] = timestamp; } uint64_t session_get_timestamp(const struct session *sess, enum session_timestamp_index idx) { return sess->timestamps[idx]; } void session_set_packet(struct session *sess, enum session_packet_index idx, const struct packet *pkt) { if (idx == SESSION_PACKET_CURRENT) { sess->packets[idx] = pkt; } else { if (sess->packets[idx]) { return; } sess->packets[idx] = packet_dup(pkt); } } void session_clean_packet(struct session *sess, enum session_packet_index idx) { if (idx == SESSION_PACKET_CURRENT) { sess->packets[idx] = NULL; } else { packet_free((struct packet *)sess->packets[idx]); sess->packets[idx] = NULL; } } const struct packet *session_get_packet(const struct session *sess, enum session_packet_index idx) { return sess->packets[idx]; } void session_set_user_data(struct session *sess, void *user_data) { sess->user_data = user_data; } void *session_get_user_data(const struct session *sess) { return sess->user_data; } /****************************************************************************** * to string ******************************************************************************/ const char *closing_reason_to_str(enum closing_reason reason) { switch (reason) { case CLOSING_BY_TIMEOUT: return "closing by timeout"; case CLOSING_BY_EVICTED: return "closing by evicted"; case CLOSING_BY_CLIENT_FIN: return "closing by client FIN"; case CLOSING_BY_CLIENT_RST: return "closing by client RST"; case CLOSING_BY_SERVER_FIN: return "closing by server FIN"; case CLOSING_BY_SERVER_RST: return "closing by server RST"; default: return "unknown"; } } const char *session_state_to_str(enum session_state state) { switch (state) { case SESSION_STATE_INIT: return "init"; case SESSION_STATE_OPENING: return "opening"; case SESSION_STATE_ACTIVE: return "active"; case SESSION_STATE_CLOSING: return "closing"; case SESSION_STATE_DISCARD: return "discard"; case SESSION_STATE_CLOSED: return "closed"; default: return "unknown"; } } const char *session_type_to_str(enum session_type type) { switch (type) { case SESSION_TYPE_TCP: return "TCP"; case SESSION_TYPE_UDP: return "UDP"; default: return "unknown"; } } const char *session_dir_to_str(enum session_dir dir) { switch (dir) { case SESSION_DIR_C2S: return "C2S"; case SESSION_DIR_S2C: return "S2C"; default: return "unknown"; } } void session_dump(struct session *sess) { char buffer[1024] = {0}; tuple6_to_str(session_get_tuple(sess), buffer, sizeof(buffer)); printf("session id : %" PRIu64 "\n", session_get_id(sess)); printf("session tuple : %s\n", buffer); printf("session tuple dir : %s\n", session_dir_to_str(session_get_tuple_dir(sess))); printf("session state : %s\n", session_state_to_str(session_get_state(sess))); printf("session type : %s\n", session_type_to_str(session_get_type(sess))); printf("session dup traffic : %d\n", session_has_dup_traffic(sess)); printf("session closing reason : %s\n", closing_reason_to_str(session_get_closing_reason(sess))); printf("session C2S packets : %" PRIu64 "\n", session_get_metric(sess, SESSION_METRIC_C2S_PACKETS)); printf("session C2S bytes : %" PRIu64 "\n", session_get_metric(sess, SESSION_METRIC_C2S_BYTES)); printf("session S2C packets : %" PRIu64 "\n", session_get_metric(sess, SESSION_METRIC_S2C_PACKETS)); printf("session S2C bytes : %" PRIu64 "\n", session_get_metric(sess, SESSION_METRIC_S2C_BYTES)); printf("session new time : %" PRIu64 "\n", session_get_timestamp(sess, SESSION_TIMESTAMP_NEW)); printf("session last time : %" PRIu64 "\n", session_get_timestamp(sess, SESSION_TIMESTAMP_LAST)); printf("session current packet ptr : %p\n", (void *)session_get_packet(sess, SESSION_PACKET_CURRENT)); printf("session current packet dir : %s\n", session_dir_to_str(session_get_cur_dir(sess))); printf("session ex data: \n"); for (uint8_t i = 0; i < g_ex_manager.count; i++) { printf(" ex_idx: %d, ex_key: %s, ex_data: %p\n", i, g_ex_manager.schemas[i].key, sess->ex_data[i]); } } /****************************************************************************** * tcp session ******************************************************************************/ static void tcp_sub_state_update(struct tcp_session *tcp_sess, enum session_dir dir, uint8_t tcp_flags) { if (tcp_flags & TH_SYN) { tcp_sess->sub_state |= (tcp_flags & TH_ACK) ? TCP_SYN_ACK_RCVD : TCP_SYN_RCVD; } if (tcp_flags & TH_FIN) { tcp_sess->sub_state |= dir == SESSION_DIR_C2S ? TCP_C2S_FIN_RCVD : TCP_S2C_FIN_RCVD; } if (tcp_flags & TH_RST) { /* * https://www.rfc-editor.org/rfc/rfc5961#section-3.2 * * If the RST bit is set and the sequence number exactly matches the * next expected sequence number (RCV.NXT), then TCP MUST reset the * connection. */ uint16_t curr_seq = dir == SESSION_DIR_C2S ? tcp_sess->c2s_seq : tcp_sess->s2c_seq; uint16_t expect_seq = dir == SESSION_DIR_C2S ? tcp_sess->s2c_ack : tcp_sess->c2s_ack; // if fin is received, the expected sequence number should be increased by 1 expect_seq += dir == SESSION_DIR_C2S ? (tcp_sess->sub_state & TCP_S2C_FIN_RCVD ? 1 : 0) : (tcp_sess->sub_state & TCP_C2S_FIN_RCVD ? 1 : 0); if (curr_seq == expect_seq) { tcp_sess->sub_state |= dir == SESSION_DIR_C2S ? TCP_C2S_RST_RCVD : TCP_S2C_RST_RCVD; } // RST is unverified if the sequence number is not as expected else { tcp_sess->sub_state |= dir == SESSION_DIR_C2S ? TCP_C2S_UNVERIFIED_RST_RCVD : TCP_S2C_UNVERIFIED_RST_RCVD; } } } int tcp_sess_init(struct session *sess, struct tcp_reassembly_options *opts) { struct tcp_session *tcp_sess = &sess->data.tcp; tcp_sess->c2s_data_queue = tcp_reassembly_new(opts); if (tcp_sess->c2s_data_queue == NULL) { return -1; } tcp_sess->s2c_data_queue = tcp_reassembly_new(opts); if (tcp_sess->s2c_data_queue == NULL) { tcp_reassembly_free(tcp_sess->c2s_data_queue); return -1; } return 0; } void tcp_sess_clean(struct session *sess) { struct tcp_session *tcp_sess = &sess->data.tcp; tcp_reassembly_free(tcp_sess->c2s_data_queue); tcp_reassembly_free(tcp_sess->s2c_data_queue); } void tcp_data_enqueue(struct session *sess, const struct pkt_layer *tcp_layer, uint64_t now) { struct tcp_session *tcp_sess = &sess->data.tcp; struct tcphdr *hdr = (struct tcphdr *)tcp_layer->hdr_ptr; uint8_t flags = tcp_hdr_get_flags(hdr); if (sess->cur_dir == SESSION_DIR_C2S) { tcp_sess->c2s_seq = tcp_hdr_get_seq(hdr); tcp_sess->c2s_ack = tcp_hdr_get_ack(hdr); if (flags & TH_SYN) { tcp_reassembly_init(tcp_sess->c2s_data_queue, tcp_sess->c2s_seq); } tcp_reassembly_insert(tcp_sess->c2s_data_queue, tcp_sess->c2s_seq, tcp_layer->pld_ptr, tcp_layer->pld_len, now); } else { tcp_sess->s2c_seq = tcp_hdr_get_seq(hdr); tcp_sess->s2c_ack = tcp_hdr_get_ack(hdr); if (flags & TH_SYN) { tcp_reassembly_init(tcp_sess->s2c_data_queue, tcp_sess->s2c_seq); } tcp_reassembly_insert(tcp_sess->s2c_data_queue, tcp_sess->s2c_seq, tcp_layer->pld_ptr, tcp_layer->pld_len, now); } tcp_sub_state_update(tcp_sess, sess->cur_dir, flags); } void tcp_data_dequeue(struct session *sess, uint32_t len) { struct tcp_session *tcp_sess = &sess->data.tcp; if (sess->cur_dir == SESSION_DIR_C2S) { tcp_reassembly_consume(tcp_sess->c2s_data_queue, len); } else { tcp_reassembly_consume(tcp_sess->s2c_data_queue, len); } } const char *tcp_data_peek(struct session *sess, uint32_t *len) { struct tcp_session *tcp_sess = &sess->data.tcp; if (sess->cur_dir == SESSION_DIR_C2S) { return tcp_reassembly_peek(tcp_sess->c2s_data_queue, len); } else { return tcp_reassembly_peek(tcp_sess->s2c_data_queue, len); } } void tcp_data_expire(struct session *sess, uint64_t now) { struct tcp_session *tcp_sess = &sess->data.tcp; tcp_reassembly_expire(tcp_sess->c2s_data_queue, now); tcp_reassembly_expire(tcp_sess->s2c_data_queue, now); } /****************************************************************************** * session ex data ******************************************************************************/ /* * the exdata prodoced by user, and comsumed by same user. * so, the exdata is not shared by different user. * otherwise, the exdata need dup by refer count, and free by refer count. * * if key exist, not allow update, return original index. */ uint8_t session_get_ex_new_index(const char *key, session_ex_free_cb *free_cb, void *args) { if (g_ex_manager.count >= EX_DATA_MAX_COUNT) { abort(); return EX_DATA_MAX_COUNT; } for (uint8_t i = 0; i < g_ex_manager.count; i++) { if (strcmp(g_ex_manager.schemas[i].key, key) == 0) { return i; } } uint8_t idx = g_ex_manager.count; g_ex_manager.count++; struct ex_schema *schema = &g_ex_manager.schemas[idx]; strncpy(schema->key, key, EX_KEY_MAX_LEN); schema->free_cb = free_cb; schema->args = args; return idx; } /* * Support update ex_data. * * if key exist: run free_cb free old value, then set new value. * if not run free_cb, old value will be memory leak. * if not allow update, new value will be memory leak. * if key not exist: set new value. */ void session_set_ex_data(struct session *sess, uint8_t idx, void *val) { if (idx >= g_ex_manager.count) { assert(0); return; } session_free_ex_data(sess, idx); sess->ex_data[idx] = val; } void *session_get0_ex_data(const struct session *sess, uint8_t idx) { if (idx >= g_ex_manager.count) { assert(0); return NULL; } return sess->ex_data[idx]; } /* * after set ex_data, the owner of ex_data is session, so user should not free it directly. * if user want to free ex_data, should use session_free_ex_data. */ void session_free_ex_data(struct session *sess, uint8_t idx) { if (idx >= g_ex_manager.count) { assert(0); return; } struct ex_schema *schema = &g_ex_manager.schemas[idx]; if (schema->free_cb != NULL && sess->ex_data[idx] != NULL) { printf("free ex_data, idx: %d, key: %s, val: %p\n", idx, schema->key, sess->ex_data[idx]); schema->free_cb(sess, idx, sess->ex_data[idx], schema->args); } sess->ex_data[idx] = NULL; } void session_free_all_ex_data(struct session *sess) { if (sess) { for (uint8_t i = 0; i < g_ex_manager.count; i++) { session_free_ex_data(sess, i); } } }