Opennet Firmware
 Alle Dateien Funktionen Variablen Gruppen Seiten
on-openvpn.sh
gehe zur Dokumentation dieser Datei
1 ## @defgroup on-openvpn Nutzer-Tunnel
2 ## @brief Alles rund um die Nutzertunnel-Verbindung: Tests, Auswahl, Aufbau, Abbau, Portweiterleitungen und Logs.
3 # Beginn der Doku-Gruppe
4 ## @{
5 
6 MIG_OPENVPN_DIR=/etc/openvpn/opennet_user
7 MIG_OPENVPN_CONFIG_TEMPLATE_FILE=/usr/share/opennet/openvpn-mig.template
8 DEFAULT_MIG_PORT=1600
9 
10 
11 ## @fn get_on_openvpn_default()
12 ## @brief Liefere einen der default-Werte der aktuellen Firmware zurück (Paket on-openvpn).
13 ## @param key Name des Schlüssels
14 ## @sa get_on_core_default
16  _get_file_dict_value "$1" "$ON_OPENVPN_DEFAULTS_FILE"
17 }
18 
19 
20 ## @fn has_mig_openvpn_credentials()
21 ## @brief Prüft, ob der Nutzer bereits einen Schlüssel und ein Zertifikat angelegt hat.
22 ## @returns Liefert "wahr", falls Schlüssel und Zertifikat vorhanden sind oder
23 ## falls in irgendeiner Form Unklarheit besteht.
25  has_openvpn_credentials_by_template "$MIG_OPENVPN_CONFIG_TEMPLATE_FILE" && return 0
26  trap "" $GUARD_TRAPS && return 1
27 }
28 
29 
30 ## @fn test_mig_connection()
31 ## @brief Prüfe, ob ein Verbindungsaufbau mit einem openvpn-Dienst möglich ist.
32 ## @param Name eines Diensts
33 ## @returns exitcode=0 falls der Test erfolgreich war
34 ## @attention Seiteneffekt: die Zustandsinformationen des Diensts (Status, Test-Zeitstempel) werden verändert.
36  trap "error_trap test_mig_connection '$*'" $GUARD_TRAPS
37  local service_name="$1"
38  # sicherstellen, dass alle vpn-relevanten Einstellungen gesetzt wurden
39  prepare_openvpn_service "$service_name" "$MIG_OPENVPN_CONFIG_TEMPLATE_FILE"
40  local timestamp=$(get_service_value "$service_name" "timestamp_connection_test")
41  local now=$(get_time_minute)
42  local recheck_age=$(get_on_openvpn_default vpn_recheck_age)
43  local nonworking_timeout=$(($recheck_age + $(get_on_openvpn_default vpn_nonworking_timeout)))
44  if [ -n "$timestamp" ] && is_timestamp_older_minutes "$timestamp" "$nonworking_timeout"; then
45  # if there was no vpn-availability for a while (nonworking_timeout minutes), declare vpn-status as not working
46  set_service_value "$service_name" "status" "n"
47  # In den naechsten 'vpn_recheck_age' Minuten wollen wir keine Pruefungen durchfuehren.
48  set_service_value "$service_name" "timestamp_connection_test" "$now"
49  trap "" $GUARD_TRAPS && return 1
50  fi
51  local host=$(get_service_value "$service_name" "host")
52  local status=$(get_service_value "$service_name" "status")
53  if [ -z "$timestamp" ] || [ -z "$status" ] || is_timestamp_older_minutes "$timestamp" "$recheck_age"; then
54  # Neue Pruefung, falls:
55  # 1) noch nie eine Pruefung stattfand
56  # oder
57  # 2) die "recheck"-Zeit abgelaufen ist
58  # oder
59  # 3) falls bisher noch kein definitives Ergebnis feststand (dies ist nur innerhalb
60  # der ersten "recheck" Minuten nach dem Booten moeglich).
61  # In jedem Fall kann der Zeitstempel gesetzt werden - egal welches Ergebnis die Pruefung hat.
62  if verify_vpn_connection "$service_name" \
63  "$VPN_DIR_TEST/on_aps.key" \
64  "$VPN_DIR_TEST/on_aps.crt" \
65  "$VPN_DIR_TEST/opennet-ca.crt"; then
66  msg_debug "vpn-availability of gw $host successfully tested"
67  set_service_value "$service_name" "status" "y"
68  set_service_value "$service_name" "timestamp_connection_test" "$now"
69  return 0
70  else
71  # kein Zeitstempel? Dann muessen wir beginnen zu zaehlen, damit der
72  # Test irgendwann in den "broken"-Zustand uebergehen kann.
73  [ -z "$timestamp" ] && set_service_value "$service_name" "timestamp_connection_test" "$now"
74  # Solange wir keinen "status" setzen, wird der Test bei jedem Lauf wiederholt, bis "nonworking_timeout"
75  # erreicht ist.
76  msg_debug "vpn test of $host failed"
77  trap "" $GUARD_TRAPS && return 1
78  fi
79  elif uci_is_true "$status"; then
80  msg_debug "vpn-availability of gw $host still valid"
81  return 0
82  else
83  # gateway is currently known to be broken
84  trap "" $GUARD_TRAPS && return 1
85  fi
86 }
87 
88 
89 ## @fn select_mig_connection()
90 ## @brief Aktiviere den angegebenen VPN-Gateway
91 ## @param Name eines Diensts
92 ## @attention Seiteneffekt: Beräumung aller herumliegenden Konfigurationen von alten Verbindungen.
94  trap "error_trap select_mig_connection '$*'" $GUARD_TRAPS
95  local wanted="$1"
96  local one_service
97  get_services "gw" | while read one_service; do
98  # loesche Flags fuer die Vorselektion
99  set_service_value "$one_service" "switch_candidate_timestamp" ""
100  # erst nach der Abschaltung der alten Dienste wollen wir den/die neuen Dienste anschalten (also nur Ausgabe)
101  [ "$one_service" = "$wanted" ] && echo "$one_service" && continue
102  disable_openvpn_service "$one_service" || true
103  done | while read one_service; do
104  enable_openvpn_service "$wanted" "host"
105  done
106 }
107 
108 
109 ## @fn find_and_select_best_gateway
110 ## @brief Ermittle den besten Gateway und prüfe, ob ein Wechsel sinnvoll ist.
111 ## @param force [optional] erzwinge den Wechsel auf den besten Gateway unabhängig von Wartezeiten (true/false)
112 ## @ref mig-switch
113 find_and_select_best_gateway() {
114  trap "error_trap find_and_select_best_gateway '$*'" $GUARD_TRAPS
115  local force_switch_now=${1:-false}
116  local service_name
117  local host
118  local best_gateway=
119  local current_gateway=
120  local result
121  local current_priority
122  local best_priority
123  local switch_candidate_timestamp
124  local now=$(get_time_minute)
125  local bettergateway_timeout=$(get_on_openvpn_default vpn_bettergateway_timeout)
126  msg_debug "Trying to find a better gateway"
127  # suche nach dem besten und dem bisher verwendeten Gateway
128  # Ignoriere dabei alle nicht-verwendbaren Gateways.
129  result=$(get_services "gw" \
132  | sort_services_by_priority \
133  | while read service_name; do
134  # Ist der beste und der aktive Gateway bereits gefunden? Dann einfach weiterspringen ...
135  # (kein Abbruch der Schleife - siehe weiter unten - Stichwort SIGPIPE)
136  [ -n "$current_gateway" ] && continue
137  host=$(get_service_value "$service_name" "host")
138  uci_is_false "$(get_service_value "$service_name" "status" "false")" && \
139  msg_debug "$host did not pass the last test" && \
140  continue
141  # der Gateway ist ein valider Kandidat
142  # Achtung: Variablen innerhalb einer "while"-Sub-Shell wirken sich nicht auf den Elternprozess aus
143  # Daher wollen wir nur ein bis zwei Zeilen:
144  # den besten
145  # [den aktiven] (falls vorhanden)
146  # Wir brechen die Ausgabe jedoch nicht nach den ersten beiden Zeilen ab. Andernfalls muessten wir
147  # uns um das SIGPIPE-Signal kuemmern (vor allem in cron-Jobs).
148  [ -z "$best_gateway" ] && best_gateway="$service_name" && echo "$best_gateway"
149  is_openvpn_service_active "$service_name" && current_gateway="$service_name" && echo "$service_name" || true
150  done)
151  best_gateway=$(echo "$result" | sed -n 1p)
152  current_gateway=$(echo "$result" | sed -n 2p)
153  if [ "$current_gateway" = "$best_gateway" ]; then
154  if [ -z "$current_gateway" ]; then
155  msg_debug "There is still no usable gateway around"
156  else
157  msg_debug "Current gateway ($current_gateway) is still the best choice"
158  # Wechselzaehler zuruecksetzen (falls er hochgezaehlt wurde)
159  set_service_value "$current_gateway" "switch_candidate_timestamp" ""
160  fi
161  return 0
162  fi
163  msg_debug "Current ($current_gateway) / best ($best_gateway)"
164  # eventuell wollen wir den aktuellen Host beibehalten (sofern er funktioniert und wir nicht zwangsweise wechseln)
165  if [ -n "$current_gateway" ] \
166  && uci_is_false "$force_switch_now" \
167  && uci_is_true "$(get_service_value "$current_gateway" "status" "false")"; then
168  # falls der beste und der aktive Gateway gleich weit entfernt sind, bleiben wir beim bisher aktiven
169  current_priority=$(get_service_priority "$current_gateway")
170  best_priority=$(get_service_priority "$best_gateway")
171  [ "$current_priority" -eq "$best_priority" ] \
172  && msg_debug "Keeping current gateway since the best gateway has the same priority" \
173  && return 0
174  # falls der beste und der aktive Gateway gleich weit entfernt sind, bleiben wir beim bisher aktiven
175  # Haben wir einen besseren Kandidaten? Muessen wir den Wechselzaehler aktivieren?
176  # Zaehle hoch bis der switch_candidate_timestamp alt genug ist
177  switch_candidate_timestamp=$(get_service_value "$current_gateway" "switch_candidate_timestamp")
178  if [ -z "$switch_candidate_timestamp" ]; then
179  # wir bleiben beim aktuellen Gateway - wir merken uns allerdings den Switch-Zeitstempel
180  set_service_value "$current_gateway" "switch_candidate_timestamp" "$now"
181  msg_debug "Starting to count down until the switching timer reaches $bettergateway_timeout minutes"
182  return 0
183  else
184  # noch nicht alt genug fuer den Wechsel?
185  is_timestamp_older_minutes "$switch_candidate_timestamp" "$bettergateway_timeout" \
186  || { msg_debug "Counting down further until we reach $bettergateway_timeout minutes"; return 0; }
187  fi
188  fi
189  # eventuell kann hier auch ein leerer String uebergeben werden - dann wird kein Gateway aktiviert (korrekt)
190  [ -n "$best_gateway" ] \
191  && msg_debug "Switching gateway from $current_gateway to $best_gateway" \
192  || msg_debug "Disabling $current_gateway without a viable alternative"
193  select_mig_connection "$best_gateway"
194 }
195 
196 
197 ## @fn get_active_mig_connections()
198 ## @brief Liefere die aktiven VPN-Verbindungen (mit Mesh-Internet-Gateways) zurück.
199 ## @returns Liste der Namen aller Dienste, die aktuell eine aktive VPN-Verbindung halten.
200 ## @attention Diese Funktion braucht recht viel Zeit.
201 get_active_mig_connections() {
202  trap "error_trap get_active_mig_connections '$*'" $GUARD_TRAPS
203  local service_name
204  get_services "gw" | while read service_name; do
205  is_openvpn_service_active "$service_name" && echo "$service_name" || true
206  done
207 }
208 
209 
210 ## @fn reset_mig_connection_test_timestamp()
211 ## @brief Löse eine erneute Prüfung dieses Gateways beim nächsten Prüflauf aus.
212 ## @param Name eines Diensts
213 ## @details Das Löschen des *timestamp_connection_test* Werts führt zu einer
214 ## erneuten Prüfung zum nächstmöglichen Zeitpunkt.
215 reset_mig_connection_test_timestamp() {
216  local service_name="$1"
217  set_service_value "$service_name" "timestamp_connection_test" ""
218 }
219 
220 
221 ## @fn reset_all_mig_connection_test_timestamps()
222 ## @brief Löse eine erneute Prüfung aller Gateways zum nächstmöglichen Zeitpunkt aus.
223 ## @sa reset_mig_connection_test_timestamp
224 reset_all_mig_connection_test_timestamps() {
225  local service_name
226  get_services "gw" | while read service_name; do
227  reset_mig_connection_test_timestamp "$service_name"
228  done
229 }
230 
231 
232 ## @fn get_mig_connection_test_age()
233 ## @brief Ermittle das Test des letzten Verbindungstests in Minuten.
234 ## @returns Das Alter des letzten Verbindungstests in Minuten oder nichts (falls noch kein Test durchgeführt wurde).
235 ## @details Anhand des Test-Alters lässt sich der Zeitpunkt der nächsten Prüfung abschätzen.
236 get_mig_connection_test_age() {
237  local service_name="$1"
238  local timestamp=$(get_service_value "$service_name" "timestamp_connection_test")
239  # noch keine Tests durchgefuehrt?
240  [ -z "$timestamp" ] && return 0
241  local now=$(get_time_minute)
242  echo "$timestamp" "$now" | awk '{ print $2 - $1 }'
243 }
244 
245 
246 ## @fn get_client_cn()
247 ## @brief Ermittle den Common-Name des Nutzer-Zertifikats.
248 ## @details Liefere eine leere Zeichenkette zurück, falls kein Zertifikat vorhanden ist.
249 get_client_cn() {
250  [ -e "$MIG_OPENVPN_DIR/on_aps.crt" ] || return 0
251  openssl x509 -in "$MIG_OPENVPN_DIR/on_aps.crt" \
252  -subject -nameopt multiline -noout 2>/dev/null | awk '/commonName/ {print $3}'
253 }
254 
255 
256 ## @fn get_mig_port_forward_range()
257 ## @brief Liefere den ersten und letzten Port der Nutzertunnel-Portweiterleitung zurück.
258 ## @param [optional] common name des Nutzer-Zertifikats
259 ## @returns zwei Zahlen durch Tabulatoren getrennt / keine Ausgabe, falls keine Main-ID gefunden wurde
260 ## @details Jeder AP bekommt einen Bereich von zehn Ports fuer die Port-Weiterleitung zugeteilt.
261 get_mig_port_forward_range() {
262  trap "error_trap get_mig_port_forward_range '$*'" $GUARD_TRAPS
263  local client_cn=${1:-}
264  [ -z "$client_cn" ] && client_cn=$(get_client_cn)
265  local port_count=10
266  local cn_address=
267  local portbase
268  local first_port
269  local last_port
270 
271  [ -z "$client_cn" ] && msg_debug "get_mig_port_forward_range: failed to get Common Name - maybe there is no certificate?" && return 0
272 
273  if echo "$client_cn" | grep -q '^\(\(1\.\)\?[0-9][0-9]\?[0-9]\?\.aps\.on\)$'; then
274  portbase=10000
275  cn_address=${client_cn%.aps.on}
276  cn_address=${cn_address#*.}
277  elif echo "$client_cn" | grep -q '^\([0-9][0-9]\?[0-9]\?\.mobile\.on\)$'; then
278  portbase=12550
279  cn_address=${client_cn%.mobile.on}
280  elif echo "$client_cn" | grep -q '^\(2[\._-][0-9][0-9]\?[0-9]\?\.aps\.on\)$'; then
281  portbase=15100
282  cn_address=${client_cn%.aps.on}
283  cn_address=${cn_address#*.}
284  elif echo "$client_cn" | grep -q '^\(3[\._-][0-9][0-9]\?[0-9]\?\.aps\.on\)$'; then
285  portbase=20200
286  cn_address=${client_cn%.aps.on}
287  cn_address=${cn_address#*.}
288  fi
289 
290  if [ -z "$cn_address" ] || [ "$cn_address" -lt 1 ] || [ "$cn_address" -gt 255 ]; then
291  msg_info "$(basename "$0"): invalidate certificate Common Name ($client_cn)"
292  else
293  first_port=$((portbase + (cn_address-1) * port_count))
294  last_port=$((first_port + port_count - 1))
295  echo -e "$first_port\t$last_port"
296  fi
297 }
298 
299 # Ende der Doku-Gruppe
300 ## @}