| 1028 |
1028 |
if ($mode == 'server') {
|
| 1029 |
1029 |
|
| 1030 |
1030 |
list($ip, $cidr) = openvpn_gen_tunnel_network($settings['tunnel_network']);
|
| 1031 |
|
list($ipv6, $prefix) = openvpn_gen_tunnel_network($settings['tunnel_networkv6']);
|
|
1031 |
if (!empty($settings['tunnel_networkv6_type']) && $settings['tunnel_networkv6_type'] === 'pd6') {
|
|
1032 |
$pd6_subnet = openvpn_get_dhcp6pd_subnet(
|
|
1033 |
$settings['tunnel_track6_interface'] ?? 'wan',
|
|
1034 |
(int)($settings['tunnel_track6_prefix_id'] ?? 0)
|
|
1035 |
);
|
|
1036 |
list($ipv6, $prefix) = $pd6_subnet ? explode('/', $pd6_subnet) : ['', ''];
|
|
1037 |
} else {
|
|
1038 |
list($ipv6, $prefix) = openvpn_gen_tunnel_network($settings['tunnel_networkv6']);
|
|
1039 |
}
|
| 1032 |
1040 |
$mask = gen_subnet_mask($cidr);
|
| 1033 |
1041 |
|
| 1034 |
1042 |
// configure tls modes
|
| ... | ... | |
| 2419 |
2427 |
return array($ipv6_1, $ipv6_2);
|
| 2420 |
2428 |
}
|
| 2421 |
2429 |
|
|
2430 |
/**
|
|
2431 |
* Given a WAN interface (friendly name, e.g. "wan") and a DHCPv6 PD
|
|
2432 |
* prefix ID integer, return the corresponding /64 subnet carved from the
|
|
2433 |
* delegated prefix currently on that interface.
|
|
2434 |
*
|
|
2435 |
* pfSense stores the delegation size in interfaces/<wan>/dhcp6-ia-pd-len as
|
|
2436 |
* the number of subnet bits (e.g. 4 for a /60 delegation), NOT the prefix
|
|
2437 |
* length itself. So the actual prefix length is (64 - dhcp6-ia-pd-len).
|
|
2438 |
*
|
|
2439 |
* Discovers the delegated base prefix by finding an existing track6 LAN
|
|
2440 |
* interface that tracks the same upstream WAN, reading its live IPv6 address,
|
|
2441 |
* and masking it down to the delegation prefix length. The prefix_id is
|
|
2442 |
* then OR'd into the lower subnet_bits of the 4th 16-bit group.
|
|
2443 |
*
|
|
2444 |
* Returns a string like "2001:db8:1:3::/64", or false on failure (logged).
|
|
2445 |
*/
|
|
2446 |
function openvpn_get_dhcp6pd_subnet($wan_iface, $prefix_id) {
|
|
2447 |
$subnet_bits = (int)config_get_path("interfaces/{$wan_iface}/dhcp6-ia-pd-len", 0);
|
|
2448 |
if ($subnet_bits < 1 || $subnet_bits > 16) {
|
|
2449 |
log_error("openvpn_get_dhcp6pd_subnet: invalid or missing dhcp6-ia-pd-len ({$subnet_bits}) for {$wan_iface}");
|
|
2450 |
return false;
|
|
2451 |
}
|
|
2452 |
|
|
2453 |
$pd_len = 64 - $subnet_bits;
|
|
2454 |
$max_id = (1 << $subnet_bits) - 1;
|
|
2455 |
if ((int)$prefix_id < 0 || (int)$prefix_id > $max_id) {
|
|
2456 |
log_error("openvpn_get_dhcp6pd_subnet: prefix_id {$prefix_id} out of range 0-{$max_id} for /{$pd_len} delegation on {$wan_iface}");
|
|
2457 |
return false;
|
|
2458 |
}
|
|
2459 |
|
|
2460 |
foreach (config_get_path('interfaces', []) as $if => $ifcfg) {
|
|
2461 |
if (!isset($ifcfg['ipaddrv6']) || $ifcfg['ipaddrv6'] !== 'track6') {
|
|
2462 |
continue;
|
|
2463 |
}
|
|
2464 |
if (!isset($ifcfg['track6-interface']) || $ifcfg['track6-interface'] !== $wan_iface) {
|
|
2465 |
continue;
|
|
2466 |
}
|
|
2467 |
$if_ipv6 = get_interface_ipv6($if);
|
|
2468 |
if (!is_ipaddrv6($if_ipv6)) {
|
|
2469 |
continue;
|
|
2470 |
}
|
|
2471 |
// Mask down to the /$pd_len delegation base, zeroing subnet+IID bits.
|
|
2472 |
$base = gen_subnetv6($if_ipv6, $pd_len);
|
|
2473 |
if (!is_ipaddrv6($base)) {
|
|
2474 |
continue;
|
|
2475 |
}
|
|
2476 |
// Expand to 8 colon-separated 16-bit groups.
|
|
2477 |
// The prefix_id occupies the lower $subnet_bits of group index 3
|
|
2478 |
// (bits 49-64). gen_subnetv6 has already zeroed those bits, so
|
|
2479 |
// we simply OR in $prefix_id.
|
|
2480 |
$expanded = Net_IPv6::uncompress($base, true);
|
|
2481 |
$groups = explode(':', $expanded);
|
|
2482 |
$groups[3] = sprintf('%04x', hexdec($groups[3]) | (int)$prefix_id);
|
|
2483 |
$subnet = Net_IPv6::compress(implode(':', $groups)) . '/64';
|
|
2484 |
return $subnet;
|
|
2485 |
}
|
|
2486 |
|
|
2487 |
log_error("openvpn_get_dhcp6pd_subnet: no active track6 interface found on {$wan_iface}");
|
|
2488 |
return false;
|
|
2489 |
}
|
|
2490 |
|
|
2491 |
/**
|
|
2492 |
* Build a list of WAN interfaces that have DHCPv6 prefix delegation configured.
|
|
2493 |
* Used to populate the UI dropdown for pd6 tunnel network type selection.
|
|
2494 |
* Returns array of [ 'friendly_name' => 'Description (/<len> PD)' ].
|
|
2495 |
*/
|
|
2496 |
function openvpn_build_dhcp6pd_wan_list() {
|
|
2497 |
$list = [];
|
|
2498 |
foreach (config_get_path('interfaces', []) as $if => $ifcfg) {
|
|
2499 |
if (!isset($ifcfg['ipaddrv6']) || $ifcfg['ipaddrv6'] !== 'dhcp6') {
|
|
2500 |
continue;
|
|
2501 |
}
|
|
2502 |
$subnet_bits = (int)($ifcfg['dhcp6-ia-pd-len'] ?? 0);
|
|
2503 |
if ($subnet_bits < 1 || $subnet_bits > 16) {
|
|
2504 |
continue;
|
|
2505 |
}
|
|
2506 |
$pd_len = 64 - $subnet_bits;
|
|
2507 |
$descr = !empty($ifcfg['descr']) ? $ifcfg['descr'] : strtoupper($if);
|
|
2508 |
$list[$if] = "{$descr} (/{$pd_len} PD)";
|
|
2509 |
}
|
|
2510 |
return $list;
|
|
2511 |
}
|
|
2512 |
|
|
2513 |
/**
|
|
2514 |
* Resync all OpenVPN instances that use DHCPv6 PD tracking on $wan_iface,
|
|
2515 |
* regardless of which interface the instance is bound to. Called from
|
|
2516 |
* rc.newwanipv6 to catch instances bound to "any" or a LAN interface that
|
|
2517 |
* openvpn_resync_all() would not otherwise restart.
|
|
2518 |
*/
|
|
2519 |
function openvpn_resync_dhcp6pd_wan($wan_iface) {
|
|
2520 |
foreach (array("server", "client") as $type) {
|
|
2521 |
foreach (config_get_path("openvpn/openvpn-{$type}", []) as $settings) {
|
|
2522 |
if (isset($settings['disable'])) {
|
|
2523 |
continue;
|
|
2524 |
}
|
|
2525 |
if (($settings['tunnel_networkv6_type'] ?? '') !== 'pd6') {
|
|
2526 |
continue;
|
|
2527 |
}
|
|
2528 |
if (($settings['tunnel_track6_interface'] ?? '') !== $wan_iface) {
|
|
2529 |
continue;
|
|
2530 |
}
|
|
2531 |
// Skip if already handled by openvpn_resync_all (bound to this WAN).
|
|
2532 |
if ($settings['interface'] === $wan_iface) {
|
|
2533 |
continue;
|
|
2534 |
}
|
|
2535 |
openvpn_resync($type, $settings);
|
|
2536 |
}
|
|
2537 |
}
|
|
2538 |
}
|
|
2539 |
|
| 2422 |
2540 |
function openvpn_clear_route($mode, $settings) {
|
| 2423 |
2541 |
if (empty($settings['tunnel_network'])) {
|
| 2424 |
2542 |
return;
|
| 2425 |
|
-- a/usr/local/www/vpn_openvpn_server.php 2026-05-02 20:17:54.655450000 +0000
|
|
2543 |
++ b/usr/local/www/vpn_openvpn_server.php 2026-05-02 20:39:07.864719743 +0000
|
| ... | ... | |
| 174 |
174 |
|
| 175 |
175 |
$pconfig['tunnel_network'] = $this_server_config['tunnel_network'];
|
| 176 |
176 |
$pconfig['tunnel_networkv6'] = $this_server_config['tunnel_networkv6'];
|
|
177 |
$pconfig['tunnel_networkv6_type'] = $this_server_config['tunnel_networkv6_type'] ?? 'static';
|
|
178 |
$pconfig['tunnel_track6_interface'] = $this_server_config['tunnel_track6_interface'] ?? '';
|
|
179 |
$pconfig['tunnel_track6_prefix_id'] = $this_server_config['tunnel_track6_prefix_id'] ?? 0;
|
| 177 |
180 |
|
| 178 |
181 |
$pconfig['remote_network'] = $this_server_config['remote_network'];
|
| 179 |
182 |
$pconfig['remote_networkv6'] = $this_server_config['remote_networkv6'];
|
| ... | ... | |
| 449 |
452 |
$input_errors[] = gettext("The submitted IPv4 Tunnel Network is already in use.");
|
| 450 |
453 |
}
|
| 451 |
454 |
|
| 452 |
|
if (!empty($pconfig['tunnel_networkv6']) && !openvpn_validate_tunnel_network($pconfig['tunnel_networkv6'], 'ipv6')) {
|
| 453 |
|
$input_errors[] = gettext("The field 'IPv6 Tunnel Network' must contain a valid IPv6 prefix or an alias with a single IPv6 prefix.");
|
| 454 |
|
}
|
|
455 |
if (($pconfig['tunnel_networkv6_type'] ?? 'static') === 'static') {
|
|
456 |
if (!empty($pconfig['tunnel_networkv6']) && !openvpn_validate_tunnel_network($pconfig['tunnel_networkv6'], 'ipv6')) {
|
|
457 |
$input_errors[] = gettext("The field 'IPv6 Tunnel Network' must contain a valid IPv6 prefix or an alias with a single IPv6 prefix.");
|
|
458 |
}
|
| 455 |
459 |
|
| 456 |
|
if (!empty($pconfig['tunnel_networkv6']) &&
|
| 457 |
|
(!isset($this_server_config) ||
|
| 458 |
|
($this_server_config['tunnel_networkv6'] != $pconfig['tunnel_networkv6'])) &&
|
| 459 |
|
openvpn_is_tunnel_network_in_use($pconfig['tunnel_networkv6'])) {
|
| 460 |
|
$input_errors[] = gettext("The submitted IPv6 Tunnel Network is already in use.");
|
|
460 |
if (!empty($pconfig['tunnel_networkv6']) &&
|
|
461 |
(!isset($this_server_config) ||
|
|
462 |
($this_server_config['tunnel_networkv6'] != $pconfig['tunnel_networkv6'])) &&
|
|
463 |
openvpn_is_tunnel_network_in_use($pconfig['tunnel_networkv6'])) {
|
|
464 |
$input_errors[] = gettext("The submitted IPv6 Tunnel Network is already in use.");
|
|
465 |
}
|
|
466 |
} else {
|
|
467 |
// pd6 type: validate WAN interface and prefix_id.
|
|
468 |
if (empty($pconfig['tunnel_track6_interface'])) {
|
|
469 |
$input_errors[] = gettext("IPv6 Tunnel Network: a WAN interface must be selected for DHCPv6 PD tracking.");
|
|
470 |
} else {
|
|
471 |
$subnet_bits = (int)config_get_path("interfaces/{$pconfig['tunnel_track6_interface']}/dhcp6-ia-pd-len", 0);
|
|
472 |
if ($subnet_bits < 1 || $subnet_bits > 16) {
|
|
473 |
$input_errors[] = gettext("The selected interface does not have a valid DHCPv6 prefix delegation configured.");
|
|
474 |
} else {
|
|
475 |
$max_id = (1 << $subnet_bits) - 1;
|
|
476 |
$pd_len = 64 - $subnet_bits;
|
|
477 |
$prefix_id = (int)($pconfig['tunnel_track6_prefix_id'] ?? 0);
|
|
478 |
if ($prefix_id < 0 || $prefix_id > $max_id) {
|
|
479 |
$input_errors[] = sprintf(gettext(
|
|
480 |
"IPv6 Prefix ID must be between 0 and %d (0x%x) for a /%d delegation."
|
|
481 |
), $max_id, $max_id, $pd_len);
|
|
482 |
}
|
|
483 |
}
|
|
484 |
}
|
| 461 |
485 |
}
|
| 462 |
486 |
|
| 463 |
487 |
if ($result = openvpn_validate_cidr($pconfig['remote_network'], 'IPv4 Remote Network', true, "ipv4", true)) {
|
| ... | ... | |
| 763 |
787 |
foreach (array('', 'v6') as $ntype) {
|
| 764 |
788 |
$server["tunnel_network{$ntype}"] = openvpn_tunnel_network_fix($pconfig["tunnel_network{$ntype}"]);
|
| 765 |
789 |
}
|
|
790 |
$server['tunnel_networkv6_type'] = $pconfig['tunnel_networkv6_type'] ?? 'static';
|
|
791 |
$server['tunnel_track6_interface'] = $pconfig['tunnel_track6_interface'] ?? '';
|
|
792 |
$server['tunnel_track6_prefix_id'] = (int)($pconfig['tunnel_track6_prefix_id'] ?? 0);
|
| 766 |
793 |
$server['remote_network'] = $pconfig['remote_network'];
|
| 767 |
794 |
$server['remote_networkv6'] = $pconfig['remote_networkv6'];
|
| 768 |
795 |
$server['gwredir'] = $pconfig['gwredir'];
|
| ... | ... | |
| 1294 |
1321 |
'including DCO, Exit Notify, and Inactive.',
|
| 1295 |
1322 |
'<br/>');
|
| 1296 |
1323 |
|
|
1324 |
$section->addInput(new Form_Select(
|
|
1325 |
'tunnel_networkv6_type',
|
|
1326 |
'IPv6 Tunnel Network',
|
|
1327 |
$pconfig['tunnel_networkv6_type'] ?? 'static',
|
|
1328 |
[
|
|
1329 |
'static' => gettext('Static (enter prefix below)'),
|
|
1330 |
'pd6' => gettext('Track Interface (DHCPv6 PD)'),
|
|
1331 |
]
|
|
1332 |
))->setHelp('Select "Static" to enter a fixed IPv6 prefix, or "Track Interface" to ' .
|
|
1333 |
'automatically derive a /64 from a DHCPv6 delegated prefix on a WAN interface. ' .
|
|
1334 |
'The tracked prefix is updated automatically when the delegated prefix changes.');
|
|
1335 |
|
| 1297 |
1336 |
$section->addInput(new Form_Input(
|
| 1298 |
1337 |
'tunnel_networkv6',
|
| 1299 |
|
'IPv6 Tunnel Network',
|
|
1338 |
'IPv6 Tunnel Network Prefix',
|
| 1300 |
1339 |
'text',
|
| 1301 |
1340 |
$pconfig['tunnel_networkv6']
|
| 1302 |
|
))->setHelp('This is the IPv6 virtual network or network type alias with a single entry used for private ' .
|
| 1303 |
|
'communications between this server and client hosts expressed using CIDR notation ' .
|
| 1304 |
|
'(e.g. fe80::/64). The ::1 address in the network will be assigned to the server ' .
|
| 1305 |
|
'virtual interface. The remaining addresses will be assigned to connecting clients.');
|
|
1341 |
))->setHelp('IPv6 virtual network in CIDR notation (e.g. fe80::/64). ' .
|
|
1342 |
'The ::1 address will be assigned to the server virtual interface. ' .
|
|
1343 |
'Only used when type is Static.');
|
|
1344 |
|
|
1345 |
$section->addInput(new Form_Select(
|
|
1346 |
'tunnel_track6_interface',
|
|
1347 |
'DHCPv6 PD WAN Interface',
|
|
1348 |
$pconfig['tunnel_track6_interface'] ?? '',
|
|
1349 |
openvpn_build_dhcp6pd_wan_list()
|
|
1350 |
))->setHelp('WAN interface with DHCPv6 prefix delegation. Only interfaces with a ' .
|
|
1351 |
'configured delegation prefix length are shown.');
|
|
1352 |
|
|
1353 |
$section->addInput(new Form_Input(
|
|
1354 |
'tunnel_track6_prefix_id',
|
|
1355 |
'DHCPv6 PD Prefix ID',
|
|
1356 |
'number',
|
|
1357 |
$pconfig['tunnel_track6_prefix_id'] ?? 0
|
|
1358 |
))->setHelp('Numeric subnet ID within the delegated prefix (0 = first /64, 1 = second, etc.). ' .
|
|
1359 |
'For a /60 delegation, valid values are 0-15. For /56, 0-255. For /48, 0-65535. ' .
|
|
1360 |
'Prefix ID 0 is typically already used by LAN.');
|
| 1306 |
1361 |
|
| 1307 |
1362 |
$section->addInput(new Form_Checkbox(
|
| 1308 |
1363 |
'serverbridge_dhcp',
|
| ... | ... | |
| 2381 |
2436 |
}
|
| 2382 |
2437 |
}
|
| 2383 |
2438 |
|
|
2439 |
function tunnel_networkv6_type_change() {
|
|
2440 |
var pd6 = ($('#tunnel_networkv6_type').val() === 'pd6');
|
|
2441 |
hideInput('tunnel_networkv6', pd6);
|
|
2442 |
hideInput('tunnel_track6_interface', !pd6);
|
|
2443 |
hideInput('tunnel_track6_prefix_id', !pd6);
|
|
2444 |
}
|
|
2445 |
|
| 2384 |
2446 |
function ping_method_change() {
|
| 2385 |
2447 |
pvalue = $('#ping_method').val();
|
| 2386 |
2448 |
|
| ... | ... | |
| 2526 |
2588 |
allow_compression_change();
|
| 2527 |
2589 |
});
|
| 2528 |
2590 |
|
|
2591 |
$('#tunnel_networkv6_type').change(function () {
|
|
2592 |
tunnel_networkv6_type_change();
|
|
2593 |
});
|
|
2594 |
|
| 2529 |
2595 |
function updateCipher(mem) {
|
| 2530 |
2596 |
var found = false;
|
| 2531 |
2597 |
var ciphers_all = <?= json_encode($openvpn_all_data_ciphers) ?>;
|
| ... | ... | |
| 2589 |
2655 |
ocspcheck_change();
|
| 2590 |
2656 |
allow_compression_change();
|
| 2591 |
2657 |
duplicate_cn_change();
|
|
2658 |
tunnel_networkv6_type_change();
|
| 2592 |
2659 |
});
|
| 2593 |
2660 |
//]]>
|
| 2594 |
2661 |
</script>
|
| 2595 |
|
-- a/etc/rc.newwanipv6 2026-05-02 20:17:54.710124000 +0000
|
|
2662 |
++ b/etc/rc.newwanipv6 2026-05-02 20:39:13.363539090 +0000
|
| ... | ... | |
| 263 |
263 |
/* start OpenVPN server & clients */
|
| 264 |
264 |
if (substr($interface_real, 0, 4) != "ovpn") {
|
| 265 |
265 |
openvpn_resync_all($interface, 'inet6');
|
|
266 |
/* Also resync any OpenVPN instances using DHCPv6 PD tracking on this WAN
|
|
267 |
* that are NOT bound directly to it (e.g. bound to "any" or a LAN iface). */
|
|
268 |
openvpn_resync_dhcp6pd_wan($interface);
|
| 266 |
269 |
}
|
| 267 |
270 |
|
| 268 |
271 |
/* reconfigure GRE/GIF tunnels */
|