#!/bin/sh
#
# Shorewall 3.4 -- /usr/share/shorewall/lib.providers
#
#     This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
#     (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net)
#
#	Complete documentation is available at http://shorewall.net
#
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of Version 2 of the GNU General Public License
#	as published by the Free Software Foundation.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
#
# This library is loaded by /usr/share/shorewall/compiler when the providers file is
# non-empty.
#

#
# Process the providers file
#
setup_providers()
{
    local table number mark duplicate interface gateway options provider address copy route loose addresses rulenum rulebase balance save_indent="$INDENT" mask= first=Yes save_indent1=

    copy_table() {
	    indent >&3 << __EOF__
ip route show table $duplicate | while read net route; do
    case \$net in
        default|nexthop)
            ;;
        *)
            run_ip route add table $number \$net \$route
            ;;
    esac
done
__EOF__
    }

    copy_and_edit_table() {
	indent >&3 << __EOF__
ip route show table $duplicate | while read net route; do
    case \$net in
        default|nexthop)
            ;;
        *)
            case \$(find_device \$route) in
                `echo $copy\) | sed 's/ /|/g'`
                    run_ip route add table $number \$net \$route
                    ;;
            esac
            ;;
    esac
done

__EOF__
    }

    balance_default_route() # $1 = weight
    {
	balance=yes

	save_command
	if [ -n "$first" ]; then
	    if [ -n "$gateway" ] ; then
		save_command "DEFAULT_ROUTE=\"nexthop via $gateway dev $interface weight $1\""
	    else
		save_command "DEFAULT_ROUTE=\"nexthop dev $interface weight $1\""
	    fi

	    first=
	else
	    if [ -n "$gateway" ] ; then
		save_command "DEFAULT_ROUTE=\"\$DEFAULT_ROUTE nexthop via $gateway dev $interface weight $1\""
	    else
		save_command "DEFAULT_ROUTE=\"\$DEFAULT_ROUTE nexthop dev $interface weight $1\""
	    fi
	fi
    }

    add_a_provider() {
	local t n iface option optional=

	[ -n "$MANGLE_ENABLED" ] || fatal_error "Providers require mangle support in your kernel and iptables"

	for t in $PROVIDERS local main default unspec; do
	    if [ "$t" = "$table" ]; then
		fatal_error "Duplicate Provider: $table, provider: \"$provider\""
	    fi

	    eval n=\$${t}_number
	    #
	    # The following is because the %$#@ shell doesn't accept hex numbers in '-eq' tests
	    #
	    if [ $(($n)) -eq $(($number)) ]; then
		fatal_error "Duplicate Provider number: $number, provider: \"$provider\""
	    fi
	done

	eval ${table}_number=$number

	indent >&3 << __EOF__
#
# Add Provider $table ($number)
#
__EOF__
	save_command "if interface_is_usable $interface; then"
	save_indent1="$INDENT"
	INDENT="$INDENT    "

	iface=$(chain_base $interface)

	save_command "${iface}_up=Yes"

	save_command "qt ip route flush table $number"

	indent >&3 << __EOF__
echo "qt ip route flush table $number" >> \${VARDIR}/undo_routing
__EOF__

	if [ "x${duplicate:=-}" != x- ]; then
	    if [ "x${copy:=-}" != "x-" ]; then
		if [ "x${copy}" = xnone ]; then
		    copy=$interface
		else
		    copy="$interface $(separate_list $copy)"
		fi
		copy_and_edit_table
	    else
		copy_table
	    fi
	elif [ "x${copy:=-}" != x- ]; then
	    fatal_error "A non-empty COPY column requires that a routing table be specified in the DUPLICATE column" 
	fi

	if [ "x$gateway" = xdetect ] ; then
	    gateway='$gateway'
	    indent >&3 << __EOF__
gateway=\$(detect_gateway $interface)

if [ -n "\$gateway" ]; then
    run_ip route replace \$gateway src \$(find_first_interface_address $interface) dev $interface table $number
    run_ip route add default via \$gateway dev $interface table $number
else
    fatal_error "Unable to detect the gateway through interface $interface"
fi

__EOF__
	elif [ "x$gateway" != "x-" -a -n "$gateway" ]; then
	    indent >&3 << __EOF__
run_ip route replace $gateway src \$(find_first_interface_address $interface) dev $interface table $number
run_ip route add default via $gateway dev $interface table $number
__EOF__
	else
	    gateway=
	    save_command "run_ip route add default dev $interface table $number"
	fi

	if [ x${mark} != x- ]; then
	    verify_mark $mark

	    if [ $(($mark)) -lt 256 ]; then
		if [ -n "$HIGH_ROUTE_MARKS" ]; then
		    fatal_error "Invalid Mark Value ($mark) with HIGH_ROUTE_MARKS=Yes"
		fi
	    elif [ -z "$HIGH_ROUTE_MARKS" ]; then
		fatal_error "Invalid Mark Value ($mark) with HIGH_ROUTE_MARKS=No"
	    fi

	    eval ${table}_mark=$mark

	    indent >&3 << __EOF__
qt ip rule del fwmark $mark
run_ip rule add fwmark $mark pref $((10000 + $mark)) table $number
echo "qt ip rule del fwmark $mark" >> \${VARDIR}/undo_routing
__EOF__
	fi

	loose=

	for option in $(separate_list $options); do
	    case $option in
		-)
		    ;;
		track)
		    list_search $interface $ROUTEMARK_INTERFACES && \
			fatal_error "Interface $interface is tracked through an earlier provider"
		    [ x${mark} = x- ] && fatal_error "The 'track' option requires a numeric value in the MARK column - Provider \"$provider\""
		    eval ${iface}_routemark=$mark
		    ROUTEMARK_INTERFACES="$ROUTEMARK_INTERFACES $interface"
		    ;;
		balance=*)
		    balance_default_route ${option#*=}
		    ;;
		balance)
		    balance_default_route 1
		    ;;
		loose)
		    loose=Yes
		    ;;
		optional)
		    optional=Yes
		    ;;
		*)
		    error_message "WARNING: Invalid option ($option) ignored in provider \"$provider\""
		    ;;
	    esac
	done

	rulenum=0

	if [ -z "$loose" ]; then
	    rulebase=$(( 20000 + ( 256 * ($number-1) ) ))
	    indent >&3 << __EOF__

