The repo was committed from WSL with core.filemode=false, so the exec bit was never recorded. After actions/checkout the entry script comes down as 100644 and tests/test_cli.sh fails with Permission denied. Set mode 100755 on every script that is invoked directly (entry, installer, test suite, mock binaries). Sourced helpers under lib/ keep 100644 per convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
151 lines
7.4 KiB
Bash
Executable File
151 lines
7.4 KiB
Bash
Executable File
#!/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')
|
|
line_v4=$(storage_get "${uuid_v4}")
|
|
assert_eq '1' "$(storage_count)" 'cmd_add_batch should persist one rule'
|
|
assert_eq '4' "$(grep -Ec '^iptables ' "${IPTABLES_MOCK_LOG}")" 'tcp/ipv4 add should emit four iptables commands'
|
|
assert_contains "${line_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'
|
|
assert_contains "$(cat "${IPTABLES_MOCK_LOG}")" '--sport 80' 'add should include reply-direction FORWARD rule'
|
|
assert_eq '✓' "$(rule_health_mark "${line_v4}")" 'healthy runtime rule should show ok marker'
|
|
|
|
"${IPTABLES_BIN}" -D FORWARD \
|
|
-p tcp -s 127.0.0.1 --sport 80 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED \
|
|
-j ACCEPT \
|
|
-m comment --comment "MGMT:${uuid_v4}"
|
|
assert_contains "$(ipt_find_by_uuid "${uuid_v4}")" "MGMT:${uuid_v4}" 'partial runtime loss should still leave uuid-tagged rules'
|
|
assert_eq '!' "$(rule_health_mark "${line_v4}")" 'partial runtime loss should mark rule unhealthy'
|
|
list_output=$(cmd_list 0)
|
|
[[ ${list_output} =~ [[:space:]]![[:space:]] ]] || fail 'cmd_list should expose degraded health marker'
|
|
|
|
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 '16' "${add_count}" 'both/both add should emit sixteen commands'
|
|
|
|
cmd_delete_uuid "${uuid_both}"
|
|
del_count=$(grep -Ec ' -D ' "${IPTABLES_MOCK_LOG}")
|
|
assert_eq '16' "${del_count}" 'deleting both/both rule should emit sixteen delete commands'
|
|
assert_eq '0' "$(storage_count)" 'cmd_delete_uuid should remove rule from storage'
|
|
|
|
reset_mock_state
|
|
cmd_add_batch tcp 9080 127.0.0.1 98 4 'desc-mark' >/dev/null
|
|
mapfile -t list_lines < <(cmd_list 0)
|
|
assert_eq "$(display_width "${list_lines[0]%%描述*}")" "$(display_width "${list_lines[1]%%desc-mark*}")" 'cmd_list should align description column by display width'
|
|
|
|
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'
|