Files
IPTables-Management/tests/test_rules_unit.sh

133 lines
6.4 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
# shellcheck source=tests/lib/assert.sh
source "${ROOT_DIR}/tests/lib/assert.sh"
status_of() {
set +e
"$@" >/dev/null 2>&1
local rc=$?
set -e
printf '%s\n' "${rc}"
}
wait_status() {
local pid=$1
set +e
wait "${pid}"
WAIT_STATUS_RESULT=$?
set -e
}
TMP_DIR=$(mktemp -d)
trap 'rm -rf "${TMP_DIR}"' EXIT
export IPF_STORAGE_DIR="${TMP_DIR}/storage"
export IPF_STORAGE_DB="${IPF_STORAGE_DIR}/rules.db"
export IPF_LOCK_FILE="${IPF_STORAGE_DIR}/.lock"
export IPTABLES_MOCK_DIR="${TMP_DIR}/iptables-mock"
export IPTABLES_MOCK_LOG="${TMP_DIR}/iptables.log"
export IPTABLES_BIN="${ROOT_DIR}/tests/mocks/iptables"
export IP6TABLES_BIN="${ROOT_DIR}/tests/mocks/ip6tables"
export IPTABLES_SAVE_BIN="${ROOT_DIR}/tests/mocks/iptables-save"
export IP6TABLES_SAVE_BIN="${ROOT_DIR}/tests/mocks/ip6tables-save"
export NETFILTER_PERSISTENT_BIN="${ROOT_DIR}/tests/mocks/persist-mock.sh"
export PERSIST_MOCK_LOG="${TMP_DIR}/persist.log"
# shellcheck source=lib/common.sh
source "${ROOT_DIR}/lib/common.sh"
# shellcheck source=lib/storage.sh
source "${ROOT_DIR}/lib/storage.sh"
# shellcheck source=lib/persist.sh
source "${ROOT_DIR}/lib/persist.sh"
# shellcheck source=lib/iptables_ops.sh
source "${ROOT_DIR}/lib/iptables_ops.sh"
# shellcheck source=lib/rules_mgr.sh
source "${ROOT_DIR}/lib/rules_mgr.sh"
reset_mock_state() {
rm -rf "${IPTABLES_MOCK_DIR}" "${IPF_STORAGE_DIR}"
mkdir -p "${IPTABLES_MOCK_DIR}" "${IPF_STORAGE_DIR}"
: >"${IPTABLES_MOCK_LOG}"
: >"${PERSIST_MOCK_LOG}"
unset IPTABLES_MOCK_FAIL_ON_N || true
unset PERSIST_MOCK_FAIL || true
unset PERSIST_MOCK_DELAY_SECS PERSIST_MOCK_ACTIVE_DIR PERSIST_MOCK_FAIL_ON_CONCURRENT || true
storage_init
}
reset_mock_state
uuid_v4=$(cmd_add_batch tcp 8080 127.0.0.1 80 4 'web service')
assert_eq '1' "$(storage_count)" 'cmd_add_batch should persist one rule'
assert_eq '3' "$(grep -Ec '^iptables ' "${IPTABLES_MOCK_LOG}")" 'tcp/ipv4 add should emit three iptables commands'
assert_contains "$(storage_get "${uuid_v4}")" "uuid=${uuid_v4}" 'stored line should contain generated uuid'
assert_eq '1' "$(grep -Ec 'persist-mock\.sh save' "${PERSIST_MOCK_LOG}")" 'successful add should trigger persist_save'
assert_contains "$(ipt_find_by_uuid "${uuid_v4}")" "MGMT:${uuid_v4}" 'ipt_find_by_uuid should locate saved mock rules'
reset_mock_state
uuid_both=$(cmd_add_batch both 5353 '127.0.0.1,::1' 53 both 'dual stack dns')
add_count=$(grep -Ec '^(iptables|ip6tables) ' "${IPTABLES_MOCK_LOG}")
assert_eq '12' "${add_count}" 'both/both add should emit twelve commands'
cmd_delete_uuid "${uuid_both}"
del_count=$(grep -Ec ' -D ' "${IPTABLES_MOCK_LOG}")
assert_eq '12' "${del_count}" 'deleting both/both rule should emit twelve delete commands'
assert_eq '0' "$(storage_count)" 'cmd_delete_uuid should remove rule from storage'
reset_mock_state
export IPTABLES_MOCK_FAIL_ON_N=2
assert_eq '1' "$(status_of cmd_add_batch tcp 9000 127.0.0.1 90 4 'rollback')" 'cmd_add_batch should fail when iptables mock injects an error'
assert_eq '0' "$(storage_count)" 'failed add should not persist storage'
assert_contains "$(cat "${IPTABLES_MOCK_LOG}")" ' -D ' 'failed add should trigger rollback deletes'
assert_eq '0' "$(wc -l < "${PERSIST_MOCK_LOG}")" 'failed add before persistence should not call persist_save'
reset_mock_state
export PERSIST_MOCK_FAIL=1
assert_eq '1' "$(status_of cmd_add_batch tcp 9001 127.0.0.1 91 4 'persist rollback add')" 'cmd_add_batch should fail when persist_save fails'
assert_eq '0' "$(storage_count)" 'persist_save failure on add should roll back storage'
assert_eq '0' "$(wc -l < "${IPTABLES_MOCK_DIR}/state.v4")" 'persist_save failure on add should remove runtime rules'
assert_contains "$(cat "${IPTABLES_MOCK_LOG}")" ' -D ' 'persist_save failure on add should trigger runtime rollback'
assert_eq '1' "$(grep -Ec 'persist-mock\.sh save' "${PERSIST_MOCK_LOG}")" 'persist_save failure on add should still attempt one save'
reset_mock_state
uuid_delete_rollback=$(cmd_add_batch tcp 9100 127.0.0.1 92 4 'persist rollback delete')
export PERSIST_MOCK_FAIL=1
assert_eq '1' "$(status_of cmd_delete_uuid "${uuid_delete_rollback}")" 'cmd_delete_uuid should fail when persist_save fails'
assert_eq '1' "$(storage_count)" 'persist_save failure on delete should restore storage'
assert_contains "$(storage_get "${uuid_delete_rollback}")" "uuid=${uuid_delete_rollback}" 'persist_save failure on delete should restore storage line'
assert_contains "$(ipt_find_by_uuid "${uuid_delete_rollback}")" "MGMT:${uuid_delete_rollback}" 'persist_save failure on delete should restore runtime rules'
assert_eq '2' "$(grep -Ec 'persist-mock\.sh save' "${PERSIST_MOCK_LOG}")" 'persist_save failure on delete should include add and delete save attempts'
reset_mock_state
uuid_storage_delete_rollback=$(cmd_add_batch tcp 9200 127.0.0.1 93 4 'storage rollback delete')
storage_delete_impl=$(declare -f storage_delete_unlocked)
storage_delete_unlocked() {
return 1
}
assert_eq '1' "$(status_of cmd_delete_uuid "${uuid_storage_delete_rollback}")" 'cmd_delete_uuid should fail when storage delete fails'
assert_eq '1' "$(storage_count)" 'storage delete failure should keep storage record'
assert_contains "$(storage_get "${uuid_storage_delete_rollback}")" "uuid=${uuid_storage_delete_rollback}" 'storage delete failure should keep original storage line'
assert_contains "$(ipt_find_by_uuid "${uuid_storage_delete_rollback}")" "MGMT:${uuid_storage_delete_rollback}" 'storage delete failure should restore runtime rules'
eval "${storage_delete_impl}"
reset_mock_state
export PERSIST_MOCK_DELAY_SECS=0.2
export PERSIST_MOCK_ACTIVE_DIR="${TMP_DIR}/persist-active"
export PERSIST_MOCK_FAIL_ON_CONCURRENT=1
cmd_add_batch tcp 9300 127.0.0.1 94 4 'parallel one' >"${TMP_DIR}/parallel-1.out" 2>"${TMP_DIR}/parallel-1.err" &
pid1=$!
sleep 0.05
cmd_add_batch tcp 9301 127.0.0.1 95 4 'parallel two' >"${TMP_DIR}/parallel-2.out" 2>"${TMP_DIR}/parallel-2.err" &
pid2=$!
wait_status "${pid1}"
assert_eq '0' "${WAIT_STATUS_RESULT}" 'first concurrent add should succeed'
wait_status "${pid2}"
assert_eq '0' "${WAIT_STATUS_RESULT}" 'second concurrent add should wait and succeed'
assert_eq '2' "$(storage_count)" 'concurrent adds should persist both rules'
assert_eq '2' "$(grep -Ec 'persist-mock\.sh save' "${PERSIST_MOCK_LOG}")" 'concurrent adds should serialize persist_save calls'
unset PERSIST_MOCK_DELAY_SECS PERSIST_MOCK_ACTIVE_DIR PERSIST_MOCK_FAIL_ON_CONCURRENT
pass 'test_rules_unit.sh'