#!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # $Id: firewall.sh 9947 2010-06-25 19:34:24Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/patches/xenserver/root/firewall.sh $ # firewall.sh -- allow some ports / protocols to vm instances # @VERSION@ source /root/func.sh lock="biglock" locked=$(getLockFile $lock) if [ "$locked" != "1" ] then exit 1 fi vpnoutmark="0x525" usage() { printf "Usage: %s: (-A|-D) -r -P protocol (-p port_range | -t icmp_type_code) -l -d -s [-G] \n" $(basename $0) >&2 } #set -x get_dev_list() { ip link show | grep -e eth[2-9] | awk -F ":" '{print $2}' ip link show | grep -e eth1[0-9] | awk -F ":" '{print $2}' } ip_to_dev() { local ip=$1 for dev in $DEV_LIST; do ip addr show dev $dev | grep inet | grep $ip &>> /dev/null [ $? -eq 0 ] && echo $dev && return 0 done return 1 } doHairpinNat () { local vrGuestIPNetwork=$(sudo ip addr show dev eth0 | grep inet | grep eth0 | awk '{print $2}' | head -1) local vrGuestIP=$(echo $vrGuestIPNetwork | awk -F'/' '{print $1}') local publicIp=$1 local prot=$2 local port=$3 local guestVmIp=$4 local guestPort=$(echo $5 | sed 's/:/-/') local op=$6 logger -t cloud "$(basename $0): create HairPin entry : public ip=$publicIp \ instance ip=$guestVmIp proto=$proto portRange=$guestPort op=$op" if [ "$prot" == "all" ] then logger -t cloud "creating hairpin nat rules for static nat" (sudo iptables -t nat $op PREROUTING -d $publicIp -i eth0 -j DNAT --to-destination $guestVmIp &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t nat $op POSTROUTING -s $vrGuestIPNetwork -d $guestVmIp -j SNAT -o eth0 --to-source $vrGuestIP &>> $OUTFILE || [ "$op" == "-D" ]) else (sudo iptables -t nat $op PREROUTING -d $publicIp -i eth0 -p $prot --dport $port -j DNAT --to-destination $guestVmIp:$guestPort &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t nat $op POSTROUTING -s $vrGuestIPNetwork -p $prot --dport $port -d $guestVmIp -j SNAT -o eth0 --to-source $vrGuestIP &>> $OUTFILE || [ "$op" == "-D" ]) fi } #Port (address translation) forwarding for tcp or udp tcp_or_udp_entry() { local instIp=$1 local dport0=$2 local dport=$(echo $2 | sed 's/:/-/') local publicIp=$3 local port=$4 local op=$5 local proto=$6 local cidrs=$7 logger -t cloud "$(basename $0): creating port fwd entry for PAT: public ip=$publicIp \ instance ip=$instIp proto=$proto port=$port dport=$dport op=$op" #if adding, this might be a duplicate, so delete the old one first [ "$op" == "-A" ] && tcp_or_udp_entry $instIp $dport0 $publicIp $port "-D" $proto $cidrs # the delete operation may have errored out but the only possible reason is # that the rules didn't exist in the first place local dev=$(ip_to_dev $publicIp) local tableNo=$(echo $dev | awk -F'eth' '{print $2}') # shortcircuit the process if error and it is an append operation # continue if it is delete (sudo iptables -t nat $op PREROUTING --proto $proto -i $dev -d $publicIp \ --destination-port $port -j DNAT \ --to-destination $instIp:$dport &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t mangle $op PREROUTING --proto $proto -i $dev -d $publicIp \ --destination-port $port -j MARK --set-mark $tableNo &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t mangle $op PREROUTING --proto $proto -i $dev -d $publicIp \ --destination-port $port -m state --state NEW -j CONNMARK --save-mark &>> $OUTFILE || [ "$op" == "-D" ]) && (doHairpinNat $publicIp $proto $port $instIp $dport0 $op) && (sudo iptables -t nat $op OUTPUT --proto $proto -d $publicIp \ --destination-port $port -j DNAT \ --to-destination $instIp:$dport &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables $op FORWARD -p $proto -s $cidrs -d $instIp -m state \ --state ESTABLISHED,RELATED -m comment --comment "$publicIp:$port" -j ACCEPT &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables $op FORWARD -p $proto -s $cidrs -d $instIp \ --destination-port $dport0 -m state --state NEW -m comment --comment "$publicIp:$port" -j ACCEPT &>> $OUTFILE) local result=$? logger -t cloud "$(basename $0): done port fwd entry for PAT: public ip=$publicIp op=$op result=$result" return $result } #Forward icmp icmp_entry() { local instIp=$1 local icmptype=$2 local publicIp=$3 local op=$4 logger -t cloud "$(basename $0): creating port fwd entry for PAT: public ip=$publicIp \ instance ip=$instIp proto=icmp port=$port dport=$dport op=$op" #if adding, this might be a duplicate, so delete the old one first [ "$op" == "-A" ] && icmp_entry $instIp $icmpType $publicIp "-D" # the delete operation may have errored out but the only possible reason is # that the rules didn't exist in the first place local dev=$(ip_to_dev $publicIp) sudo iptables -t nat $op PREROUTING --proto icmp -i $dev -d $publicIp --icmp-type $icmptype -j DNAT --to-destination $instIp &>> $OUTFILE sudo iptables -t nat $op OUTPUT --proto icmp -d $publicIp --icmp-type $icmptype -j DNAT --to-destination $instIp &>> $OUTFILE sudo iptables $op FORWARD -p icmp -s 0/0 -d $instIp --icmp-type $icmptype -j ACCEPT &>> $OUTFILE result=$? logger -t cloud "$(basename $0): done port fwd entry for PAT: public ip=$publicIp op=$op result=$result" return $result } one_to_one_fw_entry() { local publicIp=$1 local instIp=$2 local proto=$3 local portRange=$4 local op=$5 logger -t cloud "$(basename $0): create firewall entry for static nat: public ip=$publicIp \ instance ip=$instIp proto=$proto portRange=$portRange op=$op" #if adding, this might be a duplicate, so delete the old one first [ "$op" == "-A" ] && one_to_one_fw_entry $publicIp $instIp $proto $portRange "-D" # the delete operation may have errored out but the only possible reason is # that the rules didn't exist in the first place local dev=$(ip_to_dev $publicIp) [ $? -ne 0 ] && echo "Could not find device associated with $publicIp" && return 1 # shortcircuit the process if error and it is an append operation # continue if it is delete (sudo iptables -t nat $op PREROUTING -i $dev -d $publicIp --proto $proto \ --destination-port $portRange -j DNAT \ --to-destination $instIp &>> $OUTFILE || [ "$op" == "-D" ]) && (doHairpinNat $publicIp $proto $portRange $instIp $portRange $op) && (sudo iptables $op FORWARD -i $dev -o eth0 -d $instIp --proto $proto \ --destination-port $portRange -m state \ --state NEW -j ACCEPT &>> $OUTFILE ) result=$? logger -t cloud "$(basename $0): done firewall entry public ip=$publicIp op=$op result=$result" return $result } fw_chain_for_ip() { local pubIp=$1 if iptables -t mangle -N FIREWALL_$pubIp &> /dev/null then logger -t cloud "$(basename $0): created a firewall chain for $pubIp" (sudo iptables -t mangle -A FIREWALL_$pubIp -j DROP) && (sudo iptables -t mangle -I FIREWALL_$pubIp -m state --state RELATED,ESTABLISHED -j ACCEPT ) && (sudo iptables -t mangle -I PREROUTING 2 -d $pubIp -j FIREWALL_$pubIp) return $? fi logger -t cloud "fw chain for $pubIp already exists" return 0 } static_nat() { local publicIp=$1 local instIp=$2 local op=$3 local op2="-D" local rulenum= local proto="all" logger -t cloud "$(basename $0): static nat: public ip=$publicIp \ instance ip=$instIp op=$op" #TODO check error below fw_chain_for_ip $publicIp #if adding, this might be a duplicate, so delete the old one first [ "$op" == "-A" ] && static_nat $publicIp $instIp "-D" # the delete operation may have errored out but the only possible reason is # that the rules didn't exist in the first place [ "$op" == "-A" ] && op2="-I" if [ "$op" == "-A" ] then # put static nat rule one rule after VPN no-NAT rule # rule chain can be used to improve it later iptables-save -t nat|grep "POSTROUTING" | grep $vpnoutmark > /dev/null if [ $? -eq 0 ] then rulenum=2 else rulenum=1 fi fi local dev=$(ip_to_dev $publicIp) [ $? -ne 0 ] && echo "Could not find device associated with $publicIp" && return 1 local tableNo=$(echo $dev | awk -F'eth' '{print $2}') # shortcircuit the process if error and it is an append operation # continue if it is delete (sudo iptables -t mangle $op PREROUTING -i $dev -d $publicIp \ -j MARK -m state --state NEW --set-mark $tableNo &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t mangle $op PREROUTING -i $dev -d $publicIp \ -m state --state NEW -j CONNMARK --save-mark &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t mangle $op PREROUTING -s $instIp -i eth0 \ -j MARK -m state --state NEW --set-mark $tableNo &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t mangle $op PREROUTING -s $instIp -i eth0 \ -m state --state NEW -j CONNMARK --save-mark &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t nat $op PREROUTING -i $dev -d $publicIp -j DNAT \ --to-destination $instIp &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables $op FORWARD -i $dev -o eth0 -d $instIp -m state \ --state NEW -j ACCEPT &>> $OUTFILE || [ "$op" == "-D" ]) && (sudo iptables -t nat $op2 POSTROUTING $rulenum -s $instIp -j SNAT \ -o $dev --to-source $publicIp &>> $OUTFILE || [ "$op" == "-D" ]) && (doHairpinNat $publicIp $proto "all" $instIp "0:65535" $op) result=$? logger -t cloud "$(basename $0): done static nat entry public ip=$publicIp op=$op result=$result" return $result } rflag= Pflag= pflag= tflag= lflag= dflag= sflag= Gflag= op="" while getopts 'ADr:P:p:t:l:d:s:G' OPTION do case $OPTION in A) op="-A" ;; D) op="-D" ;; r) rflag=1 instanceIp="$OPTARG" ;; P) Pflag=1 protocol="$OPTARG" ;; p) pflag=1 ports="$OPTARG" ;; t) tflag=1 icmptype="$OPTARG" ;; l) lflag=1 publicIp="$OPTARG" ;; s) sflag=1 cidrs="$OPTARG" ;; d) dflag=1 dport="$OPTARG" ;; G) Gflag=1 ;; ?) usage unlock_exit 2 $lock $locked ;; esac done DEV_LIST=$(get_dev_list) OUTFILE=$(mktemp) #Firewall ports for one-to-one/static NAT if [ "$Gflag" == "1" ] then if [ "$protocol" == "" ] then static_nat $publicIp $instanceIp $op else one_to_one_fw_entry $publicIp $instanceIp $protocol $dport $op fi result=$? if [ "$result" -ne 0 ] && [ "$op" != "-D" ]; then cat $OUTFILE >&2 fi rm -f $OUTFILE if [ "$op" == "-D" ];then result=0 fi unlock_exit $result $lock $locked fi if [ "$sflag" != "1" ] then cidrs="0/0" fi case $protocol in tcp|udp) tcp_or_udp_entry $instanceIp $dport $publicIp $ports $op $protocol $cidrs result=$? if [ "$result" -ne 0 ] && [ "$op" != "-D" ];then cat $OUTFILE >&2 fi rm -f $OUTFILE if [ "$op" == "-D" ];then result=0 fi unlock_exit $result $lock $locked ;; "icmp") icmp_entry $instanceIp $icmptype $publicIp $op if [ "$op" == "-D" ];then result=0 fi unlock_exit $? $lock $locked ;; *) printf "Invalid protocol-- must be tcp, udp or icmp\n" >&2 unlock_exit 5 $lock $locked ;; esac unlock_exit 0 $lock $locked