From: David K. <da...@ke...> - 2018-06-06 00:09:57
|
If using a WAN failover that uses cellular wireless connection you may need to carefully control how much traffic you send over that link to avoid very high data overage costs. To this end I have implemented the following controls on my Astlinux box... 1) Block any given local device by MAC address from using the link For example... block my Apple TV so no video streaming to that device. 2) Block any given TCP/IP port for inbound/outbound traffic. For example... block ports used by an online backup service 3) Rate limit any given device by MAC address to e.g. 256kbps For example... discourage video streaming to kids iPhones while still allowing messaging. The above can all be implemented in firewall custom rules. Below are the functions I implemented... # ============================================================================= ## Function to block devices so that they cannot access network through a given ## interface. And/or block traffic to/from a specific tcp/udp port number. ## >>>Call this function only once per interface ## Parameters: ## interface (e.g. eth2) ## MacAddrs (list of mac addresses) ## Ports (list of ports) ## The interface local IP addresses are whitelisted block_ports_macaddrs() { local IFS=' ' local mac local interface="$1" local macs="${2//,/ }" # if comma delimited convert to space delimited local ports="$(echo $3 | tr -s ' ' ',')" # make sure comma delimited local chain=$(echo "FORWARD_$interface" | tr [a-z] [A-Z]) # uppercase interface name local ipv4=$(ip -4 addr show $interface | grep 'inet ' | awk -F' ' '{ print $2 }') local ipv6ula=$(ip -6 addr show $interface | grep 'inet6 fd' | awk -F' ' '{ print $2 }') iptables -N $chain 2>/dev/null iptables -F $chain iptables -A FORWARD_CHAIN -o $interface -j $chain if [ -n "$ipv4" ]; then ip4tables -A $chain -d "$ipv4" -j ACCEPT fi if [ -n "$ipv6ula" ]; then ip6tables -A $chain -d "$ipv6ula" -j ACCEPT fi for mac in $macs; do echo "[CUSTOM RULE] Block MAC address $mac on interface $interface" iptables -A $chain -m mac --mac-source $mac -j DROP done if [ -n "$ports" ]; then echo "[CUSTOM RULE] Block ports $ports on interface $interface" iptables -A $chain -p tcp -m multiport --dports $ports -j DROP iptables -A $chain -p tcp -m multiport --sports $ports -j DROP iptables -A $chain -p udp -m multiport --dports $ports -j DROP iptables -A $chain -p udp -m multiport --sports $ports -j DROP fi } # ============================================================================= ## Function to prepare for rate limiting for traffic between local net and ## the WAN failover wireguard net. Actual packet selection for rate ## limiting will take place in iptables. This function limits only ## internal interfaces or WAN failover, not default EXTIF. prepare_rate_limits() { local interface local IFS=' ' for interface in $INT_IF $EXT2IF; do echo "[CUSTOM RULE] Prepare $interface for rate limiting" tc qdisc del dev $interface root 2>/dev/null tc qdisc add dev $interface root handle 1: htb tc class add dev $interface parent 1: classid 1:1 htb rate 256Kbit tc class add dev $interface parent 1: classid 1:2 htb rate 512Kbit tc class add dev $interface parent 1: classid 1:3 htb rate 1024Kbit tc qdisc add dev $interface parent 1:1 handle 2: sfq perturb 10 tc qdisc add dev $interface parent 1:2 handle 3: sfq perturb 10 tc qdisc add dev $interface parent 1:3 handle 4: sfq perturb 10 tc filter add dev $interface protocol ip parent 1: prio 1 handle 1 fw flowid 1:1 tc filter add dev $interface protocol ip parent 1: prio 1 handle 2 fw flowid 1:2 tc filter add dev $interface protocol ip parent 1: prio 1 handle 3 fw flowid 1:3 done } ## Function to Rate limit some devices so that they cannot drive up huge data use. ## >>>Call this function only once per interface ## This uses kernel traffic control (tc) rules set on the net interface ## Parameters: ## interface (e.g. eth2) ## MacAddrs (list of mac addresses) ## LimitMarks (list of limit-marks corresponding to each mac address) ## Mark 1: 256 Kbps, 2: 512 Kbps, 3: 1 Mbps ## Inbound packets have the packet mark restored ## Outbound packets from selected devices are marked and the packet saved ## The interface local IP addresses are whitelisted rate_limit_macaddrs() { local IFS=' ' local mac local interface="$1" local macs="${2//,/ }" # if comma delimited convert to space delimited local rate_marks="${3//,/ }" # if comma delimited convert to space delimited local chain=$(echo "FORWARD_$interface" | tr [a-z] [A-Z]) # uppercase interface name local ipv4=$(ip -4 addr show $interface | grep 'inet ' | awk -F' ' '{ print $2 }') local ipv6ula=$(ip -6 addr show $interface | grep 'inet6 fd' | awk -F' ' '{ print $2 }') iptables -N $chain -t mangle 2>/dev/null iptables -F $chain -t mangle iptables -A FORWARD -t mangle -i $interface -j CONNMARK --restore-mark iptables -A FORWARD -t mangle -o $interface -j $chain if [ -n "$ipv4" ]; then ip4tables -A $chain -t mangle -d "$ipv4" -j ACCEPT fi if [ -n "$ipv6ula" ]; then ip6tables -A $chain -t mangle -d "$ipv6ula" -j ACCEPT fi iptables -A $chain -t mangle -j CONNMARK --restore-mark iptables -A $chain -t mangle -m mark ! --mark 0 -j ACCEPT for mac in $macs; do echo "[CUSTOM RULE] Rate limit MAC address $mac on interface $interface" mark=${rate_marks%% *} rate_marks=${rate_marks#* } iptables -A $chain -t mangle -m mac --mac-source $mac -j MARK --set-mark ${mark:-1} done iptables -A $chain -t mangle -j CONNMARK --save-mark iptables -A $chain -t mangle -j ACCEPT } # ============================================================================= ## Make the calls to block / rate limit prepare_rate_limits rate_limit_macaddrs \ "$EXT2IF" \ "52:54:00:43:1c:6e 3c:2e:ff:xx:xx:xx 8c:85:90:xx:xx:xx b8:e8:56:xx:xx:xx" \ "3 1 1 2" ##^ TestVM, iPhone, MacBook, iPad block_ports_macaddrs \ "$EXT2IF" \ "00:08:9B:xx:xx:xx 00:08:9B:xx:xx:xx 00:08:9B:xx:xx:xx 52:54:00:xx:xx:xx 08:66:98:xx:xx:xx" \ "4242" ##^ QNAP, QNAP, QNAP, UbuntuVM, AppleTV ##^ 4242 = crashplan ports Now if you are using wireguard to create a VPN link to a failover host you also want to repeat the above settings with "wg0" in place of "$EXT2IF" and because the wg0 interface is taken down during a wireguard VPN restart you also need to reset the rate limiting traffic controls "tc" commands in the up/down script for that. For example... #!/bin/bash ## Action: POST_UP PRE_DOWN action="$1" ## WireGuard Interface: (ex. wg0) interface="$2" if [ "$action" = "POST_UP" ]; then logger -t wireguard -p kern.info "WireGuard VPN is started on '$interface' interface." echo "Prepare $interface for rate limiting" tc qdisc del dev $interface root 2>/dev/null tc qdisc add dev $interface root handle 1: htb tc class add dev $interface parent 1: classid 1:1 htb rate 256Kbit tc class add dev $interface parent 1: classid 1:2 htb rate 512Kbit tc class add dev $interface parent 1: classid 1:3 htb rate 1024Kbit tc qdisc add dev $interface parent 1:1 handle 2: sfq perturb 10 tc qdisc add dev $interface parent 1:2 handle 3: sfq perturb 10 tc qdisc add dev $interface parent 1:3 handle 4: sfq perturb 10 tc filter add dev $interface protocol ip parent 1: prio 1 handle 1 fw flowid 1:1 tc filter add dev $interface protocol ip parent 1: prio 1 handle 2 fw flowid 1:2 tc filter add dev $interface protocol ip parent 1: prio 1 handle 3 fw flowid 1:3 elif [ "$action" = "PRE_DOWN" ]; then logger -t wireguard -p kern.info "WireGuard VPN is stopping '$interface' interface." fi |