rulenum=0

find_interface_addresses $interface | while read address; do
    qt ip rule del from \$address
    run_ip rule add from \$address pref \$(( $rulebase + \$rulenum )) table $number
    echo "qt ip rule del from \$address" >> \${VARDIR}/undo_routing
    rulenum=\$((\$rulenum + 1))
done
__EOF__
	else
	    indent >&3 << __EOF__

find_interface_addresses $interface | while read address; do
    qt ip rule del from \$address
done
__EOF__
	fi

    indent >&3 << __EOF__

progress_message "   Provider $table ($number) Added"

__EOF__

    INDENT="$save_indent1"
    save_command else

    if [ -n "$optional" ]; then
	save_command "    error_message \"WARNING: Interface $interface is not configured -- Provider $table ($number) not Added\""
	save_command "    ${iface}_up="
    else
	save_command "    fatal_error \"ERROR: Interface $interface is not configured -- Provider $table ($number) Cannot be Added\""
    fi

    save_command fi
    save_command

    }

    verify_provider()
    {
	local p n

	for p in $PROVIDERS main; do
	    [ "$p" = "$1" ] && return 0
	    eval n=\$${p}_number}
	    [ "$n" = "$1" ] && return 0
	done

	fatal_error "Unknown provider $1 in route rule \"$rule\""
    }

    add_an_rtrule()
    {
	verify_provider $provider

	[ "x$source"   = x- ] && source=
	[ "x$dest"     = x- ] && dest= || dest="to $dest"

	[ -n "${source}${dest}" ] || fatal_error "You must specify either the source or destination in an rt rule: \"$rule\""

	[ -n "$source" ] && case $source in
	    *:*)
		source="iif ${source%:*} from ${source#*:}"
		;;
	    *.*.*)
		source="from $source"
		;;
	    *)
		source="iif $source"
		;;
	esac

	case "$priority" in
	    [0-9][0-9][0-9][0-9]|[0-9][0-9][0-9][0-9][0-9])
		;;
	    *)
		fatal_error "Invalid priority ($priority) in rule \"$rule\""
		;;
	esac

	priority="priority $priority"

	save_command "qt ip rule del $source $dest $priority"
	save_command "run_ip rule add $source $dest $priority table $provider"
	indent >&3 << __EOF__
