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