#!/bin/sh

not_implemented ()
{
    echo "ip stub command: \"$1\" not implemented"
    exit 127
}

######################################################################

ip_link ()
{
    case "$1" in
	set)
	    shift
	    # iface="$1"
	    case "$2" in
		up)   ip_link_set_up "$1"  ;;
		down) ip_link_down_up "$1" ;;
		*)    not_implemented "\"$2\" in \"$orig_args\"" ;;
	    esac
	    ;;
	show) shift ; ip_link_show "$@" ;;
	del*) shift ; ip_link_delete "$@" ;;
	*) not_implemented "$*" ;;
    esac
}

ip_link_delete ()
{
    mkdir -p "${FAKE_IP_STATE}/interfaces-deleted"
    touch "${FAKE_IP_STATE}/interfaces-deleted/$1"
}

ip_link_set_up ()
{
    rm -f "${FAKE_IP_STATE}/interfaces-down/$1"
    rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1"
}

ip_link_set_down ()
{
    rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1"
    mkdir -p "${FAKE_IP_STATE}/interfaces-down"
    touch "${FAKE_IP_STATE}/interfaces-down/$1"
}

ip_link_show ()
{
    dev="$1"
    if [ "$dev" = "dev" -a -n "$2" ] ; then
	dev="$2"
    fi

    if [ -e "${FAKE_IP_STATE}/interfaces-deleted/$dev" ] ; then
	echo "Device \"${dev}\" does not exist." >&2
	exit 255
    fi

    mac=$(echo $dev | md5sum | sed -r -e 's@(..)(..)(..)(..)(..)(..).*@\1:\2:\3:\4:\5:\6@')
    _state="UP"
    _flags=",UP,LOWER_UP"
    if [ -e "${FAKE_IP_STATE}/interfaces-down/$dev" ] ; then
	_state="DOWN"
	_flags=""
    fi
    cat <<EOF
${n}: ${dev}: <BROADCAST,MULTICAST${_flags}> mtu 1500 qdisc pfifo_fast state ${_state} qlen 1000
    link/ether ${mac} brd ff:ff:ff:ff:ff:ff
EOF
}

# This is incomplete because it doesn't actually look up table ids in
# /etc/iproute2/rt_tables.  The rules/routes are actually associated
# with the name instead of the number.  However, we include a variable
# to fake a bad table id.
[ -n "$IP_ROUTE_BAD_TABLE_ID" ] || IP_ROUTE_BAD_TABLE_ID=false

ip_check_table ()
{
    _cmd="$1"

    [ -n "$_table" ] || not_implemented "ip rule/route without \"table\""

    # Only allow tables names from 13.per_ip_routing.  This is a cheap
    # way of avoiding implementing the default/main/local tables.
    case "$_table" in
	ctdb.*)
	    if $IP_ROUTE_BAD_TABLE_ID ; then
		# Ouch.  Simulate inconsistent errors from ip.  :-(
		case "$_cmd" in
		    route)
			echo "Error: argument "${_table}" is wrong: table id value is invalid" >&2
			
			;;
		    *)
			echo "Error: argument "${_table}" is wrong: invalid table ID" >&2
		esac
		exit 255
	    fi
	    ;;
	*) not_implemented "table=${_table} ${orig_args}" ;;
    esac
}

######################################################################

ip_addr ()
{
    case "$1" in
	show|list|"") shift ; ip_addr_show "$@" ;;
	add*)         shift ; ip_addr_add  "$@" ;;
	del*)         shift ; ip_addr_del  "$@" ;;
	*) not_implemented "\"$1\" in \"$orig_args\"" ;;
    esac
}

