Compare Pastes

Differences between the pastes #228313 (20.10.2021 09:15) and #266432 (03.11.2022 04:44).
1
 * Обновление сохранённых библиотек ...                                                                                            [ ok ]
1
 October 7, 2022 Russia-Ukraine newsCNN plsHelpUkraine11202203 
2
 * Обновление конфигурационных файлов ...                                                                                          [ ok ]
3
--- /etc/vpnc/vpnc-script	2020-09-07 08:29:16.149799300 +0300
4
+++ /etc/vpnc/._cfg0000_vpnc-script	2021-10-15 08:33:06.874483398 +0300
5
@@ -21,31 +21,36 @@
6
 ################
7
 #
8
 # List of parameters passed through environment
9
-#* reason                       -- why this script was called, one of: pre-init connect disconnect reconnect
10
-#* VPNGATEWAY                   -- vpn gateway address (always present)
11
+#* reason                       -- why this script was called, one of: pre-init connect disconnect reconnect attempt-reconnect
12
+#* VPNGATEWAY                   -- VPN gateway address (always present)
13
 #* TUNDEV                       -- tunnel device (always present)
14
+#* IDLE_TIMEOUT			-- gateway's idle timeout in seconds (OpenConnect v8.06+); unused
15
 #* INTERNAL_IP4_ADDRESS         -- address (always present)
16
-#* INTERNAL_IP4_MTU             -- mtu (often unset)
17
+#* INTERNAL_IP4_MTU             -- MTU (often unset)
18
 #* INTERNAL_IP4_NETMASK         -- netmask (often unset)
19
 #* INTERNAL_IP4_NETMASKLEN      -- netmask length (often unset)
20
 #* INTERNAL_IP4_NETADDR         -- address of network (only present if netmask is set)
21
-#* INTERNAL_IP4_DNS             -- list of dns servers
22
-#* INTERNAL_IP4_NBNS            -- list of wins servers
23
+#* INTERNAL_IP4_DNS             -- list of DNS servers
24
+#* INTERNAL_IP4_NBNS            -- list of WINS servers
25
 #* INTERNAL_IP6_ADDRESS         -- IPv6 address
26
 #* INTERNAL_IP6_NETMASK         -- IPv6 netmask
27
 #* INTERNAL_IP6_DNS             -- IPv6 list of dns servers
28
 #* CISCO_DEF_DOMAIN             -- default domain name
29
 #* CISCO_BANNER                 -- banner from server
30
+#* CISCO_SPLIT_DNS              -- DNS search domain list
31
 #* CISCO_SPLIT_INC              -- number of networks in split-network-list
32
 #* CISCO_SPLIT_INC_%d_ADDR      -- network address
33
 #* CISCO_SPLIT_INC_%d_MASK      -- subnet mask (for example: 255.255.255.0)
34
 #* CISCO_SPLIT_INC_%d_MASKLEN   -- subnet masklen (for example: 24)
35
-#* CISCO_SPLIT_INC_%d_PROTOCOL  -- protocol (often just 0)
36
-#* CISCO_SPLIT_INC_%d_SPORT     -- source port (often just 0)
37
-#* CISCO_SPLIT_INC_%d_DPORT     -- destination port (often just 0)
38
+#* CISCO_SPLIT_INC_%d_PROTOCOL  -- protocol (often just 0); unused
39
+#* CISCO_SPLIT_INC_%d_SPORT     -- source port (often just 0); unused
40
+#* CISCO_SPLIT_INC_%d_DPORT     -- destination port (often just 0); unused
41
 #* CISCO_IPV6_SPLIT_INC         -- number of networks in IPv6 split-network-list
42
 #* CISCO_IPV6_SPLIT_INC_%d_ADDR -- IPv6 network address
43
 #* CISCO_IPV6_SPLIT_INC_$%d_MASKLEN -- IPv6 subnet masklen
44
+#
45
+# The split tunnel variables above have *_EXC* counterparts for network
46
+# addresses to be excluded from the VPN tunnel.
47
 
48
 # FIXMEs:
49
 
50
@@ -66,10 +71,15 @@
51
 # Section B: Split DNS handling
52
 
53
 # 1) Maybe dnsmasq can do something like that
54
-# 2) Parse dns packets going out via tunnel and redirect them to original dns-server
55
+# 2) Parse DNS packets going out via tunnel and redirect them to original DNS-server
56
+
57
+# ======== For test logging (CI/CD will uncomment automatically) =========
58
 
59
-#env | sort
60
-#set -x
61
+#TRACE# echo "------------------"
62
+#TRACE# echo "vpnc-script environment:"
63
+#TRACE# env | egrep '^(CISCO_|INTERNAL_IP|VPNGATEWAY|TUNDEV|IDLE_TIMEOUT|reason)' | sort
64
+#TRACE# echo "------------------"
65
+#TRACE# set -x
66
 
67
 # =========== script (variable) setup ====================================
68
 
69
@@ -78,7 +88,8 @@
70
 OS="`uname -s`"
71
 
72
 HOOKS_DIR=/etc/vpnc
73
-DEFAULT_ROUTE_FILE=/var/run/vpnc/defaultroute
74
+DEFAULT_ROUTE_FILE=/var/run/vpnc/defaultroute.${PPID}
75
+DEFAULT_ROUTE_FILE_IPV6=/var/run/vpnc/defaultroute_ipv6.${PPID}
76
 RESOLV_CONF_BACKUP=/var/run/vpnc/resolv.conf-backup
77
 SCRIPTNAME=`basename $0`
78
 
79
@@ -88,9 +99,6 @@
80
 	[ -x /sbin/restorecon ] && /sbin/restorecon /var/run/vpnc
81
 fi
82
 
83
-# stupid SunOS: no blubber in /usr/bin ... (on stdout)
84
-IPROUTE="`which ip 2> /dev/null | grep '^/'`"
85
-
86
 if ifconfig --help 2>&1 | grep BusyBox > /dev/null; then
87
 	ifconfig_syntax_inet=""
88
 else
89
@@ -98,15 +106,31 @@
90
 fi
91
 
92
 if [ "$OS" = "Linux" ]; then
93
+	IPROUTE="`which ip 2> /dev/null | grep '^/'`"
94
 	ifconfig_syntax_ptp="pointopoint"
95
 	route_syntax_gw="gw"
96
 	route_syntax_del="del"
97
 	route_syntax_netmask="netmask"
98
+	route_syntax_inet6="-6"
99
+	route_syntax_inet6_host="-6"
100
+	route_syntax_inet6_net="-6"
101
+	ifconfig_syntax_add_inet6="add"
102
+	ifconfig_syntax_del() { case "$1" in *:*) echo del "$1" ;; *) echo 0.0.0.0 ;; esac; }
103
+	netstat_syntax_ipv6="-6"
104
 else
105
+	# iproute2 is Linux only; if `which ip` returns something on another OS, it's likely an unrelated tool
106
+	# (see https://github.com/dlenski/openconnect/issues/132#issuecomment-470475009)
107
+	IPROUTE=""
108
 	ifconfig_syntax_ptp=""
109
 	route_syntax_gw=""
110
 	route_syntax_del="delete"
111
 	route_syntax_netmask="-netmask"
