#!/bin/bash
#============================================================================
# Default Xen network start/stop script.
# Xend calls a network script when it starts.
# The script name to use is defined in ${XEN_CONFIG_DIR}/xend-config.sxp
# in the network-script field.
#
# This script creates a bridge (default ${netdev}), adds a device
# (defaults to the device on the default gateway route) to it, copies
# the IP addresses from the device to the bridge and adjusts the routes
# accordingly.
#
# If all goes well, this should ensure that networking stays up.
# However, some configurations are upset by this, especially
# NFS roots. If the bridged setup does not meet your needs,
# configure a different script, for example using routing instead.
#
# Usage:
#
# network-bridge (start|stop|status) {VAR=VAL}*
#
# Vars:
#
# bridge     The bridge to use (default ${netdev}).
# netdev     The interface to add to the bridge (default gateway device).
# antispoof  Whether to use iptables to prevent spoofing (default no).
#
# Internal Vars:
# pdev="p${netdev}"
# tdev=tmpbridge
#
# start:
# Creates the bridge as tdev
# Copies the IP and MAC addresses from pdev to bridge
# Renames netdev to be pdev 
# Renames tdev to bridge
# Enslaves pdev to bridge
#
# stop:
# Removes pdev from the bridge
# Transfers addresses, routes from bridge to pdev
# Renames bridge to tdev
# Renames pdev to netdev 
# Deletes tdev
#
# status:
# Print addresses, interfaces, routes
#
#============================================================================


dir=$(dirname "$0")
. "$dir/logging.sh"
. "$dir/xen-script-common.sh"
. "$dir/xen-network-common.sh"
. "$dir/locking.sh"

findCommand "$@"
evalVariables "$@"

is_network_root () {
    local rootfs=$(awk '{ if ($1 !~ /^[ \t]*#/ && $2 == "/") { print $3; }}' /etc/mtab)
    local rootopts=$(awk '{ if ($1 !~ /^[ \t]*#/ && $2 == "/") { print $4; }}' /etc/mtab)

    [[ "$rootfs" =~ "^nfs" ]] || [[ "$rootopts" =~ "_netdev" ]] && has_nfsroot=1 || has_nfsroot=0
    if [ $has_nfsroot -eq 1 ]; then
        local bparms=$(cat /proc/cmdline)
        for p in $bparms; do
            local ipaddr=$(echo $p | awk /nfsroot=/'{ print substr($1,9,index($1,":")-9) }')
            if [ "$ipaddr" != "" ]; then
                local nfsdev=$(ip route get $ipaddr | awk /$ipaddr/'{ print $3 }')
                [[ "$nfsdev" == "$netdev" ]] && return 0 || return 1
            fi
        done
    fi
    return 1
}