ip_addr_show ()
{
    dev=""
    primary=true
    secondary=true
    _to=""
    while [ -n "$1" ] ; do
	case "$1" in
	    dev)
		dev="$2" ; shift 2
		;;
            # Do stupid things and stupid things will happen!
	    primary)
		primary=true ; secondary=false ; shift
		;;
	    secondary)
		secondary=true ; primary=false ; shift
		;;
	    to)
		_to="$2" ; shift 2
		;;
	    *)
	        # Assume an interface name
		dev="$1" ; shift 1
	esac
    done
    devices="$dev"
    if [ -z "$devices" ] ; then
	# No device specified?  Get all the primaries...
	devices=$(ls "${FAKE_IP_STATE}/addresses/"*-primary 2>/dev/null | \
	    sed -e 's@.*/@@' -e 's@-primary$@@')
    fi
    calc_brd ()
    {
	case "${local#*/}" in
	    24)
		brd="${local%.*}.255"
		;;
	    *)
		not_implemented "list ... fake bits other than 24: ${local#*/}"
	esac
    }
    show_iface()
    {
	pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
	sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
	ip_link_show "$dev"
	if $primary && [ -r "$pf" ] ; then
	    read local <"$pf"
	    if [ -z "$_to" -o "${_to%/*}" = "${local%/*}" ] ; then
		calc_brd
		cat <<EOF
    inet ${local} brd ${brd} scope global ${dev}
EOF
	    fi
	fi
	if $secondary && [ -r "$sf" ] ; then
	    while read local ; do
		if [ -z "$_to" -o "${_to%/*}" = "${local%/*}" ] ; then
		    calc_brd
		    cat <<EOF
    inet ${local} brd ${brd} scope global secondary ${dev}
EOF
		fi
	    done <"$sf"
	fi
	if [ -z "$_to" ] ; then
	    cat <<EOF
       valid_lft forever preferred_lft forever
EOF
	fi
    }
    n=1
    for dev in $devices ; do
	if [ -z "$_to" ] || \
	    grep -F "${_to%/*}/" "${FAKE_IP_STATE}/addresses/${dev}-"* >/dev/null ; then
	    show_iface
	fi
	n=$(($n + 1))
    done
}

ip_addr_add ()
{
    local=""
    dev=""
    brd=""
    while [ -n "$1" ] ; do
	case "$1" in
	    *.*.*.*/*)
		local="$1" ; shift
		;;
	    local)
		local="$2" ; shift 2
		;;
	    broadcast|brd)
		# For now assume this is always '+'.
		if [ "$2" != "+" ] ; then
		    not_implemented "addr add ... brd $2 ..."
		fi
		shift 2
		;;
	    dev)
		dev="$2" ; shift 2
		;;
	    *)
		not_implemented "$@"
	esac
    done
    if [ -z "$dev" ] ; then
	not_implemented "addr add (without dev)"
    fi
    mkdir -p "${FAKE_IP_STATE}/addresses"
    pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
    sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
    # We could lock here... but we should be the only ones playing
    # around here with these stubs.
    if [ ! -f "$pf" ] ; then
	echo "$local" >"$pf"
    elif grep -Fq "$local" "$pf" ; then 
	echo "RTNETLINK answers: File exists" >&2
	exit 254
    elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then 
	echo "RTNETLINK answers: File exists" >&2
	exit 254
    else
	echo "$local" >>"$sf"
    fi
}

ip_addr_del ()
{
    local=""
    dev=""
    while [ -n "$1" ] ; do
	case "$1" in
	    *.*.*.*/*)
		local="$1" ; shift
		;;
	    local)
		local="$2" ; shift 2
		;;
	    dev)
		dev="$2" ; shift 2
		;;
	    *)
		not_implemented "addr del ... $1 ..."
	esac
    done
    if [ -z "$dev" ] ; then
	not_implemented "addr del (without dev)"
    fi
    mkdir -p "${FAKE_IP_STATE}/addresses"
    pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
    sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
    # We could lock here... but we should be the only ones playing
    # around here with these stubs.
    if [ ! -f "$pf" ] ; then
	echo "RTNETLINK answers: Cannot assign requested address" >&2
	exit 254
    elif grep -Fq "$local" "$pf" ; then
	# Remove primaries AND SECONDARIES.
	rm -f "$pf" "$sf"
    elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then 
	grep -Fv "$local" "$sf" >"${sf}.new"
	mv "${sf}.new" "$sf"
    else
	echo "RTNETLINK answers: Cannot assign requested address" >&2
	exit 254
    fi
}

######################################################################

ip_rule ()
{
    case "$1" in
	show|list|"") shift ; ip_rule_show "$@" ;;
	add)          shift ; ip_rule_add  "$@" ;;
	del*)         shift ; ip_rule_del  "$@" ;;
	*) not_implemented "$1 in \"$orig_args\"" ;;
    esac

}

# All non-default rules are in $FAKE_IP_STATE_RULES/rules.  As with
# the real version, rules can be repeated.  Deleting just deletes the
# 1st match.

ip_rule_show ()
{
    ip_rule_show_1 ()
    {
	_pre="$1"
	_table="$2"
	_selectors="$3"
	# potentially more options

	printf "%d:\t%s lookup %s \n" $_pre "$_selectors" "$_table"
    }

    ip_rule_show_some ()
    {
	_min="$1"
	_max="$2"

	[ -f "${FAKE_IP_STATE}/rules" ] || return

	while read _pre _table _selectors ; do
	    # Only print those in range
	    [ $_min -le $_pre -a $_pre -le $_max ] || continue

	    ip_rule_show_1 $_pre "$_table" "$_selectors"
	done <"${FAKE_IP_STATE}/rules"
    }

    ip_rule_show_1 0 "local" "from all"

    ip_rule_show_some 1 32765

    ip_rule_show_1 32766 "main" "from all"
    ip_rule_show_1 32767 "default" "from all"

    ip_rule_show_some 32768 2147483648
}

