| #!/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 <target-instance-ip> -P protocol (-p port_range | -t icmp_type_code) -l <public ip address> -d <target port> -s <source cidrs> [-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 |