diff --git a/src/routes.c b/src/routes.c index c9a13ac..50d47d9 100644 --- a/src/routes.c +++ b/src/routes.c @@ -248,17 +248,16 @@ static inline int cmp_addrs_bitmaskful( if (left_addr_len < right_addr_len) { maskful_addr = right_addr; - mask_len = right_addr_len >> 1; + mask_len = WOLFSENTRY_BITS_TO_BYTES(right_addr_len >> 1); target_addr = left_addr; - padding_offset = mask_len - left_addr_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(left_addr_len); } else { maskful_addr = left_addr; - mask_len = left_addr_len >> 1; + mask_len = WOLFSENTRY_BITS_TO_BYTES(left_addr_len >> 1); target_addr = right_addr; - padding_offset = mask_len - right_addr_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(right_addr_len); } - mask_len = WOLFSENTRY_BITS_TO_BYTES(mask_len); mask = maskful_addr + mask_len; for (i = 0; i < mask_len; ++i) { @@ -296,28 +295,21 @@ static inline int addr_bitmask_match_size( const byte *b, unsigned int b_len) { - const byte *longer_addr, *shorter_addr; - unsigned int mask_len, i, padding_offset; + const byte *mask; + unsigned int mask_len, i; int ret = 0; + /* specificity is the number of bits the longer, maskful address's mask constrains */ if (a_len < b_len) { - longer_addr = b; mask_len = WOLFSENTRY_BITS_TO_BYTES(b_len >> 1); - shorter_addr = a; - padding_offset = mask_len - a_len; + mask = b + mask_len; } else { - longer_addr = a; mask_len = WOLFSENTRY_BITS_TO_BYTES(a_len >> 1); - shorter_addr = b; - padding_offset = mask_len - b_len; + mask = a + mask_len; } - for (ret = 0, i = 0; i < mask_len; ++i) { - if (i < padding_offset) - ret += popcount32(~(uint32_t)(longer_addr[i])) - 24; - else - ret += popcount32(~(uint32_t)(longer_addr[i] ^ shorter_addr[i - padding_offset])) - 24; - } + for (i = 0; i < mask_len; ++i) + ret += popcount32((uint32_t)mask[i]); return ret; } @@ -1004,6 +996,10 @@ static wolfsentry_errcode_t wolfsentry_route_new( } else { if (parent_event != NULL) { WOLFSENTRY_REFCOUNT_INCREMENT(parent_event->header.refcount, ret); + if (ret < 0) { + wolfsentry_route_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT, config->config.route_private_data_alignment, *new); + *new = NULL; + } } } @@ -1063,6 +1059,10 @@ static wolfsentry_errcode_t wolfsentry_route_new_by_exports( } else { if (parent_event != NULL) { WOLFSENTRY_REFCOUNT_INCREMENT(parent_event->header.refcount, ret); + if (ret < 0) { + wolfsentry_route_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT, config->config.route_private_data_alignment, *new); + *new = NULL; + } } } @@ -1165,8 +1165,9 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_clone( /* now zero the private data section. */ memset((*new_route)->data, 0, config->config.route_private_data_size); - /* finally, copy the address section, carefully. */ - memcpy((byte *)(*new_route)->data + (*new_route)->data_addr_offset, src_route->data, src_route->data_addr_size); + /* finally, copy the address section, carefully, reading it from the + * source's own address offset rather than the start of its data. */ + memcpy((byte *)(*new_route)->data + (*new_route)->data_addr_offset, (const byte *)src_route->data + src_route->data_addr_offset, src_route->data_addr_size); } WOLFSENTRY_TABLE_ENT_HEADER_RESET(**new_ent); @@ -1179,6 +1180,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_clone( thread, #endif config->config.route_private_data_alignment, *new_route); + *new_route = NULL; WOLFSENTRY_ERROR_RERETURN(ret); } WOLFSENTRY_REFCOUNT_INCREMENT((*new_route)->parent_event->header.refcount, ret); @@ -1472,6 +1474,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_into_table( wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -1496,6 +1501,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_by_exports_into_tabl wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (route_exports->parent_event_label) { @@ -1566,6 +1574,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_into_table_and_check wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -1996,6 +2007,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_default_policy_set( if ((default_policy != WOLFSENTRY_ACTION_RES_NONE) && (! WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT))) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (WOLFSENTRY_CHECK_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT)) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + /* a port reset only qualifies a reject verdict. */ + if (WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_PORT_RESET) && + (! WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_REJECT))) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_MUTEX_OR_RETURN(); table->default_policy = default_policy; if (table == wolfsentry->routes) @@ -2280,6 +2297,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_from_table( wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if ((action_results == NULL) || (n_deleted == NULL)) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -2332,6 +2352,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_by_id( struct wolfsentry_event *trigger_event = NULL; struct wolfsentry_route *route; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (trigger_label) { @@ -2380,6 +2403,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( struct wolfsentry_eventconfig_internal *config; wolfsentry_route_flags_t current_rule_route_flags; wolfsentry_errcode_t ret; + wolfsentry_errcode_t action_ret; wolfsentry_time_t now; int penalty_triggered = 0; wolfsentry_hitcount_t derog_snap; @@ -2431,10 +2455,11 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( #endif { rule_route->meta.purge_after = new_purge_after; /* coherent assignment */ - if (route_table->purge_list.tail != &rule_route->purge_links) { - wolfsentry_list_ent_delete(&route_table->purge_list, &rule_route->purge_links); - wolfsentry_route_purge_list_insert(route_table, rule_route); - } + /* the refreshed route may now be the newest rather than the + * stalest, so always reposition it instead of leaving a + * just-refreshed tail stranded at the stale end. */ + wolfsentry_list_ent_delete(&route_table->purge_list, &rule_route->purge_links); + wolfsentry_route_purge_list_insert(route_table, rule_route); } } } @@ -2447,7 +2472,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( * trigger_event->action_list must call wolfsentry_route_set_wildcard(), * in addition to setting _ACTION_RES_INSERT. */ - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, trigger_event, @@ -2456,7 +2481,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } @@ -2474,10 +2502,15 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( wolfsentry_route_increment_hitcount(WOLFSENTRY_CONTEXT_ARGS_OUT, rule_route, action_results); } parent_event = route_table->default_event; + /* the rule route and its event changed, so refresh the cached config + * and flags to match the fallthrough route rather than reusing the + * superseded route's state. */ + config = (parent_event && parent_event->config) ? parent_event->config : &wolfsentry->config; + current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->match_action_list.header) > 0)) { - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2486,7 +2519,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } @@ -2505,29 +2541,40 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( (void)ret; } - if ((! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) && - (config->config.max_connection_count > 0)) - { + if (! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) { if (*action_results & WOLFSENTRY_ACTION_RES_CONNECT) { - if (rule_route->meta.connection_count >= config->config.max_connection_count) { + uint16_t new_connection_count; + /* count every connection so disconnects stay balanced when the + * limit changes; a zero limit only means nothing is rejected. */ + if ((config->config.max_connection_count > 0) && + (rule_route->meta.connection_count >= config->config.max_connection_count)) + { *action_results |= WOLFSENTRY_ACTION_RES_REJECT; ret = WOLFSENTRY_ERROR_ENCODE(OK); goto done; } - if (WOLFSENTRY_ATOMIC_INCREMENT_BY_ONE(rule_route->meta.connection_count) > config->config.max_connection_count) { - WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(rule_route->meta.connection_count, new_connection_count); + /* with no limit a saturated counter simply stops counting; under a + * limit it cannot track another connection, so refuse it. */ + if ((config->config.max_connection_count > 0) && + ((new_connection_count == 0) || (new_connection_count > config->config.max_connection_count))) + { + if (new_connection_count != 0) + WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); *action_results |= WOLFSENTRY_ACTION_RES_REJECT; ret = WOLFSENTRY_ERROR_ENCODE(OK); goto done; } } else if (*action_results & WOLFSENTRY_ACTION_RES_DISCONNECT) { uint16_t new_connection_count; - /* important to decrement safely, because untimely route - * deletions can lead to a decrement being assigned to a - * different route than the one originally incremented. - */ + /* decrement safely: an untimely route deletion can assign a + * decrement to a different route than the one incremented. */ WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(rule_route->meta.connection_count, 1, new_connection_count); - if (new_connection_count == MAX_UINT_OF(rule_route->meta.connection_count)) + /* only an already-zero count under an enforced limit is + * anomalous; with no limit, unconnected routes legitimately see + * disconnects, so do not warn on them. */ + if ((new_connection_count == MAX_UINT_OF(rule_route->meta.connection_count)) && + (config->config.max_connection_count > 0)) WOLFSENTRY_WARN("_RES_DISCONNECT for route #" WOLFSENTRY_ENT_ID_FMT ", whose connection_count is already zero.", rule_route->header.id); } } @@ -2638,7 +2685,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( parent_event && (wolfsentry_list_ent_get_len(&parent_event->update_action_list.header) > 0)) { WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2647,13 +2694,16 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; /* no need to refresh current_rule_route_flags */ } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->decision_action_list.header) > 0)) { WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2662,7 +2712,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; /* no need to refresh current_rule_route_flags */ } @@ -2695,6 +2748,8 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( struct wolfsentry_route *rule_route = NULL; struct wolfsentry_event *trigger_event = NULL; wolfsentry_errcode_t ret; + int apply_default_policy = 0; + static int warned_unset_default_policy = 0; if ((remote == NULL) || (local == NULL) || @@ -2767,6 +2822,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( else { /* nothing to do. */ /* carry through ret from final wolfsentry_route_lookup_0(), and impose the default policy. */ + apply_default_policy = 1; goto just_free_resources; } @@ -2806,8 +2862,29 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, trigger_event, NULL /* action_results */)); if (rule_route == NULL) { + if (! apply_default_policy) { + /* a resource failure (not a clean no-match) prevented rule + * evaluation; fail closed with an explicit reject rather than + * silently applying the default policy as a successful fallthrough, + * so an integrator that gates on _RES_REJECT drops the packet. + */ + *action_results |= WOLFSENTRY_ACTION_RES_ERROR | WOLFSENTRY_ACTION_RES_REJECT; + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } if (inexact_matches) *inexact_matches = WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD; + if (route_table->default_policy == WOLFSENTRY_ACTION_RES_NONE) { + /* claim the one-shot atomically so only a single caller warns. */ + int warn_expected = 0; +#ifdef WOLFSENTRY_THREADSAFE + if (WOLFSENTRY_ATOMIC_CMPXCHG(&warned_unset_default_policy, &warn_expected, 1, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) +#else + warn_expected = warned_unset_default_policy; + warned_unset_default_policy = 1; + if (! warn_expected) +#endif + WOLFSENTRY_WARN("%s", "default route policy is unset (NONE); unmatched traffic falls through with no accept or reject verdict.\n"); + } *action_results |= route_table->default_policy | WOLFSENTRY_ACTION_RES_FALLTHROUGH; WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN(USED_FALLBACK); } @@ -2832,6 +2909,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route_table, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2849,6 +2928,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2857,6 +2938,8 @@ static wolfsentry_errcode_t check_user_inited_result(wolfsentry_action_res_t act if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | + WOLFSENTRY_ACTION_RES_FALLTHROUGH | + WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_DEALLOCATED | WOLFSENTRY_ACTION_RES_ERROR)) @@ -2879,7 +2962,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table_w wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route_table, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2897,7 +2983,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_inited_ wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2953,6 +3042,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_id( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, id, event_label, event_label_len, caller_arg, action_results)); } @@ -2966,7 +3057,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_id_with_i wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, id, event_label, event_label_len, caller_arg, action_results)); } @@ -3019,6 +3113,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_route_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route, event_label, event_label_len, caller_arg, action_results)); } @@ -3032,7 +3128,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_route_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route, event_label, event_label_len, caller_arg, action_results)); } @@ -3042,6 +3141,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit WOLFSENTRY_LOCAL_VOID wolfsentry_route_purge_list_insert(struct wolfsentry_route_table *route_table, struct wolfsentry_route *route_to_insert) { struct wolfsentry_list_ent_header *point_ent; + /* deletion leaves an entry's own links stale, and a head insert keeps prev */ + route_to_insert->purge_links.prev = NULL; + route_to_insert->purge_links.next = NULL; for (wolfsentry_list_ent_get_first(&route_table->purge_list, &point_ent); point_ent; point_ent = point_ent->next) { const struct wolfsentry_route *point_route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(point_ent); if (point_route->meta.purge_after < route_to_insert->meta.purge_after) @@ -3057,6 +3159,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_purge_time_set( wolfsentry_time_t purge_after) { WOLFSENTRY_HAVE_MUTEX_OR_RETURN(); + if (route->header.parent_table == NULL) + WOLFSENTRY_ERROR_RETURN(NOT_PERMITTED); if (route->meta.purge_after) wolfsentry_list_ent_delete(&((struct wolfsentry_route_table *)route->header.parent_table)->purge_list, &route->purge_links); route->meta.purge_after = purge_after; @@ -3075,6 +3179,9 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( wolfsentry_errcode_t ret; int n = 0; wolfsentry_action_res_t fallback_action_results = 0; + struct wolfsentry_list_ent_header *cursor; + int examined = 0; + wolfsentry_time_t requeue_delay = 0; #ifdef WOLFSENTRY_THREADSAFE int have_mutex = 0; int got_lock = 0; @@ -3092,6 +3199,11 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( WOLFSENTRY_ERROR_RETURN(ALREADY); } + if (mode == 2) { + if ((ret = WOLFSENTRY_INTERVAL_FROM_SECONDS(WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS, 0 /* howlong_nsecs */, &requeue_delay)) < 0) + WOLFSENTRY_ERROR_RERETURN(ret); + } + if (action_results == NULL) action_results = &fallback_action_results; @@ -3121,17 +3233,39 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( table = wolfsentry->routes; while (table->purge_list.tail) { - struct wolfsentry_route *route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(table->purge_list.tail); - if (mode != 3) { - if (route->meta.purge_after > now) - break; - if ((route->meta.connection_count > 0) && - (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && - ((table->max_purgeable_idle_time == 0) || (now - route->meta.last_hit_time > table->max_purgeable_idle_time))) - { + struct wolfsentry_route *route = NULL; + int found = 0; + int requeue = 0; + wolfsentry_time_t idle = 0; + /* re-scan from the most stale entry each pass, because a delete runs + * application callbacks that can mutate the purge list; skip routes + * that still have a live connection within the idle limit so one + * protected route does not strand the expired routes behind it. */ + for (cursor = table->purge_list.tail; cursor != NULL; cursor = cursor->prev) { + /* the per-dispatch purge runs on the packet path, so bound its scan */ + if ((mode == 2) && (++examined > 8)) break; + route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(cursor); + if (mode != 3) { + if (route->meta.purge_after > now) + break; + /* a route never hit since insertion has been idle since its insertion */ + idle = WOLFSENTRY_DIFF_TIME(now, (route->meta.last_hit_time > route->meta.insert_time) ? route->meta.last_hit_time : route->meta.insert_time); + if ((route->meta.connection_count > 0) && + (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && + ((table->max_purgeable_idle_time <= 0) || (idle <= table->max_purgeable_idle_time))) + { + if (mode != 2) + continue; + /* requeue it so later bounded scans reach the routes behind it */ + requeue = 1; + } } + found = 1; + break; } + if (! found) + break; #ifdef WOLFSENTRY_THREADSAFE if (! have_mutex) { if (mode == 2) @@ -3146,6 +3280,19 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( have_mutex = 1; } #endif + if (requeue) { + wolfsentry_time_t delay = requeue_delay; + /* not before the margin, unless the idle limit expires sooner */ + if ((table->max_purgeable_idle_time > 0) && (idle >= 0) && + (table->max_purgeable_idle_time - idle < requeue_delay)) + { + delay = table->max_purgeable_idle_time - idle + 1; + } + wolfsentry_list_ent_delete(&table->purge_list, &route->purge_links); + route->meta.purge_after = WOLFSENTRY_ADD_TIME(now, delay); + wolfsentry_route_purge_list_insert(table, route); + continue; + } ret = wolfsentry_route_delete_0(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, table, NULL /* trigger_event */, route, action_results, 0 /* defer_p */); if (ret < 0) { #ifdef WOLFSENTRY_THREADSAFE @@ -3395,10 +3542,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_increment_derogatory_count( WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, (unsigned)count_to_add, new_derogatory_count); if (new_derogatory_count == 0) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); - } else { - WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, (unsigned)(-count_to_add), new_derogatory_count); + } else if (count_to_add < 0) { + /* negate in the unsigned domain so INT_MIN does not overflow. */ + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, 0U - (unsigned)count_to_add, new_derogatory_count); if (new_derogatory_count == MAX_UINT_OF(route->meta.derogatory_count)) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + } else { + new_derogatory_count = WOLFSENTRY_ATOMIC_LOAD(route->meta.derogatory_count); } if (new_derogatory_count_ptr) @@ -3421,10 +3571,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_increment_commendable_count WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, (unsigned)count_to_add, new_commendable_count); if (new_commendable_count == 0) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); - } else { - WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, (unsigned)(-count_to_add), new_commendable_count); + } else if (count_to_add < 0) { + /* negate in the unsigned domain so INT_MIN does not overflow. */ + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, 0U - (unsigned)count_to_add, new_commendable_count); if (new_commendable_count == MAX_UINT_OF(route->meta.commendable_count)) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + } else { + new_commendable_count = WOLFSENTRY_ATOMIC_LOAD(route->meta.commendable_count); } if (new_commendable_count_ptr) @@ -3483,11 +3636,24 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_set_wildcard( } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD; - route->remote.addr_len = 0; + if (route->remote.addr_len > 0) { + /* the local address is stored immediately after the remote address, + * so dropping the remote address requires sliding the local bytes + * down to keep WOLFSENTRY_ROUTE_LOCAL_ADDR() valid. */ + if (route->local.addr_len > 0) + memmove(WOLFSENTRY_ROUTE_REMOTE_ADDR(route), + WOLFSENTRY_ROUTE_LOCAL_ADDR(route), + WOLFSENTRY_ROUTE_LOCAL_ADDR_BYTES(route)); + route->remote.addr_len = 0; + route->data_addr_size = (uint16_t)WOLFSENTRY_ROUTE_LOCAL_ADDR_BYTES(route); + } } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD; - route->local.addr_len = 0; + if (route->local.addr_len > 0) { + route->local.addr_len = 0; + route->data_addr_size = (uint16_t)WOLFSENTRY_ROUTE_REMOTE_ADDR_BYTES(route); + } } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD; @@ -3585,6 +3751,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_get_default_event( if (table->default_event->label_len >= *event_label_len) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(BUFFER_TOO_SMALL); memcpy(event_label, table->default_event->label, (size_t)(table->default_event->label_len + 1)); + *event_label_len = table->default_event->label_len; WOLFSENTRY_UNLOCK_AND_RETURN_OK; } @@ -3926,9 +4093,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_address( if (sa_family == WOLFSENTRY_AF_LINK || sa_family == WOLFSENTRY_AF_LINK64) { unsigned int i; - if ((addr_bits >> 3) * 3 > (size_t)*buflen) + unsigned int addr_bytes = (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits); + if (addr_bytes * 3 > (size_t)*buflen) WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); - for (i=0; i < (addr_bits >> 3); ++i) { + for (i=0; i < addr_bytes; ++i) { if (i > 0) *buf++ = ':'; *buf++ = hexdigit_ntoa(addr[i] >> 4); @@ -3955,7 +4123,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_address( *buf++ = '0'; *buf++ = 'x'; for (i=0; i < 4; ++i) { - if (i < (addr_bits >> 3)) { + if (i < (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits)) { *buf++ = hexdigit_ntoa(addr[i] >> 4); *buf++ = hexdigit_ntoa(addr[i]); } else { @@ -4011,8 +4179,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_flag_assoc_by_name(const ch if (len < 0) len = (int)strlen(name); for (i = wolfsentry_route_flag_names; i < end_ptr_of_array(wolfsentry_route_flag_names); ++i) { - if ((strncmp(i->name, name, (size_t)len) == 0) && - (i->name[len] == 0)) + /* compare lengths first so a shared NUL does not lead to reading + * i->name[len] past the end of the name literal. */ + if ((strlen(i->name) == (size_t)len) && + (strncmp(i->name, name, (size_t)len) == 0)) { *flag = i->flag; WOLFSENTRY_RETURN_OK; @@ -4455,7 +4625,7 @@ static wolfsentry_errcode_t wolfsentry_route_render_address(WOLFSENTRY_CONTEXT_A if (sa_family == WOLFSENTRY_AF_LINK || sa_family == WOLFSENTRY_AF_LINK64) { unsigned int i; - for (i=0; i < (addr_bits >> 3); ++i) { + for (i=0; i < (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits); ++i) { if (fprintf(f, "%s%02x", i ? ":" : "", (unsigned int)addr[i]) < 0) WOLFSENTRY_ERROR_RETURN(IO_FAILED); } @@ -4813,6 +4983,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_table_clone_header( ((struct wolfsentry_route_table *)dest_table)->max_purgeable_routes = ((struct wolfsentry_route_table *)src_table)->max_purgeable_routes; + ((struct wolfsentry_route_table *)dest_table)->max_purgeable_idle_time = + ((struct wolfsentry_route_table *)src_table)->max_purgeable_idle_time; ((struct wolfsentry_route_table *)dest_table)->default_policy = ((struct wolfsentry_route_table *)src_table)->default_policy; @@ -4837,14 +5009,17 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_table_clone_header( } if (((struct wolfsentry_route_table *)src_table)->fallthrough_route != NULL) { + /* take the source reference before releasing the destination's own + * route, so a failed increment leaves the destination route intact + * rather than a freed, still-published pointer. */ + WOLFSENTRY_REFCOUNT_INCREMENT(((struct wolfsentry_route_table *)src_table)->fallthrough_route->header.refcount, ret); + WOLFSENTRY_RERETURN_IF_ERROR(ret); if (((struct wolfsentry_route_table *)dest_table)->fallthrough_route != NULL) { WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_drop_reference_1( WOLFSENTRY_CONTEXT_ARGS_OUT_EX(dest_context), ((struct wolfsentry_route_table *)dest_table)->fallthrough_route, NULL /* action_results */)); } ((struct wolfsentry_route_table *)dest_table)->fallthrough_route = ((struct wolfsentry_route_table *)src_table)->fallthrough_route; - WOLFSENTRY_REFCOUNT_INCREMENT(((struct wolfsentry_route_table *)dest_table)->fallthrough_route->header.refcount, ret); - WOLFSENTRY_RERETURN_IF_ERROR(ret); } if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)) @@ -4890,8 +5065,17 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_copy_metadata( /* pointers are advanced inside the loop. */) { if (from_i->header.id == to_i->header.id) { + /* meta carries purge_after, which determines purge_list membership + * and ordering; refresh the list around the copy so it stays + * consistent with the new value. + */ + if (to_i->meta.purge_after) + wolfsentry_list_ent_delete(&to_table->purge_list, &to_i->purge_links); to_i->flags = from_i->flags; to_i->meta = from_i->meta; + to_i->header.hitcount = from_i->header.hitcount; + if (to_i->meta.purge_after) + wolfsentry_route_purge_list_insert(to_table, to_i); } else { int cmpret = wolfsentry_route_key_cmp_1(from_i, to_i, 0 /* match_wildcards_p */, NULL /* inexact_matches */); if (cmpret < 0) { diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index fc590a0..4fb1668 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3605,6 +3605,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_eventconfig_check( WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (config->derogatory_threshold_for_penaltybox > MAX_UINT_OF(instance_of_field(struct wolfsentry_route, meta.derogatory_count))) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (config->max_connection_count > MAX_UINT_OF(instance_of_field(struct wolfsentry_route, meta.connection_count))) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); ret = wolfsentry_route_check_flags_sensical(config->route_flags_to_add_on_insert); WOLFSENTRY_RERETURN_IF_ERROR(ret); diff --git a/tests/unittests.c b/tests/unittests.c index 7a75686..bc39dbc 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3469,6 +3469,1228 @@ static int test_dynamic_rules(void) { WOLFSENTRY_RETURN_OK; } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static wolfsentry_errcode_t test_regression_bitmask_match_specificity(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id, broad_id, narrow_id; + struct wolfsentry_route_exports broad_route, narrow_route; + byte broad_addr[8], narrow_addr[8]; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + static const wolfsentry_route_flags_t common_flags = + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&broad_route, 0, sizeof broad_route); + memset(&narrow_route, 0, sizeof narrow_route); + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + + /* broad bitmask route 10.0.0.0/255.0.0.0, greenlisted (accept). */ + memcpy(broad_addr, "\x0A\x00\x00\x00\xFF\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + broad_route.sa_family = AF_INET; + broad_route.sa_proto = IPPROTO_TCP; + broad_route.remote_address = broad_addr; + broad_route.remote.addr_len = 64; + broad_route.flags = common_flags | WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &broad_route, + &broad_id, &action_results)); + + /* narrow bitmask route 10.1.2.0/255.255.255.0, penaltyboxed (reject). */ + memcpy(narrow_addr, "\x0A\x01\x02\x00\xFF\xFF\xFF\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + narrow_route.sa_family = AF_INET; + narrow_route.sa_proto = IPPROTO_TCP; + narrow_route.remote_address = narrow_addr; + narrow_route.remote.addr_len = 64; + narrow_route.flags = common_flags | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &narrow_route, + &narrow_id, &action_results)); + + (void)broad_id; + + /* 10.1.2.3 matches both; the more specific /24 route must win, so the + * verdict is reject, not accept. + */ + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x01\x02\x03", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + /* non-nested masks: 192.0.0.1 inexactly matches a two-bit mask whose + * address is closer to it and a three-bit mask whose address is not; the + * three-bit mask constrains more bits, so it must win. + */ + memcpy(broad_addr, "\xC0\x00\x00\x00\xC0\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &broad_route, + &broad_id, &action_results)); + memcpy(narrow_addr, "\x00\x00\x00\x00\x38\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &narrow_route, + &narrow_id, &action_results)); + + memcpy(remote.sa.addr, "\xC0\x00\x00\x01", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + +/* a stale purge either purged something or had nothing due */ +static int regression_purge_ok(wolfsentry_errcode_t ret) { + return WOLFSENTRY_IS_SUCCESS(ret) || WOLFSENTRY_ERROR_CODE_IS(ret, ALREADY); +} + +static wolfsentry_errcode_t test_regression_stale_purge_connected_idle(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, route_flags; + wolfsentry_ent_id_t id, id_b, route_id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, remote_b, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x09\x08\x07", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* a second route with no connection, less stale than the first; it sits + * ahead of the protected route in the purge list and must still be purged. */ + memcpy(&remote_b, &remote, sizeof remote_b); + memcpy(remote_b.sa.addr, "\x0A\x09\x08\x06", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_b.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_b, &action_results)); + + /* idle-time limit far larger than this test's wall-clock runtime, so a + * connected route is within the limit and must be preserved by a purge. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* open a connection on the route (connection_count -> 1). */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_CONNECT); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + + /* mark the protected route as the most stale entry (purge_after 1), and the + * unconnected route as less stale (purge_after 2); both are in the past. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id_b, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 2)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* the route's live connection is within the idle limit, so it must survive. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + /* the unconnected route behind it must not be stranded by the purge. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id_b, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_dispatch_error_not_masked(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, flags; + wolfsentry_ent_id_t id; + wolfsentry_errcode_t ret; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.addr_len = 0; + local.sa.sa_family = AF_INET; + local.sa.addr_len = 0; + + /* a family wildcard without the remote/local/proto wildcards it requires is + * rejected while the ephemeral target route is built, before any rule is + * found; the dispatcher must surface that error, not report a successful + * default-policy fallthrough. + */ + WOLFSENTRY_CLEAR_ALL_BITS(flags); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + ret = wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_IS_FAILURE(ret)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ERROR)); + /* the default policy is unset (NONE) here, so the failure must fail closed + * with an explicit reject rather than leaving the packet to be allowed. */ + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *ctx_clone = NULL; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t route_flags; + wolfsentry_ent_id_t id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x05\x06\x07", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* clone before the route has a purge time, so the clone's copy of the route + * is not in the clone's purge list. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + /* give the live route a (stale) purge time, so copy_metadata will propagate + * a nonzero purge_after into the clone's not-yet-listed route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* swap the clone in, copying the live metadata across. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, ctx_clone)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + /* the swapped-in route now has a stale purge_after, so it must be reachable + * from the purge list and get purged. */ + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_route_api_invariants(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route *route; + const struct wolfsentry_route *fallthrough_route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, route_flags; + wolfsentry_ent_id_t id; + char default_event_label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + int default_event_label_len; + int new_count; + char addr_fmt_buf[32]; + int addr_fmt_len; + byte link_addr[2]; + size_t i; + static const wolfsentry_action_res_t forbidden_inited[] = { + WOLFSENTRY_ACTION_RES_ACCEPT, + WOLFSENTRY_ACTION_RES_REJECT, + WOLFSENTRY_ACTION_RES_STOP, + WOLFSENTRY_ACTION_RES_DEALLOCATED, + WOLFSENTRY_ACTION_RES_ERROR + }; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x02\x03\x04", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + /* internal-only result bits must not be accepted as caller-initialized + * input to the dispatch-with-inited-result entry points. */ + action_results = WOLFSENTRY_ACTION_RES_FALLTHROUGH; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + + action_results = WOLFSENTRY_ACTION_RES_INSERTED; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + + /* a default policy may be accept or reject, never both at once. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT), + INVALID_ARG)); + + /* reading back the default event must report the written label length, not + * leave the caller's in/out length untouched. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "default_evt", -1 /* label_len */, 10 /* priority */, + NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_set_default_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, "default_evt", -1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + default_event_label_len = (int)sizeof default_event_label; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_get_default_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, default_event_label, &default_event_label_len)); + WOLFSENTRY_EXIT_ON_FALSE(default_event_label_len == (int)strlen("default_evt")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(default_event_label, "default_evt") == 0); + + /* incrementing a derogatory count by zero is a no-op that must succeed even + * when the count is saturated, not be mistaken for an averted overflow. */ + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + new_count = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_increment_derogatory_count(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 65535, &new_count)); + WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); + new_count = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_increment_derogatory_count(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 0, &new_count)); + WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* the fallthrough route belongs to no table, so setting a purge time on it + * must be refused rather than dereferencing a null parent table. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT, table, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, 1), + NOT_PERMITTED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* a link address whose length is not a whole number of bytes must render + * its final partial byte, not silently drop it. */ + link_addr[0] = 0xAB; + link_addr[1] = 0xCD; + addr_fmt_len = (int)sizeof addr_fmt_buf; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_format_address( + WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_AF_LINK, link_addr, 12 /* addr_bits */, + addr_fmt_buf, &addr_fmt_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_len == 5); + WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_buf[2] == ':'); + + /* a null action_results must be rejected up front, not dereferenced while + * clearing it. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, NULL /* action_results */), + INVALID_ARG)); + + /* a "reset" default policy (reject plus port reset) is accepted and carried + * into unmatched verdicts; a port reset cannot qualify an accept. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_PORT_RESET), + INVALID_ARG)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + memcpy(remote.sa.addr, "\x0A\x63\x63\x63", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_SUCCESS_CODE_IS( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + USED_FALLBACK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + + /* a caller may not pre-decide the verdict or pre-set internal status bits. */ + for (i = 0; i < sizeof forbidden_inited / sizeof forbidden_inited[0]; ++i) { + action_results = forbidden_inited[i]; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +/* insert an inbound TCP/IPv4 route with wildcarded local side and ports; a NULL + * remote_addr leaves the remote address empty (pair with the address wildcard). */ +static wolfsentry_errcode_t regression_insert( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *remote_addr, + unsigned int remote_prefix_bits, + const char *event_label, + wolfsentry_route_flags_t extra_flags, + wolfsentry_ent_id_t *id) +{ + wolfsentry_action_res_t action_results; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + if (remote_addr != NULL) { + remote.sa.addr_len = (wolfsentry_addr_bits_t)remote_prefix_bits; + memcpy(remote.sa.addr, remote_addr, sizeof remote.addr_buf); + } + + return wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + extra_flags + | WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD, + event_label, event_label ? -1 : 0, + id, &action_results); +} + +/* dispatch an inbound TCP/IPv4 packet from a 4-byte remote address, with the + * caller-initialized result bits in inited_results. */ +static wolfsentry_errcode_t regression_dispatch( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *remote_addr, + wolfsentry_action_res_t inited_results, + wolfsentry_ent_id_t *id, + wolfsentry_action_res_t *action_results) +{ + wolfsentry_route_flags_t inexact_matches; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, remote_addr, sizeof remote.addr_buf); + *action_results = inited_results; + + return wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + id, &inexact_matches, action_results); +} + +static wolfsentry_time_t regression_fake_now; + +static wolfsentry_errcode_t regression_fake_get_time(void *context, wolfsentry_time_t *now) { + (void)context; + *now = regression_fake_now; + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t regression_route_meta( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t id, + struct wolfsentry_route_metadata_exports *meta) +{ + struct wolfsentry_route *route; + wolfsentry_errcode_t ret, unlock_ret; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret < 0) + return ret; + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route); + if (ret >= 0) + ret = wolfsentry_route_get_metadata(route, meta); + unlock_ret = wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret >= 0) + ret = unlock_ret; + return ret; +} + +static wolfsentry_errcode_t regression_set_purge_time( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t id, + wolfsentry_time_t purge_after) +{ + struct wolfsentry_route *route; + wolfsentry_errcode_t ret, unlock_ret; + + ret = wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret < 0) + return ret; + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route); + if (ret >= 0) + ret = wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, purge_after); + unlock_ret = wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret >= 0) + ret = unlock_ret; + return ret; +} + +static wolfsentry_errcode_t regression_insert_pbox_event( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *label, + wolfsentry_hitcount_t derog_threshold, + wolfsentry_time_t penaltybox_duration, + wolfsentry_eventconfig_flags_t flags) +{ + struct wolfsentry_eventconfig event_config; + + memset(&event_config, 0, sizeof event_config); + event_config.route_private_data_size = PRIVATE_DATA_SIZE; + event_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + event_config.derogatory_threshold_for_penaltybox = derog_threshold; + event_config.penaltybox_duration = penaltybox_duration; + event_config.flags = flags; + return wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, label, -1, 10, &event_config, WOLFSENTRY_EVENT_FLAG_NONE, NULL /* id */); +} + +static wolfsentry_errcode_t test_regression_route_timing(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + unsigned int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* drive the context clock by hand so time boundaries are exact. */ + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + /* a route expiring one tick from now survives a purge, and is purged once + * the clock reaches its purge time. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x01", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now + 1)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + + /* an expired route with a live connection idle for exactly the limit is + * kept; one tick longer and it is purged. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, 100)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x02", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now)); + regression_fake_now += 100; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + + /* a route penalty-boxed by its second derogatory event stays boxed at + * exactly the box duration, and is released one tick later. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "pbox", 2, 50, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", 32, "pbox", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + regression_fake_now += 50; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + /* a zero derogatory threshold disables penalty boxing entirely. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "nothresh", 0, 50, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x04", 32, "nothresh", WOLFSENTRY_ROUTE_FLAG_GREENLISTED, &id)); + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x04", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + + /* a zero box duration is permanent, and a route boxed at insert (no box + * time recorded) is never released by the timeout. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "pboxperm", 2, 0, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", 32, "pboxperm", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", 32, "pbox", WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, &id)); + for (i = 0; i < 2; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + regression_fake_now += 1000000; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + /* with commendable events ignored, reaching the derogatory threshold boxes + * the route even though commendable events would otherwise offset it. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "ignorecommend", 2, 0, WOLFSENTRY_EVENTCONFIG_FLAG_DEROGATORY_THRESHOLD_IGNORE_COMMENDABLE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", 32, "ignorecommend", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 2; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_COMMENDABLE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_connection_limits(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_eventconfig limit_config; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + unsigned int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&limit_config, 0, sizeof limit_config); + limit_config.route_private_data_size = PRIVATE_DATA_SIZE; + limit_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + limit_config.max_connection_count = 2; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limit2", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + + /* a limit the 16-bit per-route counter cannot enforce is refused rather + * than silently capped, and the counter maximum is enforced exactly. */ + limit_config.max_connection_count = 0x10000U; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limitmax", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + limit_config.max_connection_count = 0xffffU; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limitmax", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", 32, "limitmax", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 0xffffU; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0xffffU); + limit_config.max_connection_count = 2; + + /* a route that opts out of connection counting is never limited. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS, &id)); + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + + /* a counted route admits exactly the limit, rejects the next connection + * without counting it, and reopens a slot after a disconnect. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 2; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 2); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 2); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_connection_count_zero_limit(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_eventconfig limit_config; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&limit_config, 0, sizeof limit_config); + limit_config.route_private_data_size = PRIVATE_DATA_SIZE; + limit_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + limit_config.max_connection_count = 2; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "zerolimit", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", 32, "zerolimit", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + + /* a connection opened under a limit stays counted after the limit is + * removed, and connections opened afterward are counted too. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + limit_config.max_connection_count = 0; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_update_config(WOLFSENTRY_CONTEXT_ARGS_OUT, "zerolimit", -1, &limit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_purge_list_reinsert(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t ids[3], matched_id; + static const char * const addrs[3] = { "\x0A\x08\x0A\x01", "\x0A\x08\x0A\x02", "\x0A\x08\x0A\x03" }; + int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* three expired routes, each protected by a live connection */ + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, addrs[i], 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &ids[i])); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, addrs[i], WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); + } + /* moving the stalest entry to the head must not leave a stale back link, + * so a scan that visits every protected entry still terminates. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[0], 40)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + for (i = 0; i < 3; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +/* intervals in milliseconds, unlike the microsecond absolute clock. */ +static wolfsentry_errcode_t regression_ms_interval_from_seconds(time_t howlong_secs, long howlong_nsecs, wolfsentry_time_t *howlong) { + *howlong = ((wolfsentry_time_t)howlong_secs * 1000) + (howlong_nsecs / 1000000); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t ids[9], matched_id; + byte addr[4] = { 0x0A, 0x08, 0x0B, 0x00 }; + int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + wolfsentry_get_timecbs(wolfsentry)->interval_from_seconds = regression_ms_interval_from_seconds; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* eight expired connected routes at the stale end, and one expired + * unconnected route queued behind them. */ + for (i = 0; i < 9; ++i) { + addr[3] = (byte)(i + 1); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, (const char *)addr, 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &ids[i])); + if (i < 8) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, (const char *)addr, WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + } + for (i = 0; i < 9; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); + + for (i = 0; i < 3; ++i) + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge_one_opportunistically(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[8], &meta)); + /* the connected routes are requeued one purge margin out, as an interval */ + for (i = 0; i < 8; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(meta.purge_after == regression_fake_now + (WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS * 1000)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_purge_idle_edges(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_route_exports exports; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + byte addr[4] = { 0x0A, 0x08, 0x0C, 0x01 }; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, 100)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* an imported route carrying a live connection but never hit is idle + * since its insertion, not since the epoch. */ + memset(&exports, 0, sizeof exports); + exports.sa_family = AF_INET; + exports.sa_proto = IPPROTO_TCP; + exports.remote_address = addr; + exports.remote.addr_len = 32; + exports.flags = WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + exports.meta.connection_count = 1; + exports.meta.purge_after = regression_fake_now + 1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_by_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &exports, &id, &action_results)); + regression_fake_now += 2; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* trigger_label */, 0, &action_results)); + + /* a negative idle limit, like zero, never purges a connected route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, -1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x0C\x02", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x0C\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now)); + ++regression_fake_now; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_prefix_specificity(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t broad_id, narrow_id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* equal-priority overlapping prefixes: the longer match must win inside + * the /25, and the /24 must still match outside it. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x00", 24, NULL, WOLFSENTRY_ROUTE_FLAG_GREENLISTED, &broad_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x80", 25, NULL, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, &narrow_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\xC8", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(matched_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x0A", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(matched_id == broad_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_dynamic_rules_regressions(void) { +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); +#endif + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_stale_purge_connected_idle(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_dispatch_error_not_masked(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_copy_metadata_purge_list(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_count_zero_limit(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_list_reinsert(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bounded_purge_progress(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_idle_edges(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -5749,6 +6971,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dynamic_rules failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dynamic_rules_regressions(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dynamic_rules_regressions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 225980b..640ebdf 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -1003,7 +1003,7 @@ typedef wolfsentry_errcode_t (*wolfsentry_action_callback_t)( * @{ */ -#define WOLFSENTRY_ROUTE_DEFAULT_POLICY_MASK (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR) +#define WOLFSENTRY_ROUTE_DEFAULT_POLICY_MASK (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR | WOLFSENTRY_ACTION_RES_PORT_RESET) /*!< \brief Bit mask spanning the bits allowed by wolfsentry_route_table_default_policy_set() */ /*! \brief bit field specifying attributes of a route/rule */