Project

General

Profile

Download (86.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * gwlb.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2008 Bill Marquette, Seth Mos
7
 * Copyright (c) 2008-2013 BSD Perimeter
8
 * Copyright (c) 2013-2016 Electric Sheep Fencing
9
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
10
 * All rights reserved.
11
 *
12
 * Licensed under the Apache License, Version 2.0 (the "License");
13
 * you may not use this file except in compliance with the License.
14
 * You may obtain a copy of the License at
15
 *
16
 * http://www.apache.org/licenses/LICENSE-2.0
17
 *
18
 * Unless required by applicable law or agreed to in writing, software
19
 * distributed under the License is distributed on an "AS IS" BASIS,
20
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
 * See the License for the specific language governing permissions and
22
 * limitations under the License.
23
 */
24

    
25
require_once("config.inc");
26
require_once("rrd.inc");
27
require_once("ipsec.inc");
28
require_once("interfaces.inc");
29
require_once("util.inc");
30

    
31
global $gateway_state_kill_modes;
32
$gateway_state_kill_modes = array(
33
	'' => gettext("Use global behavior (default)"),
34
	'none' => gettext("Do not kill states on gateway failure"),
35
	'down' => gettext("Kill states using this gateway when it is down"),
36
);
37

    
38
// Initialize the gateways cache variables
39
define('GW_CACHE_DISABLED', 1); // include gateways on disabled interfaces
40
define('GW_CACHE_LOCALHOST', 2); // include "Null" entries for localhost IP addresses
41
define('GW_CACHE_INACTIVE', 4); // include gateways on inactive interfaces
42
define('GW_CACHE_INDEXED', 8); // index the returned array by integers instead of the name
43
define('GW_CACHE_ALL', GW_CACHE_DISABLED | GW_CACHE_LOCALHOST | GW_CACHE_INACTIVE); // all possible gateways
44
if (!array_key_exists('GatewaysCache', $GLOBALS)) {
45
	$GLOBALS['GatewaysCache'] = null;
46
}
47

    
48
/**
49
 * Caches the gateway list and filters the result. Changes to gateways should
50
 * call refresh_gateways(), e.g. when adding or removing dynamic gateways.
51
 * 
52
 * @param array $flags Additional gateways to include.
53
 * 
54
 * @return array filtered list of gateways
55
 */
56
function get_gateways(int $flags = 0): array {
57
	if (!is_array($GLOBALS['GatewaysCache'])) {
58
		refresh_gateways(); // create the gateway list cache
59
	}
60
	$gateways = $GLOBALS['GatewaysCache'];
61
	$indexed = boolval($flags & GW_CACHE_INDEXED);
62
	if ($indexed) {
63
		$remove_disabled = false;
64
		$remove_localhost = true;
65
		$remove_inactive = false;
66
	} elseif (boolval(($flags & GW_CACHE_ALL) == GW_CACHE_ALL)) {
67
		$remove_disabled = false;
68
		$remove_localhost = false;
69
		$remove_inactive = false;
70
	} else {
71
		$remove_disabled = !boolval($flags & GW_CACHE_DISABLED);
72
		$remove_localhost = !boolval($flags & GW_CACHE_LOCALHOST);
73
		$remove_inactive = !boolval($flags & GW_CACHE_INACTIVE);
74
	}
75

    
76
	if ($remove_localhost) {
77
		unset($gateways['Null4']);
78
		unset($gateways['Null6']);
79
	}
80

    
81
	if ($remove_disabled || $remove_inactive) {
82
		foreach ($gateways as $idx => &$gw) {
83
			if ($remove_disabled && !config_path_enabled("interfaces/{$gw['friendlyiface']}")) {
84
				unset($gateways[$idx]);
85
			}
86
			if ($remove_inactive && isset($gw['inactive'])) {
87
				unset($gateways[$idx]);
88
			}
89
		}
90
	}
91

    
92
	if ($indexed) {
93
		$gateways = array_values($gateways);
94
	}
95

    
96
	if ($remove_disabled || $remove_localhost || $remove_inactive || $indexed) {
97
		$gateways = order_gateways_as_configured($gateways);
98
	}
99

    
100
	return $gateways;
101
}
102

    
103
/**
104
 * Generates a list of all gateways and adds them to the config if missing.
105
 */
106
function refresh_gateways(): void {
107
	// Generate all gateways and cache the result
108
	$GLOBALS['GatewaysCache'] = return_gateways_array(true, true, true);
109

    
110
	// Get gateways that exist in the config
111
	$saved_gateways = array_column(config_get_path('gateways/gateway_item', []), 'name', 'name');
112

    
113
	// Exclude null gateways from being saved to config
114
	foreach (array_diff_key($GLOBALS["GatewaysCache"], array_fill_keys(['Null4', 'Null6'], null)) as $gateway) {
115
		// Save any of the generated gateways that are not found in config
116
		if (!array_key_exists($gateway['name'], $saved_gateways)) {
117
			// Build the gateway elements and save the gateway to config
118
			config_set_path('gateways/gateway_item',
119
				array_merge(config_get_path('gateways/gateway_item', []), [[
120
					'interface' => $gateway['friendlyiface'],
121
					'gateway' => $gateway['dynamic'] ? 'dynamic' : $gateway['gateway'],
122
					'name' => $gateway['name'],
123
					'weight' => $gateway['weight'] ?? 1,
124
					'ipprotocol' => $gateway['ipprotocol'],
125
					'descr' => $gateway['descr'],
126
					'gw_down_kill_states' => $gateway['gw_down_kill_states'] ?? ''
127
				]])
128
			);
129
			if (!isset($write_config)) {
130
				$write_config = true;
131
			}
132
		}
133
	}
134
	if ($write_config) {
135
		write_config(gettext('Synced configured gateways'), true, true);
136
	}
137
}
138

    
139
/* Returns an array of default values used for dpinger */
140
function return_dpinger_defaults() {
141
	return array(
142
		"latencylow" => "200",
143
		"latencyhigh" => "500",
144
		"losslow" => "10",
145
		"losshigh" => "20",
146
		"interval" => "500",
147
		"loss_interval" => "2000",
148
		"time_period" => "60000",
149
		"alert_interval" => "1000",
150
		"data_payload" => "1");
151
}
152

    
153
function running_dpinger_processes() {
154
	$pidfiles = glob(g_get('varrun_path', '') . '/dpinger_*.pid');
155

    
156
	$result = array();
157
	if ($pidfiles === FALSE) {
158
		return $result;
159
	}
160

    
161
	foreach ($pidfiles as $pidfile) {
162
		if (preg_match('/^dpinger_(.+)~([^~]+)~([^~]+)\.pid$/',
163
		    basename($pidfile), $matches)) {
164
			$socket_file = preg_replace('/\.pid$/', '.sock',
165
			    $pidfile);
166
			$result[$matches[1]] = array(
167
			    'srcip'    => $matches[2],
168
			    'targetip' => $matches[3],
169
			    'pidfile'  => $pidfile,
170
			    'socket'   => $socket_file
171
			);
172
			unset($gwinfo);
173
		}
174
	}
175

    
176
	return $result;
177
}
178

    
179
/*
180
 * Stop one or more dpinger process
181
 * default parameter $gwname is '*' that will kill all running sessions
182
 * If a gateway name is passed, only this one will be killed
183
 */
184
function stop_dpinger($gwname = '') {
185
	$running_processes = running_dpinger_processes();
186

    
187
	foreach ($running_processes as $running_gwname => $process) {
188
		if ($gwname != '' && $running_gwname != $gwname) {
189
			continue;
190
		}
191

    
192
		if (isvalidpid($process['pidfile'])) {
193
			killbypid($process['pidfile'], 3);
194
		} else {
195
			@unlink($process['pidfile']);
196
		}
197
	}
198
}
199

    
200
function start_dpinger($gateway) {
201
	if (!isset($gateway['gwifip'])) {
202
		return (false);
203
	}
204

    
205
	$dpinger_defaults = return_dpinger_defaults();
206

    
207
	$prefix = g_get('varrun_path', '') . "/dpinger_{$gateway['name']}~" .
208
	    "{$gateway['gwifip']}~{$gateway['monitor']}";
209
	# dpinger socket path should not be longer then uaddr.sun_path
210
	if (strlen($prefix) > 95) {
211
		$prefix = g_get('varrun_path', '') . "/dpinger_{$gateway['name']}~" .
212
		    substr(md5($gateway['gwifip']),0,8) . "~" .
213
		    $gateway['monitor'];
214
	}
215
	$pidfile = $prefix . ".pid";
216
	$socket = $prefix . ".sock";
217
	$alarm_cmd = g_get('etc_path', '') . '/rc.gateway_alarm';
218

    
219
	$params  = "-S ";			/* Log warnings via syslog */
220
	$params .= "-r 0 ";			/* Disable unused reporting thread */
221
	$params .= "-i {$gateway['name']} ";	/* Identifier */
222
	$params .= "-B {$gateway['gwifip']} ";	/* Bind src address */
223
	$params .= "-p {$pidfile} ";		/* PID filename */
224
	$params .= "-u {$socket} ";		/* Status Socket */
225
	if (!$gateway['action_disable']) {
226
		$params .= "-C \"{$alarm_cmd}\" ";	/* Command to run on alarm */
227
	}
228

    
229
	$params .= "-d " .
230
	    (isset($gateway['data_payload']) && is_numeric($gateway['data_payload'])
231
	    ? $gateway['data_payload']
232
	    : $dpinger_defaults['data_payload']
233
	    ) . " ";
234

    
235
	$params .= "-s " .
236
	    (isset($gateway['interval']) && is_numeric($gateway['interval'])
237
	    ? $gateway['interval']
238
	    : $dpinger_defaults['interval']
239
	    ) . " ";
240

    
241
	$params .= "-l " .
242
	    (isset($gateway['loss_interval']) && is_numeric($gateway['loss_interval'])
243
	    ?  $gateway['loss_interval']
244
	    : $dpinger_defaults['loss_interval']
245
	    ) . " ";
246

    
247
	$params .= "-t " .
248
	    (isset($gateway['time_period']) && is_numeric($gateway['time_period'])
249
	    ?  $gateway['time_period']
250
	    : $dpinger_defaults['time_period']
251
	    ) . " ";
252

    
253
	$params .= "-A " .
254
	    (isset($gateway['alert_interval']) && is_numeric($gateway['alert_interval'])
255
	    ?  $gateway['alert_interval']
256
	    : $dpinger_defaults['alert_interval']
257
	    ) . " ";
258

    
259
	$params .= "-D " .
260
	    (isset($gateway['latencyhigh']) && is_numeric($gateway['latencyhigh'])
261
	    ?  $gateway['latencyhigh']
262
	    : $dpinger_defaults['latencyhigh']
263
	    ) . " ";
264

    
265
	$params .= "-L " .
266
	    (isset($gateway['losshigh']) && is_numeric($gateway['losshigh'])
267
	    ?  $gateway['losshigh']
268
	    : $dpinger_defaults['losshigh']
269
	    ) . " ";
270

    
271
	/* Make sure we don't end up with 2 process for the same GW */
272
	stop_dpinger($gateway['name']);
273

    
274
	/* Do not try to bind IPv6 where interface is in tentative state */
275
	if (is_ipaddrv6($gateway['gwifip'])) {
276
		$err = interface_wait_tentative(get_real_interface(
277
		    $gateway['interface']));
278
		if ($err == false) {
279
			log_error(gettext("Timeout waiting for IPv6 address in tentative state.  dpinger will not run."));
280
			return (false);
281
		}
282
	}
283

    
284
	/* Redirect stdout to /dev/null to avoid exec() to wait for dpinger */
285
	return mwexec("/usr/local/bin/dpinger {$params} {$gateway['monitor']} >/dev/null");
286
}
287

    
288
/*
289
 * Starts dpinger processes and adds appropriate static routes for monitor IPs
290
 */
291
function setup_gateways_monitor() {
292
	refresh_gateways(); // dynamic gateways may have changed so refresh the cache
293
	$gateways_arr = get_gateways();
294
	if (!is_array($gateways_arr)) {
295
		log_error(gettext("No gateways to monitor. dpinger will not run."));
296
		stop_dpinger();
297
		return;
298
	}
299
	if (is_platform_booting()) {
300
		echo "Setting up gateway monitors...";
301
	}
302
	$monitor_ips = array();
303
	foreach ($gateways_arr as $gwname => $gateway) {
304
		/* Do not monitor if such was requested */
305
		if (isset($gateway['monitor_disable'])) {
306
			continue;
307
		}
308
		if (empty($gateway['monitor']) || !is_ipaddr($gateway['monitor'])) {
309
			if (is_ipaddr($gateway['gateway'])) {
310
				$gateways_arr[$gwname]['monitor'] = $gateway['gateway'];
311
			} else { /* No chance to get an ip to monitor skip target. */
312
				continue;
313
			}
314
		}
315

    
316
		/* if the monitor address is already used before, skip */
317
		if (in_array($gateway['monitor'], $monitor_ips)) {
318
			continue;
319
		}
320

    
321
		/* Interface ip is needed since dpinger will bind a socket to it.
322
		 * However the config GUI should already have checked this and when
323
		 * PPPoE is used the IP address is set to "dynamic". So using is_ipaddrv4
324
		 * or is_ipaddrv6 to identify packet type would be wrong, especially as
325
		 * further checks (that can cope with the "dynamic" case) are present inside
326
		 * the if block. So using $gateway['ipprotocol'] is the better option.
327
		 */
328
		if ($gateway['ipprotocol'] == "inet") { // This is an IPv4 gateway...
329
			$gwifip = find_interface_ip($gateway['interface'], true);
330
			if (!is_ipaddrv4($gwifip)) {
331
				continue; //Skip this target
332
			}
333

    
334
			if ($gwifip == "0.0.0.0") {
335
				continue; //Skip this target - the gateway is still waiting for DHCP
336
			}
337

    
338
			/*
339
			 * If the gateway is the same as the monitor we do not add a
340
			 * route as this will break the routing table.
341
			 * Add static routes for each gateway with their monitor IP
342
			 * not strictly necessary but is a added level of protection.
343
			 */
344
			if (!config_path_enabled('system', 'dpinger_dont_add_static_routes') &&
345
					!isset($gateway['dpinger_dont_add_static_route'])) {
346
				if (is_ipaddrv4($gateway['gateway']) && $gateway['monitor'] != $gateway['gateway']) {
347
					log_error(sprintf(gettext('Removing static route for monitor %1$s and adding a new route through %2$s'), $gateway['monitor'], $gateway['gateway']));
348
					if (interface_isppp_type($gateway['friendlyiface'])) {
349
						route_add_or_change($gateway['monitor'],
350
						    '', $gateway['interface']);
351
						system_staticroutes_configure($gateway['friendlyiface']);
352
					} else {
353
						route_add_or_change($gateway['monitor'],
354
						    $gateway['gateway']);
355
					}
356

    
357
					pfSense_kill_states("0.0.0.0/0", $gateway['monitor'], $gateway['interface'], "icmp");
358
				}
359
			}
360
		} else if ($gateway['ipprotocol'] == "inet6") { // This is an IPv6 gateway...
361
			if (is_linklocal($gateway['gateway']) &&
362
			    get_ll_scope($gateway['gateway']) == '') {
363
				$gateway['gateway'] .= '%' . $gateway['interface'];
364
			}
365

    
366
			if (is_linklocal($gateway['monitor'])) {
367
				if (get_ll_scope($gateway['monitor']) == '') {
368
					$gateways_arr[$gwname]['monitor'] .= '%' . $gateway['interface'];
369
				}
370

    
371
				$gwifip = find_interface_ipv6_ll($gateway['interface'], true);
372

    
373
				if (get_ll_scope($gwifip) == '') {
374
					$gwifip .= '%' . $gateway['interface'];
375
				}
376
			} else {
377
				$gwifip = find_interface_ipv6($gateway['interface'], true);
378
			}
379

    
380
			if (!is_ipaddrv6($gwifip)) {
381
				continue; //Skip this target
382
			}
383

    
384
			/*
385
			 * If the gateway is the same as the monitor we do not add a
386
			 * route as this will break the routing table.
387
			 * Add static routes for each gateway with their monitor IP
388
			 * not strictly necessary but is a added level of protection.
389
			 */
390

    
391
			if (!config_path_enabled('system', 'dpinger_dont_add_static_routes') &&
392
					!isset($gateway['dpinger_dont_add_static_route'])) {
393
				if ($gateway['gateway'] != $gateways_arr[$gwname]['monitor']) {
394
					log_error(sprintf(gettext('Removing static route for monitor %1$s and adding a new route through %2$s'), $gateway['monitor'], $gateway['gateway']));
395
					if (interface_isppp_type($gateway['friendlyiface'])) {
396
						route_add_or_change($gateway['monitor'],
397
						    '', $gateway['interface']);
398
						system_staticroutes_configure($gateway['friendlyiface']);
399
					} else {
400
						route_add_or_change($gateway['monitor'],
401
						    $gateway['gateway']);
402
					}
403

    
404
					pfSense_kill_states("::0.0.0.0/0", $gateway['monitor'], $gateway['interface'], "icmpv6");
405
				}
406
			}
407
		} else {
408
			continue;
409
		}
410

    
411
		$monitor_ips[] = $gateway['monitor'];
412
		$gateways_arr[$gwname]['enable_dpinger'] = true;
413
		$gateways_arr[$gwname]['gwifip'] = $gwifip;
414
	}
415

    
416
	stop_dpinger();
417

    
418
	/* Start new processes */
419
	foreach ($gateways_arr as $gateway) {
420
		if (!isset($gateway['enable_dpinger'])) {
421
			continue;
422
		}
423

    
424
		if (start_dpinger($gateway) != 0) {
425
			log_error(sprintf(gettext("Error starting gateway monitor for %s"), $gateway['name']));
426
		}
427
	}
428
	if (is_platform_booting()) {
429
		echo "done.\n";
430
	}
431

    
432
	return;
433
}
434

    
435
/**
436
 * Looks for the highest priority failover tier that is currently online in a
437
 * failover gateway group. A failover tier is any tier number greater than the
438
 * lowest configured tier number. A failover gateway group must consist of at
439
 * least two different tiers.
440
 * 
441
 * @param string $gateway_group Name of an existing failover gateway group
442
 * 
443
 * @return int The tier number - returns 0 if no matches are found
444
 * @return false Not a valid failover gateway group
445
 */
446
function get_highest_failover_tier_online(?string $gateway_group): int|false {
447
	if (empty($gateway_group)) {
448
		return false;
449
	}
450

    
451
	foreach (config_get_path('gateways/gateway_group', []) as $group) {
452
		// check if this is the gateway group being queried
453
		if ($group['name'] != $gateway_group) {
454
			continue;
455
		}
456

    
457
		// make sure there are expected values and at least two gateways in the gateway group
458
		if (!is_array($group['item']) || empty($group['item']) || (count($group['item']) < 2)) {
459
			continue;
460
		}
461
		// build a list of gateways and their tiers
462
		$group_gateways = [];
463
		foreach ($group['item'] as $item) {
464
			$gateway_item = explode("|", $item);
465
			// at least a gateway name and tier is expected
466
			if (count($gateway_item) < 2) {
467
				continue;
468
			}
469

    
470
			$group_gateways[$gateway_item[0]] = intval($gateway_item[1]);
471
		}
472

    
473
		// make sure it's a failover group by checking for different tiers
474
		$group_tiers = array_unique(array_values($group_gateways));
475
		if (count($group_tiers) < 2) {
476
			break;
477
		}
478

    
479
		if (!isset($gateways_states)) {
480
			// get a list of all relevant gateway states
481
			$gateways_states = return_gateways_status(true);
482
		}
483

    
484
		$highest_tier = min($group_tiers);
485
		// remove offline gateways
486
		foreach (array_keys($group_gateways) as $gateway_name) {
487
			if ($gateways_states[$gateway_name]['status'] != 'online') {
488
				unset($group_gateways[$gateway_name]);
489
			}
490
		}
491

    
492
		// no online gateways
493
		if (empty($group_gateways)) {
494
			return 0;
495
		}
496

    
497
		// highest online is highest tier
498
		$highest_online_tier = min(array_values($group_gateways));
499
		if ($highest_online_tier == $highest_tier) {
500
			return 0;
501
		}
502

    
503
		// return the highest-priority failover tier of online gateways
504
		return $highest_online_tier;
505
	}
506

    
507
	return false;
508
}
509

    
510
/**
511
 * Loops through each gateway group and kills states for lower-priority gateways.
512
 * Supports the same gateway at different tiers in different gateway groups. A
513
 * failover gateway group name may be provided to restrict the state removal.
514
 * 
515
 * @param string $gateway_name Gateway group name to remove failover states for
516
 */
517
function remove_failover_states(?string $gateway_name = null): void {
518
	// get the global default for gateway groups
519
	$keep_failover_states = config_path_enabled('system', 'keep_failover_states');
520

    
521
	// if killing states from the firewall itself, check for default failover gateway groups
522
	$remove_states_default = config_get_path('system/remove_failover_states_default');
523
	if ($remove_states_default) {
524
		$default_gateways = [];
525

    
526
		$default_gateway4 = config_get_path('gateways/defaultgw4', '');
527
		if (!empty($default_gateway4)) {
528
			$default_gateways[$default_gateway4] = null;
529
		}
530
		$default_gateway6 = config_get_path('gateways/defaultgw6', '');
531
		if (!empty($default_gateway6)) {
532
			$default_gateways[$default_gateway6] = null;
533
		}
534
	}
535

    
536
	foreach (config_get_path('gateways/gateway_group', []) as $group) {
537
		// skip if states don't need to be killed for this gateway group
538
		if ((!empty($gateway_name) && ($group['name'] != $gateway_name)) ||
539
		    ($keep_failover_states && ($group['keep_failover_states'] != 'kill'))) {
540
			continue;
541
		}
542

    
543
		// make sure there are expected values and at least two gateways in the gateway group
544
		if (!is_array($group['item']) || empty($group['item']) || (count($group['item']) < 2)) {
545
			continue;
546
		}
547
		// build a list gateway interfaces and tiers
548
		$gateway_interfaces = [];
549
		foreach ($group['item'] as $item) {
550
			$gateway_item = explode("|", $item);
551
			// at least a gateway name and tier is expected
552
			if (count($gateway_item) < 2) {
553
				continue;
554
			}
555

    
556
			if (!isset($gateways_states)) {
557
				// get a list of all relevant gateway states
558
				$gateways_states = return_gateways_status(true);
559
			}
560

    
561
			// only process online gateways
562
			if ($gateways_states[$gateway_item[0]]['status'] != 'online') {
563
				continue;
564
			}
565

    
566
			if (!isset($all_gateways)) {
567
				// get a list of all relevant gateways
568
				$all_gateways = get_gateways();
569
			}
570

    
571
			// get the gateway's interface and tier
572
			$gateway_interfaces[$all_gateways[$gateway_item[0]]['interface']] = intval($gateway_item[1]);
573
		}
574

    
575
		// make sure there are at least two online gateways (interfaces) in this gateway group
576
		if (empty($gateway_interfaces) || (count($gateway_interfaces) < 2)) {
577
			continue;
578
		}
579

    
580
		// build a list of interfaces, used to kill states from the firewall itself
581
		if ($remove_states_default && array_key_exists($group['name'], $default_gateways)) {
582
			// get the interfaces of the default gateway group
583
			$default_gateways[$group['name']] = $gateway_interfaces;
584

    
585
			// for states from the firewall itself (when the default gateway is set to a failover gateway group)
586
			foreach (array_keys($gateway_interfaces, min(array_values($gateway_interfaces))) as $gateway_interface) {
587
				// exclude the interface of the highest priority gateway(s)
588
				unset($default_gateways[$group['name']][$gateway_interface]);
589
			}
590

    
591
			// all states are to be killed for this gateway group, skip killing only policy routing states
592
			continue;
593
		}
594

    
595
		// build a list of tiers to kill policy routing states for
596
		$tiers_to_kill = array_unique(array_values($gateway_interfaces));
597
		// avoid killing states for the higher-tier gateway(s)
598
		$tiers_to_kill = array_diff($tiers_to_kill, [min($tiers_to_kill)]);
599
		foreach ($tiers_to_kill as $tier) {
600
			mwexec_bg('/sbin/pfctl -M -k label -k ' . escapeshellarg("gw{$tier}:{$group['name']}") . ' -q');
601
		}
602
	}
603

    
604
	// kill all states for the default failover gateway groups
605
	if (!$remove_states_default) {
606
		return;
607
	}
608
	foreach ($default_gateways as $gateway_name => $gateway_interfaces) {
609
		if (empty($gateway_interfaces)) {
610
			continue;
611
		}
612

    
613
		/* filtering for the address family allows states that have been created by user
614
		   rules with a gateway of a different address family on the same interface to remain */
615
		if ($remove_states_default == 'addrfamily') {
616
			$host_prefix = ($gateway_name == $default_gateway4) ? '-k 0.0.0.0/0' : '-k ::/0';
617
		} else {
618
			$host_prefix = '';
619
		}
620
		foreach (array_keys($gateway_interfaces) as $gateway_interface) {
621
			mwexec_bg('/sbin/pfctl -M -i ' . escapeshellarg($gateway_interface) . " {$host_prefix} -q");
622
		}
623
	}
624
}
625

    
626
function get_dpinger_status($gwname, $action_disable = false) {
627
	$running_processes = running_dpinger_processes();
628

    
629
	if (!isset($running_processes[$gwname])) {
630
		log_error(sprintf(gettext(
631
		    'dpinger: No dpinger session running for gateway %s'),
632
		    $gwname));
633
		return false;
634
	}
635

    
636
	$proc = $running_processes[$gwname];
637
	unset($running_processes);
638

    
639
	$timeoutcounter = 0;
640
	while (true) {
641
		if (!file_exists($proc['socket'])) {
642
			log_error("dpinger: status socket {$proc['socket']} not found");
643
			return false;
644
		}
645
		$fp = @stream_socket_client("unix://{$proc['socket']}", $errno, $errstr, 10);
646
		if (!$fp) {
647
			log_error(sprintf(gettext('dpinger: cannot connect to status socket %1$s - %2$s (%3$s)'), $proc['socket'], $errstr, $errno));
648
			return false;
649
		}
650

    
651
		$status = '';
652
		while (!feof($fp)) {
653
			$status .= fgets($fp, 1024);
654
		}
655
		fclose($fp);
656

    
657
		$r = array();
658
		list(
659
			$r['gwname'],
660
			$r['latency_avg'],
661
			$r['latency_stddev'],
662
			$r['loss']
663
		) = explode(' ', preg_replace('/\n/', '', $status));
664

    
665
		// dpinger returns '<gwname> 0 0 0' when queried directly after it starts.
666
		// while a latency of 0 and a loss of 0 would be perfect, in a real world it doesnt happen.
667
		// or does it, anyone? if so we must 'detect' the initialization period differently..
668
		$ready = $r['latency_stddev'] != '0' || $r['loss'] != '0';
669

    
670
		if ($ready) {
671
			break;
672
		} else {
673
			$timeoutcounter++;
674
			if ($timeoutcounter > 300) {
675
				log_error(sprintf(gettext('dpinger: timeout while retrieving status for gateway %s'), $gwname));
676
				return false;
677
			}
678
			usleep(10000);
679
		}
680
	}
681

    
682
	$r['srcip'] = $proc['srcip'];
683
	$r['targetip'] = $proc['targetip'];
684

    
685
	$gateways_arr = get_gateways(GW_CACHE_ALL);
686

    
687
	unset($gw);
688
	if (isset($gateways_arr[$gwname])) {
689
		$gw = $gateways_arr[$gwname];
690
	}
691

    
692
	$r['latency_avg'] = round($r['latency_avg']/1000, 3);
693
	$r['latency_stddev'] = round($r['latency_stddev']/1000, 3);
694

    
695
	$r['status'] = "online";
696
	$r['substatus'] = "none";
697
	if (isset($gw) && isset($gw['force_down'])) {
698
		$r['status'] = "down";
699
		$r['substatus'] = "force_down";
700
	} else if (isset($gw)) {
701
		$settings = return_dpinger_defaults();
702

    
703
		$keys = array(
704
		    'latencylow',
705
		    'latencyhigh',
706
		    'losslow',
707
		    'losshigh'
708
		);
709

    
710
		/* Replace default values by user-defined */
711
		foreach ($keys as $key) {
712
			if (isset($gw[$key]) && is_numeric($gw[$key])) {
713
				$settings[$key] = $gw[$key];
714
			}
715
		}
716

    
717
		if ($r['latency_avg'] > $settings['latencyhigh']) {
718
			if (!$action_disable) {
719
				$r['status'] = "down";
720
			}
721
			$r['substatus'] = "highdelay";
722
		} else if ($r['loss'] > $settings['losshigh']) {
723
			if (!$action_disable) {
724
				$r['status'] = "down";
725
			}
726
			$r['substatus'] = "highloss";
727
		} else if ($r['latency_avg'] > $settings['latencylow']) {
728
			$r['substatus'] = "delay";
729
		} else if ($r['loss'] > $settings['losslow']) {
730
			$r['substatus'] = "loss";
731
		}
732
	}
733

    
734
	return $r;
735
}
736

    
737
/* return the status of the dpinger targets as an array */
738
function return_gateways_status($byname = false) {
739
	$dpinger_gws = running_dpinger_processes();
740
	$status = array();
741

    
742
	$gateways_arr = get_gateways(GW_CACHE_ALL);
743

    
744
	foreach ($dpinger_gws as $gwname => $gwdata) {
745
		/*
746
		 * If action is disabled for this gateway, then we want a
747
		 * detailed status.  That reports "highdelay" or "highloss"
748
		 * rather than just "down".  Because reporting the gateway
749
		 * down would be misleading (gateway action is disabled)
750
		 */
751
		$action_disable = $gateways_arr[$gwname]['action_disable'];
752
		$dpinger_status = get_dpinger_status($gwname, $action_disable);
753
		if ($dpinger_status === false) {
754
			continue;
755
		}
756

    
757
		if ($byname == false) {
758
			$target = $dpinger_status['targetip'];
759
		} else {
760
			$target = $gwname;
761
		}
762

    
763
		$status[$target] = array();
764
		$status[$target]['monitorip'] = $dpinger_status['targetip'];
765
		$status[$target]['srcip'] = $dpinger_status['srcip'];
766
		$status[$target]['name'] = $gwname;
767
		$status[$target]['delay'] =
768
		    empty($dpinger_status['latency_avg'])
769
		    ? "0ms"
770
		    : $dpinger_status['latency_avg'] . "ms";
771
		$status[$target]['stddev'] =
772
		    empty($dpinger_status['latency_stddev'])
773
		    ? "0ms"
774
		    : $dpinger_status['latency_stddev'] . "ms";
775
		$status[$target]['loss'] = empty($dpinger_status['loss'])
776
		    ? "0.0%"
777
		    : round($dpinger_status['loss'], 1) . "%";
778
		$status[$target]['status'] = $dpinger_status['status'];
779
		$status[$target]['substatus'] = $dpinger_status['substatus'];
780
	}
781

    
782
	/* tack on any gateways that have monitoring disabled
783
	 * or are down, which could cause gateway groups to fail */
784
	foreach ($gateways_arr as $gwitem) {
785
		if (!isset($gwitem['monitor_disable'])) {
786
			continue;
787
		}
788
		if (!is_ipaddr($gwitem['monitor'])) {
789
			$realif = $gwitem['interface'];
790
			$tgtip = get_interface_gateway($realif);
791
			if (!is_ipaddr($tgtip)) {
792
				$tgtip = "none";
793
			}
794
			$srcip = find_interface_ip($realif);
795
		} else {
796
			$tgtip = $gwitem['monitor'];
797
			$srcip = find_interface_ip($realif);
798
		}
799
		if ($byname == true) {
800
			$target = $gwitem['name'];
801
		} else {
802
			$target = $tgtip;
803
		}
804

    
805
		/* failsafe for down interfaces */
806
		if ($target == "none") {
807
			$target = $gwitem['name'];
808
			$status[$target]['name'] = $gwitem['name'];
809
			$status[$target]['delay'] = "0.0ms";
810
			$status[$target]['loss'] = "100.0%";
811
			$status[$target]['status'] = "down";
812
			$status[$target]['substatus'] = "down";
813
		} else {
814
			$status[$target]['monitorip'] = $tgtip;
815
			$status[$target]['srcip'] = $srcip;
816
			$status[$target]['name'] = $gwitem['name'];
817
			$status[$target]['delay'] = "";
818
			$status[$target]['loss'] = "";
819
			$status[$target]['status'] = "online";
820
			$status[$target]['substatus'] = "none";
821
		}
822

    
823
		$status[$target]['monitor_disable'] = true;
824
	}
825
	return($status);
826
}
827

    
828
function return_gateways_status_text($byname = false, $brief = false) {
829
	$gwstat = return_gateways_status($byname);
830
	$output = "";
831
	$widths = array();
832
	$col_sep = 2;
833
	if ($brief) {
834
		$collist = array('status' => "Status");
835
	} else {
836
		$collist = array('monitorip' => "Monitor",
837
				'srcip' => "Source",
838
				'delay' => "Delay",
839
				'stddev' => "StdDev",
840
				'loss' => "Loss",
841
				'status' => "Status",
842
				'substatus' => "Substatus");
843
	}
844
	foreach ($gwstat as $gw) {
845
		foreach ($gw as $gwdidx => $gwdata) {
846
			if (strlen($gwdata) > $widths[$gwdidx]) {
847
				$widths[$gwdidx] = strlen($gwdata);
848
			}
849
		}
850
	}
851

    
852
	$output .= str_pad("Name", $widths['name'] + $col_sep, " ", STR_PAD_RIGHT);
853
	foreach ($collist as $hdrcol => $hdrdesc) {
854
		if (strlen($hdrdesc) > $widths[$hdrcol]) {
855
			$widths[$hdrcol] = strlen($hdrdesc);
856
		}
857
		$output .= str_pad($hdrdesc, $widths[$hdrcol] + $col_sep, " ", (substr($hdrcol, -2, 2) == "ip") ? STR_PAD_RIGHT : STR_PAD_LEFT);
858
	}
859
	$output .= "\n";
860

    
861
	foreach ($gwstat as $idx => $gw) {
862
		$output .= str_pad($gw['name'], $widths['name'] + $col_sep, " ", STR_PAD_RIGHT);
863
		foreach (array_keys($collist) as $col) {
864
			$output .= str_pad($gw[$col], $widths[$col] + $col_sep, " ", (substr($col, -2, 2) == "ip") ? STR_PAD_RIGHT : STR_PAD_LEFT);
865
		}
866
		$output .= "\n";
867
	}
868

    
869
	return $output;
870
}
871

    
872
function compare_gateway_order_configured($a, $b) {
873
	/* XXX WAN always has precedence */
874
	if ($a['friendlyiface'] == "wan") {
875
		return -1;
876
	} elseif ($b['friendlyiface'] == "wan") {
877
		return 1;
878
	}
879

    
880
	if ($a['attribute'] === $b['attribute']) {
881
		if ($a['attribute'] === 'system') {
882
			$res = (($a['name'] < $b['name'])) ? -1 : 1;
883
			return $res;
884
		}
885
		return 0;
886
	}
887
	if ($a['attribute'] === 'system' || $b['attribute'] === 'system') {
888
		$res = (($b['attribute'] === 'system')) ? -1 : 1;
889
		return $res;
890
	}
891
	$res = ($a['attribute'] < $b['attribute']) ? -1 : 1;
892
	return $res;
893
}
894

    
895
function order_gateways_as_configured($gateways_arr) {
896
	uasort($gateways_arr, 'compare_gateway_order_configured');
897
	return $gateways_arr;
898
}
899

    
900
/* Return all configured gateways on the system
901
   $disabled = true - include gateways that are disabled
902
   $localhost = true - include "Null" entries for localhost IP addresses
903
   $inactive = true - include gateways on inactive interfaces
904
*/
905
function return_gateways_array($disabled = false, $localhost = false, $inactive = false) {
906
	$gateways_arr = array();
907
	$gateways_arr_temp = array();
908
	$cgw4 = route_get_default('inet');
909
	$cgw6 = route_get_default('inet6');
910
	$found_defaultv4 = 0;
911
	$found_defaultv6 = 0;
912

    
913
	// Ensure the interface cache is up to date first
914
	$interfaces = get_interface_arr(true);
915

    
916
	$i = -1;
917
	/* Process/add all the configured gateways. */
918
	foreach (config_get_path('gateways/gateway_item', []) as $gateway) {
919
		if (!is_array($gateway) || empty($gateway)) {
920
			continue;
921
		}
922

    
923
		/* Increment it here to do not skip items */
924
		$i++;
925
		if (isset($gateway['defaultgw'])) {
926
			unset($gateway['defaultgw']);
927
		}
928

    
929
		if (empty(config_get_path("interfaces/{$gateway['interface']}"))) {
930
			if ($inactive === false) {
931
				continue;
932
			} else {
933
				$gateway['inactive'] = true;
934
			}
935
		}
936
		$wancfg = config_get_path("interfaces/{$gateway['interface']}");
937

    
938
		/* skip disabled interfaces */
939
		if ($disabled === false && (!isset($wancfg['enable']))) {
940
			continue;
941
		}
942

    
943
		/* if the gateway is dynamic and we can find the IPv4, Great! */
944
		if (empty($gateway['gateway']) || $gateway['gateway'] == "dynamic") {
945
			if ($gateway['ipprotocol'] == "inet") {
946
				/* we know which interfaces is dynamic, this should be made a function */
947
				$gateway['gateway'] = get_interface_gateway($gateway['interface']);
948
				/* no IP address found, set to dynamic */
949
				if (!is_ipaddrv4($gateway['gateway'])) {
950
					$gateway['gateway'] = "dynamic";
951
				}
952
				$gateway['dynamic'] = true;
953
			}
954

    
955
			/* if the gateway is dynamic and we can find the IPv6, Great! */
956
			else if ($gateway['ipprotocol'] == "inet6") {
957
				/* we know which interfaces is dynamic, this should be made a function, and for v6 too */
958
				$gateway['gateway'] = get_interface_gateway_v6($gateway['interface']);
959
				/* no IPv6 address found, set to dynamic */
960
				if (!is_ipaddrv6($gateway['gateway'])) {
961
					$gateway['gateway'] = "dynamic";
962
				}
963
				$gateway['dynamic'] = true;
964
			}
965
		} else {
966
			/* getting this detection right is hard at this point because we still don't
967
				* store the address family in the gateway item */
968
			if (is_ipaddrv4($gateway['gateway'])) {
969
				$gateway['ipprotocol'] = "inet";
970
			} else if (is_ipaddrv6($gateway['gateway'])) {
971
				$gateway['ipprotocol'] = "inet6";
972
			}
973
		}
974

    
975
		if (isset($gateway['monitor_disable'])) {
976
			$gateway['monitor_disable'] = true;
977
		} else if (empty($gateway['monitor'])) {
978
			$gateway['monitor'] = $gateway['gateway'];
979
		}
980

    
981
		if (isset($gateway['action_disable'])) {
982
			$gateway['action_disable'] = true;
983
		}
984

    
985
		$gateway['friendlyiface'] = $gateway['interface'];
986
		$gateway['friendlyifdescr'] = convert_friendly_interface_to_friendly_descr($gateway['interface']);
987

    
988
		/* special treatment for tunnel interfaces */
989
		if ($gateway['ipprotocol'] == "inet6") {
990
			$gateway['interface'] = get_real_interface($gateway['interface'], "inet6", false, false);
991
		} else {
992
			$gateway['interface'] = get_real_interface($gateway['interface'], "inet", false, false);
993
		}
994

    
995
		if ($gateway['ipprotocol'] == "inet" &&
996
				($gateway['gateway'] == $cgw4)) {
997
			$gateway['isdefaultgw'] = true;
998
			$found_defaultv4 = 1;
999
		} else if ($gateway['ipprotocol'] == "inet6" &&
1000
				($gateway['gateway'] == $cgw6)) {
1001
			$gateway['isdefaultgw'] = true;
1002
			$found_defaultv6 = 1;
1003
		}
1004
		/* include the gateway index as the attribute */
1005
		$gateway['attribute'] = $i;
1006

    
1007
		/* Remember all the gateway names, even ones to be skipped because they are disabled. */
1008
		/* Then we can easily know and match them later when attempting to add dynamic gateways to the list. */
1009
		$gateways_arr_temp[$gateway['name']] = $gateway;
1010

    
1011
		/* skip disabled gateways if the caller has not asked for them to be returned. */
1012
		if (!($disabled === false && isset($gateway['disabled']))) {
1013
			$gateways_arr[$gateway['name']] = $gateway;
1014
		}
1015
	}
1016
	unset($gateway);
1017

    
1018
	//Sort the array by GW name before moving on.
1019
	ksort($gateways_arr, SORT_STRING | SORT_FLAG_CASE);
1020

    
1021
	/* Loop through all interfaces with a gateway and add it to a array */
1022
	if ($disabled == false) {
1023
		$iflist = get_configured_interface_with_descr();
1024
	} else {
1025
		$iflist = get_configured_interface_with_descr(true);
1026
	}
1027

    
1028
	/* Process/add dynamic v4 gateways. */
1029
	foreach ($iflist as $ifname => $friendly) {
1030
		if (!interface_has_gateway($ifname)) {
1031
			continue;
1032
		}
1033

    
1034
		if (empty(config_get_path("interfaces/{$ifname}"))) {
1035
			continue;
1036
		}
1037

    
1038
		$ifcfg = config_get_path("interfaces/{$ifname}");
1039
		if (!isset($ifcfg['enable'])) {
1040
			continue;
1041
		}
1042

    
1043
		if (!empty($ifcfg['ipaddr']) && is_ipaddrv4($ifcfg['ipaddr'])) {
1044
			continue;
1045
		}
1046

    
1047
		$ctype = "";
1048
		switch ($ifcfg['ipaddr']) {
1049
			case "dhcp":
1050
			case "pppoe":
1051
			case "l2tp":
1052
			case "pptp":
1053
			case "ppp":
1054
				$ctype = strtoupper($ifcfg['ipaddr']);
1055
				break;
1056
			default:
1057
				$tunnelif = substr($ifcfg['if'], 0, 3);
1058
				if (substr($ifcfg['if'], 0, 4) == "ovpn") {
1059
					switch (substr($ifcfg['if'], 4, 1)) {
1060
						case "c":
1061
							$ovpntype = "openvpn-client";
1062
							break;
1063
						case "s":
1064
							$ovpntype = "openvpn-server";
1065
							break;
1066
						default:
1067
							// unknown ovpn type
1068
							continue 3;
1069
					}
1070
					$ovpnid = substr($ifcfg['if'], 5);
1071
					foreach (config_get_path("openvpn/{$ovpntype}", []) as $ovpnconf) {
1072
						if (empty($ovpnconf)) {
1073
							continue;
1074
						}
1075
						if ($ovpnconf['vpnid'] == $ovpnid) {
1076
							// skip IPv6-only interfaces
1077
							if ($ovpnconf['create_gw'] == "v6only") {
1078
								continue 3;
1079
							}
1080
							// skip tap interfaces
1081
							if ($ovpnconf['dev_mode'] == "tap") {
1082
								continue 3;
1083
							}
1084
						}
1085
					}
1086
					$ctype = "VPNv4";
1087
				} elseif (substr($ifcfg['if'], 0, 5) == "ipsec") {
1088
					$ikeid = substr($ifcfg['if'], 5);
1089
					if (!empty(config_get_path('ipsec/phase1', [])) &&
1090
					    !empty(config_get_path('ipsec/phase2', []))) {
1091
						foreach (config_get_path('ipsec/phase1', []) as $ph1ent) {
1092
							if (empty($ph1ent) || $ph1ent['disabled']) {
1093
								continue;
1094
							}
1095
							$vtisubnet_spec = ipsec_vti($ph1ent, true);
1096
							// Skip non-VTI tunnels
1097
							if (!$vtisubnet_spec || !is_array($vtisubnet_spec)) {
1098
								continue;
1099
							}
1100
							if (!isset($ph1ent['mobile']) && ($ph1ent['iketype'] == 'ikev1' || isset($ph1ent['splitconn']))) {
1101
								foreach ($vtisubnet_spec as $idx => $vtisub) {
1102
									if ($ifcfg['if'] == ipsec_get_ifname($ph1ent, $vtisub['reqid'])) {
1103
										// If this specific VTI remote is v4, then we can make a v4 gw
1104
										if (is_ipaddrv4($vtisub['right'])) {
1105
											$ctype = "VTIv4";
1106
										}
1107
									}
1108
								}
1109
							} else {
1110
								if ($ifcfg['if'] == ipsec_get_ifname($ph1ent)) {
1111
									// If any of the VTI remotes are v4, then we can make a v4 gw
1112
									foreach ($vtisubnet_spec as $vtisub) {
1113
										if (is_ipaddrv4($vtisub['right'])) {
1114
											$ctype = "VTIv4";
1115
										}
1116
									}
1117
								}
1118
							}
1119
						}
1120
						if (empty($ctype)) {
1121
							continue 2;
1122
						}
1123
					}
1124
				} elseif ($tunnelif == "gif" || $tunnelif == "gre") {
1125
					$ctype = "TUNNELv4";
1126
				}
1127
				break;
1128
		}
1129
		$ctype = "_". strtoupper($ctype);
1130

    
1131
		$gateway = array();
1132
		$gateway['dynamic'] = false;
1133
		$gateway['ipprotocol'] = "inet";
1134
		$gateway['gateway'] = get_interface_gateway($ifname, $gateway['dynamic']);
1135
		$gateway['interface'] = get_real_interface($ifname);
1136
		$gateway['friendlyiface'] = $ifname;
1137
		$gateway['friendlyifdescr'] = convert_friendly_interface_to_friendly_descr($ifname);
1138
		$gateway['name'] = "{$friendly}{$ctype}";
1139
		$gateway['attribute'] = "system";
1140

    
1141
		if (($gateway['dynamic'] === "default") && ($found_defaultv4 == 0)) {
1142
			$gateway['isdefaultgw'] = true;
1143
			$gateway['dynamic'] = true;
1144
			$found_defaultv4 = 1;
1145
		}
1146

    
1147
		/* Loopback dummy for dynamic interfaces without a IP */
1148
		if (!is_ipaddrv4($gateway['gateway']) && $gateway['dynamic'] == true) {
1149
			$gateway['gateway'] = "dynamic";
1150
		}
1151

    
1152
		/* automatically skip known static and dynamic gateways that were previously processed */
1153
		foreach ($gateways_arr_temp as $gateway_item) {
1154
			if ((($ifname == $gateway_item['friendlyiface'] && $friendly == $gateway_item['name'])&& ($gateway['ipprotocol'] == $gateway_item['ipprotocol'])) ||
1155
			    (($ifname == $gateway_item['friendlyiface'] && $gateway_item['dynamic'] == true) && ($gateway['ipprotocol'] == $gateway_item['ipprotocol']))) {
1156
				continue 2;
1157
			}
1158
		}
1159

    
1160
		if (is_ipaddrv4($gateway['gateway'])) {
1161
			$gateway['monitor'] = $gateway['gateway'];
1162
		}
1163

    
1164
		$gateway['descr'] = "Interface {$friendly}{$ctype} Gateway";
1165
		$gateways_arr[$gateway['name']] = $gateway;
1166
	}
1167
	unset($gateway);
1168

    
1169
	/* Process/add dynamic v6 gateways. */
1170
	foreach ($iflist as $ifname => $friendly) {
1171
		/* If the user has disabled IPv6, they probably don't want any IPv6 gateways. */
1172
		if (!config_path_enabled('system', 'ipv6allow')) {
1173
			break;
1174
		}
1175

    
1176
		if (!interface_has_gatewayv6($ifname)) {
1177
			continue;
1178
		}
1179

    
1180
		if (empty(config_get_path("interfaces/{$ifname}"))) {
1181
			continue;
1182
		}
1183

    
1184
		$ifcfg = config_get_path("interfaces/{$ifname}");
1185
		if (!isset($ifcfg['enable'])) {
1186
			continue;
1187
		}
1188

    
1189
		if (!empty($ifcfg['ipaddrv6']) && is_ipaddrv6($ifcfg['ipaddrv6'])) {
1190
			continue;
1191
		}
1192

    
1193
		$ctype = "";
1194
		switch ($ifcfg['ipaddrv6']) {
1195
			case "slaac":
1196
			case "dhcp6":
1197
			case "6to4":
1198
			case "6rd":
1199
				$ctype = strtoupper($ifcfg['ipaddrv6']);
1200
				break;
1201
			default:
1202
				$tunnelif = substr($ifcfg['if'], 0, 3);
1203
				if (substr($ifcfg['if'], 0, 4) == "ovpn") {
1204
					switch (substr($ifcfg['if'], 4, 1)) {
1205
						case "c":
1206
							$ovpntype = "openvpn-client";
1207
							break;
1208
						case "s":
1209
							$ovpntype = "openvpn-server";
1210
							break;
1211
						default:
1212
							// unknown ovpn type
1213
							continue 3;
1214
					}
1215
					$ovpnid = substr($ifcfg['if'], 5);
1216
					foreach (config_get_path("openvpn/{$ovpntype}", []) as $ovpnconf) {
1217
						if (empty($ovpnconf)) {
1218
							continue;
1219
						}
1220
						if ($ovpnconf['vpnid'] == $ovpnid) {
1221
							// skip IPv4-only interfaces
1222
							if ($ovpnconf['create_gw'] == "v4only") {
1223
								continue 3;
1224
							}
1225
							// skip tap interfaces
1226
							if ($ovpnconf['dev_mode'] == "tap") {
1227
								continue 3;
1228
							}
1229
						}
1230
					}
1231
					$ctype = "VPNv6";
1232
				} elseif (substr($ifcfg['if'], 0, 5) == "ipsec") {
1233
					$ikeid = substr($ifcfg['if'], 5);
1234
					if (!empty(config_get_path('ipsec/phase1', [])) &&
1235
					    !empty(config_get_path('ipsec/phase2', []))) {
1236
						foreach (config_get_path('ipsec/phase1', []) as $ph1ent) {
1237
							if (empty($ph1ent) || $ph1ent['disabled']) {
1238
								continue;
1239
							}
1240
							$vtisubnet_spec = ipsec_vti($ph1ent, true);
1241
							// Skip non-VTI tunnels
1242
							if (!$vtisubnet_spec || !is_array($vtisubnet_spec)) {
1243
								continue;
1244
							}
1245
							if (!isset($ph1ent['mobile']) && ($ph1ent['iketype'] == 'ikev1' || isset($ph1ent['splitconn']))) {
1246
								foreach ($vtisubnet_spec as $idx => $vtisub) {
1247
									if ($ifcfg['if'] == ipsec_get_ifname($ph1ent, $vtisub['reqid'])) {
1248
										// If this specific VTI remote is v6, then we can make a v6 gw
1249
										if (is_ipaddrv6($vtisub['right'])) {
1250
											$ctype = "VTIv6";
1251
										}
1252
									}
1253
								}
1254
							} else {
1255
								if ($ifcfg['if'] == ipsec_get_ifname($ph1ent)) {
1256
									// If any of the VTI remotes are v6, then we can make a v6 gw
1257
									foreach ($vtisubnet_spec as $vtisub) {
1258
										if (is_ipaddrv6($vtisub['right'])) {
1259
											$ctype = "VTIv6";
1260
										}
1261
									}
1262
								}
1263
							}
1264
						}
1265
						if (empty($ctype)) {
1266
							continue 2;
1267
						}
1268
					}
1269
				} else if ($tunnelif == "gif" || $tunnelif == "gre") {
1270
					$ctype = "TUNNELv6";
1271
				}
1272
				break;
1273
		}
1274
		$ctype = "_". strtoupper($ctype);
1275

    
1276
		$gateway = array();
1277
		$gateway['dynamic'] = false;
1278
		$gateway['ipprotocol'] = "inet6";
1279
		$gateway['gateway'] = get_interface_gateway_v6($ifname, $gateway['dynamic']);
1280
		$gateway['interface'] = get_real_interface($ifname, "inet6");
1281
		switch ($ifcfg['ipaddrv6']) {
1282
			case "6rd":
1283
			case "6to4":
1284
				$gateway['dynamic'] = "default";
1285
				break;
1286
		}
1287
		$gateway['friendlyiface'] = $ifname;
1288
		$gateway['friendlyifdescr'] = convert_friendly_interface_to_friendly_descr($ifname);
1289
		$gateway['name'] = "{$friendly}{$ctype}";
1290
		$gateway['attribute'] = "system";
1291

    
1292
		if (($gateway['dynamic'] === "default") && ($found_defaultv6 == 0)) {
1293
			$gateway['isdefaultgw'] = true;
1294
			$gateway['dynamic'] = true;
1295
			$found_defaultv6 = 1;
1296
		}
1297

    
1298
		/* Loopback dummy for dynamic interfaces without a IP */
1299
		if (!is_ipaddrv6($gateway['gateway']) && $gateway['dynamic'] == true) {
1300
			$gateway['gateway'] = "dynamic";
1301
		}
1302

    
1303
		/* automatically skip known static and dynamic gateways that were previously processed */
1304
		foreach ($gateways_arr_temp as $gateway_item) {
1305
			if ((($ifname == $gateway_item['friendlyiface'] && $friendly == $gateway_item['name']) && ($gateway['ipprotocol'] == $gateway_item['ipprotocol'])) ||
1306
			    (($ifname == $gateway_item['friendlyiface'] && $gateway_item['dynamic'] == true) && ($gateway['ipprotocol'] == $gateway_item['ipprotocol']))) {
1307
				continue 2;
1308
			}
1309
		}
1310

    
1311
		if (is_ipaddrv6($gateway['gateway'])) {
1312
			$gateway['monitor'] = $gateway['gateway'];
1313
		}
1314

    
1315
		$gateway['descr'] = "Interface {$friendly}{$ctype} Gateway";
1316
		$gateways_arr[$gateway['name']] = $gateway;
1317
	}
1318
	unset($gateway);
1319

    
1320
	/* FIXME: Should this be enabled.
1321
	 * Some interface like wan might be default but have no info recorded
1322
	 * the config. */
1323
	/* this is a fallback if all else fails and we want to get packets out @smos */
1324
	if ($found_defaultv4 == 0 || $found_defaultv6 == 0) {
1325
		foreach ($gateways_arr as &$gateway) {
1326
			if (($gateway['friendlyiface'] == "wan") && ($found_defaultv4 == 0) && (!isset($gateway['ipprotocol']) || ($gateway['ipprotocol'] == "inet"))) {
1327
				if (file_exists(g_get('tmp_path', '') . "/{$gateway['interface']}_defaultgw")) {
1328
					$gateway['isdefaultgw'] = true;
1329
					$found_defaultv4 = 1;
1330
				}
1331
			}
1332
			else if (($gateway['friendlyiface'] == "wan") && ($found_defaultv6 == 0) && ($gateway['ipprotocol'] == "inet6")) {
1333
				if (file_exists(g_get('tmp_path', '') . "/{$gateway['interface']}_defaultgwv6")) {
1334
					$gateway['isdefaultgw'] = true;
1335
					$found_defaultv6 = 1;
1336
				}
1337
			}
1338
		}
1339
	}
1340

    
1341
	if ($localhost === true) {
1342
		/* attach localhost for Null routes */
1343
		$gwlo4 = array();
1344
		$gwlo4['name'] = "Null4";
1345
		$gwlo4['interface'] = "lo0";
1346
		$gwlo4['ipprotocol'] = "inet";
1347
		$gwlo4['gateway'] = "127.0.0.1";
1348
		$gwlo4['attribute'] = "system";
1349
		$gwlo6 = array();
1350
		$gwlo6['name'] = "Null6";
1351
		$gwlo6['interface'] = "lo0";
1352
		$gwlo6['ipprotocol'] = "inet6";
1353
		$gwlo6['gateway'] = "::1";
1354
		$gwlo6['attribute'] = "system";
1355
		$gateways_arr['Null4'] = $gwlo4;
1356
		$gateways_arr['Null6'] = $gwlo6;
1357
	}
1358

    
1359
	if ($found_defaultv4 != 1 && is_ipaddr($cgw4)) {
1360
		foreach($gateways_arr as &$gw) {
1361
			if ($gw['gateway'] == $cgw4) {
1362
				$gw['isdefaultgw'] = true;
1363
			}
1364
		}
1365
	}
1366
	if ($found_defaultv6 != 1 && is_ipaddr($cgw6)) {
1367
		foreach($gateways_arr as &$gw) {
1368
			if ($gw['gateway'] == $cgw6) {
1369
				$gw['isdefaultgw'] = true;
1370
			}
1371
		}
1372
	}
1373

    
1374
	$gways = order_gateways_as_configured($gateways_arr);
1375

    
1376
	// Add the tier names here so that system_gateways.php doesn't need to
1377
	foreach ($gways as $idx => $gway) {
1378
		$gways[$idx]['tiername'] = gateway_getgwtiername($gways, $idx);
1379
	}
1380

    
1381
	return $gways;
1382
}
1383

    
1384
function fixup_default_gateway($ipprotocol, $gateways_status, $gateways_arr) {
1385
	/*
1386
	 * NOTE: The code below is meant to replace the default gateway when it goes down.
1387
	 *	This facilitates services running on pfSense itself and are not handled by a PBR to continue working.
1388
	 */
1389
	$set_dfltgwname = '';
1390

    
1391
	if ($ipprotocol == 'inet') {
1392
		$gwdefault = config_get_path('gateways/defaultgw4', "");
1393
	} else {
1394
		$gwdefault = config_get_path('gateways/defaultgw6', "");
1395
	}
1396
	/* do not delete dynamic (frr/bgp/ospf) route
1397
	 * see https://redmine.pfsense.org/issues/12536 */
1398
	if ($gwdefault == "-") {
1399
		if (!is_dynamic_route('default', $ipprotocol)) {
1400
			route_del('default', $ipprotocol);
1401
		}
1402
		return;
1403
	}
1404
	if (isset($gateways_arr[$gwdefault])) {
1405
		// the configured gateway is a regular one. (not a gwgroup) use it as is..
1406
		$set_dfltgwname = $gwdefault;
1407
	} elseif (empty($gwdefault)) {
1408
		// 'automatic' mode, pick the first one thats 'up' or 'unmonitored' which is always considered up
1409
		$gateways_arr = order_gateways_as_configured($gateways_arr);
1410
		$fallback = "";
1411
		foreach($gateways_arr as $gwname => $gwsttng) {
1412
			if (($gwsttng['ipprotocol'] != $ipprotocol) || isset($gwsttng['force_down'])) {
1413
				continue;
1414
			}
1415

    
1416
			if (isset($gwsttng['monitor_disable']) || isset($gwsttng['action_disable']) ||
1417
			    ($gateways_status[$gwname]['status'] == "online")) {
1418
				$set_dfltgwname = $gwname;
1419
				break;
1420
			}
1421
			if (empty($fallback) && $gwsttng['interface'] != 'lo0') {
1422
				$fallback = $gwname;
1423
			}
1424
		}
1425
		if (empty($set_dfltgwname) && !empty($fallback)) {
1426
			log_error(sprintf("Gateway, none 'available' for %s, use the first one configured. '%s'", $ipprotocol, $fallback));
1427
			$set_dfltgwname = $fallback;
1428
		} else {
1429
			log_error("Gateway, NONE AVAILABLE");
1430
		}
1431
	} else {
1432
		// a gwgroup is selected
1433
		// find the best available gateway given options available..
1434
		$gwg_members = array();
1435
		$viplist = get_configured_vip_list();
1436
		foreach (config_get_path('gateways/gateway_group', []) as $group) {
1437
			if ($group['name'] == $gwdefault) {
1438
				// finds the gw members of the best available tier for this group.
1439
				$gwg_members = get_gwgroup_members_inner($group, $gateways_status, $gateways_arr, $viplist);
1440
			}
1441
		}
1442

    
1443
		if (count($gwg_members) > 0) {
1444
			$currentdefaultgwip = route_get_default($ipprotocol);
1445
			$found_current = false;
1446
			foreach($gwg_members as $gwgroupitem) {
1447
				if (!empty($currentdefaultgwip) &&
1448
				    is_array($gwgroupitem) &&
1449
				    ($gwgroupitem['gwip'] == $currentdefaultgwip)) {
1450
					$set_dfltgwname = $gwgroupitem['gw'];
1451
					$found_current = true;
1452
					if (config_path_enabled('system', 'gw-debug')) {
1453
						log_error("Keep current gateway, its already part of the group members.");
1454
					}
1455
					break;
1456
				}
1457
			}
1458
			if (!$found_current) {
1459
				$set_dfltgwname = $gwg_members[0]['gw'];
1460
				log_error(sprintf("Gateway, switch to: %s", $set_dfltgwname));
1461
			}
1462
		} else {
1463
			log_error("Gateway, NONE AVAILABLE");
1464
		}
1465
	}
1466
	if (!empty($set_dfltgwname) && isset($gateways_arr[$set_dfltgwname])) {
1467
		setdefaultgateway($gateways_arr[$set_dfltgwname]);
1468
	} elseif (empty($set_dfltgwname)) {
1469
		route_del('default', $ipprotocol);
1470
	}
1471
}
1472

    
1473
function setdefaultgateway($gw) {
1474
	if (config_path_enabled('system', 'route-debug')) {
1475
		file_put_contents("/dev/console", "\n[".getmypid()."] SET DEF GW: {$gw['name']}");
1476
	}
1477
	$ipprotocol = $gw['ipprotocol'];
1478
	if ($gw['gateway'] == "dynamic") {
1479
		if ($ipprotocol == 'inet') {
1480
			$gw['gateway'] = get_interface_gateway($gw['friendlyiface']);
1481
		} else {
1482
			$gw['gateway'] = get_interface_gateway_v6($gw['friendlyiface']);
1483
		}
1484
	}
1485
	if ($ipprotocol == 'inet6' && !is_ipaddrv6($gw['gateway'])) {
1486
		return;
1487
	}
1488
	if ($ipprotocol == 'inet' && !is_ipaddrv4($gw['gateway'])) {
1489
		return;
1490
	}
1491
	if ($ipprotocol == 'inet6') {
1492
		if (is_linklocal($gw['gateway']) && get_ll_scope($gw['gateway']) == '') {
1493
			$gw['gateway'] .= "%" . $gw['interface'];
1494
		}
1495
	}
1496
	$currentdefaultgwip = route_get_default($ipprotocol);
1497
	if ($currentdefaultgwip != $gw['gateway']) {
1498
		log_error("Default gateway setting {$gw['descr']} as default.");
1499

    
1500
		if ($ipprotocol == 'inet') {
1501
			$inet = '';
1502
		} else {
1503
			$inet = 'v6';
1504
		}
1505
		unlink_if_exists(g_get('tmp_path', '') . "/*_defaultgw{$inet}");
1506
		$defaultif = get_real_interface($gw['interface']);
1507
		if ($defaultif) {
1508
			@file_put_contents(g_get('tmp_path', '') . "/{$defaultif}_defaultgw{$inet}", $gw['gateway']);
1509
		}
1510

    
1511
		if (isset($gw["nonlocalgateway"])) {
1512
			if (is_ipaddr($gw['gateway']) && !empty($gw['interface'])) {
1513
				route_add_or_change($gw['gateway'], '',
1514
				    $gw['interface']);
1515
			}
1516
		}
1517
		if (config_path_enabled('system', 'route-debug')) {
1518
			file_put_contents("/dev/console", "\n[".getmypid()."] SET DEF GW: {$gw['name']} ({$gw['gateway']})");
1519
		}
1520
		route_add_or_change("default", $gw['gateway'], '', '',
1521
		    $ipprotocol);
1522
		return true;
1523
	}
1524
}
1525

    
1526
function get_gwgroup_members_inner($group, $gateways_status, $gateways_arr, $viplist){
1527
	$result = array();
1528
	/* create array with group gateways members separated by tier */
1529
	$tiers = array();
1530
	$backupplan = array();
1531
	$gwvip_arr = array();
1532
	foreach ($group['item'] as $item) {
1533
		list($gwname, $tier, $vipname) = explode("|", $item);
1534

    
1535
		if (is_ipaddr($viplist[$vipname])) {
1536
			if (!is_array($gwvip_arr[$group['name']])) {
1537
				$gwvip_arr[$group['name']] = array();
1538
			}
1539
			$gwvip_arr[$group['name']][$gwname] = $vipname;
1540
		}
1541

    
1542
		/* Do it here rather than reiterating again the group in case no member is up. */
1543
		if (!is_array($backupplan[$tier])) {
1544
			$backupplan[$tier] = array();
1545
		}
1546
		$backupplan[$tier][] = $gwname;
1547

    
1548
		/* check if the gateway is available before adding it to the array */
1549
		if (is_array($gateways_status[$gwname])) {
1550
			$status = $gateways_status[$gwname];
1551
			$gwdown = false;
1552
			if (stristr($status['status'], "down")) {
1553
				$gwdown = true;
1554
				switch ($status['substatus']) {
1555
					case "highloss":
1556
						$msg = sprintf(gettext('MONITOR: %1$s has packet loss, omitting from routing group %2$s'), $gwname, $group['name']);
1557
						break;
1558
					case "highdelay":
1559
						$msg = sprintf(gettext('MONITOR: %1$s has high latency, omitting from routing group %2$s'), $gwname, $group['name']);
1560
						break;
1561
					default:
1562
						$msg = sprintf(gettext('MONITOR: %1$s is down, omitting from routing group %2$s'), $gwname, $group['name']);
1563
				}
1564
			}
1565
			$statuschanged = false;
1566
			$pluginparams = array();
1567
			$pluginparams['type'] = 'gateway';
1568
			$pluginparams['name'] = $gwname;
1569
			if ($gwdown == true) {
1570
				if (!file_exists("/tmp/.down.{$gwname}")) {
1571
					@touch("/tmp/.down.{$gwname}");
1572
					$msg .= "\n".implode("|", $status);
1573
					$pluginparams['event'] = 'gateway.down';
1574
					$statuschanged = true;
1575
				}
1576
			} else {
1577
				/* Online add member */
1578
				if (!is_array($tiers[$tier])) {
1579
					$tiers[$tier] = array();
1580
				}
1581
				$tiers[$tier][] = $gwname;
1582
				if (unlink_if_exists("/tmp/.down.{$gwname}")) {
1583
					$msg = sprintf(gettext('MONITOR: %1$s is available now, adding to routing group %2$s'), $gwname, $group['name']);
1584
					$msg .= "\n".implode("|", $status);
1585
					$pluginparams['event'] = 'gateway.up';
1586
					$statuschanged = true;
1587
				}
1588
			}
1589
			if ($statuschanged) {
1590
				log_error($msg);
1591
				notify_all_remote($msg);
1592
				if (isset($gateways_arr[$gwname]['interface'])) {
1593
					$pluginparams['interface'] = $gateways_arr[$gwname]['interface'];
1594
				}
1595
				pkg_call_plugins('plugin_gateway', $pluginparams);
1596
			}
1597
		} else if (isset($gateways_arr[$gwname]['monitor_disable']) || isset($gateways_arr[$gwname]['action_disable'])) {
1598
			$tiers[$tier][] = $gwname;
1599
		}
1600
	}
1601
	$tiers_count = count($tiers);
1602
	if ($tiers_count == 0) {
1603
		/* Oh dear, we have no members! Engage Plan B */
1604
		if (config_path_enabled('system', 'gw-debug') && (!is_platform_booting())) {
1605
			$msg = sprintf(gettext('Gateways status could not be determined, considering all as up/active. (Group: %s)'), $group['name']);
1606
			log_error($msg);
1607
		}
1608
		$tiers = $backupplan;
1609
	}
1610
	/* sort the tiers array by the tier key */
1611
	ksort($tiers);
1612

    
1613
	/* we do not really foreach the tiers as we stop after the first tier */
1614
	foreach ($tiers as $tieridx => $tier) {
1615
		/* process all gateways in this tier */
1616
		foreach ($tier as $member) {
1617
			/* determine interface gateway */
1618
			if (isset($gateways_arr[$member])) {
1619
				$gateway = $gateways_arr[$member];
1620
				$int = $gateway['interface'];
1621
				$gatewayip = "";
1622
				if (is_ipaddr($gateway['gateway'])) {
1623
					$gatewayip = $gateway['gateway'];
1624
				} elseif (!empty($int)) {
1625
					if ($gateway['ipprotocol'] == 'inet') {
1626
						$gatewayip = get_interface_gateway($gateway['friendlyiface']);
1627
					} else {
1628
						$gatewayip = get_interface_gateway_v6($gateway['friendlyiface']);
1629
					}
1630
				}
1631

    
1632
				if (!empty($int)) {
1633
					$result['ipprotocol'] = $gateway['ipprotocol'];
1634
					if (is_ipaddr($gatewayip)) {
1635
						$groupmember = array();
1636
						$groupmember['gw'] = $member;
1637
						$groupmember['int'] = $int;
1638
						$groupmember['gwip'] = $gatewayip;
1639
						/* set correct linklocal gateway address,
1640
						 * see https://redmine.pfsense.org/issues/12721 */
1641
						if (is_ipaddrv6($gatewayip) && is_linklocal($gatewayip) && empty(get_ll_scope($gatewayip))) {
1642
							$groupmember['gwip'] .= '%' . $int;
1643
						}
1644
						$groupmember['weight'] = isset($gateway['weight']) ? $gateway['weight'] : 1;
1645
						if (is_array($gwvip_arr[$group['name']]) && !empty($gwvip_arr[$group['name']][$member])) {
1646
							$groupmember['vip'] = $gwvip_arr[$group['name']][$member];
1647
						}
1648
						$result[] = $groupmember;
1649
					}
1650
				}
1651
			}
1652
		}
1653
		/* we should have the 1st available tier now, exit stage left */
1654
		if (count($result) > 0) {
1655
			break;
1656
		} else {
1657
			log_error(sprintf(gettext('GATEWAYS: Group %1$s did not have any gateways up on tier %2$s!'), $group['name'], $tieridx));
1658
		}
1659
	}
1660
	// Add description field last to not influence the count() above
1661
	$result['descr'] = $group['descr'];
1662
	return $result;
1663
}
1664

    
1665
function get_gwgroup_members($groupname) {
1666
	$gateways_status = return_gateways_status(true);
1667
	$gateways_arr = get_gateways();
1668
	$viplist = get_configured_vip_list();
1669
	foreach (config_get_path('gateways/gateway_group', []) as $group) {
1670
		if ($group['name'] == $groupname) {
1671
			return get_gwgroup_members_inner($group, $gateways_status, $gateways_arr, $viplist);
1672
		}
1673
	}
1674
	return array();
1675
}
1676

    
1677
/*
1678
 * Return an array with all gateway groups with name as key
1679
 * All gateway groups will be processed before returning the array.
1680
 */
1681
function return_gateway_groups_array($fixup = false) {
1682
	/* fetch the current gateways status */
1683
	$gateways_status = return_gateways_status(true);
1684
	$gateways_arr = get_gateways();
1685
	$gateway_groups_array = array();
1686
	if ($fixup == true) {
1687
		$gw4 = lookup_gateway_or_group_by_name(config_get_path('gateways/defaultgw4', ""));
1688
		$gw6 = lookup_gateway_or_group_by_name(config_get_path('gateways/defaultgw6', ""));
1689
		if ($gw4 && $gw4['type'] == 'gatewaygroup') {
1690
			fixup_default_gateway("inet", $gateways_status, $gateways_arr);
1691
		}
1692
		if ($gw6 && $gw6['type'] == 'gatewaygroup') {
1693
			fixup_default_gateway("inet6", $gateways_status, $gateways_arr);
1694
		}
1695
	}
1696
	config_init_path('gateways/gateway_group');
1697
	if (!empty(config_get_path('gateways/gateway_group'))) {
1698
		$viplist = get_configured_vip_list();
1699
		foreach (config_get_path('gateways/gateway_group', []) as $group) {
1700
			$gateway_groups_array[$group['name']] = get_gwgroup_members_inner($group, $gateways_status, $gateways_arr, $viplist);
1701
		}
1702
	}
1703

    
1704
	return ($gateway_groups_array);
1705
}
1706

    
1707
/* Update DHCP WAN Interface ip address in gateway group item */
1708
function dhclient_update_gateway_groups_defaultroute($interface = "wan") {
1709
	$gwlist = config_get_path('gateways/gateway_item', []);
1710
	foreach ($gwlist as & $gw) {
1711
		if ($gw['interface'] != $interface) {
1712
			continue;
1713
		}
1714

    
1715
		$current_gw = get_interface_gateway($interface);
1716
		if ($gw['gateway'] <> $current_gw) {
1717
			$gw['gateway'] = $current_gw;
1718
			$changed = true;
1719
		}
1720
	}
1721
	config_set_path('gateways/gateway_item', $gwlist);
1722

    
1723
	if ($changed && $current_gw) {
1724
		write_config(sprintf(gettext(
1725
		    'Updating gateway group gateway for %1$s - new gateway is %2$s'),
1726
		    $interface, $current_gw));
1727
	}
1728
}
1729

    
1730
function lookup_gateway_or_group_by_name($gwname) {
1731
	$gateways_arr = get_gateways(GW_CACHE_ALL);
1732

    
1733
	foreach ($gateways_arr as $gw) {
1734
		if ($gw['name'] == $gwname) {
1735
			$gw['type'] = 'gateway';
1736
			return $gw;
1737
		}
1738
	}
1739

    
1740
	config_init_path('gateways/gateway_group');
1741
	foreach (config_get_path('gateways/gateway_group', []) as $gwg) {
1742
		if ($gwg['name'] == $gwname) {
1743
			$gwg['type'] = 'gatewaygroup';
1744
			return $gwg;
1745
		}
1746
	}
1747

    
1748
	return false;
1749
}
1750

    
1751
function lookup_gateway_ip_by_name($name, $disabled = false) {
1752

    
1753
	$gateways_arr = get_gateways(($disabled ? GW_CACHE_DISABLED : 0) | GW_CACHE_LOCALHOST);
1754
	foreach ($gateways_arr as $gname => $gw) {
1755
		if ($gw['name'] === $name || $gname === $name) {
1756
			return $gw['gateway'];
1757
		}
1758
	}
1759

    
1760
	return false;
1761
}
1762

    
1763
function lookup_gateway_monitor_ip_by_name($name) {
1764

    
1765
	$gateways_arr = get_gateways(GW_CACHE_LOCALHOST);
1766
	if (!empty($gateways_arr[$name])) {
1767
		$gateway = $gateways_arr[$name];
1768
		if (!is_ipaddr($gateway['monitor'])) {
1769
			return $gateway['gateway'];
1770
		}
1771

    
1772
		return $gateway['monitor'];
1773
	}
1774

    
1775
	return (false);
1776
}
1777

    
1778
function lookup_gateway_interface_by_name($name) {
1779

    
1780
	$gateways_arr = get_gateways(GW_CACHE_LOCALHOST);
1781
	if (!empty($gateways_arr[$name])) {
1782
		$interfacegw = $gateways_arr[$name]['friendlyiface'];
1783
		return ($interfacegw);
1784
	}
1785

    
1786
	return (false);
1787
}
1788

    
1789
function get_root_interface($interface) {
1790
	if (substr($interface, 0, 4) == '_vip') {
1791
		$interface = get_configured_vip_interface($interface);
1792
		if (substr($interface, 0, 4) == '_vip') {
1793
			$interface = get_configured_vip_interface($interface);
1794
		}
1795
	}
1796
	return $interface;
1797
}
1798

    
1799
function get_interface_gateway($interface, &$dynamic = false) {
1800
	$interface = get_root_interface($interface);
1801

    
1802
	$gw = NULL;
1803
	$gwcfg = config_get_path("interfaces/{$interface}");
1804
	if (!empty($gwcfg['gateway'])) {
1805
		foreach (config_get_path('gateways/gateway_item', []) as $gateway) {
1806
			if (($gateway['name'] == $gwcfg['gateway']) && (is_ipaddrv4($gateway['gateway']))) {
1807
				$gw = $gateway['gateway'];
1808
				break;
1809
			}
1810
		}
1811
	}
1812

    
1813
	// for dynamic interfaces we handle them through the $interface_router file.
1814
	if (($gw == NULL || !is_ipaddrv4($gw)) && !is_ipaddrv4($gwcfg['ipaddr'])) {
1815
		$realif = get_real_interface($interface);
1816
		if (file_exists(g_get('tmp_path', '') . "/{$realif}_router")) {
1817
			$gw = trim(@file_get_contents(g_get('tmp_path', '') . "/{$realif}_router"), " \n");
1818
			$dynamic = true;
1819
		}
1820
		if (file_exists(g_get('tmp_path', '') . "/{$realif}_defaultgw")) {
1821
			$dynamic = "default";
1822
		}
1823

    
1824
	}
1825

    
1826
	/* return gateway */
1827
	return ($gw);
1828
}
1829

    
1830
function get_interface_gateway_last($interface, $family = 'inet') {
1831
	$interface = get_root_interface($interface);
1832
	$realif = get_real_interface($interface);
1833
	$suffix = ($family == 'inet6') ? 'v6' : '';
1834
	if (file_exists(g_get('tmp_path', '') . "/{$realif}_router{$suffix}.last")) {
1835
		return trim(@file_get_contents(g_get('tmp_path', '') . "/{$realif}_router{$suffix}.last"), " \n");
1836
	}
1837
	return '';
1838
}
1839

    
1840
function get_interface_gateway_v6($interface, &$dynamic = false) {
1841
	$interface = get_root_interface($interface);
1842

    
1843
	$gw = NULL;
1844
	$gwcfg = config_get_path("interfaces/{$interface}");
1845
	if (!empty($gwcfg['gatewayv6'])) {
1846
		foreach (config_get_path('gateways/gateway_item', []) as $gateway) {
1847
			if (($gateway['name'] == $gwcfg['gatewayv6']) && (is_ipaddrv6($gateway['gateway']))) {
1848
				$gw = $gateway['gateway'];
1849
				break;
1850
			}
1851
		}
1852
	}
1853

    
1854
	// for dynamic interfaces we handle them through the $interface_router file.
1855
	if (($gw == NULL || !is_ipaddrv6($gw)) && !is_ipaddrv6($gwcfg['ipaddrv6'])) {
1856
		$realif = get_real_interface($interface);
1857
		if (file_exists(g_get('tmp_path', '') . "/{$realif}_routerv6")) {
1858
			$gw = trim(file_get_contents(g_get('tmp_path', '') . "/{$realif}_routerv6"), " \n");
1859
			$dynamic = true;
1860
		}
1861
		if (file_exists(g_get('tmp_path', '') . "/{$realif}_defaultgwv6")) {
1862
			$dynamic = "default";
1863
		}
1864
	}
1865
	/* return gateway */
1866
	return ($gw);
1867
}
1868

    
1869
/* Check a IP address against a gateway IP or name
1870
 * to verify it's address family */
1871
function validate_address_family($ipaddr, $gwname, $disabled = false) {
1872
	$v4ip = false;
1873
	$v6ip = false;
1874
	$v4gw = false;
1875
	$v6gw = false;
1876

    
1877
	if (is_ipaddrv4($ipaddr)) {
1878
		$v4ip = true;
1879
	}
1880
	if (is_ipaddrv6($ipaddr)) {
1881
		$v6ip = true;
1882
	}
1883
	if (is_ipaddrv4($gwname)) {
1884
		$v4gw = true;
1885
	}
1886
	if (is_ipaddrv6($gwname)) {
1887
		$v6gw = true;
1888
	}
1889

    
1890
	if ($v4ip && $v4gw) {
1891
		return true;
1892
	}
1893
	if ($v6ip && $v6gw) {
1894
		return true;
1895
	}
1896

    
1897
	/* still no match, carry on, lookup gateways */
1898
	if (is_ipaddrv4(lookup_gateway_ip_by_name($gwname, $disabled))) {
1899
		$v4gw = true;
1900
	}
1901
	if (is_ipaddrv6(lookup_gateway_ip_by_name($gwname, $disabled))) {
1902
		$v6gw = true;
1903
	}
1904

    
1905
	/* Check gateways including disabled gateways and gateways for disabled
1906
	 * interfaces.
1907
	 * https://redmine.pfsense.org/issues/8846
1908
	 */
1909
	$gw_array = get_gateways(GW_CACHE_DISABLED | GW_CACHE_INACTIVE);
1910
	if (is_array($gw_array[$gwname])) {
1911
		switch ($gw_array[$gwname]['ipprotocol']) {
1912
			case "inet":
1913
				$v4gw = true;
1914
				break;
1915
			case "inet6":
1916
				$v6gw = true;
1917
				break;
1918
		}
1919
	}
1920

    
1921
	if ($v4ip && $v4gw) {
1922
		return true;
1923
	}
1924
	if ($v6ip && $v6gw) {
1925
		return true;
1926
	}
1927

    
1928
	return false;
1929
}
1930

    
1931
/* check if a interface is part of a gateway group */
1932
function interface_gateway_group_member($interface, $gwgroup_name = "") {
1933
	if (empty(config_get_path('gateways/gateway_group'))) {
1934
		return false;
1935
	}
1936

    
1937
	$gateways_arr = get_gateways(GW_CACHE_LOCALHOST);
1938
	foreach (config_get_path('gateways/gateway_group', []) as $group) {
1939
		if (is_array($group['item'])) {
1940
			foreach ($group['item'] as $item) {
1941
				$elements = explode("|", $item);
1942
				$gwname = $elements[0];
1943
				if ($interface == $gateways_arr[$gwname]['interface'] &&
1944
				    (empty($gwgroup_name) || $gwgroup_name == $group['name'])) {
1945
					unset($gateways_arr);
1946
					return true;
1947
				}
1948
			}
1949
		}
1950
	}
1951
	unset($gateways_arr);
1952

    
1953
	return false;
1954
}
1955

    
1956
function gateway_is_gwgroup_member($name, $detail=false) {
1957
	$members = array();
1958
	foreach (config_get_path('gateways/gateway_group', []) as $group) {
1959
		if (is_array($group['item'])) {
1960
			foreach ($group['item'] as $item) {
1961
				list($gwname, $tier, $vipname) = explode("|", $item);
1962
				if ($name == $gwname) {
1963
					if ($detail) {
1964
						$newitem = array();
1965
						$newitem['name'] = $group['name'];
1966
						$newitem['tier'] = $tier;
1967
						$newitem['vipname'] = $vipname;
1968
						$members[] = $newitem;
1969
					} else {
1970
						$members[] = $group['name'];
1971
					}
1972
				}
1973
			}
1974
		}
1975
	}
1976

    
1977
	if (empty($members)) {
1978
		return false;
1979
	}
1980
	
1981
	return $members;
1982
}
1983
/*
1984
  Check the proposed gateway settings to see if they are valid.
1985
  $gateway_settings - the proposed array of proposed gateway settings
1986
  $id - the index of the gateway proposed to be modified (otherwise "" if adding a new gateway)
1987
  $parent_ip - the IP (v4 or v6) address about to be set on the corresponding interface (if any)
1988
  $parent_sn - the subnet about to be set on the corresponding interface (if any)
1989
  (Note: the above 2 parameters allow gateway parameters to be validated concurrently with saving
1990
   an interface, before the new interface parameters are actually saved in the config.)
1991
  Return completed $input_errors array if there is any problem.
1992
  Otherwise return an empty $input_errors array
1993
*/
1994
function validate_gateway($gateway_settings, $id = "", $parent_ip = "", $parent_sn = "", $json = false) {
1995
	if ($json) {
1996
		$gateway_settings = json_decode($gateway_settings, true);
1997
	}
1998
	global $gateway_state_kill_modes;
1999

    
2000
	refresh_gateways(); // make sure we're validating against a current gateway list
2001
	$a_gateways = get_gateways(GW_CACHE_INDEXED);
2002
	$input_errors = array();
2003

    
2004
	/* input validation */
2005
	$reqdfields = explode(" ", "name interface");
2006
	$reqdfieldsn = array(gettext("Name"), gettext("Interface"));
2007

    
2008
	do_input_validation($gateway_settings, $reqdfields, $reqdfieldsn, $input_errors);
2009

    
2010
	if (!isset($gateway_settings['name'])) {
2011
		$input_errors[] = "A valid gateway name must be specified.";
2012
	}
2013
	if (!is_validaliasname($gateway_settings['name'])) {
2014
		$input_errors[] = invalidaliasnamemsg($gateway_settings['name'], gettext("gateway"));
2015
	} else if (isset($gateway_settings['disabled'])) {
2016
		// We have a valid gateway name that the user wants to mark as disabled.
2017
		// Check if the gateway name is used in any gateway group.
2018
		foreach (config_get_path('gateways/gateway_group', []) as $group) {
2019
			foreach ($group['item'] as $item) {
2020
				$items = explode("|", $item);
2021
				if ($items[0] == $gateway_settings['name']) {
2022
					$input_errors[] = sprintf(gettext('Gateway "%1$s" cannot be disabled because it is in use on Gateway Group "%2$s"'), $gateway_settings['name'], $group['name']);
2023
				}
2024
			}
2025
		}
2026

    
2027
		// Check if the gateway name is used in any enabled Static Route.
2028
		foreach (config_get_path('staticroutes/route', []) as $route) {
2029
			if ($route['gateway'] == $gateway_settings['name']) {
2030
				if (!isset($route['disabled'])) {
2031
					// There is a static route that uses this gateway and is enabled (not disabled).
2032
					$input_errors[] = sprintf(gettext('Gateway "%1$s" cannot be disabled because it is in use on Static Route "%2$s"'), $gateway_settings['name'], $route['network']);
2033
				}
2034
			}
2035
		}
2036

    
2037
		// Check if the gateway name is used by any DNS Server
2038
		for ($dnsgw_counter = 1; !is_null($dnsgw = config_get_path("system/dns{$dnsgw_counter}gw")); $dnsgw_counter++) {
2039
			if ($gateway_settings['name'] == $dnsgw) {
2040
				// The user wants to disable this gateway, but there is a static route to the DNS server that refers to the gateway.
2041
				$input_errors[] = sprintf(gettext('Gateway "%1$s" cannot be disabled because it is in use by DNS Server "%2$s"'), $gateway_settings['name'], $dnsgw);
2042
			}
2043
		}
2044
	}
2045
	/* skip system gateways which have been automatically added */
2046
	if (($gateway_settings['gateway'] && (!is_ipaddr($gateway_settings['gateway'])) && ($gateway_settings['attribute'] !== "system")) && ($gateway_settings['gateway'] != "dynamic")) {
2047
		$input_errors[] = gettext("A valid gateway IP address must be specified.");
2048
	}
2049

    
2050
	if ($gateway_settings['gateway'] && is_ipaddr($gateway_settings['gateway'])) {
2051
		if (is_ipaddrv4($gateway_settings['gateway'])) {
2052
			if ($parent_ip == '') {
2053
				$parent_ip = get_interface_ip($gateway_settings['interface']);
2054
				$parent_sn = get_interface_subnet($gateway_settings['interface']);
2055
			}
2056
			if (empty($parent_ip) || empty($parent_sn)) {
2057
				$input_errors[] = gettext("Cannot add IPv4 Gateway Address because no IPv4 address could be found on the interface.");
2058
			} elseif (!isset($gateway_settings["nonlocalgateway"])) {
2059
				$subnets = array(gen_subnet($parent_ip, $parent_sn) . "/" . $parent_sn);
2060
				$vips = link_interface_to_vips($gateway_settings['interface']);
2061
				if (is_array($vips)) {
2062
					foreach ($vips as $vip) {
2063
						if (!is_ipaddrv4($vip['subnet'])) {
2064
							continue;
2065
						}
2066
						$subnets[] = gen_subnet($vip['subnet'], $vip['subnet_bits']) . "/" . $vip['subnet_bits'];
2067
					}
2068
				}
2069

    
2070
				$found = false;
2071
				foreach ($subnets as $subnet) {
2072
					if (ip_in_subnet($gateway_settings['gateway'], $subnet)) {
2073
						$found = true;
2074
						break;
2075
					}
2076
				}
2077

    
2078
				if ($found === false) {
2079
					$input_errors[] = sprintf(gettext("The gateway address %s does not lie within one of the chosen interface's subnets."), $gateway_settings['gateway']);
2080
				}
2081
			}
2082
		} else if (is_ipaddrv6($gateway_settings['gateway'])) {
2083
			/* do not do a subnet match on a link local address, it's valid */
2084
			if (!is_linklocal($gateway_settings['gateway'])) {
2085
				if ($parent_ip == '') {
2086
					$parent_ip = get_interface_ipv6($gateway_settings['interface']);
2087
					$parent_sn = get_interface_subnetv6($gateway_settings['interface']);
2088
				}
2089
				if (empty($parent_ip) || empty($parent_sn)) {
2090
					$input_errors[] = gettext("Cannot add IPv6 Gateway Address because no IPv6 address could be found on the interface.");
2091
				} elseif (!isset($gateway_settings["nonlocalgateway"])) {
2092
					$subnets = array(gen_subnetv6($parent_ip, $parent_sn) . "/" . $parent_sn);
2093
					$vips = link_interface_to_vips($gateway_settings['interface']);
2094
					if (is_array($vips)) {
2095
						foreach ($vips as $vip) {
2096
							if (!is_ipaddrv6($vip['subnet'])) {
2097
								continue;
2098
							}
2099
							$subnets[] = gen_subnetv6($vip['subnet'], $vip['subnet_bits']) . "/" . $vip['subnet_bits'];
2100
						}
2101
					}
2102

    
2103
					$found = false;
2104
					foreach ($subnets as $subnet) {
2105
						if (ip_in_subnet($gateway_settings['gateway'], $subnet)) {
2106
							$found = true;
2107
							break;
2108
						}
2109
					}
2110

    
2111
					if ($found === false) {
2112
						$input_errors[] = sprintf(gettext("The gateway address %s does not lie within one of the chosen interface's subnets."), $gateway_settings['gateway']);
2113
					}
2114
				}
2115
			}
2116
		}
2117

    
2118
		if (!empty(config_get_path("interfaces/{$gateway_settings['interface']}/ipaddr"))) {
2119
			if (is_ipaddr(config_get_path("interfaces/{$gateway_settings['interface']}/ipaddr")) && (empty($gateway_settings['gateway']) || $gateway_settings['gateway'] == "dynamic")) {
2120
				$input_errors[] = gettext("Dynamic gateway values cannot be specified for interfaces with a static IPv4 configuration.");
2121
			}
2122
		}
2123
		if (!empty(config_get_path("interfaces/{$gateway_settings['interface']}/ipaddrv6"))) {
2124
			if (is_ipaddr(config_get_path("interfaces/{$gateway_settings['interface']}/ipaddrv6")) && (empty($gateway_settings['gateway']) || $gateway_settings['gateway'] == "dynamic")) {
2125
				$input_errors[] = gettext("Dynamic gateway values cannot be specified for interfaces with a static IPv6 configuration.");
2126
			}
2127
		}
2128
	}
2129
	if (($gateway_settings['monitor'] != "") && ($gateway_settings['monitor'] != "dynamic")) {
2130
		validateipaddr($gateway_settings['monitor'], IPV4V6, "Monitor IP", $input_errors, false);
2131
	}
2132
	if (isset($gateway_settings['data_payload']) && is_numeric($gateway_settings['data_payload']) && $gateway_settings['data_payload'] < 0) {
2133
		$input_errors[] = gettext("A valid data payload must be specified.");
2134
	}
2135
	if (!empty($gateway_settings['skip_rules_gw_down']) && !array_key_exists($gateway_settings['skip_rules_gw_down'], $gateway_state_kill_modes)) {
2136
		$input_errors[] = gettext("Please select a valid State Killing on Gateway Failure mode.");
2137
	}
2138
	/* only allow correct IPv4 and IPv6 gateway addresses */
2139
	if (($gateway_settings['gateway'] <> "") && is_ipaddr($gateway_settings['gateway']) && $gateway_settings['gateway'] != "dynamic") {
2140
		if (is_ipaddrv6($gateway_settings['gateway']) && ($gateway_settings['ipprotocol'] == "inet")) {
2141
			$input_errors[] = sprintf(gettext("The IPv6 gateway address '%s' can not be used as a IPv4 gateway."), $gateway_settings['gateway']);
2142
		}
2143
		if (is_ipaddrv4($gateway_settings['gateway']) && ($gateway_settings['ipprotocol'] == "inet6")) {
2144
			$input_errors[] = sprintf(gettext("The IPv4 gateway address '%s' can not be used as a IPv6 gateway."), $gateway_settings['gateway']);
2145
		}
2146
	}
2147
	/* only allow correct IPv4 and IPv6 monitor addresses */
2148
	if (($gateway_settings['monitor'] <> "") && is_ipaddr($gateway_settings['monitor']) && $gateway_settings['monitor'] != "dynamic") {
2149
		if (is_ipaddrv6($gateway_settings['monitor']) && ($gateway_settings['ipprotocol'] == "inet")) {
2150
			$input_errors[] = sprintf(gettext("The IPv6 monitor address '%s' can not be used on a IPv4 gateway."), $gateway_settings['monitor']);
2151
		}
2152
		if (is_ipaddrv4($gateway_settings['monitor']) && ($gateway_settings['ipprotocol'] == "inet6")) {
2153
			$input_errors[] = sprintf(gettext("The IPv4 monitor address '%s' can not be used on a IPv6 gateway."), $gateway_settings['monitor']);
2154
		}
2155
	}
2156

    
2157
	if (isset($gateway_settings['name'])) {
2158
		/* check for overlaps */
2159
		foreach ($a_gateways as $gateway) {
2160
			if (isset($id) && ($a_gateways[$id]) && ($a_gateways[$id] === $gateway)) {
2161
				if ($gateway['name'] != $gateway_settings['name']) {
2162
					$input_errors[] = gettext("Changing name on a gateway is not allowed.");
2163
				}
2164
				continue;
2165
			}
2166
			if ($gateway_settings['name'] <> "") {
2167
				if (($gateway['name'] <> "") && ($gateway_settings['name'] == $gateway['name']) && ($gateway['attribute'] !== "system")) {
2168
					$input_errors[] = sprintf(gettext('The gateway name "%s" already exists.'), $gateway_settings['name']);
2169
					break;
2170
				}
2171
			}
2172
			if (is_ipaddr($gateway_settings['gateway'])) {
2173
				if (($gateway['gateway'] <> "") && ($gateway_settings['gateway'] == $gateway['gateway']) && ($gateway['attribute'] !== "system")) {
2174
					$input_errors[] = sprintf(gettext('The gateway IP address "%s" already exists.'), $gateway_settings['gateway']);
2175
					break;
2176
				}
2177
			}
2178
			if (is_ipaddr($gateway_settings['monitor'])) {
2179
				if (($gateway['monitor'] <> "") && ($gateway_settings['monitor'] == $gateway['monitor']) && ($gateway['attribute'] !== "system")) {
2180
					$input_errors[] = sprintf(gettext('The monitor IP address "%s" is already in use. A different monitor IP must be chosen.'), $gateway_settings['monitor']);
2181
					break;
2182
				}
2183
			}
2184
		}
2185
	}
2186

    
2187
	/* input validation of dpinger advanced parameters */
2188

    
2189
	$dpinger_default = return_dpinger_defaults();
2190
	$latencylow = $dpinger_default['latencylow'];
2191
	if ($gateway_settings['latencylow']) {
2192
		if (!is_numeric($gateway_settings['latencylow'])) {
2193
			$input_errors[] = gettext("The low latency threshold needs to be a numeric value.");
2194
		} else if ($gateway_settings['latencylow'] < 1) {
2195
			$input_errors[] = gettext("The low latency threshold needs to be positive.");
2196
		} else {
2197
			$latencylow = $gateway_settings['latencylow'];
2198
		}
2199
	}
2200

    
2201
	$latencyhigh = $dpinger_default['latencyhigh'];
2202
	if ($gateway_settings['latencyhigh']) {
2203
		if (!is_numeric($gateway_settings['latencyhigh'])) {
2204
			$input_errors[] = gettext("The high latency threshold needs to be a numeric value.");
2205
		} else if ($gateway_settings['latencyhigh'] < 1) {
2206
			$input_errors[] = gettext("The high latency threshold needs to be positive.");
2207
		} else {
2208
			$latencyhigh = $gateway_settings['latencyhigh'];
2209
		}
2210
	}
2211

    
2212
	$losslow = $dpinger_default['losslow'];
2213
	if ($gateway_settings['losslow']) {
2214
		if (!is_numeric($gateway_settings['losslow'])) {
2215
			$input_errors[] = gettext("The low Packet Loss threshold needs to be a numeric value.");
2216
		} else if ($gateway_settings['losslow'] < 1) {
2217
			$input_errors[] = gettext("The low Packet Loss threshold needs to be positive.");
2218
		} else if ($gateway_settings['losslow'] >= 100) {
2219
			$input_errors[] = gettext("The low Packet Loss threshold needs to be less than 100.");
2220
		} else {
2221
			$losslow = $gateway_settings['losslow'];
2222
		}
2223
	}
2224

    
2225
	$losshigh = $dpinger_default['losshigh'];
2226
	if ($gateway_settings['losshigh']) {
2227
		if (!is_numeric($gateway_settings['losshigh'])) {
2228
			$input_errors[] = gettext("The high Packet Loss threshold needs to be a numeric value.");
2229
		} else if ($gateway_settings['losshigh'] < 1) {
2230
			$input_errors[] = gettext("The high Packet Loss threshold needs to be positive.");
2231
		} else if ($gateway_settings['losshigh'] > 100) {
2232
			$input_errors[] = gettext("The high Packet Loss threshold needs to be 100 or less.");
2233
		} else {
2234
			$losshigh = $gateway_settings['losshigh'];
2235
		}
2236
	}
2237

    
2238
	$time_period = $dpinger_default['time_period'];
2239
	if ($gateway_settings['time_period']) {
2240
		if (!is_numeric($gateway_settings['time_period'])) {
2241
			$input_errors[] = gettext("The time period over which results are averaged needs to be a numeric value.");
2242
		} else if ($gateway_settings['time_period'] < 1) {
2243
			$input_errors[] = gettext("The time period over which results are averaged needs to be positive.");
2244
		} else {
2245
			$time_period = $gateway_settings['time_period'];
2246
		}
2247
	}
2248

    
2249
	$interval = $dpinger_default['interval'];
2250
	if ($gateway_settings['interval']) {
2251
		if (!is_numeric($gateway_settings['interval'])) {
2252
			$input_errors[] = gettext("The probe interval needs to be a numeric value.");
2253
		} else if ($gateway_settings['interval'] < 1) {
2254
			$input_errors[] = gettext("The probe interval needs to be positive.");
2255
		} else {
2256
			$interval = $gateway_settings['interval'];
2257
		}
2258
	}
2259

    
2260
	$loss_interval = $dpinger_default['loss_interval'];
2261
	if ($gateway_settings['loss_interval']) {
2262
		if (!is_numeric($gateway_settings['loss_interval'])) {
2263
			$input_errors[] = gettext("The loss interval needs to be a numeric value.");
2264
		} else if ($gateway_settings['loss_interval'] < 1) {
2265
			$input_errors[] = gettext("The loss interval setting needs to be positive.");
2266
		} else {
2267
			$loss_interval = $gateway_settings['loss_interval'];
2268
		}
2269
	}
2270

    
2271
	$alert_interval = $dpinger_default['alert_interval'];
2272
	if ($gateway_settings['alert_interval']) {
2273
		if (!is_numeric($gateway_settings['alert_interval'])) {
2274
			$input_errors[] = gettext("The alert interval needs to be a numeric value.");
2275
		} else if ($gateway_settings['alert_interval'] < 1) {
2276
			$input_errors[] = gettext("The alert interval setting needs to be positive.");
2277
		} else {
2278
			$alert_interval = $gateway_settings['alert_interval'];
2279
		}
2280
	}
2281

    
2282
	if ($latencylow >= $latencyhigh) {
2283
		$input_errors[] = gettext(
2284
		    "The high latency threshold needs to be greater than the low latency threshold");
2285
	}
2286

    
2287
	if ($losslow >= $losshigh) {
2288
		$input_errors[] = gettext(
2289
		    "The high packet loss threshold needs to be higher than the low packet loss threshold");
2290
	}
2291

    
2292
	// If the loss interval is less than latencyhigh, then high latency could never be recorded
2293
	// because those high latency packets would be considered as lost. So do not allow that.
2294
	if ($latencyhigh > $loss_interval) {
2295
		$input_errors[] = gettext("The loss interval needs to be greater than or equal to the high latency threshold.");
2296
	}
2297

    
2298
	// Ensure that the time period is greater than 2 times the probe interval plus the loss interval.
2299
	if (($interval * 2 + $loss_interval) >= $time_period) {
2300
		$input_errors[] = gettext("The time period needs to be greater than twice the probe interval plus the loss interval.");
2301
	}
2302

    
2303
	// There is no point recalculating the average latency and loss more often than the probe interval.
2304
	// So the alert interval needs to be >= probe interval.
2305
	if ($interval > $alert_interval) {
2306
		$input_errors[] = gettext("The alert interval needs to be greater than or equal to the probe interval.");
2307
	}
2308

    
2309
	if ($json) {
2310
		return json_encode($input_errors);
2311
	}
2312
	return $input_errors;
2313
}
2314

    
2315
// Save gateway settings.
2316
// $gateway_settings - the array of gateway setting parameters
2317
// $realid - the index of the gateway to be modified (otherwise "" if adding a new gateway)
2318

    
2319
// This function is responsible to:
2320
//   Setup the gateway parameter structure from the gateway settings input parameter
2321
//   Save the structure into the config
2322
//   Remove any run-time settings from gateway parameters that are changed (e.g. remove routes to addresses that are changing)
2323

    
2324
// A subsequent "apply" step will implement the added/changed gateway.
2325

    
2326
function save_gateway($gateway_settings, $realid = "", $json = false) {
2327
	if ($json) {
2328
		$gateway_settings = json_decode($gateway_settings, true);
2329
	}
2330

    
2331
	config_init_path('gateways/gateway_item');
2332
	$reloadif = "";
2333
	$gateway = array();
2334

    
2335
	if (empty($gateway_settings['interface'])) {
2336
		$gateway['interface'] = $gateway_settings['friendlyiface'];
2337
	} else {
2338
		$gateway['interface'] = $gateway_settings['interface'];
2339
	}
2340
	if (is_ipaddr($gateway_settings['gateway'])) {
2341
		$gateway['gateway'] = $gateway_settings['gateway'];
2342
	} else {
2343
		$gateway['gateway'] = "dynamic";
2344
	}
2345
	$gateway['name'] = $gateway_settings['name'];
2346
	$gateway['weight'] = $gateway_settings['weight'];
2347
	$gateway['ipprotocol'] = $gateway_settings['ipprotocol'];
2348
	if ($gateway_settings['interval']) {
2349
		$gateway['interval'] = $gateway_settings['interval'];
2350
	}
2351

    
2352
	if ($gateway_settings['time_period']) {
2353
		$gateway['time_period'] = $gateway_settings['time_period'];
2354
	}
2355
	if ($gateway_settings['alert_interval']) {
2356
		$gateway['alert_interval'] = $gateway_settings['alert_interval'];
2357
	}
2358

    
2359
	$gateway['descr'] = $gateway_settings['descr'];
2360
	if ($gateway_settings['monitor_disable'] == "yes") {
2361
		$gateway['monitor_disable'] = true;
2362
	}
2363
	if ($gateway_settings['action_disable'] == "yes") {
2364
		$gateway['action_disable'] = true;
2365
	}
2366
	if ($gateway_settings['nonlocalgateway'] == "yes") {
2367
		$gateway['nonlocalgateway'] = true;
2368
	}
2369
	if ($gateway_settings['dpinger_dont_add_static_route'] == "yes") {
2370
		$gateway['dpinger_dont_add_static_route'] = true;
2371
	}
2372
	if ($gateway_settings['force_down'] == "yes") {
2373
		$gateway['force_down'] = true;
2374
	}
2375
	$gateway['gw_down_kill_states'] = $gateway_settings['gw_down_kill_states'];
2376
	if (is_ipaddr($gateway_settings['monitor'])) {
2377
		$gateway['monitor'] = $gateway_settings['monitor'];
2378
	}
2379
	if (isset($gateway_settings['data_payload']) && is_numeric($gateway_settings['data_payload']) && $gateway_settings['data_payload'] >= 0) {
2380
		$gateway['data_payload'] = $gateway_settings['data_payload'];
2381
	}
2382

    
2383
	$a_gateway_item = config_get_path("gateways/gateway_item/{$realid}");
2384
	/* NOTE: If gateway ip is changed need to cleanup the old static interface route */
2385
	if ($gateway_settings['monitor'] != "dynamic" &&
2386
	    !empty($a_gateway_item) &&
2387
	    is_ipaddr($a_gateway_item['gateway']) &&
2388
	    $gateway['gateway'] != $a_gateway_item['gateway'] &&
2389
	    isset($a_gateway_item["nonlocalgateway"])) {
2390
		route_del($a_gateway_item['gateway']);
2391
	}
2392

    
2393
	/* NOTE: If monitor ip is changed need to cleanup the old static route */
2394
	if ($gateway_settings['monitor'] != "dynamic" &&
2395
	    !empty($a_gateway_item) &&
2396
	    is_ipaddr($a_gateway_item['monitor']) &&
2397
	    $gateway_settings['monitor'] != $a_gateway_item['monitor'] &&
2398
	    $gateway['gateway'] != $a_gateway_item['monitor']) {
2399
		route_del($a_gateway_item['monitor']);
2400
	}
2401

    
2402
	if ($gateway_settings['defaultgw'] == "yes" || $gateway_settings['defaultgw'] == "on") {
2403
		// a new default gateway is being saved.
2404
		$i = 0;
2405
		/* remove the default gateway bits for all gateways with the same address family */
2406
		foreach (config_get_path('gateways/gateway_item', []) as $gw) {
2407
			if ($gateway['ipprotocol'] == $gw['ipprotocol']) {
2408
				if ($gw['interface'] != $gateway_settings['interface'] &&
2409
					($gw['name'] == config_get_path('gateways/defaultgw4', "") ||
2410
					 $gw['name'] == config_get_path('gateways/defaultgw6', ""))) {
2411
					// remember the old default gateway interface to call with a "interface reconfigure" event.
2412
					$reloadif = $gw['interface'];
2413
				}
2414
			}
2415
			$i++;
2416
		}
2417
		if ($gateway['ipprotocol'] == "inet") {
2418
			config_set_path('gateways/defaultgw4', $gateway['name']);
2419
		} elseif ($gateway['ipprotocol'] == "inet6") {
2420
			config_set_path('gateways/defaultgw6', $gateway['name']);
2421
		}
2422
	}
2423

    
2424
	if ($gateway_settings['latencylow']) {
2425
		$gateway['latencylow'] = $gateway_settings['latencylow'];
2426
	}
2427
	if ($gateway_settings['latencyhigh']) {
2428
		$gateway['latencyhigh'] = $gateway_settings['latencyhigh'];
2429
	}
2430
	if ($gateway_settings['losslow']) {
2431
		$gateway['losslow'] = $gateway_settings['losslow'];
2432
	}
2433
	if ($gateway_settings['losshigh']) {
2434
		$gateway['losshigh'] = $gateway_settings['losshigh'];
2435
	}
2436
	if ($gateway_settings['loss_interval']) {
2437
		$gateway['loss_interval'] = $gateway_settings['loss_interval'];
2438
	}
2439

    
2440
	$a_gateway_item = config_get_path("gateways/gateway_item/{$realid}");
2441
	/* reload IPsec and OpenVPN on gateway IP, 'Mark Gateway as Down', or 'Disabled' option change
2442
	 * see https://redmine.pfsense.org/issues/13076 */
2443
	if (!empty($a_gateway_item) &&
2444
	    (isset($gateway['disabled']) ^ isset($a_gateway_item['disabled'])) ||
2445
	    (!isset($a_gateway_item['disabled']) &&
2446
	    ((($gateway['monitor'] != "dynamic") &&
2447
	    is_ipaddr($a_gateway_item['gateway']) &&
2448
	    ($gateway['gateway'] != $a_gateway_item['gateway'])) ||
2449
	    (isset($gateway['force_down']) ^ isset($a_gateway_item['force_down']))))) {
2450
		$reloadvpn = true;
2451
	}
2452

    
2453
	/* when saving the manual gateway we use the attribute which has the corresponding id */
2454
	if (isset($realid) && $a_gateway_item) {
2455
		$preserve_disabled = isset($a_gateway_item['disabled']);
2456
		$a_gateway_item = $gateway;
2457
		if ($preserve_disabled) {
2458
			$a_gateway_item['disabled'] = true;
2459
		}
2460
		config_set_path("gateways/gateway_item/{$realid}", $a_gateway_item);
2461
	} else {
2462
		config_set_path("gateways/gateway_item/", $gateway);
2463
	}
2464
	gateway_set_enabled($gateway_settings['name'], !isset($gateway_settings['disabled']));
2465

    
2466
	mark_subsystem_dirty('staticroutes');
2467

    
2468
	write_config("Gateway settings changed");
2469

    
2470
	if ($reloadvpn) {
2471
		send_event("service reload ipsec " . escapeshellarg($gateway['name']));
2472
		send_event("service reload openvpn " . escapeshellarg($gateway['name']));
2473
	}
2474

    
2475
	if (!empty($reloadif)) {
2476
		send_event("interface reconfigure {$reloadif}");
2477
	}
2478
}
2479

    
2480
function gateway_set_enabled($name, $enabled) {
2481
	$gwlist = config_get_path('gateways/gateway_item', []);
2482
	if (is_array($gwlist)) {
2483
		foreach($gwlist as $key => $item) {
2484
			if ($item['name'] == $name) {
2485
				$gateway = &$gwlist[$key];
2486
			}
2487
		}
2488
	}
2489
	if (!isset($gateway)) {
2490
		return;
2491
	}
2492
	if ($enabled) {
2493
		unset($gateway['disabled']);
2494
	} else {
2495
		/* Check if the gateway was enabled but changed to disabled. */
2496
		if (!isset($gateway['disabled'])) {
2497
			/*  If the disabled gateway was the default route, remove the default route */
2498
			if (is_ipaddr($gateway['gateway'])) {
2499
				$inet = (!is_ipaddrv4($gateway['gateway']) ? 'inet6' : 'inet');
2500
				if ($inet == 'inet') {
2501
					$cgw = route_get_default('inet');
2502
				} else {
2503
					$cgw = route_get_default('inet6');
2504
				}
2505
				if ($gateway['gateway'] == $cgw) {
2506
					route_del("default", $inet);
2507
				}
2508
			}
2509
			$gateway['disabled'] = true;
2510
		}
2511
	}
2512
	unset($gateway);
2513
	config_set_path('gateways/gateway_item', $gwlist);
2514
}
2515

    
2516
function gateway_or_gwgroup_exists($name) {
2517
	foreach(config_get_path('gateways/gateway_item', []) as $item) {
2518
		if ($item['name'] == $name) {
2519
			return true;
2520
		}
2521
	}
2522
	foreach(config_get_path('gateways/gateway_group', []) as $item) {
2523
		if ($item['name'] == $name) {
2524
			return true;
2525
		}
2526
	}
2527
	return false;
2528
}
2529

    
2530
// These two replacement functions avoid the need to call return_gateways_array() multiple times and
2531
// allow system_gateways.php to get everything it needs in a single function call
2532
function gateway_getgwtiername($gways, $idx) {
2533
	$result = "";
2534
	$gwname = $gways[$idx]['name'];
2535

    
2536
	$gw = get_gateway_or_group_by_name($gwname, $gways);
2537

    
2538
	config_init_path('gateways');
2539
	$gw4 = config_get_path('gateways/defaultgw4', "");
2540
	$gw6 = config_get_path('gateways/defaultgw6', "");
2541
	if ($gw4 == $gwname || $gw6 == $gwname) {
2542
		$result = "Default";
2543
	} else {
2544
		if ($gw['ipprotocol'] == 'inet') {
2545
			$defgw = get_gateway_or_group_by_name($gw4, $gways);
2546
		} else {
2547
			$defgw = get_gateway_or_group_by_name($gw6, $gways);
2548
		}
2549

    
2550
		if ($defgw['type'] == "gatewaygroup") {
2551
			$detail = gateway_is_gwgroup_member($gwname, true);
2552
			foreach($detail as $gwitem) {
2553
				if ($gwitem['name'] == $defgw['name']) {
2554
					if (isset($gwitem['tier'])) {
2555
						$result = "Tier " . $gwitem['tier'];
2556
						break;
2557
					}
2558
				}
2559
			}
2560
		}
2561
    }
2562

    
2563
	if (!empty($result)) {
2564
		if ($gw['ipprotocol'] == "inet") {
2565
			$result .= " (IPv4)";
2566
		} elseif ($gw['ipprotocol'] == "inet6") {
2567
			$result .= " (IPv6)";
2568
		}
2569
	}
2570

    
2571
	return $result;
2572
}
2573

    
2574
function get_gateway_or_group_by_name($gwname, $gateways_arr) {
2575
	foreach ($gateways_arr as $gw) {
2576
		if ($gw['name'] == $gwname) {
2577
			$gw['type'] = 'gateway';
2578
			return $gw;
2579
		}
2580
	}
2581

    
2582
	config_init_path('gateways/gateway_group');
2583
	foreach (config_get_path('gateways/gateway_group', []) as $gwg) {
2584
		if ($gwg['name'] == $gwname) {
2585
			$gwg['type'] = 'gatewaygroup';
2586
			return $gwg;
2587
		}
2588
	}
2589

    
2590
	return false;
2591
}
2592

    
2593
// Compose a list of available gateways but without the need to call return_gateways_array() multiple times
2594
// Previously that function could be called eight times per gateway!
2595
function available_default_gateways() {
2596
	$gways = get_gateways(GW_CACHE_DISABLED | GW_CACHE_INACTIVE);
2597

    
2598
	$items4 = array();
2599
	$items6 = array();
2600
	$items4[''] = "Automatic";
2601
	$items6[''] = "Automatic";
2602
	foreach($gways as $gw) {
2603
		$gwn = $gw['name'];
2604
		if ($gw['ipprotocol'] == "inet6") {
2605
			$items6[$gwn] = $gwn;
2606
		} else {
2607
			$items4[$gwn] = $gwn;
2608
		}
2609
	}
2610

    
2611
	$groups = return_gateway_groups_array(false);
2612
	foreach ($groups as $key => $group) {
2613
		$gwn = $group['descr'];
2614
		if ($group['ipprotocol'] == "inet6") {
2615
			$items6[$key] = "$key ($gwn)";
2616
		} else {
2617
			$items4[$key] = "$key ($gwn)";
2618
		}
2619
	}
2620
	$items4['-'] = "None";
2621
	$items6['-'] = "None";
2622

    
2623
	$defaults = array();
2624
	$defaults['v4'] = $items4;
2625
	$defaults['v6'] = $items6;
2626
	$defaults['defaultgw4'] = config_get_path('gateways/defaultgw4', "");
2627
	$defaults['defaultgw6'] = config_get_path('gateways/defaultgw6', "");
2628

    
2629
	return $defaults;
2630
}
2631

    
2632
function get_interface_gateway_name($friendlyiface, $family = 'inet') {
2633
	/* Try to locate gateway directly (e.g. static) */
2634
	if ($family == 'inet') {
2635
		$gw = config_get_path("interfaces/{$friendlyiface}/gateway", '');
2636
	} else {
2637
		$gw = config_get_path("interfaces/{$friendlyiface}/gatewayv6", '');
2638
	}
2639

    
2640
	/* If the gateway is empty that means it's dynamic so we have to hunt it down */
2641
	if (empty($gw)) {
2642
		/* Check each gateway entry for a dynamic gateway matching
2643
		 * this interface and the requested address family.
2644
		 */
2645
		foreach (get_gateways(GW_CACHE_ALL) as $gwname => $gwcfg) {
2646
			if (empty($gwcfg) ||
2647
			    !is_array($gwcfg) ||
2648
			    ($gwcfg['dynamic'] == false) ||
2649
			    ($gwcfg['friendlyiface'] != $friendlyiface) ||
2650
			    ($gwcfg['ipprotocol'] != $family)) {
2651
				continue;
2652
			}
2653
			$gw = $gwname;
2654
			break;
2655
		}
2656
	}
2657
	return $gw;
2658
}
2659

    
2660
?>
(21-21/61)