#!/usr/bin/env bash if [[ -n ${IPF_IPTABLES_OPS_SH_LOADED:-} ]]; then return 0 fi IPF_IPTABLES_OPS_SH_LOADED=1 : "${IPTABLES_BIN:=iptables}" : "${IP6TABLES_BIN:=ip6tables}" : "${IPTABLES_SAVE_BIN:=iptables-save}" : "${IP6TABLES_SAVE_BIN:=ip6tables-save}" ipt_comment_tag() { printf 'MGMT:%s\n' "$1" } ipt_protocols_for() { case ${1-} in tcp|udp) printf '%s\n' "$1" ;; both) printf 'tcp\nudp\n' ;; *) return 1 ;; esac } ipt_families_for() { case ${1-} in 4|6) printf '%s\n' "$1" ;; both) printf '4\n6\n' ;; *) return 1 ;; esac } ipt_bin_for_family() { case ${1-} in 4) printf '%s\n' "${IPTABLES_BIN}" ;; 6) printf '%s\n' "${IP6TABLES_BIN}" ;; *) return 1 ;; esac } ipt_save_bin_for_family() { case ${1-} in 4) printf '%s\n' "${IPTABLES_SAVE_BIN}" ;; 6) printf '%s\n' "${IP6TABLES_SAVE_BIN}" ;; *) return 1 ;; esac } ipt_target_for_family() { local raw=${1-} local family=${2-} if [[ ${raw} == *,* ]]; then case ${family} in 4) printf '%s\n' "${raw%%,*}" ;; 6) printf '%s\n' "${raw#*,}" ;; *) return 1 ;; esac return 0 fi printf '%s\n' "${raw}" } ipt_to_destination() { local tip_raw=${1-} local tport=${2-} local family=${3-} local tip tip=$(ipt_target_for_family "${tip_raw}" "${family}") if [[ ${family} == 6 ]]; then printf '[%s]:%s\n' "${tip}" "${tport}" else printf '%s:%s\n' "${tip}" "${tport}" fi } _ipt_exec_rule() { local bin=$1 local table=$2 local action=$3 local chain=$4 shift 4 if [[ -n ${table} ]]; then "${bin}" -t "${table}" "-${action}" "${chain}" "$@" else "${bin}" "-${action}" "${chain}" "$@" fi } _ipt_check_rule() { local bin=$1 local table=$2 local chain=$3 shift 3 if [[ -n ${table} ]]; then "${bin}" -t "${table}" -C "${chain}" "$@" >/dev/null 2>&1 else "${bin}" -C "${chain}" "$@" >/dev/null 2>&1 fi } _ipt_apply_expected_rules() { local action=$1 local uuid=$2 local proto=$3 local lport=$4 local tip_raw=$5 local tport=$6 local ipver=$7 local family protocol bin tip comment destination comment=$(ipt_comment_tag "${uuid}") while IFS= read -r family; do [[ -n ${family} ]] || continue bin=$(ipt_bin_for_family "${family}") tip=$(ipt_target_for_family "${tip_raw}" "${family}") destination=$(ipt_to_destination "${tip_raw}" "${tport}" "${family}") while IFS= read -r protocol; do [[ -n ${protocol} ]] || continue if ! _ipt_exec_rule "${bin}" nat "${action}" PREROUTING \ -p "${protocol}" --dport "${lport}" \ -j DNAT --to-destination "${destination}" \ -m comment --comment "${comment}"; then return 1 fi if ! _ipt_exec_rule "${bin}" nat "${action}" POSTROUTING \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -j MASQUERADE \ -m comment --comment "${comment}"; then return 1 fi if ! _ipt_exec_rule "${bin}" '' "${action}" FORWARD \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -m conntrack --ctstate NEW,ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}"; then return 1 fi if ! _ipt_exec_rule "${bin}" '' "${action}" FORWARD \ -p "${protocol}" -s "${tip}" --sport "${tport}" \ -m conntrack --ctstate ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}"; then return 1 fi done < <(ipt_protocols_for "${proto}") done < <(ipt_families_for "${ipver}") } ipt_apply_rule() { local uuid=$1 proto=$2 lport=$3 tip_raw=$4 tport=$5 ipver=$6 if ! _ipt_apply_expected_rules A "${uuid}" "${proto}" "${lport}" "${tip_raw}" "${tport}" "${ipver}"; then ipt_remove_rule "${uuid}" "${proto}" "${lport}" "${tip_raw}" "${tport}" "${ipver}" >/dev/null 2>&1 || true return 1 fi } ipt_rule_healthy() { local uuid=$1 proto=$2 lport=$3 tip_raw=$4 tport=$5 ipver=$6 local family protocol bin tip comment destination comment=$(ipt_comment_tag "${uuid}") while IFS= read -r family; do [[ -n ${family} ]] || continue bin=$(ipt_bin_for_family "${family}") tip=$(ipt_target_for_family "${tip_raw}" "${family}") destination=$(ipt_to_destination "${tip_raw}" "${tport}" "${family}") while IFS= read -r protocol; do [[ -n ${protocol} ]] || continue _ipt_check_rule "${bin}" nat PREROUTING \ -p "${protocol}" --dport "${lport}" \ -j DNAT --to-destination "${destination}" \ -m comment --comment "${comment}" || return 1 _ipt_check_rule "${bin}" nat POSTROUTING \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -j MASQUERADE \ -m comment --comment "${comment}" || return 1 _ipt_check_rule "${bin}" '' FORWARD \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -m conntrack --ctstate NEW,ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}" || return 1 _ipt_check_rule "${bin}" '' FORWARD \ -p "${protocol}" -s "${tip}" --sport "${tport}" \ -m conntrack --ctstate ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}" || return 1 done < <(ipt_protocols_for "${proto}") done < <(ipt_families_for "${ipver}") } ipt_remove_rule() { local uuid=$1 proto=$2 lport=$3 tip_raw=$4 tport=$5 ipver=$6 local -a families protocols local family protocol bin tip comment destination local missing=0 comment=$(ipt_comment_tag "${uuid}") mapfile -t families < <(ipt_families_for "${ipver}") mapfile -t protocols < <(ipt_protocols_for "${proto}") for ((family_index=${#families[@]} - 1; family_index >= 0; family_index--)); do family=${families[family_index]} [[ -n ${family} ]] || continue bin=$(ipt_bin_for_family "${family}") tip=$(ipt_target_for_family "${tip_raw}" "${family}") destination=$(ipt_to_destination "${tip_raw}" "${tport}" "${family}") for ((proto_index=${#protocols[@]} - 1; proto_index >= 0; proto_index--)); do protocol=${protocols[proto_index]} [[ -n ${protocol} ]] || continue if _ipt_check_rule "${bin}" '' FORWARD \ -p "${protocol}" -s "${tip}" --sport "${tport}" \ -m conntrack --ctstate ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}"; then _ipt_exec_rule "${bin}" '' D FORWARD \ -p "${protocol}" -s "${tip}" --sport "${tport}" \ -m conntrack --ctstate ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}" || return 1 else missing=1 fi if _ipt_check_rule "${bin}" '' FORWARD \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -m conntrack --ctstate NEW,ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}"; then _ipt_exec_rule "${bin}" '' D FORWARD \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -m conntrack --ctstate NEW,ESTABLISHED,RELATED \ -j ACCEPT \ -m comment --comment "${comment}" || return 1 else missing=1 fi if _ipt_check_rule "${bin}" nat POSTROUTING \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -j MASQUERADE \ -m comment --comment "${comment}"; then _ipt_exec_rule "${bin}" nat D POSTROUTING \ -p "${protocol}" -d "${tip}" --dport "${tport}" \ -j MASQUERADE \ -m comment --comment "${comment}" || return 1 else missing=1 fi if _ipt_check_rule "${bin}" nat PREROUTING \ -p "${protocol}" --dport "${lport}" \ -j DNAT --to-destination "${destination}" \ -m comment --comment "${comment}"; then _ipt_exec_rule "${bin}" nat D PREROUTING \ -p "${protocol}" --dport "${lport}" \ -j DNAT --to-destination "${destination}" \ -m comment --comment "${comment}" || return 1 else missing=1 fi done done if (( missing == 1 )); then return 0 fi } ipt_find_by_uuid() { local uuid=${1-} local family save_bin found=1 line [[ -n ${uuid} ]] || return 1 for family in 4 6; do save_bin=$(ipt_save_bin_for_family "${family}") if ! command_is_available "${save_bin}"; then continue fi while IFS= read -r line || [[ -n ${line} ]]; do [[ ${line} == *"MGMT:${uuid}"* ]] || continue printf '%s|%s\n' "${family}" "${line}" found=0 done < <("${save_bin}") done return "${found}" }