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