112
+	route_syntax_inet6="-inet6"
113
+	route_syntax_inet6_host="-inet6 -host"
114
+	route_syntax_inet6_net="-inet6 -net"
115
+	ifconfig_syntax_del() { case "$1" in *:*) echo inet6 "$1" delete ;; *) echo "$1" delete ;; esac; }
116
+	ifconfig_syntax_add_inet6="inet6"
117
+	netstat_syntax_ipv6="-f inet6"
118
 fi
119
 if [ "$OS" = "SunOS" ]; then
120
 	route_syntax_interface="-interface"
121
@@ -116,20 +140,44 @@
122
 	ifconfig_syntax_ptpv6=""
123
 fi
124
 
125
+grep '^hosts' /etc/nsswitch.conf 2>/dev/null|grep resolve >/dev/null 2>&1 && command systemd-resolve --status >/dev/null 2>&1
126
+if [ $? = 0 ];then
127
+	RESOLVEDENABLED=1
128
+else
129
+	RESOLVEDENABLED=0
130
+fi
131
+
132
 if [ -r /etc/openwrt_release ] && [ -n "$OPENWRT_INTERFACE" ]; then
133
-        . /etc/functions.sh
134
+	. /etc/functions.sh
135
 	include /lib/network
136
 	MODIFYRESOLVCONF=modify_resolvconf_openwrt
137
 	RESTORERESOLVCONF=restore_resolvconf_openwrt
138
-elif [ -x /sbin/resolvconf ] && [ "$OS" != "FreeBSD" ]; then # Optional tool on Debian, Ubuntu, Gentoo - but not FreeBSD, it seems to work different
139
+elif [ -x /usr/bin/resolvectl ] && [ ${RESOLVEDENABLED} = 1 ]; then
140
+	# For systemd-resolved (version 239 and above)
141
+	MODIFYRESOLVCONF=modify_resolved_manager
142
+	RESTORERESOLVCONF=restore_resolved_manager
143
+elif [ -x /usr/bin/busctl ] && [ ${RESOLVEDENABLED} = 1 ]; then
144
+	# For systemd-resolved (version 229 and above)
145
+	MODIFYRESOLVCONF=modify_resolved_manager_old
146
+	RESTORERESOLVCONF=restore_resolved_manager_old
147
+elif [ -x /sbin/resolvconf ] && [ "`basename $(readlink /sbin/resolvconf) 2> /dev/null`" != resolvectl ]; then
148
+	# Optional tool on Debian, Ubuntu, Gentoo, FreeBSD and DragonFly BSD
149
+	# (ignored if symlink to resolvctl, created by some versions of systemd-resolved)
150
 	MODIFYRESOLVCONF=modify_resolvconf_manager
151
 	RESTORERESOLVCONF=restore_resolvconf_manager
152
-elif [ -x /sbin/netconfig ]; then # tool on Suse after 11.1
153
+elif [ -x /sbin/netconfig ] && [ ! -f /etc/slackware-version ]; then
154
+	# tool on Suse after 11.1
155
+	# Slackware's netconfig is an unrelated tool that should not be invoked here
156
+	# (see https://www.linuxquestions.org/questions/slackware-14/vpnc-on-slackware-14-2-is-bringing-up-network-configuration-dialog-each-time-4175595447/#post5646866)
157
 	MODIFYRESOLVCONF=modify_resolvconf_suse_netconfig
158
 	RESTORERESOLVCONF=restore_resolvconf_suse_netconfig
159
-elif [ -x /sbin/modify_resolvconf ]; then # Mandatory tool on Suse earlier than 11.1
160
+elif [ -x /sbin/modify_resolvconf ]; then
161
+	# Mandatory tool on Suse earlier than 11.1
162
 	MODIFYRESOLVCONF=modify_resolvconf_suse
163
 	RESTORERESOLVCONF=restore_resolvconf_suse
164
+elif [ -x /usr/sbin/unbound-control ] && /usr/sbin/unbound-control status > /dev/null 2>&1; then
165
+	MODIFYRESOLVCONF=modify_resolvconf_unbound
166
+	RESTORERESOLVCONF=restore_resolvconf_unbound
167
 else # Generic for any OS
168
 	MODIFYRESOLVCONF=modify_resolvconf_generic
169
 	RESTORERESOLVCONF=restore_resolvconf_generic
170
@@ -142,9 +190,9 @@
171
 	HOOK="$1"
172
 
173
 	if [ -d ${HOOKS_DIR}/${HOOK}.d ]; then