echo "qt ip rule del $source $dest $priority" >> \${VARDIR}/undo_routing
__EOF__
	progress_message "Routing rule \"$rule\" $DONE"
    }
    #
    #  E x e c u t i o n    B e g i n s    H e r e
    #
    local_number=255
    main_number=254
    default_number=253
    unspec_number=0
    balance=

    progress_message2 "$DOING $1..."
    save_command
    save_command "if [ -z \"\$NOROUTES\" ]; then"
    INDENT="$INDENT    "
    indent >&3 << __EOF__
#
# Undo any changes made since the last time that we [re]started -- this will not restore the default route
#
undo_routing
#
# Save current routing table database so that it can be restored later
#
cp /etc/iproute2/rt_tables \${VARDIR}/
#
# Capture the default route(s) if we don't have it (them) already.
#
[ -f \${VARDIR}/default_route ] || ip route ls | grep -E '^\s*(default |nexthop )' > \${VARDIR}/default_route
#
# Initialize the file that holds 'undo' commands
#
> \${VARDIR}/undo_routing
__EOF__
    save_progress_message "Adding Providers..."
    save_command "DEFAULT_ROUTE="

    while read table number mark duplicate interface gateway options copy; do
	provider="$table $number $mark $duplicate $interface $gateway $options $copy"
	add_a_provider
	PROVIDERS="$PROVIDERS $table"
	progress_message "Provider $provider $DONE"
    done < $TMP_DIR/providers

    if [ -n "$PROVIDERS" ]; then
	if [ -n "$balance" ]; then
	    save_command "if [ -n \"\$DEFAULT_ROUTE\" ]; then"
	    save_command "    run_ip route replace default scope global \$DEFAULT_ROUTE"
	    save_command "    progress_message \"Default route '\$(echo \$DEFAULT_ROUTE | sed 's/\$\\s*//')' Added\""
	    save_command "else"
	    save_command "    error_message \"WARNING: No Default route added (all 'balance' providers are down)\""
	    save_command "    restore_default_route"
	    save_command "fi"
	    save_command
	else
	    save_command "#"
	    save_command "# We don't have any 'balance' providers so we retore any default route that we've saved"
	    save_command "#"
	    save_command restore_default_route
	fi

	cat >&3 << __EOF__
${INDENT}cat > /etc/iproute2/rt_tables <<EOF
#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
EOF

${INDENT}echocommand=\$(find_echo)
__EOF__
        for table in $PROVIDERS; do
	    eval number=\$${table}_number
	    indent >&3 << __EOF__
\$echocommand "$number\t$table" >>  /etc/iproute2/rt_tables
__EOF__
	done

	if [ -s $TMP_DIR/route_rules ]; then
	    progress_message2 "$DOING $(find_file route_rules)..."

	    save_command

	    while read source dest provider priority; do
		rule="$source $dest $priority $provider"
		add_an_rtrule
	    done < $TMP_DIR/route_rules
	fi
    fi

    save_command
    save_command "run_ip route flush cache"
    INDENT="$save_indent"
    save_command "fi"
    save_command
}

#
# Set up Route marking (Only called if $ROUTEMARK_INTERFACES is non-empty)
#
setup_route_marking()
{
    local mask=0xFF mark_op="--set-mark" save_indent="$INDENT"

    [ -n "$HIGH_ROUTE_MARKS" ] && mask=0xFF00 && mark_op="--or-mark"

    run_iptables -t mangle -A PREROUTING -m connmark ! --mark 0/$mask -j CONNMARK --restore-mark --mask $mask
    run_iptables -t mangle -A OUTPUT     -m connmark ! --mark 0/$mask -j CONNMARK --restore-mark --mask $mask
    createmanglechain routemark

    for interface in $ROUTEMARK_INTERFACES ; do
	iface=$(chain_base $interface)
	eval mark_value=\$${iface}_routemark

	save_command
	save_command "if [ -n \"\$${iface}_up\" ]; then"
	INDENT="$INDENT    "
	run_iptables -t mangle -A PREROUTING -i $interface -m mark --mark 0/$mask -j routemark
	run_iptables -t mangle -A routemark  -i $interface                        -j MARK $mark_op $mark_value
	INDENT="$save_indent"
	save_command "fi"
    done

    save_command

    run_iptables -t mangle -A routemark -m mark ! --mark 0/$mask -j CONNMARK --save-mark --mask $mask

}