ip_rule_common ()
{
    _from=""
    _pre=""
    _table=""
    while [ -n "$1" ] ; do
	case "$1" in
	    from)  _from="$2"  ; shift 2 ;;
	    pref)  _pre="$2"   ; shift 2 ;;
	    table) _table="$2" ; shift 2 ;;
	    *) not_implemented "$1 in \"$orig_args\"" ;;
	esac
    done

    [ -n "$_pre" ]   || not_implemented "ip rule without \"pref\""
    ip_check_table "rule"
    # Relax this if more selectors added later...
    [ -n "$_from" ]  || not_implemented "ip rule without \"from\""
}

ip_rule_add ()
{
    ip_rule_common "$@"

    _f="${FAKE_IP_STATE}/rules"
    touch "$_f"
    (
	flock 0
	# Filter order must be consistent with the comparison in ip_rule_del()
	echo "$_pre $_table${_from:+ from }$_from" >>"$_f"
    ) <"$_f"
}

ip_rule_del ()
{
    ip_rule_common "$@"

    _f="${FAKE_IP_STATE}/rules"
    touch "$_f"
    (
	flock 0
	_tmp="$(mktemp)"
	_found=false
	while read _p _t _s ; do
	    if ! $_found && \
		[ "$_p" = "$_pre" -a "$_t" = "$_table" -a \
		"$_s" = "${_from:+from }$_from" ] ; then
		# Found.  Skip this one but not future ones.
		_found=true
	    else
		echo "$_p $_t $_s" >>"$_tmp"
	    fi
	done
	if cmp -s "$_tmp" "$_f" ; then
	    # No changes, must not have found what we wanted to delete
	    echo "RTNETLINK answers: No such file or directory" >&2
	    rm -f "$_tmp"
	    exit 2
	else
	    mv "$_tmp" "$_f"
	fi
    ) <"$_f" || exit $?
}

######################################################################

ip_route ()
{
    case "$1" in
	show|list)    shift ; ip_route_show  "$@" ;;
	flush)        shift ; ip_route_flush "$@" ;;
	add)          shift ; ip_route_add   "$@" ;;
	*) not_implemented "$1 in \"ip route\"" ;;
    esac
}

ip_route_common ()
{
    [ "$1" = table ] || not_implemented "$1 in \"$orig_args\""
    _table="$2"

    ip_check_table "route"
}

# Routes are in a file per table in the directory
# $FAKE_IP_STATE/routes.  These routes just use the table ID
# that is passed and don't do any lookup.  This could be "improved" if
# necessary.

ip_route_show ()
{
    ip_route_common "$@"

    # Missing file is just an empty table
    cat "$FAKE_IP_STATE/routes/${_table}" 2>/dev/null || true
}

ip_route_flush ()
{
    ip_route_common "$@"

    rm -f "$FAKE_IP_STATE/routes/${_table}"
}

ip_route_add ()
{
    _prefix=""
    _dev=""
    _gw=""
    _table=""

    while [ -n "$1" ] ; do
	case "$1" in
	    *.*.*.*/*|*.*.*.*) _prefix="$1" ; shift 1 ;;
	    local) _prefix="$2" ; shift 2 ;;
	    dev)   _dev="$2"   ; shift 2 ;;
	    via)   _gw="$2"    ; shift 2 ;;
	    table) _table="$2" ; shift 2 ;;
	    *) not_implemented "$1 in \"$orig_args\"" ;;
	esac
    done

    ip_check_table "route"
    [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\""
    [ -n "$_dev" ] || not_implemented "ip route without \"dev\" in \"$orig_args\""

    # Alias or add missing bits
    case "$_prefix" in
	0.0.0.0/0) _prefix="default" ;;
	*/*) : ;;
	*) _prefix="${_prefix}/32" ;;
    esac

    _f="$FAKE_IP_STATE/routes/${_table}"
    mkdir -p "$FAKE_IP_STATE/routes"
    touch "$_f"

    (
	flock 0

	if [ -n "$_gw" ] ; then
	    echo "${_prefix} via ${_gw} dev ${_dev} "
	else
	    echo "${_prefix} dev ${_dev}  scope link "
	fi >>"$_f"
    ) <"$_f"
}


######################################################################

orig_args="$*"

case "$1" in
    link)   shift ; ip_link  "$@" ;;
    addr*)  shift ; ip_addr  "$@" ;;
    rule)   shift ; ip_rule  "$@" ;;
    route)  shift ; ip_route "$@" ;;
    *) not_implemented "$1" ;;
esac

exit 0
