#! /bin/bash ### Script to set up an iodine tunnel route traffic through it ### ### Copyright 2008 Barak A. Pearlmutter ### ### License: MIT ### ### Permission to use, copy, modify, and distribute this software for ### any purpose with or without fee is hereby granted, provided that ### the above copyright notice and this permission notice appear in ### all copies. ### ### THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL ### WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED ### WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE ### AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR ### CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM ### LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, ### NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN ### CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ## Cause script to bail immediately on failed command set -e ## Options for user to set. ## Minimal customization: put the two lines ## subdomain=your.tunnel.sub.domain ## passed=password_for_that_tunnel ## in the file /etc/default/iodine-client. echo "${iodine_client_rc:=/etc/default/iodine-client}" > /dev/null if [ -r ${iodine_client_rc} ]; then . ${iodine_client_rc} else echo WARNING: Cannot read ${iodine_client_rc} fi if [ -z ${subdomain} ]; then read -p "DNS tunnel DNS subdomain: " subdomain fi if [ -z ${subdomain} ]; then echo ERROR: Must set subdomain. exit 1 fi if [ -z ${passwd} ]; then read -p "Password for DNS tunnel over ${subdomain}: " passwd fi ## This is a host name used for testing DNS and for pinging echo "${testhost:=slashdot.org}" > /dev/null ## Set if local network should be taken down and then up echo "${bounce_localnet:=true}" > /dev/null ## Set for testing network availability via ping at various points echo "${test_ping_localnet:=true}" > /dev/null echo "${test_ping_tunnel:=true}" > /dev/null echo "${test_ping_final:=true}" > /dev/null ## Set if the script cannot find and then incorrectly guesses the ## local network router echo "${default_router}" > /dev/null ## Set if script uses the wrong hardware interface echo "{interface}" > /dev/null ## Set if the script should continue even if a command fails. ## Used to test script when running as non-root. if [ $(whoami) = root ]; then echo "${continue_on_error:=false}" > /dev/null else echo "${continue_on_error:=true}" > /dev/null fi ## DEBIAN PACKAGES TO INSTALL: these are needed to run this script ## iodine (for /usr/sbin/iodine) ## iproute (for /bin/ip) ## ipcalc (for /usr/bin/ipcalc) ## dnsutils (for /usr/bin/dig) ## fping (for /usr/bin/fping) ## The default tunnel MTU is 1024. ## If local DNS server restricts to 512 byte packets then do this: # ifconfig ${d} mtu 220 ## TO DO ## - avoid double ping when DNS server and local router are the same ## - option to not kill existing iodine DNS tunnels, in case there ## are meant to be more than one ## - sanify check whether default_router is on local network echo ==== Creating IP-over-DNS tunnel over local network connection... ## Find a network interface if [ -z ${interface} ]; then interface=$(tail --lines=+3 /proc/net/wireless \ | head -1 | tr -d : | awk '{print $1}') fi if [ -z ${interface} ]; then interface=$(ifconfig -a | egrep '^[^ ].*encap:Ethernet' \ | head -1 | awk '{print $1}') fi if [ -z ${interface} ]; then echo ERROR: No network interface found. exit 1 fi echo ==== Local network interface: ${interface} ## Down any existing DNS tunnel (wish there were "approved" way to do this) echo ==== Killing existing DNS tunnels... if killall --quiet --wait --verbose --signal HUP iodine; then sleep 2 fi ## Stabilize local network if ${bounce_localnet}; then echo ==== Bouncing local network connection... ifdown --force ${interface} || true ifup ${interface} || ${continue_on_error} fi ## Fetch some information about the local network addr=$(ip -4 addr show dev ${interface} scope global \ | tail -1 | awk '{print $2}') prefix_len=$(echo ${addr} | sed 'sX^.*/XX') local_net=$(ipcalc --nobinary ${addr} | awk '$1=="Network:" {print $2}') echo ==== Local address: ${addr} echo ==== Local network: ${local_net} router=$(ip -4 route list dev ${interface} \ | awk '$1=="default" {print $3}' | head -1) if [ -z ${router} ]; then ## This can happen when the default local route is already deleted if [ -z ${default_router} ]; then echo WARNING: no default route, guessing local router IP address. ## Minimum address on local net is usually right router=$(ipcalc --nobinary ${addr} | awk '$1=="HostMin:" {print $2}') else echo WARNING: no default route, using configured default router. ## But sometimes need to hardwire... router=${default_router} fi fi echo ==== Local network router: ${router} ## Test DNS service testhost_ip=$(dig +short -t A -q ${testhost}) if [ -z ${testhost_ip} ]; then echo WARNING: Failure on DNS lookup of ${testhost}. fi ## fetch DNS servers nameservers=$(awk '$1=="nameserver" {print $2}' /etc/resolv.conf) if [ -n "${nameservers}" ]; then echo ==== DNS servers: ${nameservers} else echo ERROR: No DNS servers found. exit 1 fi ## Test if local network is up if ${test_ping_localnet}; then echo ==== Ping test of local network router and DNS servers... fping -C1 ${router} ${nameservers} \ || echo WARNING: Ping test failed. fi ## Add point-to-point routes for any non-local DNS servers for n in ${nameservers}; do n_net=$(ipcalc --nobinary ${n}/${prefix_len} | awk '$1=="Network:" {print $2}') if [ "${n_net}" != "${local_net}" ]; then echo ==== Adding point-to-point route for DNS server ${n} ip -4 route add ${n}/32 via ${router} || ${continue_on_error} fi done ## Bring up DNS tunnel echo ==== Creating IP-over-DNS tunnel... iodine -P ${passwd} ${subdomain} || ${continue_on_error} ## Find DNS tunnel interface tunnel_interface=$(ifconfig -a | egrep '^dns' | awk '{print $1}' | head -1) if [ -z "${tunnel_interface}" ]; then echo WARNING: Cannot find DNS tunnel interface, using default. tunnel_interface=dns0 fi echo ==== DNS tunnel interface: ${tunnel_interface} ## Figure out router at other end of tunnel, assuming router uses final octet .1 ## (There should be some way to get this information out of iodine, since ## it *prints* it as it sets up the tunnel, so it does know it.) tunnel_remote=$(ip -4 address show dev ${tunnel_interface} \ | awk '$1=="inet" {print gensub("[.][0-9]*/.*", ".1", 1, $2)}' | head -1) if [ -z ${tunnel_remote} ]; then echo ERROR: Cannot find DNS tunnel remote endpoint. ${continue_on_error} ## set something random if debugging echo WARNING: Confabulating DNS tunnel remote endpoint. tunnel_remote=192.168.253.1 fi echo ==== DNS tunnel remote endpoint: ${tunnel_remote} if ${test_ping_tunnel}; then echo ==== Ping test of local router, nameserver, and DNS tunnel... fping -C1 ${router} ${nameservers} ${tunnel_remote} \ || echo WARNING: Ping test failed. fi ## Modify routing table to send trafic via DNS tunnel echo ==== Setting default route through DNS tunnel... ## Remove default route via local router ip -4 route del default via ${router} || ${continue_on_error} ## Add default via tunnel ip -4 route add default via ${tunnel_remote} || ${continue_on_error} ## Test if all is well if ${test_ping_final}; then echo ==== Ping test of local router, nameserver, DNS tunnel, external test host... fping -C1 ${router} ${nameservers} ${tunnel_remote} ${testhost_ip:-${testhost}} \ || echo WARNING: Ping test failed. fi