fwknop是 FireWall KNock OPerator 的简写,实现了一种叫单包授权(Single Packet Authorization (SPA))的方案。
本文将通过讲解 fwknop源码,来解析fwknop是怎么做到防重放攻击的。
server代码逻辑
找到 main 函数
main 函数流程:
读配置 config_init(&opts, argc, argv);
前面有解析命令,比如生成 key 的参数 -k
对应的就是执行参数的操作,这里看主要流程
起了一个 UDP 服务
// 起UDP 服务
if(run_udp_server(&opts) < 0)
{
log_msg(LOG_ERR, "Fatal run_udp_server() error");
clean_exit(&opts, FW_CLEANUP, EXIT_FAILURE);
}
else
{
break;
}
现在就是看 run_udp_server 中具体干了什么事情。
用的 select 事件轮询,接收数据, 然后解析,满足规则时,执行了 iptables 命令
selval = select(s_sock+1, &sfd_set, NULL, NULL, &tv);
pkt_len = recvfrom(s_sock, dgram_msg, MAX_SPA_PACKET_LEN,
0, (struct sockaddr *)&caddr, &clen);
incoming_spa // 处理 spa 包
handle_gpg_enc
fko_decrypt_spa_data
_rijndael_decrypt
fko_decode_spa_data
接下来就是 parse 函数
如果parse 成功:
create_rule 创建防火墙规则
run_extcmd 执行防火墙规则
_run_extcmd 里面有熟悉的 popen 函数,执行防火墙的规则。
那么防重放攻击是在哪里做的呢?
在 incoming_spa 函数(incoming_spa.c 文件)中, 我们看到这样一个函数:add_replay_cache
成功时返回1,失败返回0
static int
add_replay_cache(fko_srv_options_t *opts, acc_stanza_t *acc,
spa_data_t *spadat, char *raw_digest, int *added_replay_digest,
const int stanza_num, int *res)
{
if (!opts->test && *added_replay_digest == 0
&& strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0)
{
*res = add_replay(opts, raw_digest);
if (*res != SPA_MSG_SUCCESS)
{
log_msg(LOG_WARNING, "[%s] (stanza #%d) Could not add digest to replay cache",
spadat->pkt_source_ip, stanza_num);
return 0;
}
*added_replay_digest = 1;
}
return 1;
}
add_replay --》add_replay_dbm_cache
-
static int add_replay_dbm_cache(fko_srv_options_t *opts, char *digest) { #ifdef NO_DIGEST_CACHE return 0; #else #ifdef HAVE_LIBGDBM GDBM_FILE rpdb; #elif HAVE_LIBNDBM DBM *rpdb; #endif datum db_key, db_ent; int digest_len, res = SPA_MSG_SUCCESS; digest_cache_info_t dc_info; digest_len = strlen(digest); db_key.dptr = digest; db_key.dsize = digest_len; /* Check the db for the key */ #ifdef HAVE_LIBGDBM rpdb = gdbm_open( opts->config[CONF_DIGEST_DB_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0 ); #elif HAVE_LIBNDBM rpdb = dbm_open(opts->config[CONF_DIGEST_DB_FILE], O_RDWR, 0); #endif if(!rpdb) { log_msg(LOG_WARNING, "Error opening digest_cache: '%s': %s", opts->config[CONF_DIGEST_DB_FILE], MY_DBM_STRERROR(errno) ); return(SPA_MSG_DIGEST_CACHE_ERROR); } db_ent = MY_DBM_FETCH(rpdb, db_key); /* If the datum is null, we have a new entry. */ if(db_ent.dptr == NULL) { /* This is a new SPA packet that needs to be added to the cache. */ dc_info.src_ip = opts->spa_pkt.packet_src_ip; dc_info.dst_ip = opts->spa_pkt.packet_dst_ip; dc_info.src_port = opts->spa_pkt.packet_src_port; dc_info.dst_port = opts->spa_pkt.packet_dst_port; dc_info.proto = opts->spa_pkt.packet_proto; dc_info.created = time(NULL); dc_info.first_replay = dc_info.last_replay = dc_info.replay_count = 0; db_ent.dsize = sizeof(digest_cache_info_t); db_ent.dptr = (char*)&(dc_info); if(MY_DBM_STORE(rpdb, db_key, db_ent, MY_DBM_INSERT) != 0) { log_msg(LOG_WARNING, "Error adding entry digest_cache: %s", MY_DBM_STRERROR(errno) ); res = SPA_MSG_DIGEST_CACHE_ERROR; } res = SPA_MSG_SUCCESS; } else res = SPA_MSG_DIGEST_CACHE_ERROR; MY_DBM_CLOSE(rpdb); return(res); #endif /* NO_DIGEST_CACHE */ }
看 add_replay_dbm_cache 函数,其实就是一个 DB 操作。
显示查找,如果找不到,说明不存在这样的key,则进行存储, 并返回成功,如果找到了,说明以前发送过这个包。
则返回 SPA_MSG_DIGEST_CACHE_ERROR,摘要缓存失败。
所以可以总结出:防重放攻击,其实就是客户端发送的每一个密文包过来,服务器存储到了DB中,如果存储失败,表示已经存在,则置失败,防止是重放的包。