1#!/bin/sh
2
3FAKE_IP_STATE="${FAKE_NETWORK_STATE}/ip-state"
4mkdir -p "$FAKE_IP_STATE"
5
6promote_secondaries=true
7
8not_implemented ()
9{
10    echo "ip stub command: \"$1\" not implemented"
11    exit 127
12}
13
14######################################################################
15
16ip_link ()
17{
18    case "$1" in
19	set)
20	    shift
21	    # iface="$1"
22	    case "$2" in
23		up)   ip_link_set_up "$1"  ;;
24		down) ip_link_down_up "$1" ;;
25		*)    not_implemented "\"$2\" in \"$orig_args\"" ;;
26	    esac
27	    ;;
28	show) shift ; ip_link_show "$@" ;;
29	add*) shift ; ip_link_add "$@" ;;
30	del*) shift ; ip_link_delete "$@" ;;
31	*) not_implemented "$*" ;;
32    esac
33}
34
35ip_link_add ()
36{
37    _link=""
38    _name=""
39    _type=""
40
41    while [ -n "$1" ] ; do
42	case "$1" in
43	    link)
44		_link="$2"
45		shift 2
46		;;
47	    name)
48		_name="$2"
49		shift 2
50		;;
51	    type)
52		if [ "$2" != "vlan" ] ; then
53		    not_implemented "link type $1"
54		fi
55		_type="$2"
56		shift 2
57		;;
58	    id) shift 2 ;;
59	    *) not_implemented "$1" ;;
60	esac
61    done
62
63    case "$_type" in
64	vlan)
65	    if [ -z "$_name" -o -z "$_link" ] ; then
66		not_implemented "ip link add with null name or link"
67	    fi
68
69	    mkdir -p "${FAKE_IP_STATE}/interfaces-vlan"
70	    echo "$_link" >"${FAKE_IP_STATE}/interfaces-vlan/${_name}"
71	    ip_link_set_down "$_name"
72	    ;;
73    esac
74}
75
76ip_link_delete ()
77{
78    mkdir -p "${FAKE_IP_STATE}/interfaces-deleted"
79    touch "${FAKE_IP_STATE}/interfaces-deleted/$1"
80    rm -f "${FAKE_IP_STATE}/interfaces-vlan/$1"
81}
82
83ip_link_set_up ()
84{
85    rm -f "${FAKE_IP_STATE}/interfaces-down/$1"
86    rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1"
87}
88
89ip_link_set_down ()
90{
91    rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1"
92    mkdir -p "${FAKE_IP_STATE}/interfaces-down"
93    touch "${FAKE_IP_STATE}/interfaces-down/$1"
94}
95
96ip_link_show ()
97{
98    dev="$1"
99    if [ "$dev" = "dev" -a -n "$2" ] ; then
100	dev="$2"
101    fi
102
103    if [ -e "${FAKE_IP_STATE}/interfaces-deleted/$dev" ] ; then
104	echo "Device \"${dev}\" does not exist." >&2
105	exit 255
106    fi
107
108    if [ -r "${FAKE_IP_STATE}/interfaces-vlan/${dev}" ] ; then
109	read _link <"${FAKE_IP_STATE}/interfaces-vlan/${dev}"
110	dev="${dev}@${_link}"
111    fi
112
113    _state="UP"
114    _flags=",UP,LOWER_UP"
115    if [ -e "${FAKE_IP_STATE}/interfaces-down/$dev" ] ; then
116	_state="DOWN"
117	_flags=""
118    fi
119    case "$dev" in
120    lo)
121	    _mac="00:00:00:00:00:00"
122	    _brd="00:00:00:00:00:00"
123	    _type="loopback"
124	    _opts="<LOOPBACK${_flags}> mtu 65536 qdisc noqueue state UNKNOWN"
125	    ;;
126    *)
127	    _mac=$(echo $dev | cksum | sed -r -e 's@(..)(..)(..).*@fe:fe:fe:\1:\2:\3@')
128	    _brd="ff:ff:ff:ff:ff:ff"
129	    _type="ether"
130	    _opts="<BROADCAST,MULTICAST${_flags}> mtu 1500 qdisc pfifo_fast state ${_state} qlen 1000"
131    esac
132    echo "${n:-42}: ${dev}: ${_opts}"
133    echo "    link/${_type} ${_mac} brd ${_brd}"
134}
135
136# This is incomplete because it doesn't actually look up table ids in
137# /etc/iproute2/rt_tables.  The rules/routes are actually associated
138# with the name instead of the number.  However, we include a variable
139# to fake a bad table id.
140[ -n "$IP_ROUTE_BAD_TABLE_ID" ] || IP_ROUTE_BAD_TABLE_ID=false
141
142ip_check_table ()
143{
144    _cmd="$1"
145
146    if [ "$_cmd" = "route" -a -z "$_table" ] ;then
147	_table="main"
148    fi
149
150    [ -n "$_table" ] || not_implemented "ip rule/route without \"table\""
151
152    # Only allow tables names from 13.per_ip_routing and "main".  This
153    # is a cheap way of avoiding implementing the default/local
154    # tables.
155    case "$_table" in
156	ctdb.*|main)
157	    if $IP_ROUTE_BAD_TABLE_ID ; then
158		# Ouch.  Simulate inconsistent errors from ip.  :-(
159		case "$_cmd" in
160		    route)
161			echo "Error: argument "${_table}" is wrong: table id value is invalid" >&2
162
163			;;
164		    *)
165			echo "Error: argument "${_table}" is wrong: invalid table ID" >&2
166		esac
167		exit 255
168	    fi
169	    ;;
170	*) not_implemented "table=${_table} ${orig_args}" ;;
171    esac
172}
173
174######################################################################
175
176ip_addr ()
177{
178    case "$1" in
179	show|list|"") shift ; ip_addr_show "$@" ;;
180	add*)         shift ; ip_addr_add  "$@" ;;
181	del*)         shift ; ip_addr_del  "$@" ;;
182	*) not_implemented "\"$1\" in \"$orig_args\"" ;;
183    esac
184}
185
186ip_addr_show ()
187{
188    dev=""
189    primary=true
190    secondary=true
191    _to=""
192    while [ -n "$1" ] ; do
193	case "$1" in
194	    dev)
195		dev="$2" ; shift 2
196		;;
197            # Do stupid things and stupid things will happen!
198	    primary)
199		primary=true ; secondary=false ; shift
200		;;
201	    secondary)
202		secondary=true ; primary=false ; shift
203		;;
204	    to)
205		_to="$2" ; shift 2
206		;;
207	    *)
208	        # Assume an interface name
209		dev="$1" ; shift 1
210	esac
211    done
212    devices="$dev"
213    if [ -z "$devices" ] ; then
214	# No device specified?  Get all the primaries...
215	devices=$(ls "${FAKE_IP_STATE}/addresses/"*-primary 2>/dev/null | \
216	    sed -e 's@.*/@@' -e 's@-.*-primary$@@' | sort -u)
217    fi
218    calc_brd ()
219    {
220	    case "${local#*/}" in
221	    24) brd="${local%.*}.255" ;;
222	    32) brd="" ;;
223	    *) not_implemented "list ... fake bits other than 24/32: ${local#*/}"
224	esac
225    }
226    show_iface()
227    {
228	ip_link_show "$dev"
229
230	nets=$(ls "${FAKE_IP_STATE}/addresses/${dev}"-*-primary 2>/dev/null | \
231	    sed -e 's@.*/@@' -e "s@${dev}-\(.*\)-primary\$@\1@")
232
233	for net in $nets ; do
234	    pf="${FAKE_IP_STATE}/addresses/${dev}-${net}-primary"
235	    sf="${FAKE_IP_STATE}/addresses/${dev}-${net}-secondary"
236	    if $primary && [ -r "$pf" ] ; then
237		read local scope <"$pf"
238		if [ -z "$_to" -o "${_to%/*}" = "${local%/*}" ] ; then
239		    calc_brd
240		    echo "    inet ${local} ${brd:+brd ${brd} }scope ${scope} ${dev}"
241		fi
242	    fi
243	    if $secondary && [ -r "$sf" ] ; then
244		while read local scope ; do
245		    if [ -z "$_to" -o "${_to%/*}" = "${local%/*}" ] ; then
246			calc_brd
247			echo "    inet ${local} ${brd:+brd }${brd} scope ${scope} secondary ${dev}"
248		    fi
249		done <"$sf"
250	    fi
251	    if [ -z "$_to" ] ; then
252		echo "       valid_lft forever preferred_lft forever"
253	    fi
254	done
255    }
256    n=1
257    for dev in $devices ; do
258	if [ -z "$_to" ] || \
259	    grep -F "${_to%/*}/" "${FAKE_IP_STATE}/addresses/${dev}-"* >/dev/null ; then
260	    show_iface
261	fi
262	n=$(($n + 1))
263    done
264}
265
266# Copied from 13.per_ip_routing for now... so this is lazy testing  :-(
267ipv4_host_addr_to_net ()
268{
269    _host="$1"
270    _maskbits="$2"
271
272    # Convert the host address to an unsigned long by splitting out
273    # the octets and doing the math.
274    _host_ul=0
275    for _o in $(export IFS="." ; echo $_host) ; do
276	_host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
277    done
278
279    # Calculate the mask and apply it.
280    _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
281    _net_ul=$(( $_host_ul & $_mask_ul ))
282
283    # Now convert to a network address one byte at a time.
284    _net=""
285    for _o in $(seq 1 4) ; do
286	_net="$(($_net_ul & 255))${_net:+.}${_net}"
287	_net_ul=$(($_net_ul >> 8))
288    done
289
290    echo "${_net}/${_maskbits}"
291}
292
293ip_addr_add ()
294{
295    local=""
296    dev=""
297    brd=""
298    scope="global"
299    while [ -n "$1" ] ; do
300	case "$1" in
301	    *.*.*.*/*)
302		local="$1" ; shift
303		;;
304	    local)
305		local="$2" ; shift 2
306		;;
307	    broadcast|brd)
308		# For now assume this is always '+'.
309		if [ "$2" != "+" ] ; then
310		    not_implemented "addr add ... brd $2 ..."
311		fi
312		shift 2
313		;;
314	    dev)
315		dev="$2" ; shift 2
316		;;
317	    scope)
318		scope="$2" ; shift 2
319		;;
320	    *)
321		not_implemented "$@"
322	esac
323    done
324    if [ -z "$dev" ] ; then
325	not_implemented "addr add (without dev)"
326    fi
327    mkdir -p "${FAKE_IP_STATE}/addresses"
328    net_str=$(ipv4_host_addr_to_net $(IFS="/" ; echo $local))
329    net_str=$(echo "$net_str" | sed -e 's@/@_@')
330    pf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-primary"
331    sf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-secondary"
332    # We could lock here... but we should be the only ones playing
333    # around here with these stubs.
334    if [ ! -f "$pf" ] ; then
335	echo "$local $scope" >"$pf"
336    elif grep -Fq "$local" "$pf" ; then
337	echo "RTNETLINK answers: File exists" >&2
338	exit 254
339    elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then
340	echo "RTNETLINK answers: File exists" >&2
341	exit 254
342    else
343	echo "$local $scope" >>"$sf"
344    fi
345}
346
347ip_addr_del ()
348{
349    local=""
350    dev=""
351    while [ -n "$1" ] ; do
352	case "$1" in
353	    *.*.*.*/*)
354		local="$1" ; shift
355		;;
356	    local)
357		local="$2" ; shift 2
358		;;
359	    dev)
360		dev="$2" ; shift 2
361		;;
362	    *)
363		not_implemented "addr del ... $1 ..."
364	esac
365    done
366    if [ -z "$dev" ] ; then
367	not_implemented "addr del (without dev)"
368    fi
369    mkdir -p "${FAKE_IP_STATE}/addresses"
370    net_str=$(ipv4_host_addr_to_net $(IFS="/" ; echo $local))
371    net_str=$(echo "$net_str" | sed -e 's@/@_@')
372    pf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-primary"
373    sf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-secondary"
374    # We could lock here... but we should be the only ones playing
375    # around here with these stubs.
376    if [ ! -f "$pf" ] ; then
377	echo "RTNETLINK answers: Cannot assign requested address" >&2
378	exit 254
379    elif grep -Fq "$local" "$pf" ; then
380	if $promote_secondaries && [ -s "$sf" ] ; then
381	    head -n 1 "$sf" >"$pf"
382	    sed -i -e '1d' "$sf"
383	else
384	    # Remove primaries AND SECONDARIES.
385	    rm -f "$pf" "$sf"
386	fi
387    elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then
388	grep -Fv "$local" "$sf" >"${sf}.new"
389	mv "${sf}.new" "$sf"
390    else
391	echo "RTNETLINK answers: Cannot assign requested address" >&2
392	exit 254
393    fi
394}
395
396######################################################################
397
398ip_rule ()
399{
400    case "$1" in
401	show|list|"") shift ; ip_rule_show "$@" ;;
402	add)          shift ; ip_rule_add  "$@" ;;
403	del*)         shift ; ip_rule_del  "$@" ;;
404	*) not_implemented "$1 in \"$orig_args\"" ;;
405    esac
406
407}
408
409# All non-default rules are in $FAKE_IP_STATE_RULES/rules.  As with
410# the real version, rules can be repeated.  Deleting just deletes the
411# 1st match.
412
413ip_rule_show ()
414{
415    ip_rule_show_1 ()
416    {
417	_pre="$1"
418	_table="$2"
419	_selectors="$3"
420	# potentially more options
421
422	printf "%d:\t%s lookup %s \n" $_pre "$_selectors" "$_table"
423    }
424
425    ip_rule_show_some ()
426    {
427	_min="$1"
428	_max="$2"
429
430	[ -f "${FAKE_IP_STATE}/rules" ] || return
431
432	while read _pre _table _selectors ; do
433	    # Only print those in range
434	    [ $_min -le $_pre -a $_pre -le $_max ] || continue
435
436	    ip_rule_show_1 $_pre "$_table" "$_selectors"
437	done <"${FAKE_IP_STATE}/rules"
438    }
439
440    ip_rule_show_1 0 "local" "from all"
441
442    ip_rule_show_some 1 32765
443
444    ip_rule_show_1 32766 "main" "from all"
445    ip_rule_show_1 32767 "default" "from all"
446
447    ip_rule_show_some 32768 2147483648
448}
449
450ip_rule_common ()
451{
452    _from=""
453    _pre=""
454    _table=""
455    while [ -n "$1" ] ; do
456	case "$1" in
457	    from)  _from="$2"  ; shift 2 ;;
458	    pref)  _pre="$2"   ; shift 2 ;;
459	    table) _table="$2" ; shift 2 ;;
460	    *) not_implemented "$1 in \"$orig_args\"" ;;
461	esac
462    done
463
464    [ -n "$_pre" ]   || not_implemented "ip rule without \"pref\""
465    ip_check_table "rule"
466    # Relax this if more selectors added later...
467    [ -n "$_from" ]  || not_implemented "ip rule without \"from\""
468}
469
470ip_rule_add ()
471{
472    ip_rule_common "$@"
473
474    _f="${FAKE_IP_STATE}/rules"
475    touch "$_f"
476    (
477	flock 0
478	# Filter order must be consistent with the comparison in ip_rule_del()
479	echo "$_pre $_table${_from:+ from }$_from" >>"$_f"
480    ) <"$_f"
481}
482
483ip_rule_del ()
484{
485    ip_rule_common "$@"
486
487    _f="${FAKE_IP_STATE}/rules"
488    touch "$_f"
489    (
490	flock 0
491	_tmp="${_f}.new"
492	: >"$_tmp"
493	_found=false
494	while read _p _t _s ; do
495	    if ! $_found && \
496		[ "$_p" = "$_pre" -a "$_t" = "$_table" -a \
497		"$_s" = "${_from:+from }$_from" ] ; then
498		# Found.  Skip this one but not future ones.
499		_found=true
500	    else
501		echo "$_p $_t $_s" >>"$_tmp"
502	    fi
503	done
504	if cmp -s "$_tmp" "$_f" ; then
505	    # No changes, must not have found what we wanted to delete
506	    echo "RTNETLINK answers: No such file or directory" >&2
507	    rm -f "$_tmp"
508	    exit 2
509	else
510	    mv "$_tmp" "$_f"
511	fi
512    ) <"$_f" || exit $?
513}
514
515######################################################################
516
517ip_route ()
518{
519    case "$1" in
520	show|list)    shift ; ip_route_show  "$@" ;;
521	flush)        shift ; ip_route_flush "$@" ;;
522	add)          shift ; ip_route_add   "$@" ;;
523	del*)         shift ; ip_route_del   "$@" ;;
524	*) not_implemented "$1 in \"ip route\"" ;;
525    esac
526}
527
528ip_route_common ()
529{
530    if [ "$1" = table ] ; then
531	_table="$2"
532	shift 2
533    fi
534
535    ip_check_table "route"
536}
537
538# Routes are in a file per table in the directory
539# $FAKE_IP_STATE/routes.  These routes just use the table ID
540# that is passed and don't do any lookup.  This could be "improved" if
541# necessary.
542
543ip_route_show ()
544{
545    ip_route_common "$@"
546
547    # Missing file is just an empty table
548    sort "$FAKE_IP_STATE/routes/${_table}" 2>/dev/null || true
549}
550
551ip_route_flush ()
552{
553    ip_route_common "$@"
554
555    rm -f "$FAKE_IP_STATE/routes/${_table}"
556}
557
558ip_route_add ()
559{
560    _prefix=""
561    _dev=""
562    _gw=""
563    _table=""
564    _metric=""
565
566    while [ -n "$1" ] ; do
567	case "$1" in
568	    *.*.*.*/*|*.*.*.*) _prefix="$1" ; shift 1 ;;
569	    local) _prefix="$2" ; shift 2 ;;
570	    dev)   _dev="$2"   ; shift 2 ;;
571	    via)   _gw="$2"    ; shift 2 ;;
572	    table) _table="$2" ; shift 2 ;;
573	    metric) _metric="$2" ; shift 2 ;;
574	    *) not_implemented "$1 in \"$orig_args\"" ;;
575	esac
576    done
577
578    ip_check_table "route"
579    [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\""
580    # This can't be easily deduced, so print some garbage.
581    [ -n "$_dev" ] || _dev="ethXXX"
582
583    # Alias or add missing bits
584    case "$_prefix" in
585	0.0.0.0/0) _prefix="default" ;;
586	*/*) : ;;
587	*) _prefix="${_prefix}/32" ;;
588    esac
589
590    _f="$FAKE_IP_STATE/routes/${_table}"
591    mkdir -p "$FAKE_IP_STATE/routes"
592    touch "$_f"
593
594    # Check for duplicate
595    _prefix_regexp=$(echo "^${_prefix}" | sed -e 's@\.@\\.@g')
596    if [ -n "$_metric" ] ; then
597	_prefix_regexp="${_prefix_regexp} .*metric ${_metric} "
598    fi
599    if grep -q "$_prefix_regexp" "$_f" ; then
600	echo "RTNETLINK answers: File exists" >&2
601	exit 1
602    fi
603
604    (
605	flock 0
606
607	_out="${_prefix} "
608	[ -z "$_gw" ] || _out="${_out}via ${_gw} "
609	[ -z "$_dev" ] || _out="${_out}dev ${_dev} "
610	[ -n "$_gw" ] || _out="${_out} scope link "
611	[ -z "$_metric" ] || _out="${_out} metric ${_metric} "
612	echo "$_out" >>"$_f"
613    ) <"$_f"
614}
615
616ip_route_del ()
617{
618    _prefix=""
619    _dev=""
620    _gw=""
621    _table=""
622    _metric=""
623
624    while [ -n "$1" ] ; do
625	case "$1" in
626	    *.*.*.*/*|*.*.*.*) _prefix="$1" ; shift 1 ;;
627	    local) _prefix="$2" ; shift 2 ;;
628	    dev)   _dev="$2"   ; shift 2 ;;
629	    via)   _gw="$2"    ; shift 2 ;;
630	    table) _table="$2" ; shift 2 ;;
631	    metric) _metric="$2" ; shift 2 ;;
632	    *) not_implemented "$1 in \"$orig_args\"" ;;
633	esac
634    done
635
636    ip_check_table "route"
637    [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\""
638    # This can't be easily deduced, so print some garbage.
639    [ -n "$_dev" ] || _dev="ethXXX"
640
641    # Alias or add missing bits
642    case "$_prefix" in
643	0.0.0.0/0) _prefix="default" ;;
644	*/*) : ;;
645	*) _prefix="${_prefix}/32" ;;
646    esac
647
648    _f="$FAKE_IP_STATE/routes/${_table}"
649    mkdir -p "$FAKE_IP_STATE/routes"
650    touch "$_f"
651
652    (
653	flock 0
654
655	# Escape some dots
656	[ -z "$_gw" ] || _gw=$(echo "$_gw" | sed -e 's@\.@\\.@g')
657	_prefix=$(echo "$_prefix" | sed -e 's@\.@\\.@g' -e 's@/@\\/@')
658
659	_re="^${_prefix}\>.*"
660	[ -z "$_gw" ] || _re="${_re}\<via ${_gw}\>.*"
661	[ -z "$_dev" ] || _re="${_re}\<dev ${_dev}\>.*"
662	[ -z "$_metric" ] || _re="${_re}.*\<metric ${_metric}\>.*"
663	sed -i -e "/${_re}/d" "$_f"
664    ) <"$_f"
665}
666
667######################################################################
668
669orig_args="$*"
670
671case "$1" in
672    link)   shift ; ip_link  "$@" ;;
673    addr*)  shift ; ip_addr  "$@" ;;
674    rule)   shift ; ip_rule  "$@" ;;
675    route)  shift ; ip_route "$@" ;;
676    *) not_implemented "$1" ;;
677esac
678
679exit 0
680