find_alt_device () {
    local interf=$1
    local prefix=${interf%[[:digit:]]}
    local ifs=$(ip link show | grep " $prefix" |\
                awk '{ printf ("%s",substr($2,1,length($2)-1)) }' |\
                sed s/$interf//)
    echo "$ifs"
}

netdev=${netdev:-$(ip route list 0.0.0.0/0  | \
                   sed 's/.*dev \([a-z]\+[0-9]\+\).*$/\1/')}
if is_network_root ; then
    altdevs=$(find_alt_device $netdev)
    for netdev in $altdevs; do break; done
    if [ -z "$netdev" ]; then
        [ -x /usr/bin/logger ] && /usr/bin/logger "network-bridge: bridging not supported on network root; not starting"
        exit
    fi
fi
netdev=${netdev:-eth0}
bridge=${bridge:-${netdev}}
antispoof=${antispoof:-no}

pdev="p${netdev}"
tdev=tmpbridge

get_ip_info() {
    addr_pfx=`ip addr show dev $1 | egrep '^ *inet' | sed -e 's/ *inet //' -e 's/ .*//'`
    gateway=`ip route show dev $1 | fgrep default | sed 's/default via //'`
}
    
do_ifup() {
    if [ $1 != "${netdev}" ] || ! ifup $1 ; then
        if [ -n "$addr_pfx" ] ; then
            # use the info from get_ip_info()
            ip addr flush $1
            ip addr add ${addr_pfx} dev $1
        fi
        ip link set dev $1 up
        [ -n "$gateway" ] && ip route add default via ${gateway}
    fi
}

# Usage: transfer_addrs src dst
# Copy all IP addresses (including aliases) from device $src to device $dst.
transfer_addrs () {
    local src=$1
    local dst=$2
    # Don't bother if $dst already has IP addresses.
    if ip addr show dev ${dst} | egrep -q '^ *inet ' ; then
        return
    fi
    # Address lines start with 'inet' and have the device in them.
    # Replace 'inet' with 'ip addr add' and change the device name $src
    # to 'dev $src'.
    ip addr show dev ${src} | egrep '^ *inet ' | sed -e "
s/inet/ip addr add/
s@\([0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+/[0-9]\+\)@\1@
s/${src}/dev ${dst} label ${dst}/
s/secondary//
" | sh -e
    # Remove automatic routes on destination device
    ip route list | sed -ne "
/dev ${dst}\( \|$\)/ {
  s/^/ip route del /
  p
}" | sh -e
}

# Usage: transfer_routes src dst
# Get all IP routes to device $src, delete them, and
# add the same routes to device $dst.
# The original routes have to be deleted, otherwise adding them
# for $dst fails (duplicate routes).
transfer_routes () {
    local src=$1
    local dst=$2
    # List all routes and grep the ones with $src in.
    # Stick 'ip route del' on the front to delete.
    # Change $src to $dst and use 'ip route add' to add.
    ip route list | sed -ne "
/dev ${src}\( \|$\)/ {
  h
  s/^/ip route del /
  P
  g
  s/${src}/${dst}/
  s/^/ip route add /
  P
  d
}" | sh -e
}


##
# link_exists interface
#
# Returns 0 if the interface named exists (whether up or down), 1 otherwise.
#
link_exists()
{
    if ip link show "$1" >/dev/null 2>/dev/null
    then
        return 0
    else
        return 1
    fi
}

# Set the default forwarding policy for $dev to drop.
# Allow forwarding to the bridge.
antispoofing () {
    iptables -P FORWARD DROP
    iptables -F FORWARD
    iptables -A FORWARD -m physdev --physdev-in ${pdev} -j ACCEPT
}

# Usage: show_status dev bridge
# Print ifconfig and routes.
show_status () {
    local dev=$1
    local bridge=$2
    
    echo '============================================================'
    ip addr show ${dev}
    ip addr show ${bridge}
    echo ' '
    brctl show ${bridge}
    echo ' '
    ip route list
    echo ' '
    route -n
    echo '============================================================'
}

op_start () {
    if [ "${bridge}" = "null" ] ; then
	return
    fi

    if link_exists "$pdev"; then
        # The device is already up.
        return
    fi

    claim_lock "network-bridge"

    create_bridge ${tdev}

    preiftransfer ${netdev}
    transfer_addrs ${netdev} ${tdev}
    # Remember slaves for bonding interface.
    if [ -e /sys/class/net/${netdev}/bonding/slaves ]; then
	slaves=`cat /sys/class/net/${netdev}/bonding/slaves`
    fi
    # Remember the IP details for do_ifup.
    get_ip_info ${netdev}
    if ! ifdown ${netdev}; then
	ip link set ${netdev} down
	ip addr flush ${netdev}
    fi
    ip link set ${netdev} name ${pdev}
    ip link set ${tdev} name ${bridge}

    setup_bridge_port ${pdev}

    # Restore slaves
    if [ -n "${slaves}" ]; then
	ip link set ${pdev} up
	ifenslave ${pdev} ${slaves}
    fi
    add_to_bridge2 ${bridge} ${pdev}
    do_ifup ${bridge}

    if [ ${antispoof} = 'yes' ] ; then
	antispoofing
    fi

    release_lock "network-bridge"
}

op_stop () {
    if [ "${bridge}" = "null" ]; then
	return
    fi
    if ! link_exists "$bridge"; then
	return
    fi

    claim_lock "network-bridge"

    transfer_addrs ${bridge} ${pdev}
    if ! ifdown ${bridge}; then
	get_ip_info ${bridge}
    fi
    ip link set ${pdev} down
    ip addr flush ${bridge}

    brctl delif ${bridge} ${pdev}
    ip link set ${bridge} down

    ip link set ${bridge} name ${tdev}
    ip link set ${pdev} name ${netdev}
    do_ifup ${netdev}

    brctl delbr ${tdev}

    release_lock "network-bridge"
}

# adds $dev to $bridge but waits for $dev to be in running state first
add_to_bridge2() {
    local bridge=$1
    local dev=$2
    local maxtries=10

    echo -n "Waiting for ${dev} to negotiate link."
    ip link set ${dev} up
    for i in `seq ${maxtries}` ; do
	if ifconfig ${dev} | grep -q RUNNING ; then
	    break
	else
	    echo -n '.'
	    sleep 1
	fi
    done

    if [ ${i} -eq ${maxtries} ] ; then echo -n '(link isnt in running state)' ; fi
    echo

    add_to_bridge ${bridge} ${dev}
}

case "$command" in
    start)
	op_start
	;;
    
    stop)
	op_stop
	;;

    status)
	show_status ${netdev} ${bridge}
	;;

    *)
	echo "Unknown command: $command" >&2
	echo 'Valid commands are: start, stop, status' >&2
	exit 1
esac
