├── README.md └── mptcpctl /README.md: -------------------------------------------------------------------------------- 1 | Multipath TCP on Mac OSX/Linux 2 | ============================== 3 | 4 | This project allows you to use *Multipath TCP* (MPTCP) on an unmodified Mac 5 | OSX/Linux computer. This project is intended as to allow you to test MPTCP and 6 | so do not expect good performances. 7 | 8 | How does it work ? 9 | ------------------ 10 | 11 | The solution is quite straightforward: take a virtual machine running the 12 | [Linux kernel version of Multipath TCP](https://github.com/multipath-tcp/mptcp) 13 | and redirect traffic through it. The project however goes even beyond that. 14 | Indeed, this project main goals are to: 15 | 16 | * allow using the the multiple interfaces available on the host; 17 | * allow mobility. 18 | 19 | The first requirement is met by mapping the physical adapters to virtual ones. 20 | The connectivity from inside the virtual device is ensured by bridging the 21 | physical and virtual interfaces together. A NAT is also used as in some 22 | environment a MAC filtering might be in place which can cause the virtual 23 | machine to not retrieve an IP address using regular DHCP. Static IP addresses 24 | are therefore used and are NATed to the corresponding public address. The 25 | second requirement is ensured by monitoring the route changes on the host and 26 | by echoing those changes inside the virtual machine. 27 | 28 | Installation 29 | ------------ 30 | 31 | The only requirement to run Multipath TCP on your computer is to install 32 | [VirtualBox](https://www.virtualbox.org/). VirtualBox is a virtualization 33 | software that allows to run guest operating systems. In this project it is used 34 | to run the MPTCP's virtual machine. 35 | 36 | The project only depends on a single script: `mptcpctl`. To install it, simply 37 | run the following: 38 | 39 | $ sudo curl https://raw.github.com/multipath-tcp/mptcp-virtual/master/mptcpctl -o /usr/local/bin/mptcpctl 40 | $ sudo chmod +x /usr/local/bin/mptcpctl 41 | 42 | Usage 43 | ----- 44 | 45 | The first step is to import the VirtualBox appliance (this can be skipped if 46 | already imported): 47 | 48 | $ curl -L http://multipath-tcp.org/data/mptcp-virtual.ova -o /tmp/mptcp-virtual.ova 49 | $ mptcpctl import /tmp/mptcp-virtual.ova 50 | 51 | Once this step is performed then the proxy can be started: 52 | 53 | $ mptcpctl start 54 | 55 | And stopped using: 56 | 57 | $ mptcpctl stop 58 | 59 | To allow mobility (not useful if the interfaces are not changing IP addresses): 60 | 61 | $ mptcpctl monitor 62 | 63 | Once the machine has started, then you can try Multipath TCP setting 64 | `127.0.0.1:13128` as a HTTP(S) proxy in your preferred application. You can 65 | test that it works using: 66 | 67 | $ http_proxy="127.0.0.1:13128" curl multipath-tcp.org 68 | 69 | A different message should be displayed whether you are MPTCP-capable. 70 | -------------------------------------------------------------------------------- /mptcpctl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEMP_DIR="/tmp/org.multipath-tcp/" 4 | mkdir -p ${TEMP_DIR} 5 | 6 | ### ------- Communication with the virtual machine ---------- 7 | 8 | MPTCP_PROXY_TELNET_PORT=13121 9 | 10 | function proxy_send_output { 11 | # Send a command to the proxy 12 | # $1 := command line to execute on the proxy 13 | # out := result of the command line 14 | expect -c " \ 15 | log_user 0; \ 16 | spawn telnet -l root 127.0.0.1 ${MPTCP_PROXY_TELNET_PORT}; \ 17 | sleep .1 18 | expect \"Password:\"; \ 19 | send \"mptcp\n\"; \ 20 | sleep .1 21 | expect \"root@mptcp-proxy:~# \"; \ 22 | send \"${1}\n\"; \ 23 | sleep .1 24 | expect -indices \"root@mptcp-proxy:~# \"; \ 25 | sleep .1 26 | send \"exit\n\"; \ 27 | puts [string range \$expect_out(buffer) [expr ${#1} + 2] \ 28 | [expr \$expect_out(0,start) - 2]];" 29 | } 30 | 31 | function proxy_send { 32 | # Send a command to the proxy 33 | # $1 := command line to execute on the proxy 34 | proxy_send_output "$1" > /dev/null 35 | } 36 | 37 | function proxy_set_resolver { 38 | # Configure the proxy's resolv.conf 39 | # $1 := resolver to use 40 | proxy_send "echo nameserver $1 > /etc/resolv.conf" 41 | } 42 | 43 | function proxy_copy_resolv.conf { 44 | # Configure the proxy's resolv.conf with a local one 45 | # $1 := resolv.conf path 46 | proxy_set_resolver $(sed -n 's/nameserver \(.*\)/\1/p' /etc/resolv.conf | head -1) 47 | # notify squid that the resolv.conf has changed 48 | proxy_send "squid -k reconfigure" 49 | } 50 | 51 | function proxy_iface_lookup { 52 | # Returns the interface that matches a mac address 53 | # $1 := a mac address in lowercase (e.g. ab:cd:ef:12:34) 54 | # out := the interface that matches 55 | proxy_send_output "ifconfig -a | grep -B2 ${1}" | grep flags | awk '{ print $1 }' | sed -e 's/://' 56 | } 57 | 58 | function proxy_iface_setup { 59 | # Configure an interface 60 | # $1 := interface name (e.g. eth1) 61 | # $2 := a /24 network (e.g. 10.3.45.0) 62 | local iface=$1 63 | local table=${iface#${iface%?}} 64 | local network=$2 65 | local address=${network%?}2 66 | local router=${network%?}1 67 | proxy_send "ifconfig ${iface} ${address}/24 up" 68 | proxy_send "ip rule add from ${address} table ${table}" 69 | proxy_send "ip route add ${network}/24 dev ${iface} scope link table ${table}" 70 | proxy_send "ip route add default via ${router} dev ${iface} table ${table}" 71 | } 72 | 73 | function proxy_iface_destroy { 74 | # Deconfigure an interface 75 | # $1 := interface name (e.g. eth1) 76 | local iface=$1 77 | local table=${iface#${iface%?}} 78 | proxy_send "ifconfig ${iface} down" 79 | proxy_send "ip rule del lookup ${table}" 80 | proxy_send "ip route flush table ${table}" 81 | } 82 | 83 | function proxy_set_default_route { 84 | # Set the default route 85 | # $1 := interface name (e.g. eth1) 86 | # $2 := a /24 network (e.g. 10.3.45.0) 87 | proxy_send "ip route add default via ${2%?}1 dev ${1}" 88 | } 89 | 90 | function proxy_unset_default_route { 91 | # Remove the default route 92 | proxy_send "ip route del default" 93 | } 94 | 95 | function proxy_is_running { 96 | # Returns whether the proxy is running (simple scan) 97 | nc -z 127.0.0.1 ${MPTCP_PROXY_TELNET_PORT} > /dev/null 98 | [ $? -eq 0 ] 99 | } 100 | 101 | function proxy_is_running2 { 102 | # Returns whether the proxy is running (try to connect) 103 | [ -n "$(echo "QUIT" | nc -w 5 127.0.0.1 ${MPTCP_PROXY_TELNET_PORT} 2> /dev/null)" ] 104 | } 105 | 106 | ### -------- VirtualBox functions --------- 107 | 108 | VBOX_NAME="MPTCPProxy" 109 | 110 | function vbox_get_virtual_mac { 111 | # Returns the mac address of the virtual interface mapped to an adapter 112 | local mac=$(VBoxManage showvminfo ${VBOX_NAME} --machinereadable | 113 | sed -n "/^bridgeadapter.*${1}.*/{n;p;}" | 114 | sed 's/^macaddress[0-9]*="\(.*\)"/\1/') 115 | echo $mac | awk '{print tolower($0)}' | sed 's/../&:/g;s/:$//' 116 | } 117 | 118 | function vbox_clear_ifaces { 119 | local cmd="VBoxManage modifyvm ${VBOX_NAME}" 120 | for id in `seq 2 8`; do 121 | cmd="${cmd} --nic${id} none" 122 | done 123 | ${cmd} 124 | } 125 | 126 | function vbox_update_ifaces { 127 | # first remove virtual interfaces 128 | vbox_clear_ifaces 129 | 130 | # Lookup for adapter and map them to a virtual interface 131 | local cnt=2 132 | local cmd="VBoxManage modifyvm ${VBOX_NAME}" 133 | IFS=' '; for iface in $(host_detect_iface); do 134 | cmd="${cmd} --nic${cnt} bridged --bridgeadapter${cnt} ${iface}" 135 | let cnt+=1 136 | done 137 | ${cmd} 138 | } 139 | 140 | function vbox_is_running { 141 | # Check whether the virtual machine is running 142 | [ -n "$(VBoxManage list runningvms | grep ${VBOX_NAME})" ] 143 | } 144 | 145 | function vbox_exists { 146 | # Check whether the virtual machine was imported into VirtualBox 147 | [ -n "$(VBoxManage list vms | grep ${VBOX_NAME})" ] 148 | } 149 | 150 | 151 | function vbox_start { 152 | # Start the virtual machine 153 | if host_is_darwin; then 154 | cat > ${TEMP_DIR}org.multipath-tcp.vbox.plist < 156 | 157 | 158 | 159 | Label 160 | org.multipath-tcp.vbox 161 | OnDemand 162 | 163 | ProgramArguments 164 | 165 | VBoxHeadless 166 | -startvm 167 | ${VBOX_NAME} 168 | -v 169 | off 170 | 171 | RunAtLoad 172 | 173 | 174 | 175 | EOF 176 | launchctl load ${TEMP_DIR}org.multipath-tcp.vbox.plist 177 | else 178 | VBoxHeadless -startvm ${VBOX_NAME} -v off > /dev/null & 179 | fi 180 | } 181 | 182 | function vbox_stop { 183 | # Stop the virtual machine 184 | if host_is_darwin; then 185 | launchctl unload ${TEMP_DIR}org.multipath-tcp.vbox.plist 186 | else 187 | VBoxManage controlvm ${VBOX_NAME} poweroff 2> /dev/null 188 | fi 189 | } 190 | 191 | function vbox_export_to { 192 | # Export the virtual appliance to $1 193 | VBoxManage export ${VBOX_NAME} -o ${1} 194 | } 195 | 196 | function vbox_import { 197 | # Import the virtual appliance from $1 198 | VBoxManage import ${1} --options keepallmacs 199 | } 200 | 201 | function vbox_delete { 202 | # Remove the virtual appliance 203 | VBoxManage unregistervm ${VBOX_NAME} --delete 204 | } 205 | 206 | ### ------- Host commands ---------- 207 | 208 | function host_is_linux { 209 | [ "`uname -s`" == "Linux" ] 210 | } 211 | 212 | function host_is_darwin { 213 | [ "`uname -s`" == "Darwin" ] 214 | } 215 | 216 | function host_detect_iface_filter { 217 | # detect the host adapter based on filter $1 218 | if host_is_darwin; then 219 | ifconfig -l | grep -Go ${1} | tr "\n" " " 220 | else 221 | ifconfig -s -a | grep -Go ${1} | tr "\n" " " 222 | fi 223 | } 224 | 225 | function host_detect_iface { 226 | # detect the host adapter based named en* 227 | if host_is_darwin; then 228 | host_detect_iface_filter "en[0-9]" 229 | else 230 | host_detect_iface_filter "eth[0-9]" 231 | fi 232 | } 233 | 234 | function host_is_iface_inactive { 235 | # Returns whether the interface$1 is not currenly used 236 | if host_is_darwin; then 237 | [ -n "$(ifconfig ${1} | grep inactive)" ] 238 | else 239 | [ -z "$(ifconfig | grep ${1})" ] 240 | fi 241 | } 242 | 243 | function host_is_iface_active { 244 | # Returns whether the interface$1 is currenly used 245 | if host_is_darwin; then 246 | [ -z "$(ifconfig ${1} | grep inactive)" ] 247 | else 248 | [ -n "$(ifconfig | grep ${1})" ] 249 | fi 250 | } 251 | 252 | function host_iface_has_any_address { 253 | # Returns whether the interface has an IP address or not 254 | [ -n "$(ifconfig ${1} | grep 'inet ')" ] 255 | } 256 | 257 | function host_iface_has_address { 258 | # Returns whether the interface $1 is configured with an address in the 259 | # network $2 260 | if host_is_darwin; then 261 | [ -n "$(ifconfig ${1} | grep ${2%?}1)" ] 262 | else 263 | [ -n "$(ip addr show dev ${1} | grep ${2%?}1)" ] 264 | fi 265 | } 266 | 267 | function host_iface_alias_is_set { 268 | # Returns wether the interface $1 is configured with an alias 269 | host_iface_has_address ${1} $(host_iface_prefix ${1}) 270 | } 271 | 272 | function host_iface_add_alias { 273 | # Configure an alias in the network $2 on the adapter $1 274 | if host_is_darwin; then 275 | sudo ifconfig ${1} alias ${2%?}1/24 276 | else 277 | sudo ip addr add dev ${1} ${2%?}1/24 278 | fi 279 | } 280 | 281 | function host_iface_set_alias { 282 | # Configure an alias on the adapter $1 283 | host_iface_add_alias ${1} $(host_iface_prefix ${1}) 284 | } 285 | 286 | function host_iface_del_alias { 287 | # Remove an alias in the network $2 on the adapter $1 288 | if host_is_darwin; then 289 | sudo ifconfig ${1} -alias ${2%?}1 290 | else 291 | sudo ip addr del dev ${1} ${2%?}1/24 292 | fi 293 | } 294 | 295 | function host_iface_unset_alias { 296 | # Remove the alias on the adapter $1 297 | host_iface_del_alias ${1} $(host_iface_prefix ${1}) 2> /dev/null 298 | } 299 | 300 | function host_get_default_interface { 301 | # Return the default route's interface 302 | if host_is_darwin; then 303 | route -n get 8.8.8.8 | sed -n 's/.*interface: \(.*\)/\1/p' 304 | else 305 | sudo ip route get 8.8.8.8 | head -n1 | awk '{ print $5 }' 306 | fi 307 | } 308 | 309 | function host_start_natd { 310 | # Start natd on interface $1 311 | if host_is_darwin; then 312 | cat > ${TEMP_DIR}natd-${1}.conf < /dev/null 331 | fi 332 | } 333 | 334 | function host_start_ipfw { 335 | # Start ipfw to forward packets to natd 336 | host_stop_ipfw 337 | IFS=' '; for iface in $(host_detect_iface); do 338 | local network=$(host_iface_prefix ${iface}) 339 | sudo ipfw add divert $(host_natd_port ${iface}) all from ${network%?}2 to any out recv ${iface} > /dev/null 340 | sudo ipfw add divert $(host_natd_port ${iface}) all from any to me in recv ${iface} > /dev/null 341 | done 342 | } 343 | 344 | function host_stop_ipfw { 345 | # Stop ipfw 346 | sudo ipfw -f flush > /dev/null 347 | } 348 | 349 | function host_enable_ip_forward { 350 | # Enable IP forwarding 351 | if host_is_darwin; then 352 | sudo sysctl -w net.inet.ip.fw.enable=1 > /dev/null 353 | sudo sysctl -w net.inet.ip.forwarding=1 > /dev/null 354 | else 355 | sudo sysctl -w net.ipv4.ip_forward=1 > /dev/null 356 | fi 357 | } 358 | 359 | function host_hash { 360 | if host_is_darwin; then 361 | md5 362 | else 363 | md5sum | awk '{ print $1 }' 364 | fi 365 | } 366 | 367 | ### -------- Utility functions --------- 368 | 369 | function util_hash { 370 | # Return a decimal hash of string $1 371 | local dec=$((0x$(echo ${1} | host_hash))) 372 | echo ${dec#-} 373 | } 374 | 375 | function host_iface_prefix { 376 | # Return a /24 network for interface $1 377 | local hash=$(util_hash ${1}) 378 | local net16=$(expr $(expr ${hash} / 256) % 256) 379 | local net24=$(expr ${hash} % 256) 380 | echo "10.${net16}.${net24}.0" 381 | } 382 | 383 | function host_natd_port { 384 | # Return the port used by natd for interface $1 385 | echo $(expr $(expr $(util_hash ${1}) % 55536) + 10000) 386 | } 387 | 388 | function make_sure_proxy_is_running { 389 | # Ensure that the proxy is started and running before returning 390 | if ! vbox_is_running; then 391 | echo "[global] The proxy is not yet running! -> warming up." 392 | vbox_start 393 | 394 | echo "[global] Waiting for the proxy to start." 395 | while ! proxy_is_running2; do 396 | continue 397 | done 398 | fi 399 | } 400 | 401 | function make_sure_proxy_is_stopped { 402 | # Ensure that the proxy is shutdown 403 | if vbox_is_running; then 404 | vbox_stop 405 | fi 406 | } 407 | 408 | function perform_update { 409 | # Update the Proxy's interfaces and routes based on the host network 410 | # changes 411 | local update=false 412 | ! proxy_is_running && echo "[global] The proxy is not running -> no update possible." && return 413 | 414 | echo "[global] route changes might have occurred -> check." 415 | 416 | IFS=' '; for iface in $(host_detect_iface); do 417 | if host_iface_alias_is_set ${iface} && host_is_iface_inactive ${iface}; then 418 | proxy_iface=$(proxy_iface_lookup $(vbox_get_virtual_mac $iface)) 419 | echo "[${iface}] is now down and was used -> cleanup." 420 | host_iface_unset_alias $iface 421 | proxy_iface_destroy ${proxy_iface} 422 | host_stop_natd ${iface} 423 | update=true 424 | elif host_is_iface_active ${iface} && ! host_iface_alias_is_set ${iface} && \ 425 | host_iface_has_any_address; then 426 | proxy_iface=$(proxy_iface_lookup $(vbox_get_virtual_mac $iface)) 427 | network=$(host_iface_prefix ${iface}) 428 | echo "[${iface}] is now up and thus can be used -> setting up." 429 | host_iface_set_alias $iface 430 | proxy_iface_setup ${proxy_iface} ${network} 431 | host_start_natd ${iface} 432 | update=true 433 | else 434 | echo "[${iface}] no changes detected." 435 | fi 436 | done 437 | 438 | if ${update}; then 439 | default_iface=$(host_get_default_interface) 440 | default_iface_network=$(host_iface_prefix ${default_iface}) 441 | proxy_iface=$(proxy_iface_lookup $(vbox_get_virtual_mac $default_iface)) 442 | echo "[global] setting up new default route via ${proxy_iface} ${default_iface_network%?}1" 443 | proxy_unset_default_route 444 | proxy_set_default_route ${proxy_iface} ${default_iface_network} 445 | echo "[global] copy new resolv.conf" 446 | proxy_copy_resolv.conf /etc/resolv.conf 447 | host_is_darwin && host_start_ipfw 448 | fi 449 | } 450 | 451 | 452 | LAST_ROUTE_CHANGE=$(date +%s) 453 | 454 | function host_route_changed { 455 | # Callback used by host_start_monitor 456 | while read data; do 457 | local now=$(date +%s) 458 | # not yet IPv6 459 | data=$(echo ${data} | grep -v ffff:ffff:ffff:ffff::) 460 | [ -z "${data}" ] && continue 461 | [ $(expr ${now} - ${LAST_ROUTE_CHANGE}) -gt 1 ] && perform_update 462 | LAST_ROUTE_CHANGE=${now} 463 | done 464 | } 465 | 466 | function host_start_monitor { 467 | # Monitor the routes and update the proxy when changes occurs 468 | if host_is_darwin; then 469 | route -n monitor | grep --line-buffered 'default' | host_route_changed 470 | else 471 | ip monitor | grep --line-buffered 'default' | host_route_changed 472 | fi 473 | } 474 | 475 | function host_has_command { 476 | # Check whether the host has the command $1 477 | command -v ${1} >/dev/null 2>&1 || host_has_command_sudo ${1} 478 | } 479 | 480 | function host_has_command_sudo { 481 | sudo which ${1} > /dev/null 482 | } 483 | 484 | ### -------- ################# --------- 485 | 486 | # Requirements: 487 | host_is_linux || host_is_darwin || { echo >&2 "Unable to determine your system. Aborting."; exit 1; } 488 | host_has_command VBoxManage || { echo >&2 "VirtualBox was not found. Aborting."; exit 1; } 489 | host_has_command expect || { echo >&2 "expect was not found. Aborting."; exit 1; } 490 | host_has_command nc || { echo >&2 "netcat was not found. Aborting."; exit 1; } 491 | host_has_command ifconfig || { echo >&2 "ifconfig was not found. Aborting."; exit 1; } 492 | ! host_is_darwin || host_has_command natd || { echo >&2 "natd was not found. Aborting."; exit 1; } 493 | ! host_is_linux || host_has_command iptables || { echo >&2 "iptables was not found. Aborting."; exit 1; } 494 | ! host_is_darwin || host_has_command ipfw || { echo >&2 "ipfw was not found. Aborting."; exit 1; } 495 | ! host_is_darwin || host_has_command route || { echo >&2 "route was not found. Aborting."; exit 1; } 496 | host_has_command telnet || { echo >&2 "telnet was not found. Aborting."; exit 1; } 497 | ! host_is_linux || host_has_command ip || { echo >&2 "ip was not found. Aborting."; exit 1; } 498 | 499 | # Main: 500 | case $1 in 501 | start) 502 | proxy_is_running && echo "[global] The proxy is already running -> exiting" && exit 503 | ! vbox_exists && echo "[global] Please run $0 import before start" && exit 504 | 505 | vbox_update_ifaces 506 | 507 | make_sure_proxy_is_running 508 | 509 | host_enable_ip_forward 510 | 511 | default_iface=$(host_get_default_interface) 512 | IFS=' '; for iface in $(host_detect_iface); do 513 | proxy_iface=$(proxy_iface_lookup $(vbox_get_virtual_mac $iface)) 514 | network=$(host_iface_prefix $iface) 515 | 516 | echo "[${iface}] is mapped to ${proxy_iface}" 517 | 518 | if host_is_iface_inactive $iface; then 519 | echo "[${iface}] is inactive -> cleaning up" 520 | host_iface_unset_alias $iface 521 | proxy_iface_destroy ${proxy_iface} 522 | continue 523 | else 524 | echo "[${iface}] is active -> using ${network}/24" 525 | host_iface_set_alias $iface 526 | proxy_iface_setup ${proxy_iface} ${network} 527 | 528 | if [ "${iface}" == "${default_iface}" ]; then 529 | echo "[${iface}] is the current default interface" 530 | echo "[${iface}] set ${proxy_iface} as default interface" 531 | proxy_set_default_route ${proxy_iface} ${network} 532 | fi 533 | fi 534 | echo "[${iface}] start natd" 535 | host_start_natd $iface 536 | done 537 | echo "[global] copy resolv.conf" 538 | proxy_copy_resolv.conf /etc/resolv.conf 539 | host_is_darwin && echo "[global] start ipfw" 540 | host_is_darwin && host_start_ipfw 541 | ;; 542 | stop) 543 | host_is_darwin && echo "[global] stopping ipfw" 544 | host_is_darwin && host_stop_ipfw 545 | 546 | IFS=' '; for iface in $(host_detect_iface); do 547 | echo "[${iface}] -> cleaning up" 548 | host_iface_unset_alias $iface 549 | echo "[${iface}] stopping natd" 550 | host_stop_natd ${iface} 551 | done 552 | echo "[global] stopping the proxy" 553 | make_sure_proxy_is_stopped 554 | ;; 555 | update) 556 | perform_update 557 | ;; 558 | monitor) 559 | host_start_monitor 560 | ;; 561 | update-ifaces) 562 | proxy_is_running && echo "Need to stop proxy first! ($0 stop)" && exit 563 | vbox_update_ifaces 564 | ;; 565 | export) 566 | [ -z $2 ] && echo "Usage: $0 export FILE" && exit 567 | proxy_is_running && echo "Need to stop proxy first! ($0 stop)" && exit 568 | vbox_export_to $2 569 | ;; 570 | import) 571 | [ -z $2 ] && echo "Usage: $0 import FILE" && exit 572 | proxy_is_running && echo "Need to stop proxy first! ($0 stop)" && exit 573 | 574 | if vbox_exists; then 575 | echo "Virtual machine exists -> deleting." 576 | vbox_delete 577 | fi 578 | vbox_import $2 579 | vbox_update_ifaces 580 | ;; 581 | upgrade) 582 | echo "Not yet implemented" 583 | ;; 584 | status) 585 | echo -n "[global] Virtual machine is " 586 | vbox_is_running && echo "running" || echo "not running" 587 | echo -n "[global] Proxy is " 588 | proxy_is_running && echo "alive" || echo "dead" 589 | ;; 590 | *) 591 | echo "Usage: $0 {start|stop|status|import|export|upgrade|monitor|update|update-ifaces}" 592 | ;; 593 | esac 594 | --------------------------------------------------------------------------------