# High-level function which will create a ruleset for the current firewall
# configuration and load it into the kernel.
function firewall_start() {
+ local protocol="${1}"
+ assert isset protocol
+ shift
+
+ # Test mode.
+ local test="false"
+
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ --test)
+ test="true"
+ ;;
+ *)
+ error "Unrecognized argument: ${1}"
+ return ${EXIT_ERROR}
+ ;;
+ esac
+ shift
+ done
+
+ if enabled test; then
+ log INFO "Test mode enabled."
+ log INFO "The firewall ruleset will not be loaded."
+ fi
+
firewall_lock_acquire
# Initialize an empty iptables ruleset.
- iptables_init DROP
+ iptables_init "${protocol}" "DROP"
# Add default chains.
- firewall_tcp_state_flags
- firewall_connection_tracking
+ firewall_tcp_state_flags "${protocol}"
+ firewall_custom_chains "${protocol}"
+ firewall_connection_tracking "${protocol}"
+ firewall_tcp_clamp_mss "${protocol}"
# Add policies for every zone.
- policy_add_localhost
+ firewall_localhost_create_chains "${protocol}"
local zone
for zone in $(zones_get_all); do
- policy_add_zone ${zone}
+ # Create all needed chains for the zone.
+ firewall_zone_create_chains "${protocol}" "${zone}"
+
+ # After the chains that are always available have been
+ # created, we will add a custom policy to every single
+ # zone.
+
+ policy_zone_add "${protocol}" "${zone}"
done
- # Commit the new ruleset.
- iptables_commit
+ # Load the new ruleset.
+ local args
+ if enabled testmode; then
+ list_append args "--test"
+ fi
+ iptables_commit "${protocol}" ${args}
firewall_lock_release
}
function firewall_stop() {
+ local protocol="${1}"
+ assert isset protocol
+
firewall_lock_acquire
# Initialize an empty firewall ruleset
# with default policy ACCEPT.
- iptables_init ACCEPT
+ iptables_init "${protocol}" ACCEPT
- # Commit it.
- iptables_commit
+ # Load it.
+ ipables_load "${protocol}"
+
+ firewall_lock_release
+}
+
+function firewall_show() {
+ local protocol="${1}"
+ assert isset protocol
+
+ # Shows the ruleset that is currently loaded.
+ iptables_status "${protocol}"
+
+ return ${EXIT_OK}
+}
+
+function firewall_panic() {
+ local protocol="${1}"
+ assert isset protocol
+ shift
+
+ local admin_hosts="$@"
+
+ firewall_lock_acquire "${protocol}"
+
+ # Drop all communications.
+ iptables_init "${protocol}" DROP
+
+ # If an admin host is provided, some administrative
+ # things will be allowed from there.
+ local admin_host
+ for admin_host in ${admin_hosts}; do
+ iptables "${protocol}" -A INPUT -s "${admin_host}" -j ACCEPT
+ iptables "${protocol}" -A OUTPUT -d "${admin_host}" -j ACCEPT
+ done
+
+ # Load it.
+ iptables_commit "${protocol}"
firewall_lock_release
}
lock_release ${RUN_DIR}/.firewall_lock
}
+function firewall_custom_chains() {
+ local protocol="${1}"
+ assert isset protocol
+
+ log INFO "Creating CUSTOM* chains..."
+
+ # These chains are intened to be filled with
+ # rules by the user. They are processed at the very
+ # beginning so it is possible to overwrite everything.
+
+ iptables_chain_create "${protocol}" CUSTOMINPUT
+ iptables "${protocol}" -A INPUT -j CUSTOMINPUT
+
+ iptables_chain_create "${protocol}" CUSTOMFORWARD
+ iptables "${protocol}" -A FORWARD -j CUSTOMFORWARD
+
+ iptables_chain_create "${protocol}" CUSTOMOUTPUT
+ iptables "${protocol}" -A OUTPUT -j CUSTOMOUTPUT
+
+ iptables_chain_create "${protocol}" -t nat CUSTOMPREROUTING
+ iptables "${protocol}" -t nat -A PREROUTING -j CUSTOMPREROUTING
+
+ iptables_chain_create "${protocol}" -t nat CUSTOMPOSTROUTING
+ iptables "${protocol}" -t nat -A POSTROUTING -j CUSTOMPOSTROUTING
+
+ iptables_chain_create "${protocol}" -t nat CUSTOMOUTPUT
+ iptables "${protocol}" -t nat -A OUTPUT -j CUSTOMOUTPUT
+}
+
function firewall_tcp_state_flags() {
+ local protocol="${1}"
+ assert isset protocol
+
log INFO "Creating TCP State Flags chain..."
- iptables_chain_create BADTCP_LOG
- iptables -A BADTCP_LOG -p tcp -j $(iptables_LOG "Illegal TCP state: ")
- iptables -A BADTCP_LOG -j DROP
-
- iptables_chain_create BADTCP
- iptables -A BADTCP -p tcp --tcp-flags ALL NONE -j BADTCP_LOG
- iptables -A BADTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADTCP_LOG
- iptables -A BADTCP -p tcp --tcp-flags SYN,RST SYN,RST -j BADTCP_LOG
- iptables -A BADTCP -p tcp --tcp-flags FIN,RST FIN,RST -j BADTCP_LOG
- iptables -A BADTCP -p tcp --tcp-flags ACK,FIN FIN -j BADTCP_LOG
- iptables -A BADTCP -p tcp --tcp-flags ACK,PSH PSH -j BADTCP_LOG
- iptables -A BADTCP -p tcp --tcp-flags ACK,URG URG -j BADTCP_LOG
-
- iptables -A INPUT -p tcp -j BADTCP
- iptables -A OUTPUT -p tcp -j BADTCP
- iptables -A FORWARD -p tcp -j BADTCP
+
+ iptables_chain_create "${protocol}" BADTCP_LOG
+ iptables "${protocol}" -A BADTCP_LOG -p tcp -j "$(iptables_LOG "Illegal TCP state: ")"
+ iptables "${protocol}" -A BADTCP_LOG -j DROP
+
+ iptables_chain_create "${protocol}" BADTCP
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ALL NONE -j BADTCP_LOG
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADTCP_LOG
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags SYN,RST SYN,RST -j BADTCP_LOG
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags FIN,RST FIN,RST -j BADTCP_LOG
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ACK,FIN FIN -j BADTCP_LOG
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ACK,PSH PSH -j BADTCP_LOG
+ iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ACK,URG URG -j BADTCP_LOG
+
+ iptables "${protocol}" -A INPUT -p tcp -j BADTCP
+ iptables "${protocol}" -A OUTPUT -p tcp -j BADTCP
+ iptables "${protocol}" -A FORWARD -p tcp -j BADTCP
+}
+
+function firewall_tcp_clamp_mss() {
+ # Do nothing if this has been disabled.
+ enabled FIREWALL_CLAMP_PATH_MTU || return ${EXIT_OK}
+
+ local protocol="${1}"
+ assert isset protocol
+
+ log DEBUG "Adding rules to clamp MSS to path MTU..."
+
+ iptables "${protocol}" -t mangle -A FORWARD \
+ -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
}
function firewall_connection_tracking() {
+ local protocol="${1}"
+ assert isset protocol
+
log INFO "Creating Connection Tracking chain..."
- iptables_chain_create CONNTRACK
- iptables -A CONNTRACK -m state --state ESTABLISHED,RELATED -j ACCEPT
- iptables -A CONNTRACK -m state --state INVALID -j $(iptables_LOG "INVALID packet: ")
- iptables -A CONNTRACK -m state --state INVALID -j DROP
-
- iptables -A INPUT -j CONNTRACK
- iptables -A OUTPUT -j CONNTRACK
- iptables -A FORWARD -j CONNTRACK
+
+ iptables_chain_create "${protocol}" CONNTRACK
+ iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
+ iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate INVALID -j "$(iptables_LOG "INVALID packet: ")"
+ iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate INVALID -j DROP
+
+ iptables "${protocol}" -A INPUT -j CONNTRACK
+ iptables "${protocol}" -A OUTPUT -j CONNTRACK
+ iptables "${protocol}" -A FORWARD -j CONNTRACK
}
-function firewall_import_portfw() {
- local zone=${1}
+function firewall_localhost_create_chains() {
+ local protocol="${1}"
+ assert isset protocol
+
+ log DEBUG "Creating firewall chains for localhost..."
+
+ # Accept everything on lo
+ iptables "${protocol}" -A INPUT -i lo -m conntrack --ctstate NEW -j ACCEPT
+ iptables "${protocol}" -A OUTPUT -o lo -m conntrack --ctstate NEW -j ACCEPT
+}
+
+function firewall_zone_create_chains() {
+ local protocol="${1}"
+ assert isset protocol
+
+ local zone="${2}"
+ assert isset zone
+
+ log DEBUG "Creating firewall chains for zone '${zone}'."
+
+ local chain_prefix="ZONE_${zone^^}"
+
+ # Create filter chains.
+ iptables_chain_create "${protocol}" "${chain_prefix}_INPUT"
+ iptables "${protocol}" -A INPUT -i ${zone} -j "${chain_prefix}_INPUT"
+
+ iptables_chain_create "${protocol}" "${chain_prefix}_OUTPUT"
+ iptables "${protocol}" -A OUTPUT -o ${zone} -j "${chain_prefix}_OUTPUT"
+
+ # Custom rules.
+ iptables_chain_create "${protocol}" "${chain_prefix}_CUSTOM"
+
+ # Intrusion Prevention System.
+ iptables_chain_create "${protocol}" "${chain_prefix}_IPS"
+
+ # Create a chain for each other zone.
+ # This leaves us with n^2 chains. Duh.
+
+ local other_zone other_chain_prefix
+ for other_zone in $(zones_get_all); do
+ other_chain_prefix="${chain_prefix}_${other_zone^^}"
+ iptables_chain_create "${protocol}" "${other_chain_prefix}"
+
+ # Connect the chain with the FORWARD chain.
+ iptables "${protocol}" -A FORWARD -i "${zone}" -o "${other_zone}" \
+ -j "${other_chain_prefix}"
+
+ # Handle custom rules.
+ iptables "${protocol}" -A "${other_chain_prefix}" -j "${chain_prefix}_CUSTOM"
+
+ # Link IPS.
+ iptables "${protocol}" -A "${other_chain_prefix}" -j "${chain_prefix}_IPS"
+
+ # Rules.
+ iptables_chain_create "${protocol}" "${other_chain_prefix}_RULES"
+ iptables "${protocol}" -A "${other_chain_prefix}" -j "${other_chain_prefix}_RULES"
+
+ # Policy.
+ iptables_chain_create "${protocol}" "${other_chain_prefix}_POLICY"
+ iptables "${protocol}" -A "${other_chain_prefix}" -j "${other_chain_prefix}_POLICY"
+ done
+
+ ## Create mangle chain.
+ #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}"
+ #iptables "${protocol}" -t mangle -A PREROUTING -i "${zone}" -j "${chain_prefix}"
+ #iptables "${protocol}" -t mangle -A POSTROUTING -o "${zone}" -j "${chain_prefix}"
+
+ ## Quality of Service
+ #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}_QOS_INC"
+ #iptables "${protocol}" -t mangle -A "${chain_prefix}" -i "${zone}" -j "${chain_prefix}_QOS_INC"
+ #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}_QOS_OUT"
+ #iptables "${protocol}" -t mangle -A "${chain_prefix}" -o "${zone}" -j "${chain_prefix}_QOS_OUT"
+
+ # Create NAT chain.
+ iptables_chain_create "${protocol}" -t nat "${chain_prefix}"
+ iptables "${protocol}" -t nat -A PREROUTING -i "${zone}" -j "${chain_prefix}"
+ iptables "${protocol}" -t nat -A POSTROUTING -o "${zone}" -j "${chain_prefix}"
+
+ # Network Address Translation
+ iptables_chain_create "${protocol}" -t nat "${chain_prefix}_DNAT"
+ iptables "${protocol}" -t nat -A PREROUTING -i "${zone}" -j "${chain_prefix}_DNAT"
+ iptables_chain_create "${protocol}" -t nat "${chain_prefix}_SNAT"
+ iptables "${protocol}" -t nat -A POSTROUTING -o "${zone}" -j "${chain_prefix}_SNAT"
+
+ # UPnP
+ iptables_chain_create "${protocol}" -t nat "${chain_prefix}_UPNP"
+ iptables "${protocol}" -t nat -A "${chain_prefix}" -j "${chain_prefix}_UPNP"
+
+ return ${EXIT_OK}
+}
+
+function firewall_parse_rules() {
+ local file=${1}
+ assert isset file
shift
- local protocol="ipv6"
- local chain="filter"
+ # End if no rule file exists.
+ [ -r "${file}" ] || return ${EXIT_OK}
- while [ $# -gt 0 ]; do
- case "${1}" in
- --chain=*)
- chain=$(cli_get_val ${1})
- ;;
- --protocol=*)
- protocol=$(cli_get_val ${1})
- ;;
- esac
+ local cmd
+
+ local ${FIREWALL_RULES_CONFIG_PARAMS}
+ local line
+ while read -r line; do
+ # Skip empty lines.
+ [ -n "${line}" ] || continue
+
+ # Skip commented lines.
+ [ "${line:0:1}" = "#" ] && continue
+
+ # Parse the rule.
+ _firewall_parse_rule_line ${line}
+ if [ $? -ne ${EXIT_OK} ]; then
+ log WARNING "Skipping invalid line: ${line}"
+ continue
+ fi
+
+ cmd="iptables $@"
+
+ # Source IP address/net.
+ if isset src; then
+ list_append cmd "-s ${src}"
+ fi
+
+ # Destination IP address/net.
+ if isset dst; then
+ list_append cmd "-d ${dst}"
+ fi
+
+ # Protocol.
+ if isset proto; then
+ list_append cmd "-p ${proto}"
+
+ if list_match ${proto} ${FIREWALL_PROTOCOLS_SUPPORTING_PORTS}; then
+ if isset sport; then
+ list_append cmd "--sport ${sport}"
+ fi
+
+ if isset dport; then
+ list_append cmd "--dport ${dport}"
+ fi
+ fi
+ fi
+
+ # Always append the action.
+ list_append cmd "-j ${action}"
+
+ # Execute command.
+ ${cmd}
+ done < ${file}
+}
+
+function _firewall_parse_rule_line() {
+ local arg
+
+ # Clear all values.
+ for arg in ${FIREWALL_RULES_CONFIG_PARAMS}; do
+ assign "${arg}" ""
done
- assert isoneof protocol ipv4 ipv6
+ local key val
+ while read -r arg; do
+ key=$(cli_get_key ${arg})
- local allowed_chains="filter"
- if [ "${protocol}" = "ipv4" ]; then
- allowed_chains="${allowed_chains} nat"
+ if ! listmatch "${key}" ${FIREWALL_RULES_CONFIG_PARAMS}; then
+ log WARNING "Unrecognized argument: ${arg}"
+ return ${EXIT_ERROR}
+ fi
+
+ val=$(cli_get_val ${arg})
+ assign "${key}" "${val}"
+ done <<< "$(args $@)"
+
+ # action must always be set.
+ if ! isset action; then
+ log WARNING "'action' is not set: $@"
+ return ${EXIT_ERROR}
fi
- assert isoneof chain ${allowed_chains}
- # XXX TODO
+ for arg in src dst; do
+ isset ${arg} || continue
- local src dst proto
- while read src dst proto; do
- case "${chain}" in
- filter)
- ;;
- nat)
- ;;
- esac
- done < ${FIREWALL_CONFIG_PORTFW}
-}
+ # Check for valid IP addresses.
+ if ! ip_is_valid ${!arg}; then
+ log WARNING "Invalid IP address for '${arg}=${!arg}': $@"
+ return ${EXIT_ERROR}
+ fi
+ done
+ if isset proto; then
+ # Make lowercase.
+ proto=${proto,,}
+ if ! list_match "${proto}" ${FIREWALL_SUPPORTED_PROTOCOLS}; then
+ log WARNING "Unsupported protocol type 'proto=${proto}': $@"
+ return ${EXIT_ERROR}
+ fi
+ fi
+
+ for arg in sport dport; do
+ isset ${arg} || continue
+
+ # Check if port is valid.
+ if ! isinteger ${arg}; then
+ log WARNING "Invalid port '${arg}=${!arg}': $@"
+ return ${EXIT_ERROR}
+ fi
+ done
+
+ return ${EXIT_OK}
+}