Add oping as an alternative to fping in Suggests.
[debian/iodine.git] / debian / iodine-client-start
1 #! /bin/bash
2
3 ### Script to set up an iodine tunnel route traffic through it
4 ###
5 ### Copyright 2008 Barak A. Pearlmutter <bap@debian.org>
6 ###
7 ### License: MIT
8 ###
9 ### Permission to use, copy, modify, and distribute this software for
10 ### any purpose with or without fee is hereby granted, provided that
11 ### the above copyright notice and this permission notice appear in
12 ### all copies.
13 ###
14 ### THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
15 ### WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
16 ### WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
17 ### AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
18 ### CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
19 ### LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
20 ### NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 ### CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22
23 ## Cause script to bail immediately on failed command
24 set -e
25
26 function usage
27 {
28     cat <<EOF
29 'iodine-client-start' starts an iodine IP-over-DNS tunnel.
30
31 Usage: iodine-client-start [option]
32
33   -h, --help            Print help and exit
34   -v, --version         Print version info and exit
35
36 Invoking the program without options attempts to set up and configure
37 an iodine IP-over-DNS tunnel using the configuration in the file
38 /etc/default/iodine-client or by querying the user. It tries to
39 figure out the right way to set things up by observing the network,
40 and if all else fails by guessing.
41
42 QUICK CONFIGURATION
43
44 Put two lines in the file /etc/default/iodine-client
45
46         subdomain=your.tunnel.sub.domain
47
48         passwd=password_for_that_tunnel
49
50
51 or invoke the script with those environment variables set:
52
53         env subdomain=xxx passwd=xxx iodine-client-start
54
55 If these are not set, the script will query the user for them.
56
57 DETAILS
58
59 The configuration file consists of lines which are either comments
60 starting with '#', or settings of the form VAR="val". Valid VARs are:
61
62 subdomain
63     Sample value: your.tunnel.sub.domain (no default, must be set)
64
65 passwd
66     Sample value: password_for_that_tunnel (no default, must be set)
67
68 testhost
69     Hostname to ping when testing if network is working (default:
70     slashdot.org)
71
72 bounce_localnet
73     Take the local network down and then up again before starting
74     tunnel (default: false)
75
76 test_ping_localnet
77     Test if the local network is working by pinging the gateway
78     (default: true)
79
80 test_ping_tunnel
81     Test if the iodine tunnel is working after it has been set up by
82     pinging the host at the other end (default: true)
83
84 test_ping_final
85     Test if the tunnel is working after everything is ostensibly set
86     up by trying to ping an external host (default: true)
87
88 default_router
89     IP address of router on the local network---should be found
90     automatically, set this if that fails and the program guesses wrong.
91
92 interface
93     Interface to use (e.g., eth1, eth0, etc) for connection to DNS
94     server used for the iodine tunnel---should be found automatically,
95     set this if that fails and the program guesses wrong.
96
97 mtu
98     Set if tunnel MTU needs to be manually changed (lowered). Should
99     not be necessary anymore, as recent versions of iodine negotiate
100     an appropriate MTU during tunnel setup. But if that negotiation
101     does not happen, or if you are using an older version of iodine,
102     the default tunnel MTU is 1024, and if the local DNS server
103     restricts to 512 byte packets you might need to use an MTU of 220.
104
105 skip_raw_udp_mode
106     Set "-r" option in iodine command line. With this option, iodine
107     does not try to establish a direct UDP socket to the iodine server
108     on port 53. (default: true).
109
110 continue_on_error
111     Set if the script should continue even if a command fails.
112     Use to test script when running as non-root. Defaults to false
113     if running as root, true otherwise.
114 EOF
115 }
116
117 function version
118 {
119     echo iodine-client-start 1.0.5
120 }
121
122 case $# in
123     0)
124         ;;
125     1)
126         case "$1" in
127             # start)
128             #   ;;
129             # stop)
130             #   ;;
131             # restart)
132             #   ;;
133             --version|-v)
134                 version
135                 exit
136                 ;;
137             --help|-h)
138                 usage
139                 exit
140                 ;;
141             *)
142                 echo error: unknown option "$1"
143                 exit 1
144         esac
145         exit
146         ;;
147     *)
148         echo error: too many arguments "$*"
149         exit 1
150         ;;
151 esac
152
153 echo "${iodine_client_rc:=/etc/default/iodine-client}" > /dev/null
154
155 if [ -r ${iodine_client_rc} ]; then
156     . ${iodine_client_rc}
157 else
158     echo WARNING: Cannot read ${iodine_client_rc}
159 fi
160
161 if [ -z ${subdomain} ]; then
162     read -p "DNS tunnel DNS subdomain: " subdomain
163 fi
164
165 if [ -z ${subdomain} ]; then
166     echo ERROR: Must set subdomain.
167     exit 1
168 fi
169
170 if [ -z ${passwd} ]; then
171     read -p "Password for DNS tunnel over ${subdomain}: " passwd
172 fi
173
174 ## This is a host name used for testing DNS and for pinging
175 echo "${testhost:=slashdot.org}"                > /dev/null
176
177 ## Set if local network should be taken down and then up
178 echo "${bounce_localnet:=false}"                > /dev/null
179
180 ## Set for testing network availability via ping at various points
181 echo "${test_ping_localnet:=true}"              > /dev/null
182 echo "${test_ping_tunnel:=true}"                > /dev/null
183 echo "${test_ping_final:=true}"                 > /dev/null
184
185 ## Set if the script cannot find and then incorrectly guesses the
186 ## local network router
187 echo "${default_router}"                        > /dev/null
188
189 ## Set if script uses the wrong hardware interface
190 echo "${interface}"                             > /dev/null
191
192 ## Set if MTU needs to be manually altered (lowered)
193 ##  - the default tunnel MTU is 1024.
194 ##  - if local DNS server restricts to 512 byte packets then use MTU 220
195 echo "${mtu}"                                   > /dev/null
196
197 ## Set it if you want try RAW udp mode
198 echo "${skip_raw_udp_mode:=true}"           > /dev/null
199
200 ## Set if the script should continue even if a command fails.
201 ## Used to test script when running as non-root.
202 if [ $(whoami) = root ]; then
203     echo "${continue_on_error:=false}"          > /dev/null
204 else
205     echo "${continue_on_error:=true}"           > /dev/null
206 fi
207
208 ## DEBIAN PACKAGES TO INSTALL: these are needed to run this script
209 ##  iodine (for /usr/sbin/iodine)
210 ##  iproute (for /bin/ip)
211 ##  ipcalc (for /usr/bin/ipcalc)
212 ##  dnsutils (for /usr/bin/dig)
213 ##  fping (for /usr/bin/fping)
214 ##  or oping (for /usr/bin/oping)
215 ##  gawk (for /usr/bin/gawk, to use gensub())
216
217 if type -P fping > /dev/null; then
218     ping_cmd="fping -C1"
219 elif type -P oping > /dev/null; then
220     ping_cmd="oping -c1"
221 else
222     ping_cmd="echo would ping"
223 fi
224
225 ## TO DO
226 ## - avoid double ping when DNS server and local router are the same
227 ## - option to not kill existing iodine DNS tunnels, in case there
228 ##   are meant to be more than one
229 ## - sanify check whether default_router is on local network
230
231 echo ==== Creating IP-over-DNS tunnel over local network connection...
232
233
234 ## Find a network interface
235
236 if [ -z ${interface} ]; then
237     interfaces=$(tail --lines=+3 /proc/net/wireless \
238         | tr -d : | awk '{print $1}')
239     for dev in ${interfaces}; do
240         if ip -4 addr show dev ${dev} | grep -q inet; then
241             interface=${dev}
242         fi
243     done
244 fi
245
246 if [ -z ${interface} ]; then
247     interface=$(ifconfig -a | egrep '^[^ ].*encap:Ethernet' \
248         | head -1 | awk '{print $1}')
249 fi
250
251 if [ -z ${interface} ]; then
252     echo ERROR: No network interface found.
253     exit 1
254 fi
255
256 echo ==== Local network interface: ${interface}
257
258 ## Down any existing DNS tunnel (wish there were "approved" way to do this)
259
260 echo ==== Killing existing DNS tunnels...
261 if killall --quiet --wait --verbose --signal HUP iodine; then
262     sleep 2
263 fi
264
265 ## Stabilize local network
266
267 if ${bounce_localnet}; then
268     echo ==== Bouncing local network connection...
269     ifdown --force ${interface} || true
270     ifup ${interface} || ${continue_on_error}
271 fi
272
273 ## Fetch some information about the local network
274
275 addr=$(ip -4 addr show dev ${interface} scope global \
276     | tail -1 | awk '{print $2}')
277 prefix_len=$(echo ${addr} | sed 'sX^.*/XX')
278 local_net=$(ipcalc --nobinary ${addr} | awk '$1=="Network:" {print $2}')
279
280 echo ==== Local address: ${addr}
281 echo ==== Local network: ${local_net}
282
283 router=$(ip -4 route list dev ${interface} \
284     | awk '$1=="default" {print $3}' | head -1)
285 if [ -z ${router} ]; then
286     ## This can happen when the default local route is already deleted
287     if [ -z ${default_router} ]; then
288         echo WARNING: no default route, guessing local router IP address.
289         ## Minimum address on local net is usually right
290         router=$(ipcalc --nobinary ${addr} | awk '$1=="HostMin:" {print $2}')
291     else
292         echo WARNING: no default route, using configured default router.
293         ## But sometimes need to hardwire...
294         router=${default_router}
295     fi
296 fi
297
298 echo ==== Local network router: ${router}
299
300 ## Test DNS service
301
302 testhost_ip=$(dig +short -t A -q ${testhost})
303 if [ -z ${testhost_ip} ]; then
304     echo WARNING: Failure on DNS lookup of ${testhost}.
305 fi
306
307 ## fetch DNS servers
308
309 nameservers=$(awk '$1=="nameserver" {print $2}' /etc/resolv.conf)
310 if [ -n "${nameservers}" ]; then
311     echo ==== DNS servers: ${nameservers}
312 else
313     echo ERROR: No DNS servers found.
314     exit 1
315 fi
316
317 ## Test if local network is up
318
319 if ${test_ping_localnet}; then
320     echo ==== Ping test of  local network router and DNS servers...
321     ${ping_cmd} ${router} ${nameservers} \
322         || echo WARNING: Ping test failed.
323 fi
324
325 ## Add point-to-point routes for any non-local DNS servers
326
327 for n in ${nameservers}; do
328     n_net=$(ipcalc --nobinary ${n}/${prefix_len} | awk '$1=="Network:" {print $2}')
329     n_net8=$(ipcalc --nobinary ${n}/8 | awk '$1=="Network:" {print $2}')
330     if [ "${n_net}" != "${local_net}" ]; then
331         if [ "${n_net8}" != "127.0.0.0/8" ]; then
332             echo ==== Adding point-to-point route for DNS server ${n}
333             ## remove point-to-point route first, in case it is already present
334             ip -4 route del ${n}/32 || true
335             ip -4 route add ${n}/32 via ${router} || ${continue_on_error}
336         fi
337     fi
338 done
339
340 ## Bring up DNS tunnel
341
342 echo ==== Creating IP-over-DNS tunnel...
343 if ${skip_raw_udp_mode}; then
344     iodine_opts="${iodine_opts} -r"
345 fi
346
347 iodine ${iodine_opts} -P "${passwd}" "${subdomain}" || ${continue_on_error}
348
349 ## Find DNS tunnel interface
350
351 tunnel_interface=$(ifconfig -a | egrep '^dns' | awk '{print $1}' | head -1)
352 if [ -z "${tunnel_interface}" ]; then
353     echo WARNING: Cannot find DNS tunnel interface, using default.
354     tunnel_interface=dns0
355 fi
356 echo ==== DNS tunnel interface: ${tunnel_interface}
357
358 ## Maybe try to change MTU
359
360 if [ -n "${mtu}" ]; then
361     echo ==== Setting MTU of ${tunnel_interface} to ${mtu}
362     ifconfig ${tunnel_interface} mtu ${mtu}
363 fi
364
365 ## Figure out router at other end of tunnel, assuming router uses final octet .1
366 ## (There should be some way to get this information out of iodine, since
367 ## it *prints* it as it sets up the tunnel, so it does know it.)
368
369 tunnel_remote=$(ip -4 address show dev ${tunnel_interface} \
370     | gawk '$1=="inet" {print gensub("[.][0-9]*/.*", ".1", 1, $2)}' | head -1)
371
372 if [ -z ${tunnel_remote} ]; then
373     echo ERROR: Cannot find DNS tunnel remote endpoint.
374     ${continue_on_error}
375     ## set something random if debugging
376     echo WARNING: Confabulating DNS tunnel remote endpoint.
377     tunnel_remote=192.168.253.1
378 fi
379
380 echo ==== DNS tunnel remote endpoint: ${tunnel_remote}
381
382 if ${test_ping_tunnel}; then
383     echo ==== Ping test of local router, nameserver, and DNS tunnel...
384     ${ping_cmd} ${router} ${nameservers} ${tunnel_remote} \
385         || echo WARNING: Ping test failed.
386 fi
387
388 ## Modify routing table to send trafic via DNS tunnel
389
390 echo ==== Setting default route through DNS tunnel...
391
392 ## Remove default route via local router
393 ip -4 route del default via ${router} || echo WARNING: No default route to delete
394 ## Add default via tunnel
395 ip -4 route add default via ${tunnel_remote} || ${continue_on_error}
396
397 ## Test if all is well
398
399 if ${test_ping_final}; then
400     echo ==== Ping test of local router, nameserver, DNS tunnel, external test host...
401     ${ping_cmd} ${router} ${nameservers} ${tunnel_remote} ${testhost_ip:-${testhost}} \
402         || echo WARNING: Ping test failed.
403 fi