immortalwrt/package/jsda/gargoyle-firewall-util/files/gargoyle_firewall_util.sh
2019-07-29 03:03:28 +08:00

586 lines
18 KiB
Bash

# Copyright Eric Bishop, 2008-2010
# This is free software licensed under the terms of the GNU GPL v2.0
#
. /lib/functions.sh
include /lib/network
ra_mask="0x0080"
ra_mark="$ra_mask/$ra_mask"
death_mask=0x8000
death_mark="$death_mask"
wan_if=""
mask_to_cidr()
{
mask="$1"
bits=0;
mask_parts=$(echo $mask | sed 's/\./ /g')
for p in $mask_parts ; do
case $p in
255)
bits=$(($bits + 8)) ;;
254)
bits=$(($bits + 7)) ;;
252)
bits=$(($bits + 6)) ;;
248)
bits=$(($bits + 5)) ;;
240)
bits=$(($bits + 4)) ;;
224)
bits=$(($bits + 3)) ;;
192)
bits=$(($bits + 2)) ;;
128)
bits=$(($bits + 1)) ;;
esac
done
echo $bits
}
define_wan_if()
{
if [ -z "$wan_if" ]; then
#Wait for up to 15 seconds for the wan interface to indicate it is up.
wait_sec=15
while [ -z "$(uci -P /var/state get network.wan.up 2>/dev/null)" ] && [ "$wait_sec" -gt 0 ]; do
sleep 1
wait_sec=$(($wait_sec - 1))
done
#The interface name will depend on if pppoe is used or not. If pppoe is used then
#the name we are looking for is in network.wan.ifname. If there is nothing there
#use the device named by network.wan.device
wan_if=$(uci -P /var/state get network.wan.ifname 2>/dev/null)
if [ -z "$wan_if" ]; then
wan_if=$(uci -P /var/state get network.wan.device 2>/dev/null)
fi
fi
}
# parse remote_accept sections in firewall config and add necessary rules
insert_remote_accept_rules()
{
local config_name="firewall"
local section_type="remote_accept"
ssh_max_attempts=$(uci -q get dropbear.@dropbear[0].max_remote_attempts)
ssh_port=$(uci -q get dropbear.@dropbear[0].Port)
if [ -z "$ssh_max_attempts" ] || [ "$ssh_max_attempts" = "unlimited" ]; then
ssh_max_attempts=""
else
ssh_max_attempts=$(( $ssh_max_attempts + 1 ))
fi
#add rules for remote_accepts
parse_remote_accept_config()
{
vars="local_port remote_port start_port end_port proto zone"
proto="tcp udp"
zone="wan"
for var in $vars ; do
config_get $var $1 $var
done
if [ "$proto" = "tcpudp" ] || [ -z "$proto" ]; then
proto="tcp udp"
fi
for prot in $proto ; do
if [ -n "$local_port" ]; then
if [ -z "$remote_port" ]; then
remote_port="$local_port"
fi
#Discourage brute force attacks on ssh from the WAN by limiting failed conneciton attempts.
#Each attempt gets a maximum of 10 password tries by dropbear.
if [ -n "$ssh_max_attempts" ] && [ "$local_port" = "$ssh_port" ] && [ "$prot" = "tcp" ]; then
iptables -t filter -A "input_${zone}_rule" -p "$prot" --dport $ssh_port -m recent --set --name SSH_CHECK
iptables -t filter -A "input_${zone}_rule" -m recent --update --seconds 300 --hitcount $ssh_max_attempts --name SSH_CHECK -j DROP
fi
if [ "$remote_port" != "$local_port" ]; then
#since we're inserting with -I, insert redirect rule first which will then be hit second, after setting connmark
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$remote_port" -j REDIRECT --to-ports "$local_port"
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$remote_port" -j CONNMARK --set-mark "$ra_mark"
iptables -t filter -A "input_${zone}_rule" -p $prot --dport "$local_port" -m connmark --mark "$ra_mark" -j ACCEPT
else
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$remote_port" -j REDIRECT --to-ports "$local_port"
iptables -t filter -A "input_${zone}_rule" -p "$prot" --dport "$local_port" -j ACCEPT
fi
elif [ -n "$start_port" ] && [ -n "$end_port" ]; then
iptables -t nat -I "zone_"$zone"_prerouting" -p "$prot" --dport "$start_port:$end_port" -j REDIRECT
iptables -t filter -A "input_${zone}_rule" -p "$prot" --dport "$start_port:$end_port" -j ACCEPT
fi
done
}
config_load "$config_name"
config_foreach parse_remote_accept_config "$section_type"
}
insert_pf_loopback_rules()
{
config_name="firewall"
section_type="redirect"
#Need to always delete the old chains first.
delete_chain_from_table "nat" "pf_loopback_A"
delete_chain_from_table "filter" "pf_loopback_B"
delete_chain_from_table "nat" "pf_loopback_C"
define_wan_if
if [ -z "$wan_if" ] ; then return ; fi
wan_ip=$(uci -p /tmp/state get network.wan.ipaddr)
lan_mask=$(uci -p /tmp/state get network.lan.netmask)
if [ -n "$wan_ip" ] && [ -n "$lan_mask" ]; then
iptables -t nat -N "pf_loopback_A"
iptables -t filter -N "pf_loopback_B"
iptables -t nat -N "pf_loopback_C"
iptables -t nat -I zone_lan_prerouting -d $wan_ip -j pf_loopback_A
iptables -t filter -I zone_lan_forward -j pf_loopback_B
iptables -t nat -I postrouting_rule -o br-lan -j pf_loopback_C
add_pf_loopback()
{
local vars="src dest proto src_dport dest_ip dest_port"
local all_defined="1"
for var in $vars ; do
config_get $var $1 $var
loaded=$(eval echo "\$$var")
#echo $var = $loaded
if [ -z "$loaded" ] && [ ! "$var" = "$src_dport" ]; then
all_defined="0"
fi
done
if [ -z "$src_dport" ]; then
src_dport=$dest_port
fi
sdp_dash=$src_dport
sdp_colon=$(echo $sdp_dash | sed 's/\-/:/g')
dp_dash=$dest_port
dp_colon=$(echo $dp_dash | sed 's/\-/:/g')
if [ "$all_defined" = "1" ] && [ "$src" = "wan" ] && [ "$dest" = "lan" ] ; then
iptables -t nat -A pf_loopback_A -p $proto --dport $sdp_colon -j DNAT --to-destination $dest_ip:$dp_dash
iptables -t filter -A pf_loopback_B -p $proto --dport $dp_colon -d $dest_ip -j ACCEPT
iptables -t nat -A pf_loopback_C -p $proto --dport $dp_colon -d $dest_ip -s $dest_ip/$lan_mask -j MASQUERADE
fi
}
config_load "$config_name"
config_foreach add_pf_loopback "$section_type"
fi
}
insert_dmz_rule()
{
local config_name="firewall"
local section_type="dmz"
#add rules for remote_accepts
parse_dmz_config()
{
vars="to_ip from"
for var in $vars ; do
config_get $var $1 $var
done
if [ -n "$from" ]; then
from_if=$(uci -q -p /tmp/state get network.$from.ifname)
fi
# echo "from_if = $from_if"
if [ -n "$to_ip" ] && [ -n "$from" ] && [ -n "$from_if" ]; then
iptables -t nat -A "zone_"$from"_prerouting" -i $from_if -j DNAT --to-destination $to_ip
# echo "iptables -t nat -A "prerouting_"$from -i $from_if -j DNAT --to-destination $to_ip"
iptables -t filter -I "zone_"$from"_forward" -d $to_ip -j ACCEPT
fi
}
config_load "$config_name"
config_foreach parse_dmz_config "$section_type"
}
insert_restriction_rules()
{
define_wan_if
if [ -z "$wan_if" ] ; then return ; fi
if [ -e /tmp/restriction_init.lock ]; then return ; fi
touch /tmp/restriction_init.lock
egress_exists=$(iptables -t filter -L egress_restrictions 2>/dev/null)
ingress_exists=$(iptables -t filter -L ingress_restrictions 2>/dev/null)
if [ -n "$egress_exists" ]; then
delete_chain_from_table filter egress_whitelist
delete_chain_from_table filter egress_restrictions
fi
if [ -n "$ingress_exists" ]; then
delete_chain_from_table filter ingress_whitelist
delete_chain_from_table filter ingress_restrictions
fi
iptables -t filter -N egress_restrictions
iptables -t filter -N ingress_restrictions
iptables -t filter -N egress_whitelist
iptables -t filter -N ingress_whitelist
iptables -t filter -I FORWARD -o $wan_if -j egress_restrictions
iptables -t filter -I FORWARD -i $wan_if -j ingress_restrictions
iptables -t filter -I egress_restrictions -j egress_whitelist
iptables -t filter -I ingress_restrictions -j ingress_whitelist
package_name="firewall"
parse_rule_config()
{
section=$1
section_type=$(uci get "$package_name"."$section")
config_get "enabled" "$section" "enabled"
if [ -z "$enabled" ]; then enabled="1" ; fi
if [ "$enabled" = "1" ] && ( [ "$section_type" = "restriction_rule" ] || [ "$section_type" = "whitelist_rule" ] ) ; then
#convert app_proto && not_app_proto to connmark here
config_get "app_proto" "$section" "app_proto"
config_get "not_app_proto" "$section" "not_app_proto"
if [ -n "$app_proto" ]; then
app_proto_connmark=$(cat /etc/l7marker.marks 2>/dev/null | grep $app_proto | awk '{ print $2 ; }' )
app_proto_mask=$(cat /etc/l7marker.marks 2>/dev/null | grep $app_proto | awk '{ print $3 ; }' )
uci set "$package_name"."$section".connmark="$app_proto_connmark/$app_proto_mask"
fi
if [ -n "$not_app_proto" ]; then
not_app_proto_connmark=$(cat /etc/l7marker.marks 2>/dev/null | grep "$not_app_proto" | awk '{ print $2 }')
not_app_proto_mask=$(cat /etc/l7marker.marks 2>/dev/null | grep "$not_app_proto" | awk '{ print $3 }')
uci set "$package_name"."$section".not_connmark="$not_app_proto_connmark/$not_app_proto_mask"
fi
table="filter"
chain="egress_restrictions"
ingress=""
target="REJECT"
config_get "is_ingress" "$section" "is_ingress"
if [ "$is_ingress" = "1" ]; then
ingress=" -i "
if [ "$section_type" = "restriction_rule" ]; then
chain="ingress_restrictions"
else
chain="ingress_whitelist"
fi
else
if [ "$section_type" = "restriction_rule" ]; then
chain="egress_restrictions"
else
chain="egress_whitelist"
fi
fi
if [ "$section_type" = "whitelist_rule" ]; then
target="ACCEPT"
fi
make_iptables_rules -p "$package_name" -s "$section" -t "$table" -c "$chain" -g "$target" $ingress
make_iptables_rules -p "$package_name" -s "$section" -t "$table" -c "$chain" -g "$target" $ingress -r
uci del "$package_name"."$section".connmark 2>/dev/null
uci del "$package_name"."$section".not_connmark 2>/dev/null
fi
}
config_load "$package_name"
config_foreach parse_rule_config "whitelist_rule"
config_foreach parse_rule_config "restriction_rule"
rm -rf /tmp/restriction_init.lock
}
initialize_quotas()
{
define_wan_if
if [ -z "$wan_if" ] ; then return ; fi
if [ -e /tmp/quota_init.lock ]; then return ; fi
touch /tmp/quota_init.lock
lan_mask=$(uci -p /tmp/state get network.lan.netmask)
lan_ip=$(uci -p /tmp/state get network.lan.ipaddr)
full_qos_enabled=$(ls /etc/rc.d/*qos_gargoyle 2>/dev/null)
if [ -n "$full_qos_enabled" ]; then
full_up=$(uci get qos_gargoyle.upload.total_bandwidth 2>/dev/null)
full_down=$(uci get qos_gargoyle.download.total_bandwidth 2>/dev/null)
if [ -z "$full_up" ] && [ -z "$full_down" ]; then
full_qos_enabled=""
fi
fi
# restore_quotas does the hard work of building quota chains & rebuilding crontab file to do backups
#
# this initializes qos functions ONLY if we have quotas that
# have up and down speeds defined for when quota is exceeded
# and full qos is not enabled
if [ -z "$full_qos_enabled" ]; then
restore_quotas -w $wan_if -d $death_mark -m $death_mask -s "$lan_ip/$lan_mask" -c "0 0,4,8,12,16,20 * * * /usr/bin/backup_quotas >/dev/null 2>&1"
initialize_quota_qos
else
restore_quotas -q -w $wan_if -d $death_mark -m $death_mask -s "$lan_ip/$lan_mask" -c "0 0,4,8,12,16,20 * * * /usr/bin/backup_quotas >/dev/null 2>&1"
cleanup_old_quota_qos
fi
#enable cron, but only restart cron if it is currently running
#since we initialize this before cron, this will
#make sure we don't start cron twice at boot
/etc/init.d/cron enable
cron_active=$(ps | grep "crond" | grep -v "grep" )
if [ -n "$cron_active" ]; then
/etc/init.d/cron restart
fi
rm -rf /tmp/quota_init.lock
}
load_all_config_sections()
{
local config_name="$1"
local section_type="$2"
all_config_sections=""
section_order=""
config_cb()
{
if [ -n "$2" ] || [ -n "$1" ]; then
if [ -n "$section_type" ]; then
if [ "$1" = "$section_type" ]; then
all_config_sections="$all_config_sections $2"
fi
else
all_config_sections="$all_config_sections $2"
fi
fi
}
config_load "$config_name"
echo "$all_config_sections"
}
cleanup_old_quota_qos()
{
for iface in $(tc qdisc show | awk '{print $5}' | sort -u ); do
tc qdisc del dev "$iface" root >/dev/null 2>&1
done
}
initialize_quota_qos()
{
cleanup_old_quota_qos
#speeds should be in kbyte/sec, units should NOT be present in config file (unit processing should be done by front-end)
quota_sections=$(load_all_config_sections "firewall" "quota")
upload_speeds=""
download_speeds=""
config_load "firewall"
for q in $quota_sections ; do
config_get "exceeded_up_speed" $q "exceeded_up_speed"
config_get "exceeded_down_speed" $q "exceeded_down_speed"
if [ -n "$exceeded_up_speed" ] && [ -n "$exceeded_down_speed" ]; then
if [ $exceeded_up_speed -gt 0 ] && [ $exceeded_down_speed -gt 0 ]; then
upload_speeds="$exceeded_up_speed $upload_speeds"
download_speeds="$exceeded_down_speed $download_speeds"
fi
fi
done
#echo "upload_speeds = $upload_speeds"
unique_up=$( printf "%d\n" $upload_speeds 2>/dev/null | sort -u -n)
unique_down=$( printf "%d\n" $download_speeds 2>/dev/null | sort -u -n)
#echo "unique_up = $unique_up"
num_up_bands=1
num_down_bands=1
if [ -n "$upload_speeds" ]; then
num_up_bands=$((1 + $(printf "%d\n" $upload_speeds 2>/dev/null | sort -u -n | wc -l) ))
fi
if [ -n "$download_speeds" ]; then
num_down_bands=$((1 + $(printf "%d\n" $download_speeds 2>/dev/null | sort -u -n | wc -l) ))
fi
#echo "num_up_bands=$num_up_bands"
#echo "num_down_bands=$num_down_bands"
if [ -n "$wan_if" ] && [ $num_up_bands -gt 1 ] && [ $num_down_bands -gt 1 ]; then
insmod sch_prio >/dev/null 2>&1
insmod sch_tbf >/dev/null 2>&1
insmod cls_fw >/dev/null 2>&1
ifconfig imq0 down >/dev/null 2>&1
ifconfig imq1 down >/dev/null 2>&1
rmmod imq >/dev/null 2>&1
insmod imq numdevs=1 hook_chains="INPUT,FORWARD" hook_tables="mangle,mangle" >/dev/null 2>&1
ip link set imq0 up
#egress/upload
tc qdisc del dev $wan_if root >/dev/null 2>&1
tc qdisc add dev $wan_if handle 1:0 root prio bands $num_up_bands priomap 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cur_band=2
upload_shift=0
for rate_kb in $unique_up ; do
kbit=$(echo $((rate_kb*8))kbit)
mark=$(($cur_band << $upload_shift))
tc filter add dev $wan_if parent 1:0 prio $cur_band protocol ip handle $mark fw flowid 1:$cur_band
tc qdisc add dev $wan_if parent 1:$cur_band handle $cur_band: tbf rate $kbit burst $kbit limit $kbit
cur_band=$(($cur_band+1))
done
#ingress/download
tc qdisc del dev imq0 root >/dev/null 2>&1
tc qdisc add dev imq0 handle 1:0 root prio bands $num_down_bands priomap 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
cur_band=2
download_shift=8
for rate_kb in $unique_down ; do
kbit=$(echo $((rate_kb*8))kbit)
mark=$(($cur_band << $download_shift))
tc filter add dev imq0 parent 1:0 prio $cur_band protocol ip handle $mark fw flowid 1:$cur_band
tc qdisc add dev imq0 parent 1:$cur_band handle $cur_band: tbf rate $kbit burst $kbit limit $kbit
cur_band=$(($cur_band+1))
done
iptables -t mangle -I ingress_quotas -i $wan_if -j IMQ --todev 0
#tc -s qdisc show dev $wan_if
#tc -s qdisc show dev imq0
fi
}
enforce_dhcp_assignments()
{
enforce_assignments=$(uci -q get firewall.@defaults[0].enforce_dhcp_assignments)
delete_chain_from_table "filter" "lease_mismatch_check"
local pairs1
local pairs2
local pairs
pairs1=""
pairs2=""
if [ -e /tmp/dhcp.leases ]; then
pairs1=$(cat /tmp/dhcp.leases | sed '/^[ \t]*$/d' | awk ' { print $2"^"$3"\n" ; } ' )
fi
if [ -e /etc/ethers ]; then
pairs2=$(cat /etc/ethers | sed '/^[ \t]*$/d' | awk ' { print $1"^"$2"\n" ; } ' )
fi
pairs=$( printf "$pairs1\n$pairs2\n" | sort | uniq )
if [ "$enforce_assignments" = "1" ] && [ -n "$pairs" ]; then
iptables -t filter -N lease_mismatch_check
local p
for p in $pairs ; do
local mac
local ip
mac=$(echo $p | sed 's/\^.*$//g')
ip=$(echo $p | sed 's/^.*\^//g')
if [ -n "$ip" ] && [ -n "$mac" ]; then
iptables -t filter -A lease_mismatch_check ! -s "$ip" -m mac --mac-source "$mac" -j REJECT
iptables -t filter -A lease_mismatch_check -s "$ip" -m mac ! --mac-source "$mac" -j REJECT
fi
done
iptables -t filter -I delegate_forward -j lease_mismatch_check
fi
}
force_router_dns()
{
force_router_dns=$(uci get firewall.@defaults[0].force_router_dns 2> /dev/null)
if [ "$force_router_dns" = "1" ]; then
iptables -t nat -I zone_lan_prerouting -p tcp --dport 53 -j REDIRECT
iptables -t nat -I zone_lan_prerouting -p udp --dport 53 -j REDIRECT
fi
}
add_adsl_modem_routes()
{
wan_proto=$(uci -q get network.wan.proto)
if [ "$wan_proto" = "pppoe" ]; then
wan_dev=$(uci -q get network.wan.ifname) #not really the interface, but the device
iptables -A postrouting_rule -t nat -o $wan_dev -j MASQUERADE
iptables -A forwarding_rule -o $wan_dev -j ACCEPT
/etc/ppp/ip-up.d/modemaccess.sh firewall $wan_dev
fi
}
initialize_firewall()
{
iptables -I zone_lan_forward -i br-lan -o br-lan -j ACCEPT
insert_remote_accept_rules
insert_dmz_rule
enforce_dhcp_assignments
force_router_dns
add_adsl_modem_routes
isolate_guest_networks
}
guest_mac_from_uci()
{
local is_guest_network
local macaddr
config_get is_guest_network "$1" is_guest_network
if [ "$is_guest_network" = "1" ]; then
config_get macaddr "$1" macaddr
echo "$macaddr"
fi
}
get_guest_macs()
{
config_load "wireless"
config_foreach guest_mac_from_uci "wifi-iface"
}
isolate_guest_networks()
{
ebtables -t filter -F FORWARD
ebtables -t filter -F INPUT
local guest_macs=$( get_guest_macs )
if [ -n "$guest_macs" ]; then
local lanifs=`brctl show br-lan 2>/dev/null | awk ' $NF !~ /interfaces/ { print $NF } '`
local lif
local lan_ip=$(uci -p /tmp/state get network.lan.ipaddr)
for lif in $lanifs ; do
for gmac in $guest_macs ; do
local is_guest=$(ifconfig "$lif" 2>/dev/null | grep -i "$gmac")
if [ -n "$is_guest" ]; then
echo "$lif with mac $gmac is wireless guest"
#Allow access to WAN but not other LAN hosts for anyone on guest network
ebtables -t filter -A FORWARD -i "$lif" --logical-out br-lan -j DROP
#Only allow DHCP/DNS access to router for anyone on guest network
ebtables -t filter -A INPUT -i "$lif" -p ARP -j ACCEPT
ebtables -t filter -A INPUT -i "$lif" -p IPV4 --ip-protocol UDP --ip-destination-port 53 -j ACCEPT
ebtables -t filter -A INPUT -i "$lif" -p IPV4 --ip-protocol UDP --ip-destination-port 67 -j ACCEPT
ebtables -t filter -A INPUT -i "$lif" -p IPV4 --ip-destination $lan_ip -j DROP
fi
done
done
fi
}
ifup_firewall()
{
insert_restriction_rules
initialize_quotas
insert_pf_loopback_rules
}