From cdff077bb67f158e60f4d3a1643d70a3e3ac871f Mon Sep 17 00:00:00 2001 From: Damien Stuart Date: Tue, 24 Aug 2010 03:09:35 +0000 Subject: [PATCH] Added rule expire and purge for ipfw. Almost there... git-svn-id: file:///home/mbr/svn/fwknop/trunk@283 510a4753-2344-4c79-9c09-4d669213fbeb --- server/config_init.c | 6 +- server/fw_util_ipfw.c | 352 ++++++++++++++++++++++++++++++++++++++-- server/fw_util_ipfw.h | 26 ++- server/fwknopd_common.h | 20 ++- server/pcap_capture.c | 15 ++ 5 files changed, 389 insertions(+), 30 deletions(-) diff --git a/server/config_init.c b/server/config_init.c index 18367883..1fa754a1 100644 --- a/server/config_init.c +++ b/server/config_init.c @@ -395,9 +395,9 @@ validate_options(fko_srv_options_t *opts) /* Set IPFW Dynamic rule expiry interval. */ - if(opts->config[CONF_IPFW_DYNAMIC_INTERVAL] == NULL) - set_config_entry(opts, CONF_IPFW_DYNAMIC_INTERVAL, - DEF_IPFW_DYNAMIC_INTERVAL); + if(opts->config[CONF_IPFW_EXPIRE_PURGE_INTERVAL] == NULL) + set_config_entry(opts, CONF_IPFW_EXPIRE_PURGE_INTERVAL, + DEF_IPFW_EXPIRE_PURGE_INTERVAL); /* Set IPFW Dynamic rule expiry interval. */ diff --git a/server/fw_util_ipfw.c b/server/fw_util_ipfw.c index 8ca61b77..b93d8629 100644 --- a/server/fw_util_ipfw.c +++ b/server/fw_util_ipfw.c @@ -46,7 +46,7 @@ get_next_rule_num(void) for(i=0; i < fwc.max_rules; i++) { - if(fwc.rule_map[i] == 0) + if(fwc.rule_map[i] == RULE_FREE) return(fwc.start_rule_num + i); } @@ -124,6 +124,7 @@ fw_config_init(fko_srv_options_t *opts) fwc.max_rules = atoi(opts->config[CONF_IPFW_MAX_RULES]); fwc.active_set_num = atoi(opts->config[CONF_IPFW_ACTIVE_SET_NUM]); fwc.expire_set_num = atoi(opts->config[CONF_IPFW_EXPIRE_SET_NUM]); + fwc.purge_interval = atoi(opts->config[CONF_IPFW_EXPIRE_PURGE_INTERVAL]); /* Let us find it via our opts struct as well. */ @@ -178,11 +179,28 @@ fw_initialize(fko_srv_options_t *opts) fwc.active_set_num ); - (fwc.rule_map)[0] = 1; + fwc.rule_map[0] = RULE_ACTIVE; } else log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } + + /* Make sure our expire set is disabled. + */ + zero_cmd_buffers(); + + snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_DISABLE_SET_ARGS, + fwc.fw_command, + fwc.expire_set_num + ); + + res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); + + if(EXTCMD_IS_SUCCESS(res)) + log_msg(LOG_INFO, "Set ipfw set %u to disabled.", + fwc.expire_set_num); + else + log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); } int @@ -246,9 +264,6 @@ fw_cleanup(void) int process_spa_request(fko_srv_options_t *opts, spa_data_t *spadat) { - /* TODO: Implement me */ - - char nat_ip[16] = {0}; char *ndx; unsigned short rule_num; @@ -288,8 +303,19 @@ process_spa_request(fko_srv_options_t *opts, spa_data_t *spadat) if(spadat->message_type == FKO_ACCESS_MSG || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG) { + /* Pull the next available rule number. + */ rule_num = get_next_rule_num(); + /* If rule_num comes back as 0, we aready have the maximum number + * of active rules allowed so we reject and bail here. + */ + if(rule_num == 0) + { + log_msg(LOG_WARNING, "Access request rejected: Maximum allowed number of rules has been reached."); + return(-1); + } + /* Create an access command for each proto/port for the source ip. */ while(ple != NULL) @@ -317,10 +343,13 @@ process_spa_request(fko_srv_options_t *opts, spa_data_t *spadat) spadat->spa_message_remain, exp_ts ); - (fwc.rule_map)[fwc.start_rule_num + rule_num] = 1; + fwc.rule_map[rule_num - fwc.start_rule_num] = RULE_ACTIVE; + + fwc.active_rules++; + fwc.total_rules++; /* Reset the next expected expire time for this chain if it - * is warranted. + * is warranted. */ if(fwc.next_expire < now || exp_ts < fwc.next_expire) fwc.next_expire = exp_ts; @@ -334,8 +363,19 @@ process_spa_request(fko_srv_options_t *opts, spa_data_t *spadat) } else { - /* No other modes supported yet. + /* No other SPA request modes are supported yet. */ + if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG + || spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG) + { + log_msg(LOG_WARNING, "Local NAT requests are not currently supported."); + } + else if(spadat->message_type == FKO_NAT_ACCESS_MSG + || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG) + { + log_msg(LOG_WARNING, "Forwarding/NAT requests are not currently supported."); + } + return(-1); } @@ -348,17 +388,305 @@ process_spa_request(fko_srv_options_t *opts, spa_data_t *spadat) void check_firewall_rules(fko_srv_options_t *opts) { - char exp_str[12]; - char rule_num_str[6]; - char *ndx, *rn_start, *rn_end, *tmp_mark; + char exp_str[12]; + char rule_num_str[6]; + char *ndx, *rn_start, *rn_end, *tmp_mark; - int i, res, rn_offset; + int i, res; time_t now, rule_exp, min_exp = 0; + unsigned short curr_rule; time(&now); + /* Just in case we somehow lose track and fall out-of-whack. + */ + if(fwc.active_rules > fwc.max_rules) + fwc.active_rules = 0; + + /* If there are no active rules or we have not yet + * reached our expected next expire time, continue. + */ + if(fwc.active_rules == 0 || fwc.next_expire > now) + return; + zero_cmd_buffers(); + /* There should be a rule to delete. Get the current list of + * rules for this chain and delete the ones that are expired. + */ + snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_RULES_ARGS, + opts->fw_config->fw_command, + fwc.active_set_num + ); + + res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); + + if(!EXTCMD_IS_SUCCESS(res)) + { + log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); + return; + } + + if(opts->verbose > 2) + log_msg(LOG_INFO, "RES=%i, CMD_BUF: %s\nRULES LIST: %s", res, cmd_buf, cmd_out); + + /* Find the first _exp_ string (if any). + */ + ndx = strstr(cmd_out, "_exp_"); + + if(ndx == NULL) + { + /* we did not find an expected rule. + */ + log_msg(LOG_ERR, + "Did not find expire comment in rules list %i.\n", i); + + fwc.active_rules--; + return; + } + + /* Walk the list and process rules as needed. + */ + while (ndx != NULL) { + /* Jump forward and extract the timestamp + */ + ndx +=5; + + /* remember this spot for when we look for the next + * rule. + */ + tmp_mark = ndx; + + strlcpy(exp_str, ndx, 11); + rule_exp = (time_t)atoll(exp_str); + +//fprintf(stderr, "RULE_EXP=%u, NOW=%u\n", rule_exp, now); + if(rule_exp <= now) + { + /* Backtrack and get the rule number and delete it. + */ + rn_start = ndx; + while(--rn_start > cmd_out) + { + if(*rn_start == '\n') + break; + } + + if(*rn_start == '\n') + { + rn_start++; + } + else if(rn_start > cmd_out) + { + /* This should not happen. But if it does, complain, + * decrement the active rule value, and go on. + */ + log_msg(LOG_ERR, + "Rule parse error while finding rule line start."); + + fwc.active_rules--; + break; + } + + rn_end = strchr(rn_start, ' '); + + if(rn_end == NULL) + { + /* This should not happen. But if it does, complain, + * decrement the active rule value, and go on. + */ + log_msg(LOG_ERR, + "Rule parse error while finding rule number."); + + fwc.active_rules--; + break; + } + + strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1); + + curr_rule = atoi(rule_num_str); + + zero_cmd_buffers(); + + /* Move the rule to the expired rules set. + */ + snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_MOVE_RULE_ARGS, + opts->fw_config->fw_command, + curr_rule, + fwc.expire_set_num + ); + +//fprintf(stderr, "MOVE RULE CMD: %s\n", cmd_buf); + res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE, 0); + if(EXTCMD_IS_SUCCESS(res)) + { + log_msg(LOG_INFO, "Moved rule %s with expire time of %u to set %u.", + rule_num_str, rule_exp, fwc.expire_set_num + ); + + fwc.active_rules--; + fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_EXPIRED; + } + else + log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf); + } + else + { + /* Track the minimum future rule expire time. + */ + if(rule_exp > now) + min_exp = (min_exp < rule_exp) ? min_exp : rule_exp; + } + + /* Push our tracking index forward beyond (just processed) _exp_ + * string so we can continue to the next rule in the list. + */ + ndx = strstr(tmp_mark, "_exp_"); + } + + /* Set the next pending expire time accordingly. 0 if there are no + * more rules, or whatever the next expected (min_exp) time will be. + */ + if(fwc.active_rules < 1) + fwc.next_expire = 0; + else if(min_exp) + fwc.next_expire = min_exp; +} + +/* Iterate over the expired rule set and purge those that no longer have + * corresponding dynamic rules. +*/ +void +purge_expired_rules(fko_srv_options_t *opts) +{ + char exp_str[12]; + char rule_num_str[6]; + char *ndx, *next_nl, *co_end; + + int i, res; + + unsigned short curr_rule; + + /* First, we get the current active dynamic rules for the expired rule + * set. Then we compare it to the expired rules in the rule_map. Any + * rules in the map that do not have a dynamic rule, can be deleted. + */ + zero_cmd_buffers(); + + snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPFW_LIST_SET_DYN_RULES_ARGS, + opts->fw_config->fw_command, + fwc.expire_set_num + ); + + res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); + + if(!EXTCMD_IS_SUCCESS(res)) + { + log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); + return; + } + + co_end = cmd_out + strlen(cmd_out); + + if(opts->verbose > 2) + log_msg(LOG_INFO, "RES=%i, CMD_BUF: %s\nEXP RULES LIST: %s", res, cmd_buf, cmd_out); + + /* Find the "## Dynamic rules" string. + */ + ndx = strcasestr(cmd_out, "## Dynamic rules"); + + if(ndx == NULL) + { + log_msg(LOG_ERR, + "Unexpected error: did not find 'Dynamic rules' string in list output." + ); + return; + } + + /* Jump to the next newline char. + */ + ndx = strchr(ndx, '\n'); + + if(ndx == NULL) + { + log_msg(LOG_ERR, + "Unexpected error: did not find 'Dynamic rules' line terminating newline." + ); + return; + } + + + /* Walk the list of dynamic rules (if any). + */ + while(ndx != NULL) + { + ndx++; + + while(!isdigit(*ndx) && ndx < co_end) + ndx++; + + if(ndx >= co_end) + break; + + /* If we are at a digit, assume it is a rule number, extract it, + * and if it falls in the correct range, mark it (so it is not + * removed in the next step. + */ + if(isdigit(*ndx)) + { + curr_rule = atoi(ndx); + + if(curr_rule >= fwc.start_rule_num + && curr_rule < fwc.start_rule_num + fwc.max_rules) + fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_TMP_MARKED; + } + + ndx = strchr(ndx, '\n'); + } + + /* Now, walk the rule map an remove any still marked as expired. + */ + for(i=0; ifw_config->fw_command, + fwc.expire_set_num, + curr_rule + ); + + res = run_extcmd(cmd_buf, cmd_out, STANDARD_CMD_OUT_BUFSIZE, 0); + + if(!EXTCMD_IS_SUCCESS(res)) + { + log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, cmd_out); + continue; + } + + log_msg(LOG_INFO, "Purged rule %u from set %u", curr_rule, fwc.expire_set_num); + + fwc.rule_map[curr_rule - fwc.start_rule_num] = RULE_FREE; + + fwc.total_rules--; + } } #endif /* FIREWALL_IPFW */ diff --git a/server/fw_util_ipfw.h b/server/fw_util_ipfw.h index 0ccd0bc9..f1738eb4 100644 --- a/server/fw_util_ipfw.h +++ b/server/fw_util_ipfw.h @@ -26,15 +26,27 @@ #ifndef FW_UTIL_IPFW_H #define FW_UTIL_IPFW_H +enum { + RULE_FREE = 0, + RULE_ACTIVE, + RULE_EXPIRED, + RULE_TMP_MARKED +}; + /* ipfw command args */ -#define IPFW_ADD_RULE_ARGS "add %u set %u pass %u from %s to me dst-port %u setup keep-state // _exp_%u" -#define IPFW_ADD_CHECK_STATE_ARGS "add %u set %u check-state" -#define IPFW_MOVE_RULE_ARGS "set move rule %u to %u" -#define IPFW_MOVE_SET_ARGS "set move %u to %u" -#define IPFW_DEL_RULE_ARGS "set %u delete %u" -#define IPFW_DEL_RULE_SET_ARGS "delete set %u" -#define IPFW_LIST_RULES_ARGS "-d -S -T set %u list" +#define IPFW_ADD_RULE_ARGS "add %u set %u pass %u from %s to me dst-port %u setup keep-state // _exp_%u" +#define IPFW_ADD_CHECK_STATE_ARGS "add %u set %u check-state" +#define IPFW_MOVE_RULE_ARGS "set move rule %u to %u" +#define IPFW_MOVE_SET_ARGS "set move %u to %u" +#define IPFW_DISABLE_SET_ARGS "set disable %u" +#define IPFW_DEL_RULE_ARGS "set %u delete %u" +#define IPFW_DEL_RULE_SET_ARGS "delete set %u" +#define IPFW_LIST_RULES_ARGS "-d -S -T set %u list" +#define IPFW_LIST_SET_RULES_ARGS "set %u list" +#define IPFW_LIST_SET_DYN_RULES_ARGS "-d set %u list" + +void purge_expired_rules(fko_srv_options_t *opts); #endif /* FW_UTIL_IPFW_H */ diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index fab87e82..3630411e 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -106,12 +106,12 @@ */ #elif FIREWALL_IPFW - #define DEF_IPFW_START_RULE_NUM "10000" - #define DEF_IPFW_MAX_RULES "1000" - #define DEF_IPFW_ACTIVE_SET_NUM "1" - #define DEF_IPFW_EXPIRE_SET_NUM "2" - #define DEF_IPFW_DYNAMIC_INTERVAL "60" - #define DEF_IPFW_ADD_CHECK_STATE "N" + #define DEF_IPFW_START_RULE_NUM "10000" + #define DEF_IPFW_MAX_RULES "1000" + #define DEF_IPFW_ACTIVE_SET_NUM "1" + #define DEF_IPFW_EXPIRE_SET_NUM "2" + #define DEF_IPFW_EXPIRE_PURGE_INTERVAL "30" + #define DEF_IPFW_ADD_CHECK_STATE "N" #elif FIREWALL_IPF @@ -183,7 +183,7 @@ enum { CONF_IPFW_MAX_RULES, CONF_IPFW_ACTIVE_SET_NUM, CONF_IPFW_EXPIRE_SET_NUM, - CONF_IPFW_DYNAMIC_INTERVAL, + CONF_IPFW_EXPIRE_PURGE_INTERVAL, CONF_IPFW_ADD_CHECK_STATE, #elif FIREWALL_IPF /* --DSS Place-holder */ @@ -251,7 +251,7 @@ static char *config_map[NUMBER_OF_CONFIG_ENTRIES] = { "IPFW_MAX_RULES", "IPFW_ACTIVE_SET_NUM", "IPFW_EXPIRE_SET_NUM", - "IPFW_DYNAMIC_INTERVAL", + "IPFW_EXPIRE_PURGE_INTERVAL", "IPFW_ADD_CHECK_STATE", #elif FIREWALL_IPF /* --DSS Place-holder */ @@ -374,10 +374,14 @@ typedef struct acc_stanza struct fw_config { unsigned short start_rule_num; unsigned short max_rules; + unsigned short active_rules; + unsigned short total_rules; unsigned short active_set_num; unsigned short expire_set_num; + unsigned short purge_interval; unsigned char *rule_map; time_t next_expire; + time_t last_purge; char fw_command[MAX_PATH_LEN]; }; diff --git a/server/pcap_capture.c b/server/pcap_capture.c index ed8069b8..6d51712d 100644 --- a/server/pcap_capture.c +++ b/server/pcap_capture.c @@ -55,6 +55,7 @@ pcap_capture(fko_srv_options_t *opts) int promisc = 0; int status; pid_t child_pid; + time_t now; /* Set promiscuous mode if ENABLE_PCAP_PROMISC is set to 'Y'. */ @@ -264,6 +265,20 @@ pcap_capture(fko_srv_options_t *opts) */ check_firewall_rules(opts); +#if FIREWALL_IPFW + /* Purge expired rules that no longer have any corresponding + * dynamic rules. + */ + if(opts->fw_config->total_rule > 0) + { + time(&now); + if(opts->fw_config->last_purge < (now - opts->fw_config->purge_interval)) + { + purge_expired_rules(opts); + opts->fw_config->last_purge = now; + } + } +#endif } pcap_close(pcap);