174
-	    for script in ${HOOKS_DIR}/${HOOK}.d/* ; do
175
-		[ -f $script ] && . $script
176
-	    done
177
+		for script in ${HOOKS_DIR}/${HOOK}.d/* ; do
178
+			[ -f $script ] && . $script
179
+		done
180
 	fi
181
 }
182
 
183
@@ -174,75 +222,113 @@
184
 	fi
185
 
186
 	if [ -n "$INTERNAL_IP4_NETMASK" ]; then
187
-		set_network_route $INTERNAL_IP4_NETADDR $INTERNAL_IP4_NETMASK $INTERNAL_IP4_NETMASKLEN
188
+		set_network_route "$INTERNAL_IP4_NETADDR" "$INTERNAL_IP4_NETMASK" "$INTERNAL_IP4_NETMASKLEN" "$TUNDEV"
189
 	fi
190
 
191
 	# If the netmask is provided, it contains the address _and_ netmask
192
 	if [ -n "$INTERNAL_IP6_ADDRESS" ] && [ -z "$INTERNAL_IP6_NETMASK" ]; then
193
-	    INTERNAL_IP6_NETMASK="$INTERNAL_IP6_ADDRESS/128"
194
+		INTERNAL_IP6_NETMASK="$INTERNAL_IP6_ADDRESS/128"
195
 	fi
196
 	if [ -n "$INTERNAL_IP6_NETMASK" ]; then
197
-	    if [ -n "$IPROUTE" ]; then
198
-		$IPROUTE -6 addr add $INTERNAL_IP6_NETMASK dev $TUNDEV
199
-	    else
200
-		# Unlike for Legacy IP, we don't specify the dest_address
201
-		# here on *BSD. OpenBSD for one will refuse to accept
202
-		# incoming packets to that address if we do.
203
-		# OpenVPN does the same (gives dest_address for Legacy IP
204
-		# but not for IPv6).
205
-		# Only Solaris needs it; hence $ifconfig_syntax_ptpv6
206
-	        ifconfig "$TUNDEV" inet6 $INTERNAL_IP6_NETMASK $ifconfig_syntax_ptpv6 mtu $MTU up
207
-	    fi
208
+		if [ -n "$IPROUTE" ]; then
209
+			$IPROUTE -6 addr add $INTERNAL_IP6_NETMASK dev $TUNDEV
210
+		else
211
+			# Unlike for Legacy IP, we don't specify the dest_address
212
+			# here on *BSD. OpenBSD for one will refuse to accept
213
+			# incoming packets to that address if we do.
214
+			# OpenVPN does the same (gives dest_address for Legacy IP
215
+			# but not for IPv6).
216
+			# Only Solaris needs it; hence $ifconfig_syntax_ptpv6
217
+			ifconfig "$TUNDEV" $ifconfig_syntax_add_inet6 $INTERNAL_IP6_NETMASK $ifconfig_syntax_ptpv6 mtu $MTU up
218
+		fi
219
 	fi
220
 }
221
 
222
-destroy_tun_device() {
223
-	case "$OS" in
224
-	NetBSD|OpenBSD) # and probably others...
225
-		ifconfig "$TUNDEV" destroy
226
-		;;
227
-	FreeBSD)
228
-		ifconfig "$TUNDEV" destroy > /dev/null 2>&1 &
229
-		;;
230
-	esac
231
-}
232
-
233
 # =========== route handling ====================================
234
 
235
 if [ -n "$IPROUTE" ]; then
236
 	fix_ip_get_output () {
237
 		sed -e 's/ /\n/g' | \
238
-		    sed -ne '1p;/via/{N;p};/dev/{N;p};/src/{N;p};/mtu/{N;p}'
239
+		    sed -ne "1 s|\$|${1}|p;/via/{N;p};/dev/{N;p};/src/{N;p};/mtu/{N;p};/metric/{N;p}"
240
 	}
241
 
242
 	set_vpngateway_route() {
243
 		$IPROUTE route add `$IPROUTE route get "$VPNGATEWAY" | fix_ip_get_output`
244
-		$IPROUTE route flush cache
245
+		$IPROUTE route flush cache 2>/dev/null
246
+	}
247
+
248
+	set_vpngateway_route_attempt_reconnect() {
249
+		# We'll attempt to add a host route to the gateway through every route that matches
250
+		# its address (excluding those through TUNDEV because the goal is to avoid loopback).
251
+
252
+		echo "$VPNGATEWAY" | grep -q : && FAMILY=-6 ROOT=::/0 || FAMILY=-4 ROOT=0/0
253
+		# put metric in front, sort by metric, then chop off first two fields (metric and destination)
254
+		$IPROUTE $FAMILY route show to "$VPNGATEWAY" root "$ROOT" |
255
+		    awk '/dev '"$TUNDEV"'/ { next; } { printf "%s %s\n", (match($0, /metric ([^ ]+)/) ? substr($0, RSTART+7, RLENGTH-7) : 4294967295), $0; }' |
256
+		    sort -n | cut -d' ' -f3- |
257
+		while read LINE ; do
258
+			# We do not want to use 'replace', since a route to the gateway that already
259
+			# exists is mostly likely the correct one (e.g. the case of a reconnect attempt
260
+			# after dead-peer detection, but no change in the underlying network devices).
261
+			$IPROUTE $FAMILY route add `echo "$VPNGATEWAY $LINE" | fix_ip_get_output` 2>/dev/null
262
+		done
263
+		$IPROUTE $FAMILY route flush cache 2>/dev/null
264
 	}
265
 
266
 	del_vpngateway_route() {
267
 		$IPROUTE route $route_syntax_del "$VPNGATEWAY"
268
-		$IPROUTE route flush cache
269
+		$IPROUTE route flush cache 2>/dev/null
270
 	}
271
 
272
 	set_default_route() {
273
 		$IPROUTE route | grep '^default' | fix_ip_get_output > "$DEFAULT_ROUTE_FILE"
274
 		$IPROUTE route replace default dev "$TUNDEV"
275
-		$IPROUTE route flush cache
276
+		$IPROUTE route flush cache 2>/dev/null
277
 	}
278
 
279
 	set_network_route() {
280
 		NETWORK="$1"
281
 		NETMASK="$2"
282
 		NETMASKLEN="$3"
283
-		$IPROUTE route replace "$NETWORK/$NETMASKLEN" dev "$TUNDEV"
284
-		$IPROUTE route flush cache
285
+		NETDEV="$4"
286
+		NETGW="$5"
287
+		if [ -n "$NETGW" ]; then
288
+			$IPROUTE route replace "$NETWORK/$NETMASKLEN" dev "$NETDEV" via "$NETGW"
289
+		else
290
+			$IPROUTE route replace "$NETWORK/$NETMASKLEN" dev "$NETDEV"
291
+		fi
292
+		$IPROUTE route flush cache 2>/dev/null
293
+	}
294
+
295
+	set_exclude_route() {
296
+		# add explicit route to keep current routing for this target
297
+		# (keep traffic separate from VPN tunnel)
298
+		NETWORK="$1"
299
+		NETMASK="$2"
300
+		NETMASKLEN="$3"
301
+		ARGS=`$IPROUTE route get "$NETWORK" 2>/dev/null | fix_ip_get_output "/$NETMASKLEN"`
302
+		if [ -z "$ARGS" ]; then
303
+			echo "cannot find route for exclude route $NETWORK/$NETMASKLEN, ignoring" >&2
304
+			return
305
+		fi
306
+		$IPROUTE route add $ARGS
307
+		$IPROUTE route flush cache 2>/dev/null
308
+	}
309
+
310
+	del_exclude_route() {
311
+		# FIXME: In theory, this could delete existing routes which are
312
+		# identical to split-exclude routes specified by VPNGATEWAY
313
+		NETWORK="$1"
314
+		NETMASK="$2"
315
+		NETMASKLEN="$3"
316
+		$IPROUTE route $route_syntax_del "$NETWORK/$NETMASKLEN"
317
+		$IPROUTE route flush cache 2>/dev/null
318
 	}
319
 
320
 	reset_default_route() {
321
 		if [ -s "$DEFAULT_ROUTE_FILE" ]; then
322
 			$IPROUTE route replace `cat "$DEFAULT_ROUTE_FILE"`
323
-			$IPROUTE route flush cache
324
+			$IPROUTE route flush cache 2>/dev/null
325
 			rm -f -- "$DEFAULT_ROUTE_FILE"
326
 		fi
327
 	}
328
@@ -251,48 +337,101 @@
329
 		NETWORK="$1"
330
 		NETMASK="$2"
331
 		NETMASKLEN="$3"
332
-		$IPROUTE route $route_syntax_del "$NETWORK/$NETMASKLEN" dev "$TUNDEV"
333
-		$IPROUTE route flush cache
334
+		NETDEV="$4"
335
+		$IPROUTE route $route_syntax_del "$NETWORK/$NETMASKLEN" dev "$NETDEV"
336
+		$IPROUTE route flush cache 2>/dev/null
337
 	}
338
 
339
 	set_ipv6_default_route() {
340
 		# We don't save/restore IPv6 default route; just add a higher-priority one.
341
 		$IPROUTE -6 route add default dev "$TUNDEV" metric 1
342
-		$IPROUTE -6 route flush cache
343
+		$IPROUTE -6 route flush cache 2>/dev/null
344
 	}
345
 
346
 	set_ipv6_network_route() {
347
 		NETWORK="$1"
348
 		NETMASKLEN="$2"
349
-		$IPROUTE -6 route replace "$NETWORK/$NETMASKLEN" dev "$TUNDEV"
350
-		$IPROUTE route flush cache
351
+		NETDEV="$3"
352
+		NETGW="$4"
353
+		if [ -n "$NETGW" ]; then
354
+			$IPROUTE -6 route replace "$NETWORK/$NETMASKLEN" dev "$NETDEV" via "$NETGW"
355
+		else
356
+			$IPROUTE -6 route replace "$NETWORK/$NETMASKLEN" dev "$NETDEV"
357
+		fi
358
+		$IPROUTE route flush cache 2>/dev/null
359
+	}
360
+
361
+	set_ipv6_exclude_route() {
362
+		# add explicit route to keep current routing for this target
363
+		# (keep traffic separate from VPN tunnel)
364
+		NETWORK="$1"
365
+		NETMASKLEN="$2"
366
+		ARGS=`$IPROUTE route get "$NETWORK" 2>/dev/null | fix_ip_get_output "/$NETMASKLEN"`
367
+		if [ -z "$ARGS" ]; then
368
+			echo "cannot find route for exclude route $NETWORK/$NETMASKLEN, ignoring" >&2
369
+			return
370
+		fi
371
+		$IPROUTE -6 route add $ARGS
372
+		$IPROUTE route flush cache 2>/dev/null
373
 	}
374
 
375
 	reset_ipv6_default_route() {
376
 		$IPROUTE -6 route del default dev "$TUNDEV"
377
-		$IPROUTE route flush cache
378
+		$IPROUTE route flush cache 2>/dev/null
379
 	}
380
 
381
 	del_ipv6_network_route() {
382
 		NETWORK="$1"
383
 		NETMASKLEN="$2"
384
-		$IPROUTE -6 route del "$NETWORK/$NETMASKLEN" dev "$TUNDEV"
385
-		$IPROUTE -6 route flush cache
386
+		NETDEV="$3"
387
+		$IPROUTE -6 route del "$NETWORK/$NETMASKLEN" dev "$NETDEV"
388
+		$IPROUTE -6 route flush cache 2>/dev/null
389
+	}
390
+
391
+	del_ipv6_exclude_route() {
392
+		# FIXME: In theory, this could delete existing routes which are
393
+		# identical to split-exclude routes specificed by VPNGATEWAY
394
+		NETWORK="$1"
395
+		NETMASKLEN="$2"
396
+		$IPROUTE -6 route del "$NETWORK/$NETMASKLEN"
397
+		$IPROUTE -6 route flush cache 2>/dev/null
398
 	}
399
 else # use route command
400
 	get_default_gw() {
401
-		# isn't -n supposed to give --numeric output?
402
-		# apperently not...
403
-		# Get rid of lines containing IPv6 addresses (':')
404
-		netstat -r -n | awk '/:/ { next; } /^(default|0\.0\.0\.0)/ { print $2; }'
405
+		# Intended behavior, starting with `netstat -r -n` output:
406
+		# - keep lines starting with 'default' or '0.0.0.0', but exclude bogus routes '0.0.0.0/nn' where nn != 0
407
+		# - remove lines containing IPv6 addresses (':')
408
+		# - remove lines for link-local routes (https://superuser.com/a/1067742)
409
+		netstat -r -n | awk '/:/ { next; } /link#/ { next; } /^(default|0\.0\.0\.0([[:space:]]|\/0))/ { print $2; exit; }'
410
+	}
411
+
412
+	get_default_gw_excl_tunnel() {
413
+		# Get rid of lines containing $TUNDEV (we don't want loopback)
414
+		netstat -r -n | awk '/:/ { next; } /link#/ { next; } /[[:space:]]'"$TUNDEV"'([[:space:]]|$)/ { next; } /^(default|0\.0\.0\.0([[:space:]]|\/0))/ { print $2; exit; }'
415
 	}
416
 
417
 	set_vpngateway_route() {
418
-		route add -host "$VPNGATEWAY" $route_syntax_gw "`get_default_gw`"
419
+		# Unlike with iproute2, there is no way to determine which current
420
+		# route(s) match the VPN gateway, so we simply find a default
421
+		# route and use its gateway.
422
+		case "$VPNGATEWAY" in
423
+		*:*)	route add $route_syntax_inet6_host "$VPNGATEWAY" $route_syntax_gw "`get_ipv6_default_gw`";;
424
+		*)	route add -host "$VPNGATEWAY" $route_syntax_gw "`get_default_gw`";;
425
+		esac
426
+	}
427
+
428
+	set_vpngateway_route_attempt_reconnect() {
429
+		case "$VPNGATEWAY" in
430
+		*:*)	route add $route_syntax_inet6_host "$VPNGATEWAY" $route_syntax_gw "`get_ipv6_default_gw_excl_tunnel`";;
431
+		*)	route add -host "$VPNGATEWAY" $route_syntax_gw "`get_default_gw_excl_tunnel`";;
432
+		esac
433
 	}
434
 
435
 	del_vpngateway_route() {
436
-		route $route_syntax_del -host "$VPNGATEWAY" $route_syntax_gw "`get_default_gw`"
437
+		case "$VPNGATEWAY" in
438
+		*:*)	route $route_syntax_del $route_syntax_inet6_host "$VPNGATEWAY" $route_syntax_gw "`get_ipv6_default_gw`";;
439
+		*)	route $route_syntax_del -host "$VPNGATEWAY" $route_syntax_gw "`get_default_gw`";;
440
+		esac
441
 	}
442
 
443
 	set_default_route() {
444
@@ -306,8 +445,36 @@
445
 		NETWORK="$1"
446
 		NETMASK="$2"
447
 		NETMASKLEN="$3"
448
-		del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
449
-		route add -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$INTERNAL_IP4_ADDRESS" $route_syntax_interface
450
+		if [ -n "$5" ]; then
451
+			NETGW="$5"
452
+		else
453
+			NETGW="$INTERNAL_IP4_ADDRESS"
454
+		fi
455
+		route add -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$NETGW" $route_syntax_interface
456
+	}
457
+
458
+	set_exclude_route() {
459
+		NETWORK="$1"
460
+		NETMASK="$2"
461
+		NETMASKLEN="$3"
462
+		DEFAULTGW="${DEFAULTGW:-`get_default_gw`}"
463
+		if [ -z "$DEFAULTGW" ]; then
464
+			echo "cannot find route for exclude route $NETWORK/$NETMASKLEN, ignoring" >&2
465
+			return
466
+		fi
467
+		# Add explicit route to keep traffic for this target separate
468
+		# from tunnel. FIXME: We use default gateway - this is our best
469
+		# guess in absence of "ip" command to query effective route.
470
+		route add -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$DEFAULTGW" $route_syntax_interface
471
+	}
472
+
473
+	del_exclude_route() {
474
+		# FIXME: This can delete existing routes in case they're
475
+		# identical to split-exclude routes specified by VPNGATEWAY
476
+		NETWORK="$1"
477
+		NETMASK="$2"
478
+		NETMASKLEN="$3"
479
+		route $route_syntax_del -net "$NETWORK" $route_syntax_netmask "$NETMASK"
480
 	}
481
 
482
 	reset_default_route() {
483
@@ -319,38 +486,98 @@
484
 	}
485
 
486
 	del_network_route() {
487
-		case "$OS" in
488
-		Linux|NetBSD|OpenBSD|Darwin|SunOS) # and probably others...
489
-			# routes are deleted automatically on device shutdown
490
-			return
491
-			;;
492
-		esac
493
 		NETWORK="$1"
494
 		NETMASK="$2"
495
 		NETMASKLEN="$3"
496
-		route $route_syntax_del -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$INTERNAL_IP4_ADDRESS"
497
+		if [ -n "$5" ]; then
498
+			NETGW="$5"
499
+		else
500
+			NETGW="$INTERNAL_IP4_ADDRESS"
501
+		fi
502
+		route $route_syntax_del -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$NETGW"
503
+	}
504
+
505
+	get_ipv6_default_gw() {
506
+		# Intended behavior, starting with `netstat -r -n` IPv6 output:
507
+		# - keep lines starting with 'default' or '::'
508
+		# - append %$interface to link-local routes (fe80::/10)
509
+		# - remove lines for loopback interface (lo)
510
+		# FIXME: is there a better way to exclude loopback routes than filtering interface /^lo/?
511
+		netstat -r -n $netstat_syntax_ipv6 | awk '/^(default|::\/0)/ { if ($NF!~/^lo/) { print ($2~/^fe[89ab]/ ? $2"%"$NF : $2); } }'
512
+	}
513
+
514
+	get_ipv6_default_gw_excl_tunnel() {
515
+		netstat -r -n $netstat_syntax_ipv6 | awk '/^(default|::\/0)/ { if ($NF!~/^lo/ && /$NF!~/'"$TUNDEV"'([[:space:]]|$)/) { print ($2~/^fe[89ab]/ ? $2"%"$NF : $2); } }'
516
 	}
517
 
518
 	set_ipv6_default_route() {
519
-		route add -inet6 default "$INTERNAL_IP6_ADDRESS" $route_syntax_interface
520
+		DEFAULTGW="`get_ipv6_default_gw`"
521
+		echo "$DEFAULTGW" > "$DEFAULT_ROUTE_FILE_IPV6"
522
+		route $route_syntax_del $route_syntax_inet6 default $route_syntax_gw "$DEFAULTGW"
523
+		route add $route_syntax_inet6 default $route_syntax_gw "$INTERNAL_IP6_ADDRESS" $route_syntax_interface
524
 	}
525
 
526
 	set_ipv6_network_route() {
527
 		NETWORK="$1"
528
 		NETMASK="$2"
529
-		route add -inet6 -net "$NETWORK/$NETMASK" "$INTERNAL_IP6_ADDRESS" $route_syntax_interface
530
+		DEVICE="$3"
531
+		if [ -n "$4" ]; then
532
+			NETGW="$4"
533
+		elif [ "$OS" = "Linux" ]; then
534
+			route add $route_syntax_inet6_net "$NETWORK/$NETMASK" dev "$DEVICE"
535
+			return
536
+		else
537
+			NETGW="$INTERNAL_IP6_ADDRESS"
538
+		fi
539
+
540
+		route add $route_syntax_inet6_net "$NETWORK/$NETMASK" $route_syntax_gw "$NETGW" $route_syntax_interface
541
+		:
542
+	}
543
+
544
+	set_ipv6_exclude_route() {
545
+		NETWORK="$1"
546
+		NETMASK="$2"
547
+		IPV6DEFAULTGW="${IPV6DEFAULTGW:-`get_ipv6_default_gw`}"
548
+		if [ -z "$IPV6DEFAULTGW" ]; then
549
+			echo "cannot find route for exclude route $NETWORK/$NETMASKLEN, ignoring" >&2
550
+			return
551
+		fi
552
+		# Add explicit route to keep traffic for this target separate
553
+		# from tunnel. FIXME: We use default gateway - this is our best
554
+		# guess in absence of "ip" command to query effective route.
555
+		route add $route_syntax_inet6_net "$NETWORK/$NETMASK" "$IPV6DEFAULTGW" $route_syntax_interface
556
 		:
557
 	}
558
 
559
 	reset_ipv6_default_route() {
560
-		route $route_syntax_del -inet6 default "$INTERNAL_IP6_ADDRESS"
561
+		if [ -s "$DEFAULT_ROUTE_FILE_IPV6" ]; then
562
+			route $route_syntax_del $route_syntax_inet6 default $route_syntax_gw "`get_ipv6_default_gw`" $route_syntax_interface
563
+			route add $route_syntax_inet6 default $route_syntax_gw `cat "$DEFAULT_ROUTE_FILE_IPV6"`
564
+			rm -f -- "$DEFAULT_ROUTE_FILE_IPV6"
565
+		fi
566
 		:
567
 	}
568
 
569
 	del_ipv6_network_route() {
570
 		NETWORK="$1"
571
 		NETMASK="$2"
572
-		route $route_syntax_del -inet6 "$NETWORK/$NETMASK" "$INTERNAL_IP6_ADDRESS"
573
+		DEVICE="$3"
574
+		if [ -n "$4" ]; then
575
+			NETGW="$4"
576
+		elif [ "$OS" = "Linux" ]; then
577
+			route $route_syntax_del $route_syntax_inet6 "$NETWORK/$NETMASK" dev "$DEVICE"
578
+			return
579
+		else
580
+			NETGW="$INTERNAL_IP6_ADDRESS"
581
+		fi
582
+		route $route_syntax_del $route_syntax_inet6 "$NETWORK/$NETMASK" $route_syntax_gw "$NETGW"
583
+		:
584
+	}
585
+
586
+	del_ipv6_exclude_route() {
587
+		NETWORK="$1"
588
+		NETMASK="$2"
589
+		route $route_syntax_del $route_syntax_inet6 "$NETWORK/$NETMASK"
590
 		:
591
 	}
592
 
593
@@ -366,48 +593,31 @@
594
 # and will be overwritten by vpnc
595
 # as long as the above mark is intact"
596
 
597
-	# Remember the original value of CISCO_DEF_DOMAIN we need it later
598
-	CISCO_DEF_DOMAIN_ORIG="$CISCO_DEF_DOMAIN"
599
-	# Don't step on INTERNAL_IP4_DNS value, use a temporary variable
600
-	INTERNAL_IP4_DNS_TEMP="$INTERNAL_IP4_DNS"
601
+	DOMAINS="$CISCO_DEF_DOMAIN"
602
+
603
 	exec 6< "$RESOLV_CONF_BACKUP"
604
 	while read LINE <&6 ; do
605
 		case "$LINE" in
606
-			nameserver*)
607
-				if [ -n "$INTERNAL_IP4_DNS_TEMP" ]; then
608
-					read ONE_NAMESERVER INTERNAL_IP4_DNS_TEMP <<-EOF
609
-	$INTERNAL_IP4_DNS_TEMP
610
-EOF
611
-					LINE="nameserver $ONE_NAMESERVER"
612
-				else
613
-					LINE=""
614
-				fi
615
-				;;
616
-			search*)
617
-				if [ -n "$CISCO_DEF_DOMAIN" ]; then
618
-					LINE="$LINE $CISCO_DEF_DOMAIN"
619
-					CISCO_DEF_DOMAIN=""
620
-				fi
621
-				;;
622
-			domain*)
623
-				if [ -n "$CISCO_DEF_DOMAIN" ]; then
624
-					LINE="domain $CISCO_DEF_DOMAIN"
625
-					CISCO_DEF_DOMAIN=""
626
-				fi
627
-				;;
628
+			# omit; we will overwrite these
629
+			nameserver*) ;;
630
+			# extract listed domains and prepend to list
631
+			domain* | search*) DOMAINS="${LINE#* } $DOMAINS" ;;
632
+			# retain other lines
633
+			*) NEW_RESOLVCONF="$NEW_RESOLVCONF
634
+$LINE" ;;
635
 		esac
636
-		NEW_RESOLVCONF="$NEW_RESOLVCONF
637
-$LINE"
638
 	done
639
 	exec 6<&-
640
 
641
-	for i in $INTERNAL_IP4_DNS_TEMP ; do
642
+	for i in $INTERNAL_IP4_DNS ; do
643
 		NEW_RESOLVCONF="$NEW_RESOLVCONF
644
 nameserver $i"
645
 	done
646
-	if [ -n "$CISCO_DEF_DOMAIN" ]; then
647
+	# note that "search" is mutually exclusive with "domain";
648
+	# "search" allows multiple domains to be listed, so use that
649
+	if [ -n "$DOMAINS" ]; then
650
 		NEW_RESOLVCONF="$NEW_RESOLVCONF
651
-search $CISCO_DEF_DOMAIN"
652
+search $DOMAINS"
653
 	fi
654
 	echo "$NEW_RESOLVCONF" > /etc/resolv.conf
655
 
656
@@ -425,12 +635,31 @@
657
 						# Cannot use multiple DNS matching in this case
658
 						OVERRIDE_PRIMARY='d.add OverridePrimary # 1'
659
 					fi
660
+					# Overriding the default gateway breaks split routing
661
+					OVERRIDE_GATEWAY=""
662
+					# Not overriding the default gateway breaks usage of
663
+					# INTERNAL_IP4_DNS. Prepend INTERNAL_IP4_DNS to list
664
+					# of used DNS servers
665
+					SERVICE=`echo "show State:/Network/Global/IPv4" | scutil | grep -oE '[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}'`
666
+					SERVICE_DNS=`echo "show State:/Network/Service/$SERVICE/DNS" | scutil | grep -oE '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | xargs`
667
+					if [ X"$SERVICE_DNS" != X"$INTERNAL_IP4_DNS" ]; then
668
+						scutil >/dev/null 2>&1 <<-EOF
669
+							open
670
+							get State:/Network/Service/$SERVICE/DNS
671
+							d.add ServerAddresses * $INTERNAL_IP4_DNS $SERVICE_DNS
672
+							set State:/Network/Service/$SERVICE/DNS
673
+							close
674
+						EOF
675
+					fi
676
+				else
677
+					# No split routing. Override default gateway
678
+					OVERRIDE_GATEWAY="d.add Router $INTERNAL_IP4_ADDRESS"
679
 				fi
680
 				# Uncomment the following if/fi pair to use multiple
681
 				# DNS matching when available.  When multiple DNS matching
682
 				# is present, anything reading the /etc/resolv.conf file
683
 				# directly will probably not work as intended.
684
-				#if [ -z "$CISCO_DEF_DOMAIN_ORIG" ]; then
685
+				#if [ -z "$CISCO_DEF_DOMAIN" ]; then
686
 					# Cannot use multiple DNS matching without a domain
687
 					OVERRIDE_PRIMARY='d.add OverridePrimary # 1'
688
 				#fi
689
@@ -440,8 +669,7 @@
690
 					d.add ServerAddresses * $INTERNAL_IP4_DNS
691
 					set State:/Network/Service/$TUNDEV/DNS
692
 					d.init
693
-					# next line overrides the default gateway and breaks split routing
694
-					# d.add Router $INTERNAL_IP4_ADDRESS
695
+					$OVERRIDE_GATEWAY
696
 					d.add Addresses * $INTERNAL_IP4_ADDRESS
697
 					d.add SubnetMasks * 255.255.255.255
698
 					d.add InterfaceName $TUNDEV
699
@@ -449,13 +677,13 @@
700
 					set State:/Network/Service/$TUNDEV/IPv4
701
 					close
702
 				EOF
703
-				if [ -n "$CISCO_DEF_DOMAIN_ORIG" ]; then
704
+				if [ -n "$CISCO_DEF_DOMAIN" ]; then
705
 					scutil >/dev/null 2>&1 <<-EOF
706
 						open
707
 						get State:/Network/Service/$TUNDEV/DNS
708
-						d.add DomainName $CISCO_DEF_DOMAIN_ORIG
709
-						d.add SearchDomains * $CISCO_DEF_DOMAIN_ORIG
710
-						d.add SupplementalMatchDomains * $CISCO_DEF_DOMAIN_ORIG
711
+						d.add DomainName $CISCO_DEF_DOMAIN
712
+						d.add SearchDomains * $CISCO_DEF_DOMAIN
713
+						d.add SupplementalMatchDomains * $CISCO_DEF_DOMAIN
714
 						set State:/Network/Service/$TUNDEV/DNS
715
 						close
716
 					EOF
717
@@ -485,6 +713,21 @@
718
 					remove State:/Network/Service/$TUNDEV/DNS
719
 					close
720
 				EOF
721
+				# Split routing required prepending of INTERNAL_IP4_DNS
722
+				# to list of used DNS servers
723
+				if [ -n "$CISCO_SPLIT_INC" ]; then
724
+					SERVICE=`echo "show State:/Network/Global/IPv4" | scutil | grep -oE '[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}'`
725
+					SERVICE_DNS=`echo "show State:/Network/Service/$SERVICE/DNS" | scutil | grep -oE '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | xargs`
726
+					if [ X"$SERVICE_DNS" != X"$INTERNAL_IP4_DNS" ]; then
727
+						scutil >/dev/null 2>&1 <<-EOF
728
+							open
729
+							get State:/Network/Service/$SERVICE/DNS
730
+							d.add ServerAddresses * ${SERVICE_DNS##$INTERNAL_IP4_DNS}
731
+							set State:/Network/Service/$SERVICE/DNS
732
+							close
733
+						EOF
734
+					fi
735
+				fi
736
 				;;
737
 		esac
738
 	fi
739
@@ -548,7 +791,7 @@
740
 	done
741
 	if [ -n "$CISCO_DEF_DOMAIN" ]; then
742
 		NEW_RESOLVCONF="$NEW_RESOLVCONF
743
-domain $CISCO_DEF_DOMAIN"
744
+search $CISCO_DEF_DOMAIN"
745
 	fi
746
 	echo "$NEW_RESOLVCONF" | /sbin/resolvconf -a $TUNDEV
747
 }
748
@@ -557,30 +800,118 @@
749
 	/sbin/resolvconf -d $TUNDEV
750
 }
751
 
752
-# ========= Toplevel state handling  =======================================
753
+AF_INET=2
754
 
755
-kernel_is_2_6_or_above() {
756
-	case `uname -r` in
757
-		1.*|2.[012345]*)
758
-			return 1
759
-			;;
760
-		*)
761
-			return 0
762
-			;;
763
-	esac
764
+get_if_index() {
765
+	local link
766
+	link="$(ip link show dev "$1")" || return $?
767
+	echo ${link} | awk -F: '{print $1}'
768
+}
769
+
770
+busctl_call() {
771
+	local dest node
772
+	dest=org.freedesktop.resolve1
773
+	node=/org/freedesktop/resolve1
774
+	busctl call "$dest" "${node}" "${dest}.Manager" "$@"
775
+}
776
+
777
+busctl_set_nameservers() {
778
+	local if_index addresses args addr
779
+	if_index=$1
780
+	shift
781
+	addresses="$@"
782
+	args="$if_index $#"
783
+	for addr in ${addresses}; do
784
+		args="$args ${AF_INET} 4 $(echo $addr | sed 's/[.]/ /g')"
785
+	done
786
+	busctl_call SetLinkDNS 'ia(iay)' ${args}
787
+}
788
+
789
+resolvectl_set_nameservers() {
790
+	local if_index addresses
791
+	if_index=$1
792
+	shift
793
+	addresses="$@"
794
+	/usr/bin/resolvectl dns $if_index $addresses
795
+}
796
+
797
+busctl_set_search() {
798
+	local if_index domains args domain
799
+	if_index=$1
800
+	shift
801
+	domains="$@"
802
+	args="$if_index $#"
803
+	for domain in ${domains}; do
804
+		args="$args ${domain} false"
805
+	done
806
+	busctl_call SetLinkDomains 'ia(sb)' ${args}
807
+}
808
+
809
+resolvectl_set_search() {
810
+	local if_index domains
811
+	if_index=$1
812
+	shift
813
+	domains="$@"
814
+	/usr/bin/resolvectl domain $if_index $domains
815
+}
816
+
817
+modify_resolved_manager() {
818
+	local if_index split_dns_list
819
+	if_index=$(get_if_index $TUNDEV)
820
+	split_dns_list=$(echo $CISCO_SPLIT_DNS | tr ',' ' ')
821
+	resolvectl_set_nameservers $if_index $INTERNAL_IP4_DNS
822
+	if [ -n "$CISCO_DEF_DOMAIN" ] || [ -n "$split_dns_list" ]; then
823
+		resolvectl_set_search $if_index $CISCO_DEF_DOMAIN $split_dns_list
824
+	fi
825
+}
826
+
827
+modify_resolved_manager_old() {
828
+	local if_index
829
+	if_index=$(get_if_index $TUNDEV)
830
+	busctl_set_nameservers $if_index $INTERNAL_IP4_DNS
831
+	if [ -n "$CISCO_DEF_DOMAIN" ]; then
832
+		busctl_set_search $if_index $CISCO_DEF_DOMAIN
833
+	fi
834
+}
835
+
836
+restore_resolved_manager() {
837
+	local if_index
838
+	if_index=$(get_if_index $TUNDEV)
839
+	/usr/bin/resolvectl revert $if_index
840
+}
841
+
842
+restore_resolved_manager_old() {
843
+	local if_index
844
+	if_index=$(get_if_index $TUNDEV)
845
+	busctl_call RevertLink 'i' $if_index
846
+}
847
+
848
+# === resolv.conf handling via unbound =========
849
+
850
+modify_resolvconf_unbound() {
851
+	if [ -n "$CISCO_DEF_DOMAIN" ]; then
852
+		/usr/sbin/unbound-control forward_add +i ${CISCO_DEF_DOMAIN} ${INTERNAL_IP4_DNS}
853
+		/usr/sbin/unbound-control flush_requestlist
854
+		/usr/sbin/unbound-control flush_zone ${CISCO_DEF_DOMAIN}
855
+	fi
856
+}
857
+
858
+restore_resolvconf_unbound() {
859
+	if [ -n "$CISCO_DEF_DOMAIN" ]; then
860
+		/usr/sbin/unbound-control forward_remove +i ${CISCO_DEF_DOMAIN}
861
+		/usr/sbin/unbound-control flush_zone ${CISCO_DEF_DOMAIN}
862
+		/usr/sbin/unbound-control flush_requestlist
863
+	fi
864
 }
865
 
866
+# ========= Toplevel state handling  =======================================
867
+
868
 do_pre_init() {
869
 	if [ "$OS" = "Linux" ]; then
870
-		if (exec 6<> /dev/net/tun) > /dev/null 2>&1 ; then
871
+		if (exec 6< /dev/net/tun) > /dev/null 2>&1 ; then
872
 			:
873
 		else # can't open /dev/net/tun
874
 			test -e /proc/sys/kernel/modprobe && `cat /proc/sys/kernel/modprobe` tun 2>/dev/null
875
-			# fix for broken devfs in kernel 2.6.x
876
-			if [ "`readlink /dev/net/tun`" = misc/net/tun \
877
-				-a ! -e /dev/net/misc/net/tun -a -e /dev/misc/net/tun ] ; then
878
-				ln -sf /dev/misc/net/tun /dev/net/tun
879
-			fi
880
 			# make sure tun device exists
881
 			if [ ! -e /dev/net/tun ]; then
882
 				mkdir -p /dev/net
883
@@ -588,18 +919,12 @@
884
 				[ -x /sbin/restorecon ] && /sbin/restorecon /dev/net/tun
885
 			fi
886
 			# workaround for a possible latency caused by udev, sleep max. 10s
887
-			if kernel_is_2_6_or_above ; then
888
-				for x in `seq 100` ; do
889
-					(exec 6<> /dev/net/tun) > /dev/null 2>&1 && break;
890
-					sleep 0.1
891
-				done
892
-			fi
893
+			for x in $(seq 100) ; do
894
+				(exec 6<> /dev/net/tun) > /dev/null 2>&1 && break;
895
+				sleep 0.1
896
+			done
897
 		fi
898
-	elif [ "$OS" = "FreeBSD" ]; then
899
-		if ! kldstat -q -m if_tun > /dev/null; then
900
-			kldload if_tun
901
-		fi
902
-
903
+	elif [ "$OS" = "FreeBSD" -o "$OS" = "DragonFly" ]; then
904
 		if ! ifconfig $TUNDEV > /dev/null; then
905
 			ifconfig $TUNDEV create
906
 		fi
907
@@ -628,16 +953,42 @@
908
 		echo
909
 	fi
910
 
911
-	set_vpngateway_route
912
+	case "$VPNGATEWAY" in
913
+		127.*|::1) ;; # localhost (probably proxy)
914
+		*) set_vpngateway_route ;;
915
+	esac
916
 	do_ifconfig
917
+	if [ -n "$CISCO_SPLIT_EXC" ]; then
918
+		i=0
919
+		while [ $i -lt $CISCO_SPLIT_EXC ] ; do
920
+			eval NETWORK="\${CISCO_SPLIT_EXC_${i}_ADDR}"
921
+			eval NETMASK="\${CISCO_SPLIT_EXC_${i}_MASK}"
922
+			eval NETMASKLEN="\${CISCO_SPLIT_EXC_${i}_MASKLEN}"
923
+			case "$NETWORK" in
924
+				0.*|127.*|169.254.*) echo "ignoring non-forwardable exclude route $NETWORK/$NETMASKLEN" >&2 ;;
925
+				*) set_exclude_route "$NETWORK" "$NETMASK" "$NETMASKLEN" ;;
926
+			esac
927
+			i=`expr $i + 1`
928
+		done
929
+	fi
930
+	if [ -n "$CISCO_IPV6_SPLIT_EXC" ]; then
931
+		# untested
932
+		i=0
933
+		while [ $i -lt $CISCO_IPV6_SPLIT_EXC ] ; do
934
+			eval NETWORK="\${CISCO_IPV6_SPLIT_EXC_${i}_ADDR}"
935
+			eval NETMASKLEN="\${CISCO_IPV6_SPLIT_EXC_${i}_MASKLEN}"
936
+			set_ipv6_exclude_route "$NETWORK" "$NETMASKLEN"
937
+			i=`expr $i + 1`
938
+		done
939
+	fi
940
 	if [ -n "$CISCO_SPLIT_INC" ]; then
941
 		i=0
942
 		while [ $i -lt $CISCO_SPLIT_INC ] ; do
943
 			eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
944
 			eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
945
 			eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
946
-			if [ $NETWORK != "0.0.0.0" ]; then
947
-				set_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
948
+			if [ "$NETWORK" != "0.0.0.0" ]; then
949
+				set_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN" "$TUNDEV"
950
 			else
951
 				set_default_route
952
 			fi
953
@@ -645,7 +996,7 @@
954
 		done
955
 		for i in $INTERNAL_IP4_DNS ; do
956
 			echo "$i" | grep : >/dev/null || \
957
-				set_network_route "$i" "255.255.255.255" "32"
958
+				set_network_route "$i" "255.255.255.255" "32" "$TUNDEV"
959
 		done
960
 	elif [ -n "$INTERNAL_IP4_ADDRESS" ]; then
961
 		set_default_route
962
@@ -655,16 +1006,16 @@
963
 		while [ $i -lt $CISCO_IPV6_SPLIT_INC ] ; do
964
 			eval NETWORK="\${CISCO_IPV6_SPLIT_INC_${i}_ADDR}"
965
 			eval NETMASKLEN="\${CISCO_IPV6_SPLIT_INC_${i}_MASKLEN}"
966
-			if [ $NETMASKLEN -lt 128 ]; then
967
-				set_ipv6_network_route "$NETWORK" "$NETMASKLEN"
968
-			else
969
+			if [ $NETMASKLEN -eq 0 ]; then
970
 				set_ipv6_default_route
971
+			else
972
+				set_ipv6_network_route "$NETWORK" "$NETMASKLEN" "$TUNDEV"
973
 			fi
974
 			i=`expr $i + 1`
975
 		done
976
 		for i in $INTERNAL_IP4_DNS ; do
977
 			if echo "$i" | grep : >/dev/null; then
978
-				set_ipv6_network_route "$i" "128"
979
+				set_ipv6_network_route "$i" "128" "$TUNDEV"
980
 			fi
981
 		done
982
 	elif [ -n "$INTERNAL_IP6_NETMASK" -o -n "$INTERNAL_IP6_ADDRESS" ]; then
983
@@ -683,21 +1034,44 @@
984
 			eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
985
 			eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
986
 			eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
987
-			if [ $NETWORK != "0.0.0.0" ]; then
988
+			if [ "$NETWORK" != "0.0.0.0" ]; then
989
 				# FIXME: This doesn't restore previously overwritten
990
 				#        routes.
991
-				del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
992
+				del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN" "$TUNDEV"
993
 			else
994
 				reset_default_route
995
 			fi
996
 			i=`expr $i + 1`
997
 		done
998
 		for i in $INTERNAL_IP4_DNS ; do
999
-			del_network_route "$i" "255.255.255.255" "32"
1000
+			del_network_route "$i" "255.255.255.255" "32" "$TUNDEV"
1001
 		done
1002
 	else
1003
 		reset_default_route
1004
 	fi
1005
+	if [ -n "$CISCO_SPLIT_EXC" ]; then
1006
+		i=0
1007
+		while [ $i -lt $CISCO_SPLIT_EXC ] ; do
1008
+			eval NETWORK="\${CISCO_SPLIT_EXC_${i}_ADDR}"
1009
+			eval NETMASK="\${CISCO_SPLIT_EXC_${i}_MASK}"
1010
+			eval NETMASKLEN="\${CISCO_SPLIT_EXC_${i}_MASKLEN}"
1011
+			case "$NETWORK" in
1012
+				0.*|127.*|169.254.*) ;; # ignoring non-forwardable exclude route
1013
+				*) del_exclude_route "$NETWORK" "$NETMASK" "$NETMASKLEN" ;;
1014
+			esac
1015
+			i=`expr $i + 1`
1016
+		done
1017
+	fi
1018
+	if [ -n "$CISCO_IPV6_SPLIT_EXC" ]; then
1019
+		# untested
1020
+		i=0
1021
+		while [ $i -lt $CISCO_IPV6_SPLIT_EXC ] ; do
1022
+			eval NETWORK="\${CISCO_IPV6_SPLIT_EXC_${i}_ADDR}"
1023
+			eval NETMASKLEN="\${CISCO_IPV6_SPLIT_EXC_${i}_MASKLEN}"
1024
+			del_ipv6_exclude_route "$NETWORK" "$NETMASKLEN"
1025
+			i=`expr $i + 1`
1026
+		done
1027
+	fi
1028
 	if [ -n "$CISCO_IPV6_SPLIT_INC" ]; then
1029
 		i=0
1030
 		while [ $i -lt $CISCO_IPV6_SPLIT_INC ] ; do
1031
@@ -706,12 +1080,12 @@
1032
 			if [ $NETMASKLEN -eq 0 ]; then
1033
 				reset_ipv6_default_route
1034
 			else
1035
-				del_ipv6_network_route "$NETWORK" "$NETMASKLEN"
1036
+				del_ipv6_network_route "$NETWORK" "$NETMASKLEN" "$TUNDEV"
1037
 			fi
1038
 			i=`expr $i + 1`
1039
 		done
1040
 		for i in $INTERNAL_IP6_DNS ; do
1041
-			del_ipv6_network_route "$i" "128"
1042
+			del_ipv6_network_route "$i" "128" "$TUNDEV"
1043
 		done
1044
 	elif [ -n "$INTERNAL_IP6_NETMASK" -o -n "$INTERNAL_IP6_ADDRESS" ]; then
1045
 		reset_ipv6_default_route
1046
@@ -735,19 +1109,32 @@
1047
 		if [ -n "$INTERNAL_IP6_NETMASK" ]; then
1048
 			$IPROUTE -6 addr del $INTERNAL_IP6_NETMASK dev $TUNDEV
1049
 		fi
1050
+		$IPROUTE link set dev "$TUNDEV" down
1051
 	else
1052
 		if [ -n "$INTERNAL_IP4_ADDRESS" ]; then
1053
-			ifconfig "$TUNDEV" 0.0.0.0
1054
+			ifconfig "$TUNDEV" `ifconfig_syntax_del "$INTERNAL_IP4_ADDRESS"`
1055
 		fi
1056
 		if [ -n "$INTERNAL_IP6_ADDRESS" ] && [ -z "$INTERNAL_IP6_NETMASK" ]; then
1057
 			INTERNAL_IP6_NETMASK="$INTERNAL_IP6_ADDRESS/128"
1058
 		fi
1059
 		if [ -n "$INTERNAL_IP6_NETMASK" ]; then
1060
-			ifconfig "$TUNDEV" inet6 del $INTERNAL_IP6_NETMASK
1061
+			ifconfig "$TUNDEV" `ifconfig_syntax_del "$INTERNAL_IP6_NETMASK"`
1062
 		fi
1063
+		ifconfig "$TUNDEV" down
1064
 	fi
1065
 
1066
-	destroy_tun_device
1067
+	case "$OS" in
1068
+	NetBSD|OpenBSD) # and probably others...
1069
+		ifconfig "$TUNDEV" destroy
1070
+		;;
1071
+	FreeBSD|DragonFly)
1072
+		ifconfig "$TUNDEV" destroy > /dev/null 2>&1 &
1073
+		;;
1074
+	esac
1075
+}
1076
+
1077
+do_attempt_reconnect() {
1078
+	set_vpngateway_route_attempt_reconnect
1079
 }
1080
 
1081
 #### Main
1082
@@ -772,7 +1159,17 @@
1083
 		do_disconnect
1084
 		run_hooks post-disconnect
1085
 		;;
1086
+	attempt-reconnect)
1087
+		# Invoked before each attempt to re-establish the session.
1088
+		# If the underlying physical connection changed, we might
1089
+		# be left with a route to the VPN server through the VPN
1090
+		# itself, which would need to be fixed.
1091
+		run_hooks attempt-reconnect
1092
+		do_attempt_reconnect
1093
+		run_hooks post-attempt-reconnect
1094
+		;;
1095
 	reconnect)
1096
+		# After successfully re-establishing the session.
1097
 		run_hooks reconnect
1098
 		;;
1099
 	*)
1100
1101
 * (1 из 2) -- /etc/vpnc/vpnc-script
1102
1103
1104