releasing version 0.6.0~rc1-10
[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 continue_on_error
106     Set if the script should continue even if a command fails.
107     Use to test script when running as non-root. Defaults to false
108     if running as root, true otherwise.
109 EOF
110 }
111
112 function version
113 {
114     echo iodine-client-start 1.0.3
115 }
116
117 case $# in
118     0)
119         ;;
120     1)
121         case "$1" in
122             # start)
123             #   ;;
124             # stop)
125             #   ;;
126             # restart)
127             #   ;;
128             --version|-v)
129                 version
130                 exit
131                 ;;
132             --help|-h)
133                 usage
134                 exit
135                 ;;
136             *)
137                 echo error: unknown option "$1"
138                 exit 1
139         esac
140         exit
141         ;;
142     *)
143         echo error: too many arguments "$*"
144         exit 1
145         ;;
146 esac
147
148 echo "${iodine_client_rc:=/etc/default/iodine-client}" > /dev/null
149
150 if [ -r ${iodine_client_rc} ]; then
151     . ${iodine_client_rc}
152 else
153     echo WARNING: Cannot read ${iodine_client_rc}
154 fi
155
156 if [ -z ${subdomain} ]; then
157     read -p "DNS tunnel DNS subdomain: " subdomain
158 fi
159
160 if [ -z ${subdomain} ]; then
161     echo ERROR: Must set subdomain.
162     exit 1
163 fi
164
165 if [ -z ${passwd} ]; then
166     read -p "Password for DNS tunnel over ${subdomain}: " passwd
167 fi
168
169 ## This is a host name used for testing DNS and for pinging
170 echo "${testhost:=slashdot.org}"                > /dev/null
171
172 ## Set if local network should be taken down and then up
173 echo "${bounce_localnet:=false}"                > /dev/null
174
175 ## Set for testing network availability via ping at various points
176 echo "${test_ping_localnet:=true}"              > /dev/null
177 echo "${test_ping_tunnel:=true}"                > /dev/null
178 echo "${test_ping_final:=true}"                 > /dev/null
179
180 ## Set if the script cannot find and then incorrectly guesses the
181 ## local network router
182 echo "${default_router}"                        > /dev/null
183
184 ## Set if script uses the wrong hardware interface
185 echo "${interface}"                             > /dev/null
186
187 ## Set if MTU needs to be manually altered (lowered)
188 ##  - the default tunnel MTU is 1024.
189 ##  - if local DNS server restricts to 512 byte packets then use MTU 220
190 echo "${mtu}"                                   > /dev/null
191
192 ## Set if the script should continue even if a command fails.
193 ## Used to test script when running as non-root.
194 if [ $(whoami) = root ]; then
195     echo "${continue_on_error:=false}"          > /dev/null
196 else
197     echo "${continue_on_error:=true}"           > /dev/null
198 fi
199
200 ## DEBIAN PACKAGES TO INSTALL: these are needed to run this script
201 ##  iodine (for /usr/sbin/iodine)
202 ##  iproute (for /bin/ip)
203 ##  ipcalc (for /usr/bin/ipcalc)
204 ##  dnsutils (for /usr/bin/dig)
205 ##  fping (for /usr/bin/fping)
206 ##  gawk (for /usr/bin/gawk, to use gensub())
207
208 ## TO DO
209 ## - avoid double ping when DNS server and local router are the same
210 ## - option to not kill existing iodine DNS tunnels, in case there
211 ##   are meant to be more than one
212 ## - sanify check whether default_router is on local network
213
214 echo ==== Creating IP-over-DNS tunnel over local network connection...
215
216
217 ## Find a network interface
218
219 if [ -z ${interface} ]; then
220     interface=$(tail --lines=+3 /proc/net/wireless \
221         | head -1 | tr -d : | awk '{print $1}')
222 fi
223
224 if [ -z ${interface} ]; then
225     interface=$(ifconfig -a | egrep '^[^ ].*encap:Ethernet' \
226         | head -1 | awk '{print $1}')
227 fi
228
229 if [ -z ${interface} ]; then
230     echo ERROR: No network interface found.
231     exit 1
232 fi
233
234 echo ==== Local network interface: ${interface}
235
236 ## Down any existing DNS tunnel (wish there were "approved" way to do this)
237
238 echo ==== Killing existing DNS tunnels...
239 if killall --quiet --wait --verbose --signal HUP iodine; then
240     sleep 2
241 fi
242
243 ## Stabilize local network
244
245 if ${bounce_localnet}; then
246     echo ==== Bouncing local network connection...
247     ifdown --force ${interface} || true
248     ifup ${interface} || ${continue_on_error}
249 fi
250
251 ## Fetch some information about the local network
252
253 addr=$(ip -4 addr show dev ${interface} scope global \
254     | tail -1 | awk '{print $2}')
255 prefix_len=$(echo ${addr} | sed 'sX^.*/XX')
256 local_net=$(ipcalc --nobinary ${addr} | awk '$1=="Network:" {print $2}')
257
258 echo ==== Local address: ${addr}
259 echo ==== Local network: ${local_net}
260
261 router=$(ip -4 route list dev ${interface} \
262     | awk '$1=="default" {print $3}' | head -1)
263 if [ -z ${router} ]; then
264     ## This can happen when the default local route is already deleted
265     if [ -z ${default_router} ]; then
266         echo WARNING: no default route, guessing local router IP address.
267         ## Minimum address on local net is usually right
268         router=$(ipcalc --nobinary ${addr} | awk '$1=="HostMin:" {print $2}')
269     else
270         echo WARNING: no default route, using configured default router.
271         ## But sometimes need to hardwire...
272         router=${default_router}
273     fi
274 fi
275
276 echo ==== Local network router: ${router}
277
278 ## Test DNS service
279
280 testhost_ip=$(dig +short -t A -q ${testhost})
281 if [ -z ${testhost_ip} ]; then
282     echo WARNING: Failure on DNS lookup of ${testhost}.
283 fi
284
285 ## fetch DNS servers
286
287 nameservers=$(awk '$1=="nameserver" {print $2}' /etc/resolv.conf)
288 if [ -n "${nameservers}" ]; then
289     echo ==== DNS servers: ${nameservers}
290 else
291     echo ERROR: No DNS servers found.
292     exit 1
293 fi
294
295 ## Test if local network is up
296
297 if ${test_ping_localnet}; then
298     echo ==== Ping test of  local network router and DNS servers...
299     fping -C1 ${router} ${nameservers} \
300         || echo WARNING: Ping test failed.
301 fi
302
303 ## Add point-to-point routes for any non-local DNS servers
304
305 for n in ${nameservers}; do
306     n_net=$(ipcalc --nobinary ${n}/${prefix_len} | awk '$1=="Network:" {print $2}')
307     if [ "${n_net}" != "${local_net}" ]; then
308         echo ==== Adding point-to-point route for DNS server ${n}
309         ## remove point-to-point route first, in case it is already present
310         ip -4 route del ${n}/32 || true
311         ip -4 route add ${n}/32 via ${router} || ${continue_on_error}
312     fi
313 done
314
315 ## Bring up DNS tunnel
316
317 echo ==== Creating IP-over-DNS tunnel...
318 iodine -P "${passwd}" "${subdomain}" || ${continue_on_error}
319
320 ## Find DNS tunnel interface
321
322 tunnel_interface=$(ifconfig -a | egrep '^dns' | awk '{print $1}' | head -1)
323 if [ -z "${tunnel_interface}" ]; then
324     echo WARNING: Cannot find DNS tunnel interface, using default.
325     tunnel_interface=dns0
326 fi
327 echo ==== DNS tunnel interface: ${tunnel_interface}
328
329 ## Maybe try to change MTU
330
331 if [ -n "${mtu}" ]; then
332     echo ==== Setting MTU of ${tunnel_interface} to ${mtu}
333     ifconfig ${tunnel_interface} mtu ${mtu}
334 fi
335
336 ## Figure out router at other end of tunnel, assuming router uses final octet .1
337 ## (There should be some way to get this information out of iodine, since
338 ## it *prints* it as it sets up the tunnel, so it does know it.)
339
340 tunnel_remote=$(ip -4 address show dev ${tunnel_interface} \
341     | gawk '$1=="inet" {print gensub("[.][0-9]*/.*", ".1", 1, $2)}' | head -1)
342
343 if [ -z ${tunnel_remote} ]; then
344     echo ERROR: Cannot find DNS tunnel remote endpoint.
345     ${continue_on_error}
346     ## set something random if debugging
347     echo WARNING: Confabulating DNS tunnel remote endpoint.
348     tunnel_remote=192.168.253.1
349 fi
350
351 echo ==== DNS tunnel remote endpoint: ${tunnel_remote}
352
353 if ${test_ping_tunnel}; then
354     echo ==== Ping test of local router, nameserver, and DNS tunnel...
355     fping -C1 ${router} ${nameservers} ${tunnel_remote} \
356         || echo WARNING: Ping test failed.
357 fi
358
359 ## Modify routing table to send trafic via DNS tunnel
360
361 echo ==== Setting default route through DNS tunnel...
362
363 ## Remove default route via local router
364 ip -4 route del default via ${router} || echo WARNING: No default route to delete
365 ## Add default via tunnel
366 ip -4 route add default via ${tunnel_remote} || ${continue_on_error}
367
368 ## Test if all is well
369
370 if ${test_ping_final}; then
371     echo ==== Ping test of local router, nameserver, DNS tunnel, external test host...
372     fping -C1 ${router} ${nameservers} ${tunnel_remote} ${testhost_ip:-${testhost}} \
373         || echo WARNING: Ping test failed.
374 fi