diff --git a/.gitignore b/.gitignore index 4a6e65142..b53bb27cf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ docs/book/ \#*# .#* __pycache__ +.clang-format +microkit-sdk-2.1.0/ \ No newline at end of file diff --git a/dep/libmicrokitco b/dep/libmicrokitco index 4bf88ee12..2fa0c1255 160000 --- a/dep/libmicrokitco +++ b/dep/libmicrokitco @@ -1 +1 @@ -Subproject commit 4bf88ee12c19823ff8c6d3122b6a298a5d8147ea +Subproject commit 2fa0c1255f43c8ea979aa1b9fe5265e49f2ac1c7 diff --git a/dep/sddf b/dep/sddf index d1f5252ea..7e0a30a6a 160000 --- a/dep/sddf +++ b/dep/sddf @@ -1 +1 @@ -Subproject commit d1f5252ea64edab6087552eb4220e23c019c2fbe +Subproject commit 7e0a30a6aac138dea05ec2bdb2f265eeb222a47a diff --git a/examples/firewall/filters/tcp_filter.c b/examples/firewall/filters/tcp_filter.c index 725249cf5..78a9730f0 100644 --- a/examples/firewall/filters/tcp_filter.c +++ b/examples/firewall/filters/tcp_filter.c @@ -3,20 +3,21 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include -#include -#include -#include -#include #include -#include #include +#include #include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include __attribute__((__section__(".fw_filter_config"))) fw_filter_config_t filter_config; __attribute__((__section__(".net_client_config"))) net_client_config_t net_config; @@ -29,11 +30,35 @@ fw_queue_t router_queue; /* Holds filtering rules and state */ fw_filter_state_t filter_state; -static void filter(void) -{ +/* Current tick, used to track aging instances */ +// Courtney: This has not yet been implemented, i.e. the TCP filter does not yet +// have access to the timer driver and thus does not receive ticks. +uint64_t curr_tick = 0; + +// Helper fn to allocate the tracking instance slot +static inline fw_filter_err_t fw_tcp_create_and_bind_instance(fw_filter_state_t *state, ipv4_hdr_t *ip_hdr, + tcp_hdr_t *tcp_hdr, uint16_t rule_id, + fw_tcp_instance_t **instance) { + uint32_t initial_seq = ntohl(tcp_hdr->seq); + fw_filter_err_t fw_err = fw_filter_add_instance(state, ip_hdr->src_ip, tcp_hdr->src_port, ip_hdr->dst_ip, + tcp_hdr->dst_port, rule_id, initial_seq); + // If the slot was successfully created or already existed, link the pointer + if (fw_err == FILTER_ERR_OKAY || fw_err == FILTER_ERR_DUPLICATE) { + // Dummy id used, + uint16_t dummy_rule_id; + fw_tcp_filter_find_action(state, ip_hdr->src_ip, tcp_hdr->src_port, ip_hdr->dst_ip, tcp_hdr->dst_port, + &dummy_rule_id, instance); + } + + return fw_err; +} + +static void filter(void) { bool transmitted = false; bool returned = false; bool reprocess = true; + int enqueue_err; + while (reprocess) { while (!net_queue_empty_active(&rx_queue)) { net_buff_desc_t buffer; @@ -45,14 +70,18 @@ static void filter(void) tcp_hdr_t *tcp_hdr = (tcp_hdr_t *)(pkt_vaddr + transport_layer_offset(ip_hdr)); uint16_t rule_id = 0; - fw_action_t action = fw_filter_find_action(&filter_state, ip_hdr->src_ip, tcp_hdr->src_port, ip_hdr->dst_ip, - tcp_hdr->dst_port, &rule_id); + fw_tcp_instance_t *instance = NULL; + fw_action_t action = fw_tcp_filter_find_action(&filter_state, ip_hdr->src_ip, tcp_hdr->src_port, + ip_hdr->dst_ip, tcp_hdr->dst_port, &rule_id, &instance); switch (action) { case FILTER_ACT_CONNECT: { + uint32_t initial_seq = ntohl(tcp_hdr->seq); + /* Add an established connection in shared memory for corresponding filter */ - fw_filter_err_t fw_err = fw_filter_add_instance(&filter_state, ip_hdr->src_ip, tcp_hdr->src_port, - ip_hdr->dst_ip, tcp_hdr->dst_port, rule_id); + fw_filter_err_t fw_err = + fw_filter_add_instance(&filter_state, ip_hdr->src_ip, tcp_hdr->src_port, ip_hdr->dst_ip, + tcp_hdr->dst_port, rule_id, initial_seq); if ((fw_err == FILTER_ERR_OKAY || fw_err == FILTER_ERR_DUPLICATE) && FW_DEBUG_OUTPUT) { sddf_printf( @@ -69,6 +98,7 @@ static void filter(void) filter_config.interface, rule_id, ipaddr_to_string(ip_hdr->src_ip, ip_addr_buf0), htons(tcp_hdr->src_port), ipaddr_to_string(ip_hdr->dst_ip, ip_addr_buf1), htons(tcp_hdr->dst_port), fw_filter_err_str[fw_err]); + goto drop_packet; } } case FILTER_ACT_ESTABLISHED: @@ -79,10 +109,14 @@ static void filter(void) tcp_hdr->check = 0; #endif - err = fw_enqueue(&router_queue, &buffer); - assert(!err); + enqueue_err = fw_enqueue(&router_queue, &buffer); + assert(!enqueue_err); transmitted = true; + if (instance != NULL) { + instance->timestamp = curr_tick; + } + if (FW_DEBUG_OUTPUT) { if (action == FILTER_ACT_ALLOW || action == FILTER_ACT_CONNECT) { sddf_printf( @@ -104,9 +138,10 @@ static void filter(void) } case FILTER_ACT_DROP: default: { + drop_packet: /* Return the buffer to the rx virtualiser */ - err = net_enqueue_free(&rx_queue, buffer); - assert(!err); + enqueue_err = net_enqueue_free(&rx_queue, buffer); + assert(!enqueue_err); returned = true; if (FW_DEBUG_OUTPUT) { @@ -119,6 +154,46 @@ static void filter(void) break; } } + + // Handle state transitions on packets that matched a tracking instances + if (instance != NULL) { + // Determine direction relative to the original connection initiator + bool is_forward = (instance->src_ip == ip_hdr->src_ip && instance->src_port == tcp_hdr->src_port); + + uint32_t packet_seq = ntohl(tcp_hdr->seq); + uint8_t flags = tcp_hdr->flags; + + // Payload length tracking calculations + uint16_t ip_hdr_len = ipv4_header_length(ip_hdr); + uint16_t tcp_hdr_len = (tcp_hdr->doff) * 4; + uint32_t payload_len = ntohs(ip_hdr->total_len) - ip_hdr_len - tcp_hdr_len; + + if (is_forward) { + uint32_t control_adjustment = ((flags & FW_TCP_SYN_BIT) || (flags & FW_TCP_FIN_BIT)) ? 1 : 0; + instance->local_next_seq = packet_seq + payload_len + control_adjustment; + } else { + uint32_t control_adjustment = ((flags & FW_TCP_SYN_BIT) || (flags & FW_TCP_FIN_BIT)) ? 1 : 0; + instance->extern_next_seq = packet_seq + payload_len + control_adjustment; + } + + fw_tcp_conn_state_t new_state = fw_tcp_next_state(instance->current_state, flags, is_forward); + if (new_state == TCP_INVALID) { + /* Flag for deletion */ + action = FILTER_ACT_DROP; + } else { + instance->current_state = new_state; + if (new_state != TCP_NONE) { + // Track historical telemetry data, we don't do it if none for debugging purposes + if (is_forward) { + instance->local.flags = flags; + instance->local.seq = packet_seq; + } else { + instance->external.flags = flags; + instance->external.seq = packet_seq; + } + } + } + } } net_request_signal_active(&rx_queue); @@ -139,8 +214,7 @@ static void filter(void) } } -microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) -{ +microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) { switch (microkit_msginfo_get_label(msginfo)) { case FILTER_SET_DEFAULT_ACTION: { fw_action_t action = microkit_mr_get(FILTER_SET_DEFAULT_ARG_ACTION); @@ -211,8 +285,7 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) return microkit_msginfo_new(0, 0); } -void notified(microkit_channel ch) -{ +void notified(microkit_channel ch) { if (ch == net_config.rx.id) { filter(); } else { @@ -221,8 +294,7 @@ void notified(microkit_channel ch) } } -void init(void) -{ +void init(void) { assert(net_config_check_magic((void *)&net_config)); net_queue_init(&rx_queue, net_config.rx.free_queue.vaddr, net_config.rx.active_queue.vaddr, @@ -236,4 +308,4 @@ void init(void) filter_config.external_instances, filter_config.instances_capacity, filter_config.initial_rules, filter_config.num_initial_rules, filter_config.num_external_instances); -} +} \ No newline at end of file diff --git a/include/lions/firewall/filter.h b/include/lions/firewall/filter.h index 5ea4d2b80..e92a5bf41 100644 --- a/include/lions/firewall/filter.h +++ b/include/lions/firewall/filter.h @@ -6,14 +6,14 @@ #pragma once +#include +#include #include -#include -#include -#include #include #include -#include -#include +#include +#include +#include /* The default action of a filter is always stored at index 0 of the rule table, and has a fixed rule ID of 0 */ @@ -21,23 +21,26 @@ and has a fixed rule ID of 0 */ #define DEFAULT_ACTION_RULE_ID 0 typedef enum { - /* no error */ - FILTER_ERR_OKAY = 0, - /* data structure is full */ - FILTER_ERR_FULL, - /* duplicate entry exists */ - FILTER_ERR_DUPLICATE, - /* entry clashes with existing entry */ - FILTER_ERR_CLASH, - /* rule id does not point to a valid entry, or is the default action rule id */ - FILTER_ERR_INVALID_RULE_ID, - /* unsupported action */ - FILTER_ERR_UNSUPPORTED_ACTION + /* no error */ + FILTER_ERR_OKAY = 0, + /* data structure is full */ + FILTER_ERR_FULL, + /* duplicate entry exists */ + FILTER_ERR_DUPLICATE, + /* entry clashes with existing entry */ + FILTER_ERR_CLASH, + /* rule id does not point to a valid entry, or is the default action rule id*/ + FILTER_ERR_INVALID_RULE_ID, + /* unsupported action */ + FILTER_ERR_UNSUPPORTED_ACTION } fw_filter_err_t; -static const char *fw_filter_err_str[] = { - "Ok.", "Out of memory error.", "Duplicate entry.", "Clashing entry.", "Invalid rule ID.", "Unsupported action." -}; +static const char *fw_filter_err_str[] = {"Ok.", + "Out of memory error.", + "Duplicate entry.", + "Clashing entry.", + "Invalid rule ID.", + "Unsupported action."}; typedef enum { /* allow traffic */ @@ -52,29 +55,30 @@ typedef enum { FILTER_ACT_ESTABLISHED = 5, } fw_action_t; -static const char *fw_filter_action_str[] = { "No rule", "Allow", "Drop", "Reject", "Connect", "Established" }; +static const char *fw_filter_action_str[] = { + "No rule", "Allow", "Drop", "Reject", "Connect", "Established"}; typedef struct fw_rule { - /* action to be applied to traffic matching rule */ - uint8_t action; - /* source IP */ - uint32_t src_ip; - /* destination IP */ - uint32_t dst_ip; - /* source port number */ - uint16_t src_port; - /* destination port number */ - uint16_t dst_port; - /* source subnet, 0 is any IP */ - uint8_t src_subnet; - /* destination subnet, 0 is any IP */ - uint8_t dst_subnet; - /* rule applies to any source port */ - bool src_port_any; - /* rule applies to any destination port */ - bool dst_port_any; - /* rule id assigned */ - uint16_t rule_id; + /* action to be applied to traffic matching rule */ + uint8_t action; + /* source IP */ + uint32_t src_ip; + /* destination IP */ + uint32_t dst_ip; + /* source port number */ + uint16_t src_port; + /* destination port number */ + uint16_t dst_port; + /* source subnet, 0 is any IP */ + uint8_t src_subnet; + /* destination subnet, 0 is any IP */ + uint8_t dst_subnet; + /* rule applies to any source port */ + bool src_port_any; + /* rule applies to any destination port */ + bool dst_port_any; + /* rule id assigned */ + uint16_t rule_id; } fw_rule_t; /** @@ -84,78 +88,87 @@ typedef struct fw_rule { * can search for and identify return traffic. */ typedef struct fw_instance { - /* source ip of traffic */ - uint32_t src_ip; - /* destination ip of traffic */ - uint32_t dst_ip; - /* source port of traffic */ - uint16_t src_port; - /* destination port of traffic */ - uint16_t dst_port; - /* ID of the rule this instance was created from. Allows instances - to be removed upon rule removal */ - uint16_t rule_id; + /* source ip of traffic */ + uint32_t src_ip; + /* destination ip of traffic */ + uint32_t dst_ip; + /* source port of traffic */ + uint16_t src_port; + /* destination port of traffic */ + uint16_t dst_port; + /* ID of the rule this instance was created from. Allows instances + to be removed upon rule removal */ + uint16_t rule_id; } fw_instance_t; typedef struct fw_instances_table { - uint16_t size; - fw_instance_t instances[]; + uint16_t size; + fw_instance_t instances[]; } fw_instances_table_t; typedef struct fw_rule_table { - uint16_t size; - fw_rule_t rules[]; + uint16_t size; + fw_rule_t rules[]; } fw_rule_table_t; typedef struct fw_rule_id_bitmap { - uint16_t last_allocated_rule_id; - uint64_t id_bitmap[]; + uint16_t last_allocated_rule_id; + uint64_t id_bitmap[]; } fw_rule_id_bitmap_t; typedef struct fw_filter_state { - /* filter rules */ - fw_rule_table_t *rule_table; - /* capacity of filter rules */ - uint16_t rules_capacity; - /* bitmap to track filter rule ids */ - fw_rule_id_bitmap_t *rule_id_bitmap; - /* instances created by this filter, - to be searched by neighbour filter */ - fw_instances_table_t *internal_instances_table; - /* instances created by neighbour filter, - to be searched by this filter */ - fw_instances_table_t *external_instances_table[FW_MAX_INTERFACES]; - /* capacity of both instance tables */ - uint16_t instances_capacity; - /* number of interfaces */ - uint8_t num_interfaces; + /* filter rules */ + fw_rule_table_t *rule_table; + /* capacity of filter rules */ + uint16_t rules_capacity; + /* bitmap to track filter rule ids */ + fw_rule_id_bitmap_t *rule_id_bitmap; + /* instances created by this filter, + to be searched by neighbour filter */ + fw_instances_table_t *internal_instances_table; + /* instances created by neighbour filter, + to be searched by this filter */ + fw_instances_table_t *external_instances_table[FW_MAX_INTERFACES]; + /* capacity of both instance tables */ + uint16_t instances_capacity; + /* number of interfaces */ + uint8_t num_interfaces; } fw_filter_state_t; /* PP call parameters for webserver to call filters and update rules */ typedef enum fw_filter_pp_type { - FILTER_SET_DEFAULT_ACTION = 0, - FILTER_ADD_RULE, - FILTER_DEL_RULE, + FILTER_SET_DEFAULT_ACTION = 0, + FILTER_ADD_RULE, + FILTER_DEL_RULE, } fw_filter_pp_type_t; -typedef enum { FILTER_SET_DEFAULT_ARG_ACTION = 0, FILTER_DEFAULT_NUM_ARGS } fw_filter_default_args_t; +typedef enum { + FILTER_SET_DEFAULT_ARG_ACTION = 0, + FILTER_DEFAULT_NUM_ARGS +} fw_filter_default_args_t; typedef enum { - FILTER_ADD_ARG_ACTION = 0, - FILTER_ADD_ARG_SRC_IP, - FILTER_ADD_ARG_SRC_SUBNET, - FILTER_ADD_ARG_SRC_PORT, - FILTER_ADD_ARG_SRC_ANY_PORT, - FILTER_ADD_ARG_DST_IP, - FILTER_ADD_ARG_DST_SUBNET, - FILTER_ADD_ARG_DST_PORT, - FILTER_ADD_ARG_DST_ANY_PORT, - FILTER_ADD_NUM_ARGS + FILTER_ADD_ARG_ACTION = 0, + FILTER_ADD_ARG_SRC_IP, + FILTER_ADD_ARG_SRC_SUBNET, + FILTER_ADD_ARG_SRC_PORT, + FILTER_ADD_ARG_SRC_ANY_PORT, + FILTER_ADD_ARG_DST_IP, + FILTER_ADD_ARG_DST_SUBNET, + FILTER_ADD_ARG_DST_PORT, + FILTER_ADD_ARG_DST_ANY_PORT, + FILTER_ADD_NUM_ARGS } fw_filter_add_args_t; -typedef enum { FILTER_DELETE_ARG_RULE_ID = 0, FILTER_DELETE_NUM_ARGS } fw_filter_delete_args_t; +typedef enum { + FILTER_DELETE_ARG_RULE_ID = 0, + FILTER_DELETE_NUM_ARGS +} fw_filter_delete_args_t; -typedef enum { FILTER_RET_ERR = 0, FILTER_RET_RULE_ID = 1 } fw_filter_ret_args_t; +typedef enum { + FILTER_RET_ERR = 0, + FILTER_RET_RULE_ID = 1 +} fw_filter_ret_args_t; /* The rule ID allocation bitmap uses blocks of 64 bits */ #define RULE_ID_BITMAP_BLK_SIZE 64 @@ -170,31 +183,33 @@ typedef enum { FILTER_RET_ERR = 0, FILTER_RET_RULE_ID = 1 } fw_filter_ret_args_t * @return FILTER_ERR_OKAY if ID allocated successfully, FILTER_ERR_FULL if no * IDs available. */ -static fw_filter_err_t rules_reserve_id(fw_filter_state_t *state, uint16_t *rule_id) -{ - if (state->rule_table->size >= state->rules_capacity) { - return FILTER_ERR_FULL; - } +static fw_filter_err_t rules_reserve_id(fw_filter_state_t *state, + uint16_t *rule_id) { + if (state->rule_table->size >= state->rules_capacity) { + return FILTER_ERR_FULL; + } - uint16_t id_to_reserve = DEFAULT_ACTION_RULE_ID; - for (uint16_t i = 0; i < state->rules_capacity; i++) { - uint16_t id_to_check = (state->rule_id_bitmap->last_allocated_rule_id + 1 + i) % state->rules_capacity; + uint16_t id_to_reserve = DEFAULT_ACTION_RULE_ID; + for (uint16_t i = 0; i < state->rules_capacity; i++) { + uint16_t id_to_check = + (state->rule_id_bitmap->last_allocated_rule_id + 1 + i) % + state->rules_capacity; - uint16_t block_idx = id_to_check / RULE_ID_BITMAP_BLK_SIZE; - uint64_t mask = 1ULL << (id_to_check % RULE_ID_BITMAP_BLK_SIZE); + uint16_t block_idx = id_to_check / RULE_ID_BITMAP_BLK_SIZE; + uint64_t mask = 1ULL << (id_to_check % RULE_ID_BITMAP_BLK_SIZE); - if (!(state->rule_id_bitmap->id_bitmap[block_idx] & mask)) { - state->rule_id_bitmap->id_bitmap[block_idx] |= mask; - state->rule_id_bitmap->last_allocated_rule_id = id_to_check; - id_to_reserve = id_to_check; - break; - } + if (!(state->rule_id_bitmap->id_bitmap[block_idx] & mask)) { + state->rule_id_bitmap->id_bitmap[block_idx] |= mask; + state->rule_id_bitmap->last_allocated_rule_id = id_to_check; + id_to_reserve = id_to_check; + break; } + } - assert(id_to_reserve != DEFAULT_ACTION_RULE_ID); - *rule_id = id_to_reserve; + assert(id_to_reserve != DEFAULT_ACTION_RULE_ID); + *rule_id = id_to_reserve; - return FILTER_ERR_OKAY; + return FILTER_ERR_OKAY; } /** @@ -207,21 +222,21 @@ static fw_filter_err_t rules_reserve_id(fw_filter_state_t *state, uint16_t *rule * @return FILTER_ERR_OKAY if ID was allocated and freed successfully, error * otherwise. */ -static fw_filter_err_t rules_free_id(fw_filter_state_t *state, uint16_t rule_id) -{ - if (rule_id == DEFAULT_ACTION_RULE_ID || rule_id >= state->rules_capacity) { - return FILTER_ERR_INVALID_RULE_ID; - } +static fw_filter_err_t rules_free_id(fw_filter_state_t *state, + uint16_t rule_id) { + if (rule_id == DEFAULT_ACTION_RULE_ID || rule_id >= state->rules_capacity) { + return FILTER_ERR_INVALID_RULE_ID; + } - uint16_t block_idx = rule_id / RULE_ID_BITMAP_BLK_SIZE; - uint64_t mask = 1ULL << (rule_id % RULE_ID_BITMAP_BLK_SIZE); + uint16_t block_idx = rule_id / RULE_ID_BITMAP_BLK_SIZE; + uint64_t mask = 1ULL << (rule_id % RULE_ID_BITMAP_BLK_SIZE); - if (!(state->rule_id_bitmap->id_bitmap[block_idx] & mask)) { - return FILTER_ERR_INVALID_RULE_ID; - } + if (!(state->rule_id_bitmap->id_bitmap[block_idx] & mask)) { + return FILTER_ERR_INVALID_RULE_ID; + } - state->rule_id_bitmap->id_bitmap[block_idx] &= ~mask; - return FILTER_ERR_OKAY; + state->rule_id_bitmap->id_bitmap[block_idx] &= ~mask; + return FILTER_ERR_OKAY; } /** @@ -241,74 +256,78 @@ static fw_filter_err_t rules_free_id(fw_filter_state_t *state, uint16_t rule_id) * * @return error status. */ -static inline fw_filter_err_t fw_filter_add_rule(fw_filter_state_t *state, uint32_t src_ip, uint16_t src_port, - uint32_t dst_ip, uint16_t dst_port, uint8_t src_subnet, - uint8_t dst_subnet, bool src_port_any, bool dst_port_any, - fw_action_t action, uint16_t *rule_id) -{ - if (state->rule_table->size >= state->rules_capacity) { - return FILTER_ERR_FULL; +static inline fw_filter_err_t +fw_filter_add_rule(fw_filter_state_t *state, uint32_t src_ip, uint16_t src_port, + uint32_t dst_ip, uint16_t dst_port, uint8_t src_subnet, + uint8_t dst_subnet, bool src_port_any, bool dst_port_any, + fw_action_t action, uint16_t *rule_id) { + if (state->rule_table->size >= state->rules_capacity) { + return FILTER_ERR_FULL; + } + + for (uint16_t i = 0; i < state->rule_table->size; i++) { + fw_rule_t *rule = (fw_rule_t *)(state->rule_table->rules + i); + + /* Check that this entry won't cause clashes */ + + /* One rule applies to any src port, one applies to a specific src port */ + if ((src_port_any && !rule->src_port_any) || + (!src_port_any && rule->src_port_any)) { + continue; } - for (uint16_t i = 0; i < state->rule_table->size; i++) { - fw_rule_t *rule = (fw_rule_t *)(state->rule_table->rules + i); - - /* Check that this entry won't cause clashes */ - - /* One rule applies to any src port, one applies to a specific src port */ - if ((src_port_any && !rule->src_port_any) || (!src_port_any && rule->src_port_any)) { - continue; - } - - /* One rule applies to any dst port, one applies to a specific dst port */ - if ((dst_port_any && !rule->dst_port_any) || (!dst_port_any && rule->dst_port_any)) { - continue; - } - - /* One rule applies to one port, one applies to another */ - if (src_port != rule->src_port || dst_port != rule->dst_port) { - continue; - } + /* One rule applies to any dst port, one applies to a specific dst port */ + if ((dst_port_any && !rule->dst_port_any) || + (!dst_port_any && rule->dst_port_any)) { + continue; + } - /* One rule applies to a larger subnet than the other */ - if (src_subnet != rule->src_subnet || dst_subnet != rule->dst_subnet) { - continue; - } + /* One rule applies to one port, one applies to another */ + if (src_port != rule->src_port || dst_port != rule->dst_port) { + continue; + } - /* Rules apply to different source subnets */ - if ((subnet_mask(src_subnet) & src_ip) != (subnet_mask(rule->src_subnet) & rule->src_ip)) { - continue; - } + /* One rule applies to a larger subnet than the other */ + if (src_subnet != rule->src_subnet || dst_subnet != rule->dst_subnet) { + continue; + } - /* Rules apply to different destination subnets */ - if ((subnet_mask(dst_subnet) & dst_ip) != (subnet_mask(rule->dst_subnet) & rule->dst_ip)) { - continue; - } + /* Rules apply to different source subnets */ + if ((subnet_mask(src_subnet) & src_ip) != + (subnet_mask(rule->src_subnet) & rule->src_ip)) { + continue; + } - /* There is a clash! */ - if (action == rule->action) { - return FILTER_ERR_DUPLICATE; - } else { - return FILTER_ERR_CLASH; - } + /* Rules apply to different destination subnets */ + if ((subnet_mask(dst_subnet) & dst_ip) != + (subnet_mask(rule->dst_subnet) & rule->dst_ip)) { + continue; } - fw_rule_t *empty_slot = state->rule_table->rules + state->rule_table->size; - empty_slot->src_ip = subnet_mask(src_subnet) & src_ip; - empty_slot->src_port = src_port; - empty_slot->dst_ip = subnet_mask(dst_subnet) & dst_ip; - empty_slot->dst_port = dst_port; - empty_slot->src_subnet = src_subnet; - empty_slot->dst_subnet = dst_subnet; - empty_slot->src_port_any = src_port_any; - empty_slot->dst_port_any = dst_port_any; - empty_slot->action = action; - - assert(rules_reserve_id(state, rule_id) == FILTER_ERR_OKAY); - - empty_slot->rule_id = *rule_id; - state->rule_table->size++; - return FILTER_ERR_OKAY; + /* There is a clash! */ + if (action == rule->action) { + return FILTER_ERR_DUPLICATE; + } else { + return FILTER_ERR_CLASH; + } + } + + fw_rule_t *empty_slot = state->rule_table->rules + state->rule_table->size; + empty_slot->src_ip = subnet_mask(src_subnet) & src_ip; + empty_slot->src_port = src_port; + empty_slot->dst_ip = subnet_mask(dst_subnet) & dst_ip; + empty_slot->dst_port = dst_port; + empty_slot->src_subnet = src_subnet; + empty_slot->dst_subnet = dst_subnet; + empty_slot->src_port_any = src_port_any; + empty_slot->dst_port_any = dst_port_any; + empty_slot->action = action; + + assert(rules_reserve_id(state, rule_id) == FILTER_ERR_OKAY); + + empty_slot->rule_id = *rule_id; + state->rule_table->size++; + return FILTER_ERR_OKAY; } /** @@ -325,50 +344,59 @@ static inline fw_filter_err_t fw_filter_add_rule(fw_filter_state_t *state, uint3 * @param num_rules number of initial rules. * @param num_external_instances number of external instances. */ -static inline void fw_filter_state_init(fw_filter_state_t *state, void *rules, void *rule_id_bitmap, - uint16_t rules_capacity, void *internal_instances, - region_resource_t *external_instances, uint16_t instances_capacity, - fw_rule_t *initial_rules, uint8_t num_rules, uint8_t num_external_instances) -{ - state->rule_table = (fw_rule_table_t *)rules; - state->rules_capacity = rules_capacity; - state->rule_id_bitmap = (fw_rule_id_bitmap_t *)rule_id_bitmap; - state->instances_capacity = instances_capacity; - state->internal_instances_table = (fw_instances_table_t *)internal_instances; - state->num_interfaces = num_external_instances; - /* Populate the array of possible external instance tables */ - for (size_t i = 0; i < num_external_instances; i++) { - state->external_instances_table[i] = (fw_instances_table_t *)external_instances[i].vaddr; - } - - /* Allocate the default action rule ID for the default action */ - uint16_t default_block_idx = DEFAULT_ACTION_RULE_ID / RULE_ID_BITMAP_BLK_SIZE; - uint64_t default_mask = 1ULL << (DEFAULT_ACTION_RULE_ID % RULE_ID_BITMAP_BLK_SIZE); - - /* No other rules should exist at this point */ - assert((state->rule_id_bitmap->id_bitmap[default_block_idx] & default_mask) == 0); - assert(state->rule_table->size == 0); - - /* First rule must be the default rule */ - assert(num_rules >= 1); - assert(initial_rules[DEFAULT_ACTION_IDX].src_subnet == 0 && initial_rules[DEFAULT_ACTION_IDX].src_port_any); - assert(initial_rules[DEFAULT_ACTION_IDX].dst_subnet == 0 && initial_rules[DEFAULT_ACTION_IDX].dst_port_any); - assert(initial_rules[DEFAULT_ACTION_IDX].rule_id == DEFAULT_ACTION_RULE_ID); - - state->rule_id_bitmap->id_bitmap[default_block_idx] |= default_mask; - state->rule_id_bitmap->last_allocated_rule_id = DEFAULT_ACTION_RULE_ID; - - state->rule_table->rules[DEFAULT_ACTION_IDX] = initial_rules[DEFAULT_ACTION_IDX]; - state->rule_table->size++; - - for (uint8_t r = 1; r < num_rules; r++) { - fw_filter_err_t err = fw_filter_add_rule(state, initial_rules[r].src_ip, initial_rules[r].src_port, - initial_rules[r].dst_ip, initial_rules[r].dst_port, - initial_rules[r].src_subnet, initial_rules[r].dst_subnet, - initial_rules[r].src_port_any, initial_rules[r].dst_port_any, - initial_rules[r].action, &initial_rules[r].rule_id); - assert(err == FILTER_ERR_OKAY); - } +static inline void +fw_filter_state_init(fw_filter_state_t *state, void *rules, + void *rule_id_bitmap, uint16_t rules_capacity, + void *internal_instances, + region_resource_t *external_instances, + uint16_t instances_capacity, fw_rule_t *initial_rules, + uint8_t num_rules, uint8_t num_external_instances) { + state->rule_table = (fw_rule_table_t *)rules; + state->rules_capacity = rules_capacity; + state->rule_id_bitmap = (fw_rule_id_bitmap_t *)rule_id_bitmap; + state->instances_capacity = instances_capacity; + state->internal_instances_table = (fw_instances_table_t *)internal_instances; + state->num_interfaces = num_external_instances; + /* Populate the array of possible external instance tables */ + for (size_t i = 0; i < num_external_instances; i++) { + state->external_instances_table[i] = + (fw_instances_table_t *)external_instances[i].vaddr; + } + + /* Allocate the default action rule ID for the default action */ + uint16_t default_block_idx = DEFAULT_ACTION_RULE_ID / RULE_ID_BITMAP_BLK_SIZE; + uint64_t default_mask = 1ULL + << (DEFAULT_ACTION_RULE_ID % RULE_ID_BITMAP_BLK_SIZE); + + /* No other rules should exist at this point */ + assert((state->rule_id_bitmap->id_bitmap[default_block_idx] & default_mask) == + 0); + assert(state->rule_table->size == 0); + + /* First rule must be the default rule */ + assert(num_rules >= 1); + assert(initial_rules[DEFAULT_ACTION_IDX].src_subnet == 0 && + initial_rules[DEFAULT_ACTION_IDX].src_port_any); + assert(initial_rules[DEFAULT_ACTION_IDX].dst_subnet == 0 && + initial_rules[DEFAULT_ACTION_IDX].dst_port_any); + assert(initial_rules[DEFAULT_ACTION_IDX].rule_id == DEFAULT_ACTION_RULE_ID); + + state->rule_id_bitmap->id_bitmap[default_block_idx] |= default_mask; + state->rule_id_bitmap->last_allocated_rule_id = DEFAULT_ACTION_RULE_ID; + + state->rule_table->rules[DEFAULT_ACTION_IDX] = + initial_rules[DEFAULT_ACTION_IDX]; + state->rule_table->size++; + + for (uint8_t r = 1; r < num_rules; r++) { + fw_filter_err_t err = fw_filter_add_rule( + state, initial_rules[r].src_ip, initial_rules[r].src_port, + initial_rules[r].dst_ip, initial_rules[r].dst_port, + initial_rules[r].src_subnet, initial_rules[r].dst_subnet, + initial_rules[r].src_port_any, initial_rules[r].dst_port_any, + initial_rules[r].action, &initial_rules[r].rule_id); + assert(err == FILTER_ERR_OKAY); + } } /** @@ -380,37 +408,41 @@ static inline void fw_filter_state_init(fw_filter_state_t *state, void *rules, v * @param src_port source port of instance traffic. * @param dst_ip destination ip of instance traffic. * @param dst_port destination port of instance traffic. - * @param default_action whether connect rule was matched via filter's default action. + * @param default_action whether connect rule was matched via filter's default + * action. * @param rule_id id of connect rule. * * @return error status. */ -static inline fw_filter_err_t fw_filter_add_instance(fw_filter_state_t *state, uint32_t src_ip, uint16_t src_port, - uint32_t dst_ip, uint16_t dst_port, uint16_t rule_id) -{ - if (state->internal_instances_table->size >= state->instances_capacity) { - return FILTER_ERR_FULL; - } - - for (uint16_t i = 0; i < state->internal_instances_table->size; i++) { - fw_instance_t *instance = state->internal_instances_table->instances + i; - - /* Connection has already been established */ - if (instance->rule_id == rule_id && instance->src_ip == src_ip && instance->src_port == src_port - && instance->dst_ip == dst_ip && instance->dst_port == dst_port) { - return FILTER_ERR_DUPLICATE; - } +static inline fw_filter_err_t +fw_filter_add_instance(fw_filter_state_t *state, uint32_t src_ip, + uint16_t src_port, uint32_t dst_ip, uint16_t dst_port, + uint16_t rule_id, uint32_t seq) { + if (state->internal_instances_table->size >= state->instances_capacity) { + return FILTER_ERR_FULL; + } + + for (uint16_t i = 0; i < state->internal_instances_table->size; i++) { + fw_instance_t *instance = state->internal_instances_table->instances + i; + + /* Connection has already been established */ + if (instance->rule_id == rule_id && instance->src_ip == src_ip && + instance->src_port == src_port && instance->dst_ip == dst_ip && + instance->dst_port == dst_port) { + return FILTER_ERR_DUPLICATE; } - - fw_instance_t *empty_slot = state->internal_instances_table->instances + state->internal_instances_table->size; - empty_slot->rule_id = rule_id; - empty_slot->src_ip = src_ip; - empty_slot->src_port = src_port; - empty_slot->dst_ip = dst_ip; - empty_slot->dst_port = dst_port; - state->internal_instances_table->size++; - - return FILTER_ERR_OKAY; + } + + fw_instance_t *empty_slot = state->internal_instances_table->instances + + state->internal_instances_table->size; + empty_slot->rule_id = rule_id; + empty_slot->src_ip = src_ip; + empty_slot->src_port = src_port; + empty_slot->dst_ip = dst_ip; + empty_slot->dst_port = dst_port; + state->internal_instances_table->size++; + + return FILTER_ERR_OKAY; } /** @@ -428,73 +460,79 @@ static inline fw_filter_err_t fw_filter_add_instance(fw_filter_state_t *state, u * * @return filter action to be applied. None is returned if no match is found. */ -static inline fw_action_t fw_filter_find_action(fw_filter_state_t *state, uint32_t src_ip, uint16_t src_port, - uint32_t dst_ip, uint16_t dst_port, uint16_t *rule_id) -{ - /* First check external instances */ - for (size_t iface = 0; iface < state->num_interfaces; iface++) { - for (uint16_t i = 0; i < state->external_instances_table[iface]->size; i++) { - fw_instance_t *instance = state->external_instances_table[iface]->instances + i; - - if (instance->src_port != dst_port || instance->dst_port != src_port) { - continue; - } - - if (instance->src_ip != dst_ip || instance->dst_ip != src_ip) { - continue; - } - - *rule_id = instance->rule_id; - return FILTER_ACT_ESTABLISHED; - } +static inline fw_action_t +fw_filter_find_action(fw_filter_state_t *state, uint32_t src_ip, + uint16_t src_port, uint32_t dst_ip, uint16_t dst_port, + uint16_t *rule_id) { + /* First check external instances */ + for (size_t iface = 0; iface < state->num_interfaces; iface++) { + for (uint16_t i = 0; i < state->external_instances_table[iface]->size; + i++) { + fw_instance_t *instance = + state->external_instances_table[iface]->instances + i; + + if (instance->src_port != dst_port || instance->dst_port != src_port) { + continue; + } + + if (instance->src_ip != dst_ip || instance->dst_ip != src_ip) { + continue; + } + + *rule_id = instance->rule_id; + return FILTER_ACT_ESTABLISHED; } + } - /* Check rules for best match otherwise we match with the default rule */ - fw_rule_t *match = &state->rule_table->rules[DEFAULT_ACTION_IDX]; - for (uint16_t i = DEFAULT_ACTION_IDX + 1; i < state->rule_table->size; i++) { - fw_rule_t *rule = state->rule_table->rules + i; + /* Check rules for best match otherwise we match with the default rule */ + fw_rule_t *match = &state->rule_table->rules[DEFAULT_ACTION_IDX]; + for (uint16_t i = DEFAULT_ACTION_IDX + 1; i < state->rule_table->size; i++) { + fw_rule_t *rule = state->rule_table->rules + i; - /* Check port numbers first */ - if ((!rule->src_port_any && rule->src_port != src_port) - || (!rule->dst_port_any && rule->dst_port != dst_port)) { - continue; - } + /* Check port numbers first */ + if ((!rule->src_port_any && rule->src_port != src_port) || + (!rule->dst_port_any && rule->dst_port != dst_port)) { + continue; + } - /* Match on src addr first */ - if ((subnet_mask(rule->src_subnet) & src_ip) != (subnet_mask(rule->src_subnet) & rule->src_ip)) { - continue; - } + /* Match on src addr first */ + if ((subnet_mask(rule->src_subnet) & src_ip) != + (subnet_mask(rule->src_subnet) & rule->src_ip)) { + continue; + } - /* Match on src addr first */ - if ((subnet_mask(rule->dst_subnet) & dst_ip) != (subnet_mask(rule->dst_subnet) & rule->dst_ip)) { - continue; - } + /* Match on src addr first */ + if ((subnet_mask(rule->dst_subnet) & dst_ip) != + (subnet_mask(rule->dst_subnet) & rule->dst_ip)) { + continue; + } - /* This if the first match we've found */ - if (match == NULL) { - match = rule; - } + /* This if the first match we've found */ + if (match == NULL) { + match = rule; + } - /* We give priority to source matches over destination matches */ - if (rule->src_subnet == match->src_subnet) { - if (rule->dst_subnet == match->dst_subnet) { - if (rule->src_port_any == match->src_port_any) { - if (!rule->dst_port_any && match->dst_port_any) { - match = rule; /* destination port number is a stronger match */ - } - } else if (!rule->src_port_any && match->src_port_any) { - match = rule; /* source port number is a stronger match */ - } - } else if (rule->dst_subnet > match->dst_subnet) { /* destination subnet is a longer match */ - match = rule; - } - } else if (rule->src_subnet > match->src_subnet) { - match = rule; /* source subnet is a longer match */ + /* We give priority to source matches over destination matches */ + if (rule->src_subnet == match->src_subnet) { + if (rule->dst_subnet == match->dst_subnet) { + if (rule->src_port_any == match->src_port_any) { + if (!rule->dst_port_any && match->dst_port_any) { + match = rule; /* destination port number is a stronger match */ + } + } else if (!rule->src_port_any && match->src_port_any) { + match = rule; /* source port number is a stronger match */ } + } else if (rule->dst_subnet > + match->dst_subnet) { /* destination subnet is a longer match */ + match = rule; + } + } else if (rule->src_subnet > match->src_subnet) { + match = rule; /* source subnet is a longer match */ } + } - *rule_id = match->rule_id; - return (fw_action_t)match->action; + *rule_id = match->rule_id; + return (fw_action_t)match->action; } /** @@ -506,23 +544,24 @@ static inline fw_action_t fw_filter_find_action(fw_filter_state_t *state, uint32 * * @return error status. */ -static fw_filter_err_t fw_filter_remove_instances(fw_filter_state_t *state, uint16_t rule_id) -{ - uint16_t i = 0; - while (i < state->internal_instances_table->size) { - fw_instance_t *instance = state->internal_instances_table->instances + i; - - if (rule_id != instance->rule_id) { - i++; - continue; - } - - state->internal_instances_table->instances[i] = - state->internal_instances_table->instances[state->internal_instances_table->size - 1]; - state->internal_instances_table->size--; +static fw_filter_err_t fw_filter_remove_instances(fw_filter_state_t *state, + uint16_t rule_id) { + uint16_t i = 0; + while (i < state->internal_instances_table->size) { + fw_instance_t *instance = state->internal_instances_table->instances + i; + + if (rule_id != instance->rule_id) { + i++; + continue; } - return FILTER_ERR_OKAY; + state->internal_instances_table->instances[i] = + state->internal_instances_table + ->instances[state->internal_instances_table->size - 1]; + state->internal_instances_table->size--; + } + + return FILTER_ERR_OKAY; } /** @@ -533,21 +572,23 @@ static fw_filter_err_t fw_filter_remove_instances(fw_filter_state_t *state, uint * * @return error status. */ -static inline fw_filter_err_t fw_filter_update_default_action(fw_filter_state_t *state, fw_action_t new_action) -{ - fw_action_t old_action = state->rule_table->rules[DEFAULT_ACTION_IDX].action; - if (new_action == old_action) { - return FILTER_ERR_OKAY; - } +static inline fw_filter_err_t +fw_filter_update_default_action(fw_filter_state_t *state, + fw_action_t new_action) { + fw_action_t old_action = state->rule_table->rules[DEFAULT_ACTION_IDX].action; + if (new_action == old_action) { + return FILTER_ERR_OKAY; + } - if (old_action == FILTER_ACT_CONNECT) { - fw_filter_err_t err = fw_filter_remove_instances(state, DEFAULT_ACTION_RULE_ID); - assert(err == FILTER_ERR_OKAY); - } + if (old_action == FILTER_ACT_CONNECT) { + fw_filter_err_t err = + fw_filter_remove_instances(state, DEFAULT_ACTION_RULE_ID); + assert(err == FILTER_ERR_OKAY); + } - state->rule_table->rules[DEFAULT_ACTION_IDX].action = new_action; + state->rule_table->rules[DEFAULT_ACTION_IDX].action = new_action; - return FILTER_ERR_OKAY; + return FILTER_ERR_OKAY; } /** @@ -558,29 +599,29 @@ static inline fw_filter_err_t fw_filter_update_default_action(fw_filter_state_t * * @return error status. */ -static inline fw_filter_err_t fw_filter_remove_rule(fw_filter_state_t *state, uint16_t rule_id) -{ - fw_filter_err_t err = rules_free_id(state, rule_id); - if (err != FILTER_ERR_OKAY) { - return err; - } - - fw_rule_t *rule = NULL; - for (uint16_t i = DEFAULT_ACTION_IDX + 1; i < state->rule_table->size; i++) { - if (state->rule_table->rules[i].rule_id == rule_id) { - rule = state->rule_table->rules + i; - break; - } +static inline fw_filter_err_t fw_filter_remove_rule(fw_filter_state_t *state, + uint16_t rule_id) { + fw_filter_err_t err = rules_free_id(state, rule_id); + if (err != FILTER_ERR_OKAY) { + return err; + } + + fw_rule_t *rule = NULL; + for (uint16_t i = DEFAULT_ACTION_IDX + 1; i < state->rule_table->size; i++) { + if (state->rule_table->rules[i].rule_id == rule_id) { + rule = state->rule_table->rules + i; + break; } + } - assert(rule != NULL); + assert(rule != NULL); - if ((fw_action_t)rule->action == FILTER_ACT_CONNECT) { - assert(fw_filter_remove_instances(state, rule_id) == FILTER_ERR_OKAY); - } + if ((fw_action_t)rule->action == FILTER_ACT_CONNECT) { + assert(fw_filter_remove_instances(state, rule_id) == FILTER_ERR_OKAY); + } - generic_array_shift(state->rule_table->rules, sizeof(fw_rule_t), state->rule_table->size, - rule - state->rule_table->rules); - state->rule_table->size--; - return FILTER_ERR_OKAY; + generic_array_shift(state->rule_table->rules, sizeof(fw_rule_t), + state->rule_table->size, rule - state->rule_table->rules); + state->rule_table->size--; + return FILTER_ERR_OKAY; } diff --git a/include/lions/firewall/tcp_filter.h b/include/lions/firewall/tcp_filter.h new file mode 100644 index 000000000..091143f1a --- /dev/null +++ b/include/lions/firewall/tcp_filter.h @@ -0,0 +1,379 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +// TODO: +// - Handle simultaneous closing https://www.rfc-editor.org/rfc/rfc793#section-3.5 +// - Figure out a solution to whether local or external instances should be checked first +// - Rectify whether dst_ip/src_ip should be stored in src_ip/dst_ip of instances (which filter's perspective?) +// - Implement timer ticks for removing timed out and closed connections from instances +// - Handle re-opening connections after closure +// - Handle re-using filter data structure with different pointer types +// - TCP instance regions are a different size to the generic, since `fw_tcp_instance_t` and `fw_instance_t` are not +// necessarily the same size. This needs to be reflected in the metaprogram. + +/* Data recorded from the last received packet in a TCP connection */ +typedef struct fw_tcp_interface_state { + uint8_t flags; /* flags set in last received instance packet. fin flag is only unset upon final ack */ + uint32_t seq; /* sequence number of last received instance packet. Once fin is received, seq is only implemented + upon final ack */ +} fw_tcp_interface_state_t; + +/* TCP filter specific instance */ +typedef struct fw_tcp_instance { + /* source ip of traffic */ + uint32_t src_ip; + /* destination ip of traffic */ + uint32_t dst_ip; + /* source port of traffic */ + uint16_t src_port; + /* destination port of traffic */ + uint16_t dst_port; + /* What state it is currently expected to be in currently */ + fw_tcp_conn_state_t current_state; + + fw_tcp_interface_state_t local; + fw_tcp_interface_state_t external; + /* Byte numbers expected from both sides */ + uint32_t local_next_seq; + uint32_t extern_next_seq; + /* tick of last packet received */ + uint64_t timestamp; + /* ID of the rule this instance was created from. Allows instances + to be removed upon rule removal */ + uint16_t rule_id; +} fw_tcp_instance_t; + +/* States relative to the filter's instance based on + * https://www.ibm.com/support/pages/flowchart-tcp-connections-and-their-definition/ */ +typedef enum { + /* no traffic has been seen (listen and closed combined) */ + TCP_NONE, + /* TCP client has sent its first message in the three-way handshake. This message has the SYN bit set */ + TCP_SYN_SENT, + /* TCP server has received the first TCP message from the client in the three-way TCP open hand-shake, aka SYN-ACK + received */ + TCP_SYN_RCVD, + /* three-way syn handshake has been completed, ACK from original client returned */ + TCP_ESTABLISHED, + /* local side sent a FIN; waiting for an ACK or a FIN from the remote side (FIN-WAIT-1) */ + TCP_FIN_WAIT_1, + /* remote side acknowledged our FIN; waiting for the remote side's FIN (FIN-WAIT-2) */ + TCP_FIN_WAIT_2, + /* remote side sent a FIN and we acknowledged it; waiting for local application to close (CLOSE-WAIT) */ + TCP_CLOSE_WAIT, + /* local side sent its final FIN after being in CLOSE_WAIT; waiting for final ACK (LAST-ACK) */ + TCP_LAST_ACK, + /* simultaneous close: both sides sent FINs without receiving ACKs first (CLOSING) */ + TCP_CLOSING, + /* this connection is closed but the firewall is waiting so stray packets are handled (TIME-WAIT) */ + TCP_TIME_WAIT, + /* A specific error case for if the transition is invalid and should be dropped */ + TCP_INVALID, +} fw_tcp_conn_state_t; + +/* Bits used to store TCP flags */ +#define FW_TCP_FIN_BIT (1 << 0) +#define FW_TCP_SYN_BIT (1 << 1) +#define FW_TCP_RST_BIT (1 << 2) +#define FW_TCP_ACK_BIT (1 << 4) + +/* Convert TCP flags to a word */ +static inline uint8_t fw_tcp_flags_to_bits(bool syn, bool ack, bool fin, bool rst) { + uint8_t result = 0; + if (syn) { + result |= FW_TCP_SYN_BIT; + } + + if (ack) { + result |= FW_TCP_ACK_BIT; + } + + if (fin) { + result |= FW_TCP_FIN_BIT; + } + + if (rst) { + result |= FW_TCP_RST_BIT; + } + + return result; +} + +/* Check if a network packet matches a tracked connection instance in either direction */ +static inline bool fw_tcp_instance_match(const fw_tcp_instance_t *instance, uint32_t src_ip, uint16_t src_port, + uint32_t dst_ip, uint16_t dst_port) { + bool forward = (instance->src_ip == src_ip && instance->src_port == src_port && instance->dst_ip == dst_ip && + instance->dst_port == dst_port); + + bool reverse = (instance->src_ip == dst_ip && instance->src_port == dst_port && instance->dst_ip == src_ip && + instance->dst_port == src_port); + + return forward || reverse; +} + +/* Find firewall action for a given src & dst ip & port. Matches instances first, +followed by the most specific rule. */ +static fw_action_t fw_tcp_filter_find_action(fw_filter_state_t *state, uint32_t src_ip, uint16_t src_port, + uint32_t dst_ip, uint16_t dst_port, uint16_t *rule_id, + fw_tcp_instance_t **instance) { + /* We give priority to local instances */ + for (uint16_t i = 0; i < state->internal_instances_table->size; i++) { + fw_tcp_instance_t *curr_instance = (fw_tcp_instance_t *)((uint8_t *)state->internal_instances_table->instances + + (i * sizeof(fw_tcp_instance_t))); + if (!fw_tcp_instance_match(curr_instance, src_ip, src_port, dst_ip, dst_port)) { + continue; + } + + *rule_id = curr_instance->rule_id; + if (instance) { + *instance = curr_instance; + } + return FILTER_ACT_ESTABLISHED; + } + + /* Then the other filter's instances */ + for (uint16_t i = 0; i < state->external_instances_table->size; i++) { + fw_tcp_instance_t *curr_instance = (fw_tcp_instance_t *)((uint8_t *)state->external_instances_table->instances + + (i * sizeof(fw_tcp_instance_t))); + if (!fw_tcp_instance_match(curr_instance, src_ip, src_port, dst_ip, dst_port)) { + continue; + } + + *rule_id = curr_instance->rule_id; + if (instance) { + *instance = curr_instance; + } + return FILTER_ACT_ESTABLISHED; + } + + /* Check rules for best match otherwise we match with the default rule */ + fw_rule_t *match = NULL; + for (uint16_t i = DEFAULT_ACTION_IDX + 1; i < state->rule_table->size; i++) { + fw_rule_t *rule = state->rule_table->rules + i; + + /* Check port numbers first */ + if ((!rule->src_port_any && rule->src_port != src_port) || + (!rule->dst_port_any && rule->dst_port != dst_port)) { + continue; + } + + /* Match on src addr first */ + if ((subnet_mask(rule->src_subnet) & src_ip) != (subnet_mask(rule->src_subnet) & rule->src_ip)) { + continue; + } + + /* Match on src addr first */ + if ((subnet_mask(rule->dst_subnet) & dst_ip) != (subnet_mask(rule->dst_subnet) & rule->dst_ip)) { + continue; + } + + /* This if the first match we've found */ + if (match == NULL) { + match = rule; + } + + /* We give priority to source matches over destination matches */ + if (rule->src_subnet == match->src_subnet) { + if (rule->dst_subnet == match->dst_subnet) { + if (rule->src_port_any == match->src_port_any) { + if (!rule->dst_port_any && match->dst_port_any) { + match = rule; /* destination port number is a stronger match */ + } + } else if (!rule->src_port_any && match->src_port_any) { + match = rule; /* source port number is a stronger match */ + } + } else if (rule->dst_subnet > match->dst_subnet) { /* destination subnet is a longer match */ + match = rule; + } + } else if (rule->src_subnet > match->src_subnet) { + match = rule; /* source subnet is a longer match */ + } + } + + if (match == NULL) { + match = &state->rule_table->rules[DEFAULT_ACTION_IDX]; + } + + *rule_id = match->rule_id; + return (fw_action_t)match->action; +} + +/* Valid flags for the TCP final ack sent/closed connection states */ +static inline bool fw_tcp_final_ack_sent(uint8_t local_flags, uint8_t extern_flags) { + return ((local_flags & FW_TCP_FIN_BIT) && (extern_flags & FW_TCP_FIN_BIT) && (local_flags & FW_TCP_ACK_BIT) && + (extern_flags & FW_TCP_ACK_BIT)); +} + +static inline fw_tcp_conn_state_t fw_tcp_next_state(fw_tcp_conn_state_t current, uint8_t flags, bool is_forward) { + bool syn = (flags & FW_TCP_SYN_BIT); + bool ack = (flags & FW_TCP_ACK_BIT); + bool fin = (flags & FW_TCP_FIN_BIT); + bool rst = (flags & FW_TCP_RST_BIT); + + // Immediate teardown if RST flag is present + if (rst) { + return TCP_NONE; + } + + switch (current) { + case TCP_NONE: + // Initiates handshake + if (syn && !ack && is_forward) + return TCP_SYN_SENT; + // TCP_INVALID for out-of-order packets to unallocated sessions + return TCP_INVALID; + case TCP_SYN_SENT: + // Syn ack response + if (syn && ack && !is_forward) + return TCP_SYN_RCVD; + // Simultaneous open + if (syn && !ack && !is_forward) + return TCP_SYN_RCVD; + // Allow local SYN retransmissions + if (syn && !ack && is_forward) + return current; + // unexpected flags or bad sequences during initial handshake are invalid + return TCP_INVALID; + case TCP_SYN_RCVD: + // Final ack in 3 way handshake is sent + if (ack && !syn && is_forward) + return TCP_ESTABLISHED; + // Allow external SYN ACK retransmissions if the final local ACK was dropped + if (syn && ack && !is_forward) + return current; + // Allow SYN retransmissions if executing a simultaneous open + if (syn && !ack && !is_forward) + return current; + return TCP_INVALID; + case TCP_ESTABLISHED: + if (fin) { + // Active close initiated by forward path client + if (is_forward) + return TCP_FIN_WAIT_1; + // Passive close initiated by external path server + else + return TCP_CLOSE_WAIT; + } + // If invalid syn when connection is already established, invalid packet + if (syn) + return TCP_INVALID; + // Normal packet can pass through + return current; + // Active closer sent a FIN, waiting for response + case TCP_FIN_WAIT_1: + // Other side acknowledges FIN and sends its own FIN-ACK in normal close + if (ack && fin && !is_forward) + return TCP_TIME_WAIT; + // Simultaneous close, other side sent a FIN, but has not ACKed local sent FIN yet + if (fin && !ack && !is_forward) + return TCP_CLOSING; + // Other side acknowledged FIN in normal close + if (ack && !is_forward) + return TCP_FIN_WAIT_2; + + // Local can retransmit fin + if (fin && !ack && is_forward) + return current; + + // Allow pure ACK packets to pass through such as data ACKs + if (ack) + return current; + return TCP_INVALID; + // Other side acknowledged our FIN + case TCP_FIN_WAIT_2: + // Received the final FIN from the remote side + if (fin && !is_forward) + return TCP_TIME_WAIT; + if (ack) + return current; + + return TCP_INVALID; + case TCP_CLOSING: + // Remote side ACKed our original FIN + if (ack && !is_forward) + return TCP_TIME_WAIT; + // Other side can retransmit FIN if closing simultaneously + if (fin && !is_forward) + return current; + // Invalid if malformed packet when closing + return TCP_INVALID; + // Passive close, external is closing + case TCP_CLOSE_WAIT: + // Local is ready to terminate and sends its final FIN packet + if (fin && is_forward) + return TCP_LAST_ACK; + // Allow data ack packets to continue flowing while this side closing decides to close. + return current; + // Passive closer sent its final FIN, waiting for ack from remote server + case TCP_LAST_ACK: + // Server sends the final ACK back, meaning connection is fully closed + if (ack && !is_forward) + return TCP_NONE; + // Local can retransmit final FIN + if (fin && is_forward) + return current; + return TCP_INVALID; + case TCP_TIME_WAIT: + // Lingering state handled exclusively via timer tick sweeps, currently no timer so just a stub + return current; + } + return TCP_INVALID; +} + +/* Create a new connection instance generated from a FILTER_ACT_CONNECT rule in a filters +local instances region */ +static inline fw_filter_err_t fw_filter_add_instance(fw_filter_state_t *state, uint32_t src_ip, uint16_t src_port, + uint32_t dst_ip, uint16_t dst_port, uint16_t rule_id, + uint32_t seq) { + fw_tcp_instance_t *internal_array = (fw_tcp_instance_t *)state->internal_instances_table->instances; + for (uint16_t i = 0; i < state->internal_instances_table->size; i++) { + fw_tcp_instance_t *instance = &internal_array[i]; + + // Cleanup happens here, if any closed instance, remove + if (instance->current_state == TCP_NONE || instance->current_state == TCP_TIME_WAIT) { + uint16_t last_idx = state->internal_instances_table->size - 1; + + // Swap the closed entry with the last active entry in the array + internal_array[i] = internal_array[last_idx]; + + // Shrink the tracker size counter + state->internal_instances_table->size--; + + // Do not increment yet, as need to inspect newly swapped element + continue; + } + + /* Check whether connection has already been established */ + if (fw_tcp_instance_match(instance, src_ip, src_port, dst_ip, dst_port)) { + return FILTER_ERR_DUPLICATE; + } + } + + if (state->internal_instances_table->size >= state->instances_capacity) { + return FILTER_ERR_FULL; + } + + fw_tcp_instance_t *empty_slot = &internal_array[state->internal_instances_table->size]; + empty_slot->rule_id = rule_id; + empty_slot->src_ip = src_ip; + empty_slot->src_port = src_port; + empty_slot->dst_ip = dst_ip; + empty_slot->dst_port = dst_port; + empty_slot->local.flags = FW_TCP_SYN_BIT; + empty_slot->local.seq = seq; + empty_slot->external.flags = 0; + empty_slot->external.seq = 0; + empty_slot->current_state = TCP_SYN_SENT; + state->internal_instances_table->size++; + return FILTER_ERR_OKAY; +}