From 7791bb5de18bc2e3562f27ada5a662edcad77747 Mon Sep 17 00:00:00 2001 From: "R. Christian McDonald" Date: Fri, 18 Jun 2021 16:00:54 -0400 Subject: [PATCH] Merge in devel code (#117) * back merge main onto devel (#94) * Update main to latest (#77) * Updated pkg-plist * Makefile fix * Experimenting with wireguard service * Update README.md Co-authored-by: vajonam <152501+vajonam@users.noreply.github.com> Co-authored-by: Manojav Sridhar * docs: add theonemcdonald as a contributor (#84) * docs: update README.md [skip ci] * docs: create .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * Update README.md * Update README.md * Update .all-contributorsrc * Update .all-contributorsrc * Update README.md * Update README.md * Update Makefile * Cleanup * Cleanup * Clean upload of v0.1.2 * Create FUNDING.yml * Add files via upload Co-authored-by: vajonam <152501+vajonam@users.noreply.github.com> Co-authored-by: Manojav Sridhar Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * Clean ups * Syntax * Updated README * Improve input error clarity * Syntax * More syntax * Fix missing address, allowedip fields after tunnel or peer input errors * Reorganized peer post validation * Reorder all input errors to be consistent with UI order * Fix input being flushed on peer validation error * Fix * Test * Fix #98 * Also Fix #98 * v0.1.3 will be reserved for the next PR with Netgate * Further fixes #98 * More fixes for #98... * Removed exit() while working on #98 * Refactor wg_generate_tunnel_address_popover_link for readability * Working on guiconfig cleaning * Should fix #99 * Fix variable #99 * Fix re-saving unchanged tunnel or peer * Fix broke status icon * Back out some boiler plate code * Relocate pf reload trigger on tunnel sync * Test * Fixes some php errors on newer PHP versions * this has to be absolute apparently * Can't redeclare this * wg_clamp_key and wg_is_key_clamped functions * wg_gen_publickey now detects if a privkey was clamped or not * fix wg_gen_keypair to correctly consume new gen_publickey * Bump net/wireguard-kmod to 0.0.20210606 * Fix some logic in new functioons * syntax * Clamp private keys on UI * Don't block unclamped private keys in the UI * Validate pre-shared key * Missed a call that needs tweaking * Slight cleanup * Candidate 0.1.3 build for Netgate PR * Small bump * Tweak subsystem names * Testing * Add some comments to .conf files for the curious * Add some useful debug bits to .conf files * Testing extra services restart on apply * We are now going to restart extra services (currently dpinger and unbound) on config apply (in addition to service restart) * Bump v0.1.2_5 * Enable data-sortable on relevant tables * Missed a table * Peers should become unassigned when their tunnel is deleted * allowedips needs to be an array even when empty * Implement package apply conf on tunnels_edit.php * Tweaks to form post handling * Syntax * Private and PSKs are now hidden by default * Syntax * sortable tables doesn't play nicely with popovers, will revisit in the future * Slight UI tweaks * syntax * Improve sync logic * Test * Testing * Implement conf file downloads from UI * Fixed incorrect tunnel name variable * Bump 0.1.2_6 * Testing * fix plist * Add timestamp to conf download * Typo * " * Working on DNS race * Syntax * Working on DNS * Syntax * MVCd the settings page * Syntax * syntax * More DNS work * More DNS work * Playing with DNS bits * DNS testing * More DNS work * Working on DNS improvements * Resync pakage on post * DNS work * A bit more DNS work * DNS Testing * DNS work * Final DNS work * Some backend rewrites * Cleanups * Some more work * Tweaks * Fixed some variables * Fix variable * Work * Testing some refactoring * Fix some GUI stuff after refactor * Fixes from refactoring * Fix typo * Typo fix * Fix bug * Fix gui bug * This should be count() not max() Co-authored-by: vajonam <152501+vajonam@users.noreply.github.com> Co-authored-by: Manojav Sridhar Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .../files/usr/local/pkg/wireguard/wg.inc | 534 ++++++++----- .../files/usr/local/pkg/wireguard/wg_api.inc | 736 ++++++++++++------ .../usr/local/pkg/wireguard/wg_globals.inc | 49 +- .../usr/local/pkg/wireguard/wg_guiconfig.inc | 182 +++-- .../usr/local/pkg/wireguard/wg_install.inc | 12 +- .../usr/local/pkg/wireguard/wg_service.inc | 80 +- .../usr/local/pkg/wireguard/wg_validate.inc | 38 +- .../usr/local/www/wg/status_wireguard.php | 9 +- .../files/usr/local/www/wg/vpn_wg_peers.php | 35 +- .../usr/local/www/wg/vpn_wg_settings.php | 100 ++- .../files/usr/local/www/wg/vpn_wg_tunnels.php | 7 +- .../usr/local/www/wg/vpn_wg_tunnels_edit.php | 45 +- 12 files changed, 1161 insertions(+), 666 deletions(-) diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg.inc index 2617b62e..cd459bdb 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg.inc @@ -48,13 +48,7 @@ function wg_toggle_tunnel($tunnel_name) { $input_errors = array(); - // Get the config array index - $tun_idx = wg_get_tunnel_array_index($tunnel_name); - - // Make sure we have a valid tunnel in the xml - if (isset($wgg['tunnels'][$tun_idx])) { - - $tunnel = $wgg['tunnels'][$tun_idx]; + if (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($tunnel_name)) { $enabled = ($tunnel['enabled'] == 'yes'); @@ -76,6 +70,9 @@ function wg_toggle_tunnel($tunnel_name) { // Sync with configuration backend write_config("[{$wgg['pkg_name']}] Tunnel {$tunnel['name']} {$action_txt}."); + // Resync the package + wg_resync(); + // We've got meaningful changes... $changes = true; @@ -91,7 +88,7 @@ function wg_toggle_tunnel($tunnel_name) { } /* - * This toggles a given peer based on peerid + * This toggles a given peer based on config array index */ function wg_toggle_peer($peer_idx) { global $wgg; @@ -104,10 +101,7 @@ function wg_toggle_peer($peer_idx) { $input_errors = array(); - // Make sure we have a valid peer - if (isset($wgg['peers'][$peer_idx])) { - - $peer = $wgg['peers'][$peer_idx]; + if (list($peer_idx, $peer, $is_new) = wg_peer_get_config($peer_idx)) { $enabled = ($peer['enabled'] == 'yes'); @@ -120,12 +114,13 @@ function wg_toggle_peer($peer_idx) { $action_text = $enabled ? 'disabled' : 'enabled'; // Sync with configuration backend - write_config("[{$wgg['pkg_name']}] Peer {$peer_id} {$action_text}."); + write_config("[{$wgg['pkg_name']}] Peer {$peer_idx} {$action_text}."); - $tunnel_names = array_map(fn($x) => $x['name'], $wgg['tunnels']); + // Resync the package + wg_resync(); // This checks if the peer tunnel is a valid tunnel - if (in_array($peer['tun'], $tunnel_names)) { + if (wg_is_valid_tunnel($peer['tun'])) { // We've got meaningful changes... $changes = true; @@ -156,10 +151,7 @@ function wg_delete_peer($peer_idx) { $input_errors = array(); - if (isset($wgg['peers'][$peer_idx])) { - - // We need to save this because we are about to unset it - $peer = $wgg['peers'][$peer_idx]; + if (list($peer_idx, $peer, $is_new) = wg_peer_get_config($peer_idx)) { // Boilerplate... if (empty($input_errors)) { @@ -170,10 +162,11 @@ function wg_delete_peer($peer_idx) { // Sync with configuration backend write_config("[{$wgg['pkg_name']}] Peer {$peer_idx} deleted."); - $tunnel_names = array_map(fn($x) => $x['name'], $wgg['tunnels']); + // Resync the package + wg_resync(); // This checks if the peer's tunnel was a valid tunnel - if (in_array($peer['tun'], $tunnel_names)) { + if (wg_is_valid_tunnel($peer['tun'])) { // We've got meaningful changes... $changes = true; @@ -204,13 +197,7 @@ function wg_delete_tunnel($tunnel_name) { $input_errors = array(); - // Get the config array index - $tun_idx = wg_get_tunnel_array_index($tunnel_name); - - // Make sure we have a valid tunnel - if (isset($wgg['tunnels'][$tun_idx])) { - - $tunnel = $wgg['tunnels'][$tun_idx]; + if (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($tunnel_name)) { // We can't delete assigned tunnels if (is_wg_tunnel_assigned($tunnel['name'])) { @@ -232,6 +219,9 @@ function wg_delete_tunnel($tunnel_name) { // Mark any peers as unassigned wg_tunnel_unassign_peers($tunnel['name']); + // Resync the package + wg_resync(); + // We've got meaningful changes... $changes = true; @@ -251,19 +241,20 @@ function wg_tunnel_unassign_peers($tunnel_name) { wg_globals(); - if (isset($wgg['peers']) && is_array($wgg['peers'])) { - - $peers = $wgg['peers']; + $changes = false; + + foreach (wg_tunnel_get_peers_config($tunnel_name) as $peer_config) { - foreach ($peers as $peer_idx => $peer) { + list($peer_idx, $peer, $is_new) = $peer_config; - if ($peer['tun'] == $tunnel_name) { + $wgg['peers'][$peer_idx]['tun'] = 'unassigned'; - $wgg['peers'][$peer_idx]['tun'] = 'unassigned'; + // We need to sync with backend + $changes = true; - } + } - } + if ($changes) { // Sync with configuration backend write_config("[{$wgg['pkg_name']}] Tunnel {$tunnel_name} peers unassigned."); @@ -365,6 +356,149 @@ function wg_parse_post_repeatables($post, $fields = null) { } +/* + * This resolves peer endpoint hostnames per tunnel and updates the kernel accordingly + * This returns a DateTime object corresponding to the last update time + */ +function wg_resolve_endpoints() { + global $wgg; + + // Get the latest info + wg_globals(); + + // Do we have any peers? + if (isset($wgg['peers']) && is_array($wgg['peers'])) { + + foreach ($wgg['peers'] as $peer) { + + if (!empty($peer['endpoint'])) { + + // Strip port if empty + $peer['port'] = !empty($peer['port']) ? ":{$peer['port']}" : null; + + $endpoint = "{$peer['endpoint']}{$peer['port']}"; + + wg_peer_set_endpoint($peer['tun'], $peer['publickey'], $endpoint); + + } + + } + + } + + $last_update_time = new DateTime(); + + return $last_update_time; + +} + +/* + * Determines if endpoint hostnames should be re-resolved + * based on the last time the hostnames were re-resolved + * + * An interval of 0 will effectively disable re-resolving + */ +function wg_should_reresolve_endpoints($last_update_time) { + global $wgg; + + try { + + // Get the current timestamp + $current_time = (new DateTime)->getTimestamp(); + + // Calculate the time for the next update event + $time_to_update = $last_update_time->getTimestamp() + wg_get_endpoint_resolve_interval(); + + // Should we update? A 0 TTL means disabled. + return (wg_get_endpoint_resolve_interval() > 0 && ($time_to_update <= $current_time)); + + } catch (Exception $e) { + + // Something probably went wrong with DateTime, just bail out... + return false; + + } + +} + +/* + * Gets the hostname resolve interval (in seconds) + * Returns 0 if something is wrong with the configuration + */ +function wg_get_endpoint_resolve_interval() { + global $config, $wgg; + + // Are we tracking the system value? + if (isset($wgg['config']['resolve_interval_track']) + && ($wgg['config']['resolve_interval_track'] == 'yes')) { + + if (isset($config['system']['aliasesresolveinterval']) + && is_numeric($config['system']['aliasesresolveinterval'])) { + + return $config['system']['aliasesresolveinterval']; + + } + + // The pfSense default interval + return 300; + + } + + // Looks like we are using our own value... + if (isset($wgg['config']['resolve_interval']) + && is_numericint($wgg['config']['resolve_interval'])) { + + return $wgg['config']['resolve_interval']; + + } + + // Something is wrong, so just return the default... + return $wgg['default_resolve_interval']; + +} + +function wg_do_settings_post($post) { + global $wgg; + + wg_globals(); + + $pconfig = $input_errors = array(); + + // Assume no changes will be made... + $changes = false; + + // We need to save the "old config" to compare against later... + $pconfig = $old_config = $wgg['config']; + + $pconfig['keep_conf'] = empty($post['keep_conf']) ? 'no' : $post['keep_conf']; + + $pconfig['hide_secrets'] = empty($post['hide_secrets']) ? 'no' : $post['hide_secrets']; + + $pconfig['resolve_interval'] = empty($post['resolve_interval']) && ($post['resolve_interval'] !== '0') ? $wgg['default_resolve_interval'] : $post['resolve_interval']; + + $pconfig['resolve_interval_track'] = empty($post['resolve_interval_track']) ? 'no' : $post['resolve_interval_track']; + + $input_errors = wg_validate_settings_post($pconfig); + + if (empty($input_errors)) { + + $wgg['config'] = $pconfig; + + // Sync with configuration backend + write_config("[{$wgg['pkg_name']}] Package settings saved."); + + // Resync the package + wg_resync(); + + // Do we have meaningful changes? + $changes = ($pconfig != $old_config); + + } + + return array('input_errors' => $input_errors, 'changes' => $changes, 'pconfig' => $pconfig); + +} + /* * Takes a raw post for a peer, validates it, and saves it to the configuration system */ @@ -378,17 +512,13 @@ function wg_do_peer_post($post) { // Assume no changes will be made... $changes = false; - $peer_idx = $post['index']; - - wg_init_config_arr($wgg['peers'][$peer_idx], array('allowedips', 'row')); + list($peer_idx, $pconfig, $is_new) = wg_peer_get_config($post['index'], true); // We need to save the "old config" to compare against later... - $pconfig = $old_config = $wgg['peers'][$peer_idx]; + $old_pconfig = $pconfig; $pconfig['enabled'] = empty($post['enabled']) ? 'no' : $post['enabled']; - $old_tun = $pconfig['tun']; - $pconfig['tun'] = $post['tun']; $pconfig['descr'] = $post['descr']; @@ -412,7 +542,7 @@ function wg_do_peer_post($post) { } - $input_errors = wg_validate_peer_post($pconfig); + $input_errors = wg_validate_peer_post($pconfig, $peer_idx); if (empty($input_errors)) { @@ -421,31 +551,27 @@ function wg_do_peer_post($post) { // Sync with configuration backend write_config("[{$wgg['pkg_name']}] Peer {$pconfig['descr']} updated."); - // Check if anything actually changed or was this just a clean re-save? - if (md5(serialize($pconfig)) != md5(serialize($old_config))) { + // Resync the package + wg_resync(); - $tunnel_names = array_map(fn($x) => $x['name'], $wgg['tunnels']); + /* + * Figure out what tunnel to sync to make these changes (if any). + * + * If the peer is re-unassigned, then $old_pconfig['tun'] needs to be resynced. + */ + foreach (array($pconfig['tun'], $old_pconfig['tun']) as $tun) { - // This checks if the pconfig tunnel is a valid tunnel - if (in_array($pconfig['tun'], $tunnel_names)) { + if (wg_is_valid_tunnel($tun)) { - // We've got meaningful changes... - $changes = true; - - // What tunnel would we need to sync to apply these changes? - $tun_to_sync = $pconfig['tun']; + $changes = ($pconfig != $old_pconfig) || $is_new; - // Now try the old_config tunnel? - } elseif (in_array($old_config['tun'], $tunnel_names)) { + $tun_to_sync = $tun; - // We've got meaningful changes... - $changes = true; - - // What tunnel would we need to sync to apply these changes? - $tun_to_sync = $old_config['tun']; + // We found it... + break; } - + } } @@ -467,12 +593,10 @@ function wg_do_tunnel_post($post) { // Assume no changes will be made... $changes = false; - $tun_idx = $post['index']; - - wg_init_config_arr($wgg['tunnels'][$tun_idx], array('addresses', 'row')); + list($tun_idx, $pconfig, $is_new) = wg_tunnel_get_config($post['index'], true); // We need to save the "old config" to compare against later... - $pconfig = $old_config = $wgg['tunnels'][$tun_idx]; + $old_pconfig = $pconfig; $key = wg_gen_publickey($post['privatekey']); @@ -501,16 +625,14 @@ function wg_do_tunnel_post($post) { // Sync with configuration backend write_config("[{$wgg['pkg_name']}] Tunnel {$pconfig['name']} updated."); - // Check if anything actually changed or was this just a clean re-save? - if (md5(serialize($pconfig)) != md5(serialize($old_config))) { + // Resync the package + wg_resync(); - // We've got meaningful changes... - $changes = true; + // Do we have meaningful changes? + $changes = ($pconfig != $old_pconfig); - // What tunnel would we need to sync to apply these changes? - $tun_to_sync = $pconfig['name']; - - } + // What tunnel would we need to sync to apply these changes? + $tun_to_sync = $pconfig['name']; } @@ -577,167 +699,169 @@ function wg_apply_list_add($entry, $list) { } /* - * This builds, rebuilds, or destroys tunnel interfaces - * If $tunnels is empty, this will apply to all configured tunnel interfaces + * This compares the running vs configured tunnels and destroys any rogues */ -function wg_tunnel_sync($tunnel_names = null, $restart_services = false) { +function wg_destroy_rogue_tunnels(&$cmds = null) { global $wgg; - $tunnels = array(); - - // Let's assume everything will be fine $ret_code = 0; - // Fetch and build latest config files - wg_resync(); + $running_ifs = wg_get_running_ifs(); - // This is only necessary if the sevice is running - if (wg_is_service_running()) { + $config_ifs = wg_get_configured_ifs(); - // Should we build all the tunnels? - if (is_null($tunnel_names) || !is_array($tunnel_names)) { + $rogue_tunnels = array_diff($running_ifs, $config_ifs); - // Are there any tunnels to build? - if (isset($wgg['tunnels']) && is_array($wgg['tunnels'])) { + foreach ($rogue_tunnels as $tunnel) { - $tunnel_names = array_map(fn($x) => $x['name'], $wgg['tunnels']); + $cmds[] = $res = wg_ifconfig_if_destroy($tunnel); - } else { + $ret_code = $res['ret_code']; - // Nope... - $tunnel_names = array(); + } - } + return ($ret_code == 0); - } +} - // Build (or destroy) each tunnel - foreach ($tunnel_names as $tunnel_name) { +/* + * This builds, rebuilds, or destroys tunnel interfaces + * If $tunnel_names is null, this will apply to all configured tunnel interfaces + */ +function wg_tunnel_sync($tunnel_names = null, $restart_services = false, $resolve_endpoints = true, $json = false) { + global $wgg; + + // Fetch and build the latest info + wg_resync(); + + // Let's assume everything will be fine + $ret_code = 0; - $tun_idx = wg_get_tunnel_array_index($tunnel_name); + $tunnels = array(); + + // We need to destroy any rogue tunnels not created by pfSense + wg_destroy_rogue_tunnels(); + + // This is only necessary if the sevice is running + if (wg_is_service_running()) { + + $tunnel_names = (is_null($tunnel_names) || !is_array($tunnel_names)) ? wg_get_configured_ifs() : $tunnel_names; - $tun_sync_status = wg_tunnel_sync_by_name($tunnel_name); + // Sync each tunnel + foreach ($tunnel_names as $tunnel_name) { + + // Attempt to sync the tunnel + $tun_sync_status = wg_tunnel_sync_by_name($tunnel_name, false); // Build an aggregated return code $ret_code |= $tun_sync_status['ret_code']; + // Collect the return from each individual tunnel $tunnels[] = array( - 'name' => $tunnel_name, - 'ret_code' => $tun_sync_status['ret_code'], - 'errors' => $tun_sync_status['errors'], - 'config' => $tun_sync_status['config']); + 'name' => $tun_sync_status['config']['name'], + 'ret_code' => $tun_sync_status['ret_code'], + 'errors' => $tun_sync_status['errors'], + 'config' => $tun_sync_status['config']); } // Reload the filter filter_configure(); + // Handle any extra services that need restarting if ($restart_services) { wg_restart_extra_services(); } + // Handle resolving endpoints + if ($resolve_endpoints) { + + wg_resolve_endpoints(); + + } + } - return array('ret_code' => $ret_code, 'tunnels' => $tunnels); + $res = array('ret_code' => $ret_code, 'tunnels' => $tunnels); + + return $json ? json_encode($res) : $res; } /* - * This builds, rebuilds, or destroys a specific tunnel interface by name + * This builds or rebuilds a specific tunnel interface by name */ -function wg_tunnel_sync_by_name($tunnel_name) { +function wg_tunnel_sync_by_name($tunnel_name, $json = false) { global $wgg; + // Get the latest info wg_globals(); - $ret_code = 0; - - $errors = $tunnel = array(); + $cmds = $errors = $tunnel = array(); - $tun_idx = wg_get_tunnel_array_index($tunnel_name); + // We've got a tunnel that we need to build... + if (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($tunnel_name)) { - // We've got a tunnel we need to build... - if (isset($wgg['tunnels'][$tun_idx])) { - - $tunnel = $wgg['tunnels'][$tun_idx]; + // Determine desired state of the tunnel + $state = (isset($tunnel['enabled']) && $tunnel['enabled'] == 'yes'); // Create the tunnel interface if it does not yet exist - if (wg_interface_create($tunnel['name']) <> 0) { - - $ret_code |= WG_ERROR_IF_CREATE; - - } - - // Add the tunnel interface to the WireGuard interface group - if (wg_interface_group_add($tunnel['name']) <> 0) { - - $ret_code |= WG_ERROR_IF_GROUP; - - } - - // Update the addresses on the tunnel interface - if (wg_interface_update_addresses($tunnel['name']) <> 0) { + if (wg_ifconfig_if_create($tunnel['name'], $cmds)) { - $ret_code |= WG_ERROR_IF_ADDRESSES; + // Add the tunnel interface to the WireGuard interface group + wg_interface_update_groups($tunnel['name'], $cmds); - } - - // Toggle the interface accordingly instead of tearing it down completely - if (isset($tunnel['enabled']) && $tunnel['enabled'] == 'yes') { - - if (wg_ifconfig_up_interface($tunnel['name']) <> 0) { - - $ret_code |= WG_ERROR_IF_UP; - - } - - } else { + // Update the addresses on the tunnel interface + wg_interface_update_addresses($tunnel['name'], $cmds); - if (wg_ifconfig_down_interface($tunnel['name']) <> 0) { + // Toggle the interface accordingly instead of tearing it down completely + wg_ifconfig_if_updown($tunnel['name'], $state, $cmds); - $ret_code |= WG_ERROR_IF_DOWN; + // Sync interface with WireGuard wg(8) + wg_wg_if_sync($tunnel['name'], $cmds); - } } - // Sync interface with WireGuard wg(8) - if (wg_interface_syncconf($tunnel['name']) <> 0) { - - $ret_code |= WG_ERROR_IF_SYNC; - - } + } - // We've got a tunnel we need to destroy... - } elseif (in_array($tunnel_name, wg_get_real_ifs())) { + $parsed_cmds = wg_parse_cmds_array($cmds); - if (wg_interface_destroy($tunnel_name) <> 0) { + // Build the return array + $res = array( + 'ret_code' => $parsed_cmds['ret_code'], + 'errors' => $parsed_cmds['errors'], + 'cmds' => $parsed_cmds['cmds'], + 'config' => $tunnel); - $ret_code |= WG_ERROR_IF_DESTROY; + return $json ? json_encode($res, JSON_PRETTY_PRINT) : $res; - } +} - // No idea what's going on... - } else { +/* + * Parses an array of $cmds + */ +function wg_parse_cmds_array($cmds) { - $ret_code |= WG_ERROR_IF_NAME; + $ret_code = 0; + + $errors = array(); - } + if (isset($cmds) && is_array($cmds)) { - // Now collect the errors... - foreach ($wgg['error_flags']['tunnel'] as $error_mask => $error_text) { + foreach($cmds as $cmd) { - if (($ret_code & $error_mask) > 0) { + $ret_code |= $cmd['ret_code']; - $errors[$error_mask] = $error_text; + // Unwrap and collect any errors + array_push($errors, ...$cmd['errors']); } } - // Build the return array - return array('name' => $tunnel_name, 'ret_code' => $ret_code, 'errors' => $errors, 'config' => $tunnel); + return array('ret_code' => $ret_code, 'errors' => $errors, 'cmds' => $cmds); } @@ -747,6 +871,9 @@ function wg_tunnel_sync_by_name($tunnel_name) { function wg_resync() { global $g, $wgg; + // Update globals + wg_globals(); + // Create WireGuard configuration files wg_create_config_files(); @@ -766,8 +893,7 @@ function wg_resync() { // We don't want active tunnels when the service isn't running if (!wg_is_service_running() && - is_module_loaded($wgg['kmod']) && - !is_subsystem_dirty($wgg['subsystems']['postboot'])) { + is_module_loaded($wgg['kmod'])) { wg_destroy_tunnels(); @@ -833,7 +959,7 @@ function wg_create_config_files($clean = true) { foreach ($wgg['tunnels'] as $tunnel) { - make_wg_conf($tunnel); + wg_make_tunnel_conf_file($tunnel, false); } @@ -847,12 +973,8 @@ function wg_create_config_files($clean = true) { function wg_delete_config_files() { global $wgg; - $confpaths = array(); - - $confpaths = array_merge(array($wgg['conf_path']), $wgg['conf_paths_to_clean']); - // Loop through each potential conf path and delete all .conf files - foreach ($confpaths as $path) { + foreach ($wgg['conf_paths_to_clean'] as $path) { unlink_if_exists("{$path}/*.conf"); @@ -871,7 +993,7 @@ function wg_htmlspecialchars(&...$vars) { if (isset($var) && is_array($var)) { - array_walk($var, function(&$x) { + array_walk_recursive($var, function(&$x) { $x = htmlspecialchars($x); @@ -898,7 +1020,7 @@ function wg_escapeshellarg(&...$vars) { if (isset($var) && is_array($var)) { - array_walk($var, function(&$x) { + array_walk_recursive($var, function(&$x) { $x = escapeshellarg($x); @@ -920,12 +1042,8 @@ function wg_escapeshellarg(&...$vars) { function wg_remove_config_settings() { global $config, $wgg; - $confpaths = array(); - - $confpaths = array_merge(array($wgg['xml_path']), $wgg['xml_paths_to_clean']); - // Loop through each potential conf path and unset it - foreach ($confpaths as $path) { + foreach ($wgg['xml_paths_to_clean'] as $path) { array_unset_value($config, $path); @@ -939,7 +1057,7 @@ function wg_remove_config_settings() { /* * Writes a WireGuard configuration file for a given tunnel to the configuration path */ -function make_wg_conf($tunnel) { +function wg_make_tunnel_conf_file($tunnel, $include_endpoint = false) { global $wgg; $txt = "# This WireGuard config file has been created automatically. Do not edit!\n\n"; @@ -976,57 +1094,57 @@ function make_wg_conf($tunnel) { $txt .= "\n"; // Process peers section - $peers = wg_get_tunnel_peers($tunnel['name']); - - if (is_array($peers) && count($peers) > 0) { + foreach (wg_tunnel_get_peers_config($tunnel['name']) as $peer_config) { - foreach ($peers as $peer) { + // Pull out relevant bits + list($peer_idx, $peer, $is_new) = $peer_config; - if (isset($peer['enabled']) && $peer['enabled'] == 'yes') { + if (isset($peer['enabled']) && $peer['enabled'] == 'yes') { - $txt .= "# Peer: {$peer['descr']}\n"; + $txt .= "# Peer: {$peer['descr']}\n"; - $txt .= "[Peer]\n"; + $txt .= "[Peer]\n"; - if (!empty($peer['publickey'])) { + if (!empty($peer['publickey'])) { - $txt .= "PublicKey = {$peer['publickey']}\n"; + $txt .= "PublicKey = {$peer['publickey']}\n"; - } - - if (!empty($peer['presharedkey'])) { + } - $txt .= "PresharedKey = {$peer['presharedkey']}\n"; + if (!empty($peer['presharedkey'])) { - } + $txt .= "PresharedKey = {$peer['presharedkey']}\n"; - if (is_array($peer['allowedips']['row'])) { + } - $allowedips = implode(',', array_map(fn($x) => "{$x['address']}/{$x['mask']}", $peer['allowedips']['row'])); + if (is_array($peer['allowedips']['row']) && count($peer['allowedips']['row']) > 0) { - $txt .= "AllowedIPs = {$allowedips}\n"; + $allowedips = implode(', ', array_map(fn($x) => "{$x['address']}/{$x['mask']}", $peer['allowedips']['row'])); - } + $txt .= "AllowedIPs = {$allowedips}\n"; - if (!empty($peer['endpoint'])) { + } - $peer['endpoint'] = is_ipaddrv6($peer['endpoint']) ? "[{$peer['endpoint']}]" : $peer['endpoint']; + if (!empty($peer['endpoint'])) { - $peer['port'] = empty($peer['port']) ? $wgg['default_port'] : $peer['port']; + $peer['endpoint'] = is_ipaddrv6($peer['endpoint']) ? "[{$peer['endpoint']}]" : $peer['endpoint']; - $txt .= "Endpoint = {$peer['endpoint']}:{$peer['port']}\n"; + $peer['port'] = empty($peer['port']) ? $wgg['default_port'] : $peer['port']; - } + $comment = $include_endpoint ? null : '# '; - if (!empty($peer['persistentkeepalive'])) { + // NOTE: Endpoint needs to be commented out to avoid potential failures caused by DNS + $txt .= "{$comment}Endpoint = {$peer['endpoint']}:{$peer['port']}\n"; - $txt .= "PersistentKeepalive = {$peer['persistentkeepalive']}\n"; - } + } - $txt .= "\n"; + if (!empty($peer['persistentkeepalive'])) { + $txt .= "PersistentKeepalive = {$peer['persistentkeepalive']}\n"; } + $txt .= "\n"; + } } @@ -1044,7 +1162,7 @@ function make_wg_conf($tunnel) { function wg_download_tunnel($tunnel_name, $failure_redirect) { global $wgg; - // Make sure conf files are current + // Fetch and build the latest info wg_resync(); $now = new DateTimeImmutable(); diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_api.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_api.inc index 0a0d14be..4f79c295 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_api.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_api.inc @@ -32,111 +32,123 @@ require_once('service-utils.inc'); require_once('wireguard/wg_globals.inc'); require_once('wireguard/wg_install.inc'); -// Returns a massive associative array of current wg status -function wg_status() { +/* + * A wrapper for wg_get_running_config to export everything plus extras for the status page + */ +function wg_get_status($json = false) { + + return wg_get_running_config(true, $json); + +} + +/* + * Returns a massive associative array of current wg status + * + * $extras returns additional info not provided by `wg show all dump` + */ +function wg_get_running_config($extras = false, $json = false) { global $wgg; - $tunnel_keys = array('private_key', 'public_key', 'listen_port', 'fwmark'); + $tunnel_output_keys = array('private_key', 'public_key', 'listen_port', 'fwmark'); - $peer_keys = array('public_key', 'preshared_key', 'endpoint', 'allowed_ips', 'latest_handshake', 'transfer_rx', 'transfer_tx', 'persistent_keepalive'); - - $ret_array = $wg_output_rows = array(); - - exec("{$wgg['wg']} show all dump", $wg_output_rows, $ret_code); + $peer_output_keys = array('public_key', 'preshared_key', 'endpoint', 'allowed_ips', 'latest_handshake', 'transfer_rx', 'transfer_tx', 'persistent_keepalive'); + + $ret_config = $cmd_output_rows = array(); + + exec("{$wgg['wg']} show all dump", $cmd_output_rows, $ret_code); if ($ret_code == 0) { - - foreach ($wg_output_rows as $wg_index => $wg_row) { + + foreach ($cmd_output_rows as $row) { $tmp_tunnel = $tmp_peer = array(); - $a_device = explode("\t", $wg_row); + $a_device = explode("\t", $row); $current_device = $a_device[0]; if (strcmp($current_device, $last_device)) { - foreach ($tunnel_keys as $index => $key) { + foreach ($tunnel_output_keys as $key_index => $key) { - $tmp_tunnel[$key] = $a_device[$index + 1]; + $tmp_tunnel[$key] = $a_device[$key_index + 1]; } - // Gets some extra information about tunnels not returned by `wg show all dump` - $tunnel_if_stats = pfSense_get_interface_stats($current_device); + if ($extras) { + + // Gets some extra information about tunnels not returned by `wg show all dump` + $tunnel_if_stats = pfSense_get_interface_stats($current_device); - $tun_idx = wg_get_tunnel_array_index($current_device); + $tun_idx = wg_get_tunnel_array_index($current_device); - $tmp_tunnel['status'] = wg_interface_status($current_device) ? 'up' : 'down'; + $tmp_tunnel['status'] = wg_interface_status($current_device) ? 'up' : 'down'; - $tmp_tunnel['transfer_rx'] = $tunnel_if_stats['inbytes']; + $tmp_tunnel['transfer_rx'] = $tunnel_if_stats['inbytes']; - $tmp_tunnel['transfer_tx'] = $tunnel_if_stats['outbytes']; + $tmp_tunnel['transfer_tx'] = $tunnel_if_stats['outbytes']; - $tmp_tunnel['inpkts'] = $tunnel_if_stats['inpkts']; + $tmp_tunnel['inpkts'] = $tunnel_if_stats['inpkts']; - $tmp_tunnel['outpkts'] = $tunnel_if_stats['outpkts']; + $tmp_tunnel['outpkts'] = $tunnel_if_stats['outpkts']; - $tmp_tunnel['mtu'] = $tunnel_if_stats['mtu']; + $tmp_tunnel['mtu'] = $tunnel_if_stats['mtu']; - $tmp_tunnel['config'] = $wgg['tunnels'][$tun_idx]; + $tmp_tunnel['config'] = $wgg['tunnels'][$tun_idx]; + + } // Add the tunnel to the array - $ret_array[$current_device] = $tmp_tunnel; - + $ret_config[$current_device] = $tmp_tunnel; + // Now provision an empty peer array - $ret_array[$current_device]['peers'] = array(); - + $ret_config[$current_device]['peers'] = array(); + $last_device = $a_device[0]; } else { - foreach ($peer_keys as $index => $key) { + foreach ($peer_output_keys as $key_index => $key) { - $tmp_peer[$key] = $a_device[$index + 1]; + $tmp_peer[$key] = $a_device[$key_index + 1]; } - // Gets some extra information about peers not returned by `wg show all dump` - $peer_id = wg_get_peer_id($tmp_peer['public_key'], $last_device); - - $tmp_peer['config'] = $wgg['peers'][$peer_id]; - - // Add the peer to the array - $ret_array[$last_device]['peers'][$a_device[1]] = $tmp_peer; - - } + if ($extras) { + // Gets some extra information about peers not returned by `wg show all dump` + $peer_idx = wg_get_peer_idx($tmp_peer['public_key'], $last_device); - } + $tmp_peer['config'] = $wgg['peers'][$peer_idx]; - } + } - // Consumers of this function always expect an array type - return $ret_array; + // Add the peer to the array + $ret_config[$last_device]['peers'][$a_device[1]] = $tmp_peer; -} + } -// Wrapper for wg_status to output json -function wg_status_json($pretty = false) { + } - $a_json = wg_status(); + } - return json_encode($a_json, ($pretty ? JSON_PRETTY_PRINT : null) | JSON_UNESCAPED_SLASHES); + return ($json ? json_encode($ret_config, JSON_UNESCAPED_SLASHES) : $ret_config); } -// Returns a peer array index by public key and tunnel name -function wg_get_peer_id($public_key, $tunnel_name) { +/* + * Returns a peer's config array index by public key and tunnel name + */ +function wg_get_peer_idx($public_key, $tunnel_name) { global $wgg; if (isset($wgg['peers']) && is_array($wgg['peers'])) { - foreach ($wgg['peers'] as $peer_id => $peer){ + foreach ($wgg['peers'] as $peer_idx => $peer){ if ($public_key == $peer['publickey'] && $tunnel_name = $peer['tun']) { - return $peer_id; + return $peer_idx; } @@ -166,124 +178,151 @@ function wg_get_address_family($address) { } -function wg_ifconfig_del_address($wg_ifname, $masked_address, $ret_posix_style = true) { +function wg_ifconfig_if_address_adddel($if_name, $masked_address, $add = true, &$cmds = null) { global $wgg; - // Assume family validation will fail... - $ret_val = 1; + $esa = fn($s) => escapeshellarg($s); + + $ret_code = 0; + + $action = $add ? 'add' : 'delete'; + + $error = $add ? WG_ERROR_IF_SETADDR : WG_ERROR_IF_DELADDR; - // Gets the ifconfig address family while also validating the input at the same time - if ($family = wg_get_address_family($masked_address)) { + if (wg_is_valid_tunnel($if_name, true) + && ($family = wg_get_address_family($masked_address))) { - list($address, $address_subnet) = explode('/', $masked_address); + $cmd = "{$wgg['ifconfig']} {$esa($if_name)} {$esa($family)} {$esa($masked_address)} {$esa($action)} 2>&1"; - wg_escapeshellarg($wg_ifname, $address); + $cmds[] = $res = wg_exec($cmd, 'interface', $error); - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname} {$family} {$address} delete"); + $ret_code = $res['ret_code']; } - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + return ($ret_code == 0); } -function wg_ifconfig_set_address($wg_ifname, $masked_address, $ret_posix_style = true) { +function wg_interface_in_group($if_name, $group = null) { global $wgg; - // Assume family validation will fail... - $ret_val = 1; + $esa = fn($s) => escapeshellarg($s); + + $ifs = array(); + + // Assume this will fail... + $res = false; + + // Default to the package-installed interface group... + $group = (is_null($group)) ? $wgg['ifgroupentry']['ifname'] : $group; + + if (wg_is_valid_tunnel($if_name, true)) { - // Gets the ifconfig address family while also validating the input at the same time - if ($family = wg_get_address_family($masked_address)) { + $cmd = "{$wgg['ifconfig']} -g {$esa($group)} 2>&1"; - wg_escapeshellarg($wg_ifname, $masked_address); + exec($cmd, $ifs, $ret_code); - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname} {$family} {$masked_address} add"); + $res = ($ret_code <> 0) ? $res : in_array($if_name, $ifs); } - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + return $res; } -function wg_ifconfig_down_interface($wg_ifname, $ret_posix_style = true) { +function wg_ifconfig_if_updown($if_name, $up = true, &$cmds = null) { global $wgg; - wg_escapeshellarg($wg_ifname); + $esa = fn($s) => escapeshellarg($s); + + $state = fn($up) => $up ? 'up' : 'down'; - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname} down"); + $ret_code = 0; - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + // Is this even a real interface? + if (wg_is_valid_tunnel($if_name, true)) { -} + // Are we trying to down-an-up or up-a-down? Anything else is wrong in this context... + if ((!wg_interface_status($if_name) && $up) || (wg_interface_status($if_name) && !$up)) { -function wg_ifconfig_up_interface($wg_ifname, $ret_posix_style = true) { - global $wgg; + $cmd = "{$wgg['ifconfig']} {$esa($if_name)} {$esa($state($up))} 2>&1"; - wg_escapeshellarg($wg_ifname); + $error = $up ? WG_ERROR_IF_UP : WG_ERROR_IF_DOWN; + + $cmds[] = $res = wg_exec($cmd, 'interface', $error); + + $ret_code = $res['ret_code']; + + } - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname} up"); + } - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + return ($ret_code == 0); } /* * This updates the addresses of the specified interface without tearing it down */ -function wg_interface_update_addresses($wg_ifname, $ret_posix_style = true) { +function wg_interface_update_addresses($if_name, &$cmds = null) { global $wgg; - // Assume everything will be okay... - $ret_val = 0; + // Assume this will be successful... + $res = true; - $tun_idx = wg_get_tunnel_array_index($wg_ifname); + if (wg_is_valid_tunnel($if_name, true) + && (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($if_name))) { - $tunnel = $wgg['tunnels'][$tun_idx]; + // Assigned tunnel interfaces are handled by pfSense and should be ignored here + if (!is_wg_tunnel_assigned($tunnel['name'])) { - wg_init_config_arr($tunnel, array('addresses', 'row')); + // Get an array of the current addresses assigned to the tunnel interface + $current = pfSense_getall_interface_addresses($tunnel['name']); - // Assigned tunnel interfaces are handled by pfSense and should be ignored here - if (!is_wg_tunnel_assigned($wg_ifname)) { + // Get an array of the addresses to be assigned to the interface + $desired = array_map(function($x) { - // Get an array of the current addresses assigned to the tunnel interface - $current = pfSense_getall_interface_addresses($wg_ifname); + return "{$x['address']}/{$x['mask']}"; - // Get an array of the addresses to be assigned to the interface - $desired = array_map(fn(&$x) => "{$x['address']}/{$x['mask']}", $tunnel['addresses']['row']); + }, $tunnel['addresses']['row']); - // Determine the addresses to remove - $addresses_to_remove = array_diff($current, array_intersect($current, $desired)); + // Determine the addresses to remove + $addresses_to_remove = array_diff($current, array_intersect($current, $desired)); - // Now remove them - foreach ($addresses_to_remove as $address) { + // Now remove them + foreach ($addresses_to_remove as $address) { - $ret_val |= wg_ifconfig_del_address($wg_ifname, $address); + $res &= wg_ifconfig_if_address_adddel($tunnel['name'], $address, false, $cmds); - } + } - // Determine the addresses to add - $addresses_to_add = array_diff($desired, array_intersect($current, $desired)); + // Determine the addresses to add + $addresses_to_add = array_diff($desired, array_intersect($current, $desired)); - // Now add them - foreach ($addresses_to_add as $address) { + // Now add them + foreach ($addresses_to_add as $address) { - $ret_val |= wg_ifconfig_set_address($wg_ifname, $address); + $res &= wg_ifconfig_if_address_adddel($tunnel['name'], $address, true, $cmds); - } + } + + // Need to let pfSense handle the assigned interfaces + } elseif (is_wg_tunnel_assigned($tunnel['name'])) { - // Need to let pfSense handle the assigned interfaces - } elseif (is_wg_tunnel_assigned($wg_ifname)) { + if ($pfsense_if_name = wg_get_pfsense_interface_info($tunnel['name'])) { - if ($pfsense_if_name = wg_get_pfsense_interface_info($wg_ifname)) { + // This doesn't return anything useful... + interface_reconfigure($pfsense_if_name['name']); - interface_reconfigure($pfsense_if_name['name']); + } } } - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + // This will return false if anything went wrong... + return $res; } @@ -304,7 +343,7 @@ function wg_get_pfsense_interface_info($tunnel_name) { $ret_array['name'] = $tmp_name; - $ret_array['descr'] = $ifdescr[$tmp_name]; + $ret_array['descr'] = $ifdescr[$tmp_name]; return $ret_array; @@ -315,128 +354,262 @@ function wg_get_pfsense_interface_info($tunnel_name) { } -function wg_interface_syncconf($wg_ifname, $ret_posix_style = true) { +/* + * A wrapper for just setting peer endpoints + */ +function wg_peer_set_endpoint($tunnel_name, $public_key, $endpoint, &$cmds = null) { + + return wg_peer_set_config($tunnel_name, $public_key, 'endpoint', $endpoint, WG_ERROR_PEER_ENDPOINT, $cmds); + +} + +/* + * A wrapper for `wg set peer ` + */ +function wg_peer_set_config($tunnel_name, $public_key, $key = null, $value = null, $error_flag = 0, &$cmds = null) { + global $wgg; + + $esa = fn($s) => escapeshellarg($s); + + $key = (!is_null($key)) ? " {$esa($key)}" : null; + + $value = (!is_null($value)) ? " {$esa($value)}" : null; + + if (wg_is_valid_tunnel($tunnel_name, true)) { + + $cmd = "{$wgg['wg']} set {$esa($tunnel_name)} peer {$esa($public_key)}{$key}{$value} 2>&1"; + + $cmds[] = $res = wg_exec($cmd, 'peer', $error_flag); + + $ret_code = $res['ret_code']; + + } + + return ($ret_code == 0); + +} + +function wg_wg_if_sync($if_name, &$cmds = null) { global $wgg; - $ret_val = 1; + $esa = fn($s) => escapeshellarg($s); - $wg_conf_path = "{$wgg['conf_path']}/{$wg_ifname}.conf"; + $ret_code = 0; - if (file_exists($wg_conf_path)) { + // We need to make sure latest conf files are on disk + wg_resync(); - wg_escapeshellarg($wg_ifname, $wg_conf_path); + $conf_path = "{$wgg['conf_path']}/{$if_name}.conf"; - $ret_val = mwexec("{$wgg['wg']} syncconf {$wg_ifname} {$wg_conf_path}"); + if (file_exists($conf_path) + && wg_is_valid_tunnel($if_name, true)) { + + $cmd = "{$wgg['wg']} syncconf {$esa($if_name)} {$esa($conf_path)}"; + + $cmds[] = $res = wg_exec($cmd, 'interface', WG_ERROR_IF_SYNC); + + $ret_code = $res['ret_code']; } - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + return ($ret_code == 0); } /* * This creates a WireGuard interface of a specified name */ -function wg_interface_create($wg_ifname, $ret_posix_style = true) { +function wg_ifconfig_if_create($if_name, &$cmds = null) { global $wgg; - wg_escapeshellarg($wg_ifname); + $esa = fn($s) => escapeshellarg($s); - // First check if the interface already exists - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname}"); + $ret_code = 0; - if ($ret_val <> 0) { - - // Create the interface with the specified name - $ret_val = mwexec("{$wgg['ifconfig']} wg create name {$wg_ifname}"); + if (!wg_is_valid_tunnel($if_name, true)) { + + $cmd = "{$wgg['ifconfig']} wg create name {$esa($if_name)} 2>&1"; + + $cmds[] = $res = wg_exec($cmd, 'interface', WG_ERROR_IF_CREATE); + + $ret_code = $res['ret_code']; } + + return ($ret_code == 0); + +} + +function wg_exec($cmd, $error_type = null, $error_flag = 1) { + + $ret_code = 0; + + $output = $errors = array(); + + exec($cmd, $output, $ret_code); + + $ret_code = ($ret_code <> 0) ? $error_flag : $ret_code; + + $errors = wg_get_errors($error_type, $ret_code); - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + return array('cmd' => $cmd, 'output' => $output, 'ret_code' => $ret_code, 'errors' => $errors); + +} + +function wg_get_errors($type, $ret_code) { + global $wgg; + + $errors = array(); + + if (isset($wgg['error_flags'][$type])) { + + // Now collect the errors... + foreach ($wgg['error_flags'][$type] as $error_mask => $error_text) { + + if (($ret_code & $error_mask) > 0) { + + $errors[$error_mask] = $error_text; + + } + + } + + } + + return $errors; } /* * This destroys a WireGuard interface of a specified name */ -function wg_interface_destroy($wg_ifname, $ret_posix_style = true) { +function wg_ifconfig_if_destroy($if_name, &$cmds = null) { global $wgg; - wg_escapeshellarg($wg_ifname); + $esa = fn($s) => escapeshellarg($s); - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname} destroy"); + $ret_code = 0; - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + if (wg_is_valid_tunnel($if_name, true)) { + + $cmd = "{$wgg['ifconfig']} {$esa($if_name)} destroy 2>&1"; + + $cmds[] = $res = wg_exec($cmd, 'interface', WG_ERROR_IF_DESTROY); + + $ret_code = $res['ret_code']; + + } + + return ($ret_code == 0); + +} + +/* + * Returns an array of running WireGuard tunnel interfaces per wg(8) + */ +function wg_get_running_ifs() { + + return array_keys(wg_get_running_config()); } /* - * Returns an array of the current WireGuard tunnel interfaces running as per ifconfig + * Returns an array of configured WireGuard tunnel interfaces */ -function wg_get_real_ifs() { +function wg_get_configured_ifs() { global $wgg; - $ret_array = array(); + $tunnels = array(); - exec("{$wgg['ifconfig']} -g wg", $ret_array, $ret_code); - - return ($ret_code == 0) ? $ret_array : array(); + if (isset($wgg['tunnels']) && is_array($wgg['tunnels'])) { + + $tunnels = array_map(fn($x) => $x['name'], $wgg['tunnels']); + + } + + return $tunnels; + +} + +function wg_ifconfig_if_group_add($if_name, $group, &$cmds = null) { + global $wgg; + + $esa = fn($s) => escapeshellarg($s); + + $ret_code = 0; + + // Is this a real interface and not already in the group? + if (in_array($if_name, pfSense_interface_listget()) + && (!wg_interface_in_group($if_name, $group))) { + + $cmd = "{$wgg['ifconfig']} {$esa($if_name)} group {$esa($group)} 2>&1"; + + $cmds[] = $res = wg_exec($cmd, 'interface', WG_ERROR_IF_GROUP); + + $ret_code = ($res['ret_code']); + + } + + return ($ret_code == 0); } /* - * This adds a WireGuard interface to the WireGuard interface group + * This adds a WireGuard interface to the WireGuard interface groups */ -function wg_interface_group_add($wg_ifname, $ret_posix_style = true) { +function wg_interface_update_groups($if_name, &$cmds = null) { global $wgg; - $wg_ifgroup = $wgg['ifgroupentry']['ifname']; + // Need to make sure tunnels land in at *least* these groups + $groups = array('wg', $wgg['ifgroupentry']['ifname']); + + // Assume this will be successful... + $res = true; - wg_escapeshellarg($wg_ifname, $wg_ifgroup); + // Is this a real interface? + if (in_array($if_name, pfSense_interface_listget())) { - // First check if the interface already exists - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname}"); + foreach ($groups as $group) { - if ($ret_val == 0) { + $res &= wg_ifconfig_if_group_add($if_name, $group, $cmds); - // Add interface to interface group - $ret_val = mwexec("{$wgg['ifconfig']} {$wg_ifname} group {$wg_ifgroup}"); + } } - - return ($ret_posix_style ? ($ret_val <> 0) : ($ret_val == 0)); + + return $res; } -function wg_interface_status($wg_ifname) { +function wg_interface_status($if_name) { - $if_flags = wg_ifconfig_interface_flags($wg_ifname); + $if_flags = wg_ifconfig_get_flags($if_name); - return in_array("UP", $if_flags); + return in_array('UP', $if_flags); } -function wg_ifconfig_interface_flags($wg_ifname) { +function wg_ifconfig_get_flags($if_name) { global $wgg; - wg_escapeshellarg($wg_ifname); + $esa = fn($s) => escapeshellarg($s); - $ret_array = $output = array(); + $flags = $output = array(); - exec("{$wgg['ifconfig']} {$wg_ifname}", $output, $ret_code); + if (wg_is_valid_tunnel($if_name, true)) { - if ($ret_code == 0) { + exec("{$wgg['ifconfig']} {$esa($if_name)}", $output, $ret_code); - if (preg_match("/flags=.*<(?P.*)>/", $output[0], $matches)) { + if (($ret_code == 0) && preg_match("/flags=.*<(?P.*)>/", $output[0], $matches)) { - $ret_array = explode(',', $matches['flags']); + $flags = explode(',', $matches['flags']); } } // Consumers of this function always expect an array type - return $ret_array; + return $flags; } @@ -444,33 +617,32 @@ function wg_ifconfig_interface_flags($wg_ifname) { function wg_pkg_info() { global $wgg; - $ret_array = array(); + $esa = fn($s) => escapeshellarg($s); - $a_fields = array('name'=> '%n', 'version' => '%v', 'comment' => '%c'); + $ret_array = array(); - $a_return_keys = array_values(array_flip($a_fields)); + $fields = array('name'=> '%n', 'version' => '%v', 'comment' => '%c'); - $field_string = implode("\t", $a_fields); + $return_keys = array_values(array_flip($fields)); - $a_packages = $wgg['depends_names']; + $field_string = implode("\t", $fields); // Each package needs to be escaped individually before imploding - wg_escapeshellarg($field_string, $a_packages); - - $packages_string = implode(" ", $a_packages); + $packages = array_map(fn($x) => escapeshellarg($x), $wgg['depends_names']); + + $packages_string = implode(' ', $packages); - exec("{$wgg['pkg']} query {$field_string} {$packages_string}", $packages, $ret_code); + exec("{$wgg['pkg']} query {$esa($field_string)} {$packages_string}", $output, $ret_code); if ($ret_code == 0) { - foreach ($packages as $pkg_index => $package) { + foreach ($output as $pkg_index => $package) { $fields = explode("\t", $package); foreach ($fields as $field_index => $field) { - $ret_array[$pkg_index][$a_return_keys[$field_index]] = $field; - + $ret_array[$pkg_index][$return_keys[$field_index]] = $field; } @@ -489,9 +661,7 @@ function wg_gen_keypair($json = false) { $privkey = exec("{$wgg['wg']} genkey"); - $res = wg_gen_publickey($privkey, $json); - - return $res; + return wg_gen_publickey($privkey, $json); } @@ -499,16 +669,13 @@ function wg_gen_keypair($json = false) { function wg_gen_publickey($privkey, $json = false) { global $wgg; - // We want to pass the unmodified privkey through to the return array - $safe_privkey = $privkey; - - $was_clamped = wg_is_key_clamped($privkey); + $esa = fn($s) => escapeshellarg($s); - $privkey_clamped = wg_clamp_key($privkey); + $was_clamped = wg_is_key_clamped($privkey); - wg_escapeshellarg($safe_privkey); + $privkey_clamped = wg_clamp_key($privkey); - $pubkey = exec("echo {$safe_privkey} | {$wgg['wg']} pubkey"); + $pubkey = exec("echo {$esa($privkey)} | {$wgg['wg']} pubkey"); $res = array('privkey' => $privkey, 'privkey_clamped' => $privkey_clamped, 'pubkey' => $pubkey, 'was_clamped' => $was_clamped); @@ -516,6 +683,28 @@ function wg_gen_publickey($privkey, $json = false) { } +/* + * Reference0: https://lists.zx2c4.com/pipermail/wireguard/2021-June/006787.html + * Reference1: https://git.zx2c4.com/wireguard-freebsd/tree/src/crypto.h#n100 + * + * Even though any 256-bit bitstring generated from a csprng is a fine private key, + * the kernel clamps private keys according to the transformation implemented here. + * Some WireGuard key generators aren't properly pre-clamping generated keys and this + * can cause confusion for some users who might stumble across local .conf files + * and wg(8) output and see 'different' private keys, even though these keys result in + * the same public key. + * + * The way WireGuard is implemented, you can technically have two valid private keys, + * one that is pre-clamped and one that is not, that can both result in the same public key. + * + * These routines detect if the key is clamped or not, so we can at least include a note + * in the .conf file so users who stumble onto them during troubleshooting won't freak out. + * + * These routines might also become useful in the future for logging facilities. + * + * Private keys and pre-shared Keys must undergo this transformaton. + * + */ function wg_clamp_key($key) { if (wg_is_valid_key($key)) { @@ -538,6 +727,9 @@ function wg_clamp_key($key) { } +/* + * Checks if a given $key is clamped + */ function wg_is_key_clamped($key) { if (wg_is_valid_key($key)) { @@ -550,16 +742,20 @@ function wg_is_key_clamped($key) { } -// Checks if a given private, public, or pre-shared key is valid +/* + * Reference0: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html + * + * Checks if a given private, public, or pre-shared key is valid + */ function wg_is_valid_key($key) { - // Reference: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html - return preg_match('/^[A-Za-z0-9+\/]{42}[A|E|I|M|Q|U|Y|c|g|k|o|s|w|4|8|0]=$/', $key); } -// Generate a pre-shared key +/* + * Generate a pre-shared key + */ function wg_gen_psk() { global $wgg; @@ -569,7 +765,9 @@ function wg_gen_psk() { } -// Return the next available WireGuard port +/* + * Return the next available WireGuard port + */ function next_wg_port() { global $wgg; @@ -596,7 +794,7 @@ function next_wg_port() { if (!$found) { return $idx; - + } } @@ -646,7 +844,7 @@ function next_wg_if() { for ($ifnum = 0; $ifnum < $wgg['max_tunnels']; $ifnum++) { $want_if = "{$wgg['if_prefix']}{$ifnum}"; - + if (!in_array($want_if, $used_ifs)) { return $want_if; @@ -656,7 +854,7 @@ function next_wg_if() { } return -1; - + } // Check if wg tunnel is assigned to an interface @@ -664,35 +862,51 @@ function is_wg_tunnel_assigned($tunnel_name, $disabled = true) { $if_list = get_configured_interface_list_by_realif($disabled); - $is_assigned = array_key_exists($tunnel_name, $if_list); + return array_key_exists($tunnel_name, $if_list); - return $is_assigned; - } -// Check if at least one tunnel is assigned -function is_wg_assigned($disabled = true) { +function wg_peer_is_valid($tunnel_name, $public_key, $running_state = false) { global $wgg; - // Assume that no tunnels are assigned - $is_assigned = false; + $peers = array(); - $if_list = get_configured_interface_list_by_realif($disabled); + if (isset($wgg['peers']) && is_array($wgg['peers']) && !$running_state) { - foreach ($if_list as $realif => $name) { + $peers = wg_tunnel_get_peers_config_keys($tunnel_name); - // We found one, no need to keep checking - if (substr($realif, 0, strlen($wgg['if_prefix'])) == $wgg['if_prefix']) { - - $is_assigned = true; + } elseif ($running_state) { - break; - - } - - } + $peers = wg_tunnel_get_peers_running_keys($tunnel_name); + + } + + return in_array($public_key, $peers); + +} + + +/* + * Check if a wg tunnel is valid + */ +function wg_is_valid_tunnel($tunnel_name, $running_state = false) { + global $wgg; + + $tunnels = array(); + + // Checks desired/configured tunnels + if (isset($wgg['tunnels']) && is_array($wgg['tunnels']) && !$running_state) { - return $is_assigned; + $tunnels = wg_get_configured_ifs(); + + // Checks running tunnels + } elseif ($running_state) { + + $tunnels = wg_get_running_ifs(); + + } + + return in_array($tunnel_name, $tunnels); } @@ -722,31 +936,73 @@ function is_wg_enabled() { } +function wg_tunnel_get_config_by_name($tunnel_name) { + global $wgg; + + $tun_idx = wg_get_tunnel_array_index($tunnel_name); + + return wg_tunnel_get_config($tun_idx, false); + +} + +function wg_tunnel_get_config($tun_idx, $return_empty = false) { + global $wgg; + + $tunnel = array(); + + $valid = (isset($wgg['tunnels'][$tun_idx]) && is_array($wgg['tunnels'][$tun_idx])); + + $is_new = !$valid; + + $tun_idx = !$valid ? count($wgg['tunnels']) : $tun_idx; + + $tunnel = $wgg['tunnels'][$tun_idx]; + + wg_init_config_arr($tunnel, array('addresses', 'row')); + + return ($valid || $return_empty) ? array($tun_idx, $tunnel, $is_new) : false; + +} + +function wg_peer_get_config($peer_idx, $return_empty = false) { + global $wgg; + + $peer = array(); + + $valid = (isset($wgg['peers'][$peer_idx]) && is_array($wgg['peers'][$peer_idx])); + + $is_new = !$valid; + + $peer_idx = !$valid ? count($wgg['peers']) + 1 : $peer_idx; + + $peer = $wgg['peers'][$peer_idx]; + + wg_init_config_arr($peer, array('allowedips', 'row')); + + return ($valid || $return_empty) ? array($peer_idx, $peer, $is_new) : false; + +} + /* - * This returns an array of peers for a given tunnel + * This returns an array of peer configs for a given tunnel */ -function wg_get_tunnel_peers($tunnel_name) { +function wg_tunnel_get_peers_config($tunnel_name) { global $wgg; wg_globals(); - $a_ret = array(); + $ret_peers = array(); - $tun_idx = wg_get_tunnel_array_index($tunnel_name); - - if (isset($wgg['tunnels'][$tun_idx])) { + if (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($tunnel_name)) { if (isset($wgg['peers']) && is_array($wgg['peers'])) { // Look through array of peers for matching tunnel name foreach ($wgg['peers'] as $peer_idx => $peer) { - if ($peer['tun'] == $tunnel_name) { + if ($peer['tun'] == $tunnel['name']) { - // We need the array index for future manipulations - $peer['index'] = $peer_idx; - - $a_ret[] = $peer; + $ret_peers[] = wg_peer_get_config($peer_idx, false); } @@ -757,7 +1013,49 @@ function wg_get_tunnel_peers($tunnel_name) { } // Return the list of filtered peers - return $a_ret; + return $ret_peers; + +} + +/* + * This returns an array of peer keys for a given tunnel + * + * These are actually running and bound to a tunnel + */ +function wg_tunnel_get_peers_running_keys($tunnel_name) { + global $wgg; + + $esa = fn($s) => escapeshellarg($s); + + $output = $res = array(); + + $cmd = "{$wgg['wg']} show {$esa($tunnel_name)} peers 2>&1"; + + exec($cmd, $output, $ret_code); + + $res = ($ret_code <> 0) ? $res : $output; + + return $res; + +} + +/* + * This returns an array of peer keys for a given tunnel + * + * These are actually configured and bound to a tunnel + */ +function wg_tunnel_get_peers_config_keys($tunnel_name) { + + // Pull out the public keys + $keys = array_map(function($s) { + + list($peer_idx, $peer, $is_new) = $s; + + return $peer['publickey']; + + }, wg_tunnel_get_peers_config($tunnel_name)); + + return $keys; } @@ -765,11 +1063,11 @@ function wg_get_tunnel_peers($tunnel_name) { * Return WireGuard tunnel networks for a given address family */ function wg_get_tunnel_networks($family = 'both') { - global $config, $wgg; + global $wgg; $wg_tunnel_networks = array(); - if (is_wg_enabled()) { + if (is_wg_enabled() && isset($wgg['tunnels']) && is_array($wgg['tunnels'])) { foreach ($wgg['tunnels'] as $tunnel) { diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_globals.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_globals.inc index cd624cf8..743b234e 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_globals.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_globals.inc @@ -27,19 +27,23 @@ require_once('globals.inc'); global $g, $wgg; -define('WG_ERROR_IF_NAME', 1); -define('WG_ERROR_IF_CREATE', 2); -define('WG_ERROR_IF_GROUP', 4); -define('WG_ERROR_IF_ADDRESSES', 8); -define('WG_ERROR_IF_UP', 16); -define('WG_ERROR_IF_DOWN', 32); -define('WG_ERROR_IF_SYNC', 64); -define('WG_ERROR_IF_DESTROY', 128); - -define('WG_ERROR_SVC_RUNNING', 1); -define('WG_ERROR_SVC_START', 2); -define('WG_ERROR_SVC_STOP', 4); -define('WG_ERROR_SVC_BUILD', 8); +define('WG_ERROR_PEER_SET', 1); +define('WG_ERROR_PEER_ENDPOINT', 2); + +define('WG_ERROR_IF_NAME', 1); +define('WG_ERROR_IF_CREATE', 2); +define('WG_ERROR_IF_GROUP', 4); +define('WG_ERROR_IF_SETADDR', 8); +define('WG_ERROR_IF_DELADDR', 16); +define('WG_ERROR_IF_UP', 32); +define('WG_ERROR_IF_DOWN', 64); +define('WG_ERROR_IF_SYNC', 128); +define('WG_ERROR_IF_DESTROY', 256); + +define('WG_ERROR_SVC_RUNNING', 1); +define('WG_ERROR_SVC_START', 2); +define('WG_ERROR_SVC_STOP', 4); +define('WG_ERROR_SVC_CREATE', 8); $wgg = array( 'wg' => '/usr/local/bin/wg', @@ -72,11 +76,15 @@ $wgg = array( 300 => array('class' => 'text-warning', 'title' => gettext('Greater than 5 minutes')), 0 => array('class' => 'text-success', 'title' => gettext('Less than 5 minutes'))), 'error_flags' => array( - 'tunnel' => array( + 'peer' => array( + WG_ERROR_PEER_SET => gettext('Unable to set peer configuration'), + WG_ERROR_PEER_ENDPOINT => gettext('Unable to resolve peer endpoint')), + 'interface' => array( WG_ERROR_IF_NAME => gettext('Invalid WireGuard tunnel name'), WG_ERROR_IF_CREATE => gettext('Unable to create WireGuard tunnel interface'), WG_ERROR_IF_GROUP => gettext('Unable to add WireGuard tunnel interface to the WireGuard interface group'), - WG_ERROR_IF_ADDRESSES => gettext('Unable to update WireGuard tunnel interface addresses'), + WG_ERROR_IF_SETADDR => gettext('Unable to set WireGuard tunnel interface address(es)'), + WG_ERROR_IF_DELADDR => gettext('Unable to delete WireGuard tunnel interface address(es)'), WG_ERROR_IF_UP => gettext('Unable to bring up WireGuard tunnel interface'), WG_ERROR_IF_DOWN => gettext('Unable to bring down WireGuard tunnel interface'), WG_ERROR_IF_SYNC => gettext('Unable to sync WireGuard tunnel configuration with wg(8)'), @@ -85,11 +93,12 @@ $wgg = array( WG_ERROR_SVC_RUNNING => gettext('WireGuard service is already running'), WG_ERROR_SVC_START => gettext('Unable to start WireGuard service'), WG_ERROR_SVC_STOP => gettext('Unable to stop WireGuard service'), - WG_ERROR_SVC_BUILD => gettext('Unable to build WireGuard tunnel(s)'))), - 'default_mtu' => 1420, - 'default_port' => 51820, - 'max_port' => 65535, - 'max_tunnels' => 32768 + WG_ERROR_SVC_CREATE => gettext('Unable to create WireGuard tunnel(s)'))), + 'default_mtu' => 1420, + 'default_port' => 51820, + 'default_resolve_interval' => 300, + 'max_port' => 65535, + 'max_tunnels' => 32768 ); // These all depend on one more more of the above values diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_guiconfig.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_guiconfig.inc index 4c2e6ff2..08b71c3a 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_guiconfig.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_guiconfig.inc @@ -20,7 +20,7 @@ */ // pfSense includes -require_once('guiconfig.inc'); +//require_once('guiconfig.inc'); require_once('util.inc'); // WireGuard includes @@ -43,7 +43,7 @@ function wg_print_service_warning($only_with_tunnels = true) { function wg_print_config_apply_box() { global $wgg; - $getText = function($text) { return gettext($text); }; + $getText = fn($s) => gettext($s); if (is_subsystem_dirty($wgg['subsystems']['wg'])) { @@ -58,7 +58,7 @@ function wg_print_config_apply_box() { // Formats peer endpoint address and port for the UI function wg_format_endpoint($header = true, $peer = null, $endpoint_key = 'endpoint', $port_key = 'port') { - $getText = function($text) { return gettext($text); }; + $getText = fn($s) => gettext($s); if ($header) { @@ -86,14 +86,14 @@ function wg_format_endpoint($header = true, $peer = null, $endpoint_key = 'endpo function wg_get_tun_list() { global $config, $wgg; + // Format tunnel description if one is configured + $tunDescr = fn($tunnel) => !empty($tunnel['descr']) ? " ({$tunnel['descr']})" : null; + $a_ret = array(); // Always include the unassigned option first $a_ret['unassigned'] = 'Unassigned'; - // Format tunnel description if one is configured - $tunDescr = function($tunnel) { return (!empty($tunnel['descr'])) ? " ({$tunnel['descr']})" : null; }; - if (is_array($wgg['tunnels'])) { foreach ($wgg['tunnels'] as $tunnel) { @@ -120,28 +120,40 @@ function wg_truncate_pretty($text, $max_length) { } -function wg_interface_status_icon($status, $enabled_class = "fa fa-arrow-up text-success", $disabled_class = "fa fa-arrow-down text-danger") { +function wg_interface_status_icon($status = 'up', $enabled_class = "fa fa-arrow-up text-success", $disabled_class = "fa fa-arrow-down text-danger") { global $wgg; - $ret_class = ($status === "up" || $status === true) ? " {$enabled_class}" : " {$disabled_class}"; + $hsa = fn($s) => htmlspecialchars($s); - wg_htmlspecialchars($ret_class, $status); + $ret_class = ($status === 'up') ? $enabled_class : $disabled_class; - $ret_html = ""; + $ret_html = ""; return $ret_html; } -// Returns the appropriate fa icon class for handshake status icon -function wg_handshake_status_icon($latest_handshake_stamp = 0, $fa_icon = "fa-handshake") { +/* + * Returns the appropriate icon for peer handshake status based on latest handshake time + * + * $latest_handshake should be format compatible with DateTime + */ +function wg_handshake_status_icon($latest_handshake = '@0', $tunnel_status = 'up', $fa_icon = "fa-handshake") { global $wgg; - $end_time = new DateTimeImmutable; + $hsa = fn($s) => htmlspecialchars($s); + + try { + + $end_time = new DateTimeImmutable; + + $start_time = new DateTime($latest_handshake); + + } catch (Exception $e) { - $start_time = new DateTime(); + return null; - $start_time->setTimestamp($latest_handshake_stamp); + } $diff_time = $end_time->diff($start_time); @@ -163,9 +175,7 @@ function wg_handshake_status_icon($latest_handshake_stamp = 0, $fa_icon = "fa-ha } - wg_htmlspecialchars($fa_icon, $ret_class, $ret_title); - - $ret_html = ""; + $ret_html = ""; return $ret_html; @@ -173,6 +183,7 @@ function wg_handshake_status_icon($latest_handshake_stamp = 0, $fa_icon = "fa-ha /* * Formats a given time difference in a human-friendly way + * * $start and $end should be format compatible with DateTime */ function wg_human_time_diff($start, $end = null, $num_units = 2, $from_epoch = false) { @@ -206,9 +217,9 @@ function wg_human_time_diff($start, $end = null, $num_units = 2, $from_epoch = f $interval = $end->diff($start); - $doPlural = function($nb, $str) { return ($nb > 1) ? gettext("{$str}s") : gettext($str); }; + $doPlural = fn($nb, $str) => ($nb > 1) ? gettext("{$str}s") : gettext($str); - $doTense = function($interval) { return ($interval->invert) ? gettext('ago') : gettext('from now'); }; + $doTense = fn($interval) => ($interval->invert) ? gettext('ago') : gettext('from now'); $a_format = array(); @@ -246,7 +257,8 @@ function wg_secret_input_type() { $type = 'text'; - if (isset($wgg['config']['hide_secrets']) && $wgg['config']['hide_secrets'] =='yes') { + if (isset($wgg['config']['hide_secrets']) + && $wgg['config']['hide_secrets'] =='yes') { $type = 'password'; @@ -256,40 +268,65 @@ function wg_secret_input_type() { } -// Gets the appropriate class based on whether or not the target is enabled or disabled -function wg_entrystatus_class($target_device) { +function wg_tunnel_status_class($tunnel = null) { global $wgg; - $class = 'disabled'; + if (isset($tunnel) && is_array($tunnel)) { + + if (wg_is_service_running() && ($tunnel['enabled'] == 'yes')) { - if (isset($target_device) && - $target_device['enabled'] == 'yes' && - is_module_loaded($wgg['kmod'])) { + return 'enabled'; - $class = 'enabled'; + } } - return $class; + return 'disabled'; + +} + +function wg_peer_status_class($peer = null) { + global $wgg; + + if (isset($peer) && is_array($peer)) { + + $tunnel_state = true; + + // We want to visually disable peers if the tunnel is disabled... + if (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($peer['tun'])) { + + $tunnel_state = ($tunnel['enabled'] == 'yes'); + + } + + if (wg_is_service_running() && ($peer['enabled'] == 'yes') && $tunnel_state) { + + return 'enabled'; + + } + + } + + return 'disabled'; } // Generates a toggle icon link based on the status of the target -function wg_generate_toggle_icon_link($target_device, $title = '', $href = '#', $usepost = true, $icon_enabled = 'fa-ban', $icon_disabled = 'fa-check-square-o') { +function wg_generate_toggle_icon_link($target_device, $title = '', $href = '#', $post = true, $icon_enabled = 'fa-ban', $icon_disabled = 'fa-check-square-o') { + + $hsc = fn($s) => htmlspecialchars($s); $ret_html = null; if (isset($target_device)) { - $s_icon = (isset($target_device['enabled']) && $target_device['enabled'] == 'yes') ? $icon_enabled : $icon_disabled; - - $s_title = gettext($title); + $icon = (isset($target_device['enabled']) && $target_device['enabled'] == 'yes') ? $icon_enabled : $icon_disabled; - $s_usepost = $usepost ? 'usepost' : ''; + $title = gettext($title); - wg_htmlspecialchars($s_icon, $s_title, $href, $s_usepost); + $usepost = $post ? ' usepost' : null; - $ret_html = ""; + $ret_html = ""; } @@ -300,24 +337,13 @@ function wg_generate_toggle_icon_link($target_device, $title = '', $href = '#', function wg_generate_tunnel_address_popover_link($tunnel_name) { global $wgg; - $getText = function($text) { return gettext($text); }; - - $tun_idx = wg_get_tunnel_array_index($tunnel_name); + $getText = fn($s) => gettext($s); - if (isset($wgg['tunnels'][$tun_idx]) && is_array($wgg['tunnels'][$tun_idx])) { + $hsc = fn($s) => htmlspecialchars($s); - $tunnel = $wgg['tunnels'][$tun_idx]; + if (list($tun_idx, $tunnel, $is_new) = wg_tunnel_get_config_by_name($tunnel_name, false)) { - wg_init_config_arr($tunnel, array('addresses', 'row')); - - // Smash address + mask - $addresses = array_map(function($x) { - - return array( - 'address' => "{$x['address']}/{$x['mask']}", - 'descr' => $x['descr']); - - }, $tunnel['addresses']['row']); + $addresses = $tunnel['addresses']['row']; if (!is_wg_tunnel_assigned($tunnel['name'])) { @@ -339,12 +365,12 @@ function wg_generate_tunnel_address_popover_link($tunnel_name) { foreach ($addresses as $address) { - wg_htmlspecialchars($address); + $address['address'] = "{$address['address']}/{$address['mask']}"; $data_html .= <<<"ROW" - {$address['address']} - {$address['descr']} + {$hsc($address['address'])} + {$hsc($address['descr'])} ROW; @@ -358,7 +384,7 @@ function wg_generate_tunnel_address_popover_link($tunnel_name) { $title = gettext('Interface Addresses'); - $hint = "{$addresses[0]['address']} (+{$extras})"; + $hint = "{$addresses[0]['address']}/{$addresses[0]['mask']} (+{$extras})"; $popover_params = " data-toggle=\"popover\" data-trigger=\"hover focus\" data-content=\"{$data_html}\" data-html=\"true\""; @@ -366,7 +392,7 @@ function wg_generate_tunnel_address_popover_link($tunnel_name) { $title = $addresses[0]['descr']; - $hint = $addresses[0]['address']; + $hint = "{$addresses[0]['address']}/{$addresses[0]['mask']}"; $popover_params = null; @@ -374,9 +400,7 @@ function wg_generate_tunnel_address_popover_link($tunnel_name) { $href = "vpn_wg_tunnels_edit.php?tun={$tunnel['name']}"; - wg_htmlspecialchars($href, $title, $hint); - - $ret_html = "{$hint}"; + $ret_html = "{$hsc($hint)}"; } else { @@ -390,11 +414,9 @@ function wg_generate_tunnel_address_popover_link($tunnel_name) { $wg_pfsense_if = wg_get_pfsense_interface_info($tunnel['name']); - wg_htmlspecialchars($wg_pfsense_if); - $ret_html = <<<"RET" - {$wg_pfsense_if['descr']} ({$wg_pfsense_if['name']}) + {$hsc($wg_pfsense_if['descr'])} ({$hsc($wg_pfsense_if['name'])}) RET; @@ -409,22 +431,13 @@ function wg_generate_tunnel_address_popover_link($tunnel_name) { function wg_generate_peer_allowedips_popup_link($peer_idx) { global $wgg; - $getText = function($text) { return gettext($text); }; + $getText = fn($s) => gettext($s); - if (isset($wgg['peers'][$peer_idx]) && is_array($wgg['peers'][$peer_idx])) { + $hsc= fn($s) => htmlspecialchars($s); - $peer = $wgg['peers'][$peer_idx]; + if (list($peer_idx, $peer, $is_new) = wg_peer_get_config($peer_idx, false)) { - wg_init_config_arr($peer, array('allowedips', 'row')); - - // Smash address + mask - $allowedips = array_map(function($x) { - - return array( - 'address' => "{$x['address']}/{$x['mask']}", - 'descr' => $x['descr']); - - }, $peer['allowedips']['row']); + $allowedips = $peer['allowedips']['row']; if (is_array($allowedips) && count($allowedips) > 0) { @@ -444,12 +457,13 @@ function wg_generate_peer_allowedips_popup_link($peer_idx) { foreach ($allowedips as $allowedip) { - wg_htmlspecialchars($allowedip); + // Smash the address + mask for the UI + $allowedip['address'] = "{$allowedip['address']}/{$allowedip['mask']}"; $data_html .= <<<"ROW" - {$allowedip['address']} - {$allowedip['descr']} + {$hsc($allowedip['address'])} + {$hsc($allowedip['descr'])} ROW; @@ -463,7 +477,7 @@ function wg_generate_peer_allowedips_popup_link($peer_idx) { $title = gettext('Allowed IPs'); - $hint = "{$allowedips[0]['address']} (+{$extras})"; + $hint = "{$allowedips[0]['address']}/{$allowedips[0]['mask']} (+{$extras})"; $popover_params = " data-toggle=\"popover\" data-trigger=\"hover focus\" data-content=\"{$data_html}\" data-html=\"true\""; @@ -471,7 +485,7 @@ function wg_generate_peer_allowedips_popup_link($peer_idx) { $title = $allowedips[0]['descr']; - $hint = $allowedips[0]['address']; + $hint = "{$allowedips[0]['address']}/{$allowedips[0]['mask']}"; $popover_params = null; @@ -479,9 +493,7 @@ function wg_generate_peer_allowedips_popup_link($peer_idx) { $href ="vpn_wg_peers_edit.php?peer={$peer_idx}"; - wg_htmlspecialchars($href, $title, $hint); - - $ret_html = "{$hint}"; + $ret_html = "{$hsc($hint)}"; } else { @@ -489,10 +501,12 @@ function wg_generate_peer_allowedips_popup_link($peer_idx) { } - return $ret_html; + return $ret_html; } + return null; + } ?> \ No newline at end of file diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_install.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_install.inc index bb44a324..46fc1662 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_install.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_install.inc @@ -409,15 +409,19 @@ function wg_service_deinstall($force_stop = false) { function wg_destroy_tunnels() { global $wgg; - $ret_code = 0; + $cmds = array(); - // We only care about the ones in the running state - $tunnels_to_destroy = wg_get_real_ifs(); + // We only care about the ones that are actually running + $tunnels_to_destroy = wg_get_running_ifs(); // Tear down interfaces foreach ($tunnels_to_destroy as $tunnel) { - $ret_code |= wg_interface_destroy($tunnel); + if ($res = wg_ifconfig_if_destroy($tunnel)) { + + $cmds[] = $res; + + } } diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_service.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_service.inc index b0e0f18d..9b5a0389 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_service.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_service.inc @@ -128,7 +128,7 @@ function wg_service_cli_restart($serialize = true) { if ($ret_code <> 0) { - return $ret_Code; + return $ret_code; } } @@ -173,7 +173,7 @@ function wg_service_cli_stop($serialize = true) { $ret_code |= WG_ERROR_SVC_STOP; - wg_service_error_handler($ret_code, $serialize); + wg_service_parse_errors($ret_code, $serialize); // Terminate early... return $ret_code; @@ -185,7 +185,7 @@ function wg_service_cli_stop($serialize = true) { $ret_code |= WG_ERROR_SVC_STOP; - wg_service_error_handler($ret_code, $serialize); + wg_service_parse_errors($ret_code, $serialize); } @@ -231,7 +231,7 @@ function wg_service_cli_start($serialize = true) { $ret_code |= WG_ERROR_SVC_START; - wg_service_error_handler($ret_code, $serialize); + wg_service_parse_errors($ret_code, $serialize); return $ret_code; @@ -241,7 +241,7 @@ function wg_service_cli_start($serialize = true) { $ret_code |= WG_ERROR_SVC_RUNNING; - wg_service_error_handler($ret_code, $serialize); + wg_service_parse_errors($ret_code, $serialize); return $ret_code; @@ -260,18 +260,18 @@ function wg_service_cli_start($serialize = true) { } - // We don't need to do this if we are in postboot - if (!is_subsystem_dirty($wgg['subsystems']['postboot'])) { - - // Build all (i.e. null) the tunnels - $sync_status = wg_tunnel_sync(null, false); + /* + * Build all (i.e. null) the tunnels + * + * NOTE: Don't restart services, they will be restarted later... + * NOTE: Don't resolve endpoints, they will be resolved later... + */ + $sync_status = wg_tunnel_sync(null, false, false); - if ($sync_status['ret_code'] <> 0 ) { + if ($sync_status['ret_code'] <> 0 ) { - $ret_code |= WG_ERROR_SVC_BUILD; + $ret_code |= WG_ERROR_SVC_CREATE; - } - } if (platform_booting()) { @@ -280,9 +280,6 @@ function wg_service_cli_start($serialize = true) { fwrite(STDERR, "done. \n"); - // Set postboot state - mark_subsystem_dirty($wgg['subsystems']['postboot']); - return $ret_code; } @@ -296,13 +293,13 @@ function wg_service_cli_start($serialize = true) { wg_destroy_tunnels(); - wg_service_error_handler($ret_code, $serialize); + wg_service_parse_errors($ret_code, $serialize); return $ret_code; } elseif ($newpid) { - wg_service_error_handler($ret_code, $serialize, array('sync_status' => $sync_status)); + wg_service_parse_errors($ret_code, $serialize); return $ret_code; @@ -347,16 +344,8 @@ function wg_service_cli_start($serialize = true) { } else { - // We don't need to restart additional services if we are in postboot - if (!is_subsystem_dirty($wgg['subsystems']['postboot'])) { - - // Restart any additional services - $ret_code = wg_restart_extra_services(); - - } - - // Clear postboot state - clear_subsystem_dirty($wgg['subsystems']['postboot']); + // Nowe we restart any additional services + $ret_code = wg_restart_extra_services(); return $ret_code; @@ -378,6 +367,8 @@ function wg_restart_extra_services() { // unbound services_unbound_configure(); + // TODO: This is where we will add facilities for users to pick what services to restart + } // This is currently just a best effort attempt... @@ -406,6 +397,9 @@ function wg_service_daemon() { } + // Now we resolve endpoint hostnames here... + $last_update_time = wg_resolve_endpoints(); + // Main WireGuard service loop while (true) { @@ -415,6 +409,14 @@ function wg_service_daemon() { break; } + + // Check if we should reresolve hostnames + if (wg_should_reresolve_endpoints($last_update_time)) { + + // Reresolve endpoint hostnames again + $last_update_time = wg_resolve_endpoints(); + + } // Wait a bit before trying again sleep(1); @@ -506,21 +508,21 @@ function wg_daemon_sig_handler($signo) { } -function wg_service_error_handler($ret_code, $serialize_output = true, $extras = array()) { +function wg_service_parse_errors($ret_code, $serialize_output = true, $extras = null) { global $wgg; $errors = array(); + // Collect any errors foreach ($wgg['error_flags']['service'] as $error_mask => $error_text) { if (($ret_code & $error_mask) > 0) { - - if ($serialize_output) { - $errors[$error_mask] = $error_text; + $errors[$error_mask] = $error_text; - } else { + if (!$serialize_output) { + // Send each error to STDERR as it is found fwrite(STDERR, "{$error_text}\n"); } @@ -531,15 +533,17 @@ function wg_service_error_handler($ret_code, $serialize_output = true, $extras = if ($serialize_output) { - $ret_array = array_merge(array('ret_code' => $ret_code, 'errors' => $errors), $extras); + $res = array('ret_code' => $ret_code, 'errors' => $errors); - $ret_serialized = serialize($ret_array); + $res = is_array($extras) ? array_merge($res, $extras) : $res; - fwrite(STDOUT, "{$ret_serialized}\n"); + $res_serialized = serialize($res); + + fwrite(STDOUT, "{$res_serialized}\n"); } - return $ret_code; + return; } diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_validate.inc b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_validate.inc index a2562e4e..5637892c 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_validate.inc +++ b/net/pfSense-pkg-WireGuard/files/usr/local/pkg/wireguard/wg_validate.inc @@ -30,11 +30,32 @@ require_once('wireguard/wg_api.inc'); wg_globals(); +/* + * Validate package settings + */ +function wg_validate_settings_post($pconfig) { + + $input_errors = array(); + + // Check dns ttl + $dns_ttl = $pconfig['dns_ttl']; + + if (!empty($dns_ttl) && !is_numericint($dns_ttl)) { + + $input_errors[] = "Invalid endpoint DNS TTL interval ({$dns_ttl})."; + + } + + // Consumers expect an array + return $input_errors; + +} + /* * Valildate a tunnel * These validation checks should be in the same order as the UI for consistency */ -function wg_validate_tunnel_post($pconfig) { +function wg_validate_tunnel_post($pconfig, $idx_from_post) { $input_errors = array(); @@ -108,7 +129,7 @@ function wg_validate_tunnel_post($pconfig) { * Valildate a peer * These validation checks should be in the same order as the UI for consistency */ -function wg_validate_peer_post($pconfig) { +function wg_validate_peer_post($pconfig, $posted_peer_idx) { $input_errors = array(); @@ -149,17 +170,16 @@ function wg_validate_peer_post($pconfig) { } elseif (!empty($pconfig['tun'])) { - $peers = wg_get_tunnel_peers($pconfig['tun']); - - $peer_idx = wg_get_peer_id($pconfig['publickey'], $pconfig['tun']); + foreach (wg_tunnel_get_peers_config($pconfig['tun']) as $peer_config) { - foreach ($peers as $peer) { + // Pull out relevant bits + list($peer_idx, $peer, $is_new) = $peer_config; - // We don't want to allow duplicate public keys to be assigned on the same tunnel - if (($peer['publickey'] == $pconfig['publickey']) && ($peer['index'] != $peer_idx)) { + // We don't want duplicate public keys per tunnel, but re-saving is okay... + if (($peer['publickey'] == $pconfig['publickey']) && ($peer_idx != $posted_peer_idx)) { $input_errors[] = "The public key ({$pconfig['publickey']}) is already assigned to a peer on this tunnel ({$pconfig['tun']})."; - + break; } diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/status_wireguard.php b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/status_wireguard.php index 1855b56f..9a887da2 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/status_wireguard.php +++ b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/status_wireguard.php @@ -53,8 +53,7 @@ $tunnels_to_apply = wg_apply_list_get('tunnels'); - // TODO: Make extra services restart (true) a package setting - $sync_status = wg_tunnel_sync($tunnels_to_apply, true); + $sync_status = wg_tunnel_sync($tunnels_to_apply, true, true); $ret_code |= $sync_status['ret_code']; @@ -97,7 +96,7 @@ display_top_tabs($tab_array); -$a_devices = wg_status(); +$a_devices = wg_get_status(); ?> @@ -160,7 +159,7 @@ ?> - + @@ -168,7 +167,7 @@ - + diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_peers.php b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_peers.php index 162c408c..e136a5c1 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_peers.php +++ b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_peers.php @@ -51,8 +51,7 @@ $tunnels_to_apply = wg_apply_list_get('tunnels'); - // TODO: Make extra services restart (true) a package setting - $sync_status = wg_tunnel_sync($tunnels_to_apply, true); + $sync_status = wg_tunnel_sync($tunnels_to_apply, true, true); $ret_code |= $sync_status['ret_code']; @@ -114,18 +113,18 @@ } -$shortcut_section = "wireguard"; +$shortcut_section = 'wireguard'; -$pgtitle = array(gettext("VPN"), gettext("WireGuard"), gettext("Peers")); -$pglinks = array("", "/wg/vpn_wg_tunnels.php", "@self"); +$pgtitle = array(gettext('VPN'), gettext('WireGuard'), gettext('Peers')); +$pglinks = array('', '/wg/vpn_wg_tunnels.php', '@self'); $tab_array = array(); -$tab_array[] = array(gettext("Tunnels"), false, "/wg/vpn_wg_tunnels.php"); -$tab_array[] = array(gettext("Peers"), true, "/wg/vpn_wg_peers.php"); -$tab_array[] = array(gettext("Settings"), false, "/wg/vpn_wg_settings.php"); -$tab_array[] = array(gettext("Status"), false, "/wg/status_wireguard.php"); +$tab_array[] = array(gettext('Tunnels'), false, '/wg/vpn_wg_tunnels.php'); +$tab_array[] = array(gettext('Peers'), true, '/wg/vpn_wg_peers.php'); +$tab_array[] = array(gettext('Settings'), false, '/wg/vpn_wg_settings.php'); +$tab_array[] = array(gettext('Status'), false, '/wg/status_wireguard.php'); -include("head.inc"); +include('head.inc'); wg_print_service_warning(); @@ -154,12 +153,12 @@ - - - - + + + + - + @@ -168,7 +167,7 @@ foreach ($wgg['peers'] as $peer_idx => $peer): ?> - ';" class=""> + ';" class=""> @@ -203,7 +202,7 @@ diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_settings.php b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_settings.php index 2d7f6451..2ac050ad 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_settings.php +++ b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_settings.php @@ -53,8 +53,7 @@ $tunnels_to_apply = wg_apply_list_get('tunnels'); - // TODO: Make extra services restart (true) a package setting - $sync_status = wg_tunnel_sync($tunnels_to_apply, true); + $sync_status = wg_tunnel_sync($tunnels_to_apply, true, true); $ret_code |= $sync_status['ret_code']; @@ -76,19 +75,13 @@ case 'save': - if (empty($input_errors)) { + $res = wg_do_settings_post($_POST); - $pconfig = $_POST; + $input_errors = $res['input_errors']; - $wgg['config']['keep_conf'] = $pconfig['keep_conf'] ? 'yes' : 'no'; - - $wgg['config']['hide_secrets'] = $pconfig['hide_secrets'] ? 'yes' : 'no'; + $pconfig = $res['pconfig']; - write_config("[{$wgg['pkg_name']}] Package settings saved."); - - $save_success = true; - - } + $save_success = (empty($input_errors) && $res['changes']); break; @@ -105,28 +98,32 @@ } -// Default yes for new installations (i.e. keep_conf is empty) -$pconfig['keep_conf'] = (isset($wgg['config']['keep_conf'])) ? $wgg['config']['keep_conf'] : 'yes'; +// Defaults for new installations + +$pconfig['keep_conf'] = isset($wgg['config']['keep_conf']) ? $wgg['config']['keep_conf'] : 'yes'; + +$pconfig['hide_secrets'] = isset($wgg['config']['hide_secrets']) ? $wgg['config']['hide_secrets'] : 'yes'; + +$pconfig['resolve_interval'] = isset($wgg['config']['resolve_interval']) ? $wgg['config']['default_resolve_interval'] : $wgg['resolve_interval']; -// Default yes for new installations (i.e. hide_secrets is empty) -$pconfig['hide_secrets'] = (isset($wgg['config']['hide_secrets'])) ? $wgg['config']['hide_secrets'] : 'yes'; +$pconfig['resolve_interval_track'] = isset($wgg['config']['resolve_interval_track']) ? $wgg['config']['resolve_interval_track'] : 'no'; -$shortcut_section = "wireguard"; +$shortcut_section = 'wireguard'; -$pgtitle = array(gettext("VPN"), gettext("WireGuard"), gettext("Settings")); -$pglinks = array("", "/wg/vpn_wg_tunnels.php", "@self"); +$pgtitle = array(gettext('VPN'), gettext('WireGuard'), gettext('Settings')); +$pglinks = array('', '/wg/vpn_wg_tunnels.php', '@self'); $tab_array = array(); -$tab_array[] = array(gettext("Tunnels"), false, "/wg/vpn_wg_tunnels.php"); -$tab_array[] = array(gettext("Peers"), false, "/wg/vpn_wg_peers.php"); -$tab_array[] = array(gettext("Settings"), true, "/wg/vpn_wg_settings.php"); -$tab_array[] = array(gettext("Status"), false, "/wg/status_wireguard.php"); +$tab_array[] = array(gettext('Tunnels'), false, '/wg/vpn_wg_tunnels.php'); +$tab_array[] = array(gettext('Peers'), false, '/wg/vpn_wg_peers.php'); +$tab_array[] = array(gettext('Settings'), true, '/wg/vpn_wg_settings.php'); +$tab_array[] = array(gettext('Status'), false, '/wg/status_wireguard.php'); -include("head.inc"); +include('head.inc'); if ($save_success) { - print_info_box(gettext("The changes have been applied successfully."), 'success'); + print_info_box(gettext('The changes have been applied successfully.'), 'success'); } @@ -150,28 +147,49 @@ $form = new Form(false); -$section = new Form_Section("General Settings"); +$section = new Form_Section('General Settings'); $section->addInput(new Form_Checkbox( 'keep_conf', 'Keep Configuration', - gettext('Enable'), - $pconfig['keep_conf'] == 'yes' -))->setHelp('Note: ' - . 'With \'Keep Configurations\' enabled (default), all tunnel configurations and package settings will persist on install/de-install.' + gettext('Enable'), + $pconfig['keep_conf'] == 'yes' +))->setHelp("Note: + With 'Keep Configurations' enabled (default), all tunnel configurations and package settings will persist on install/de-install." ); +$group = new Form_Group('Endpoint Hostname Resolve Interval'); + +$group->add(new Form_Input( + 'resolve_interval', + 'Endpoint Hostname Resolve Interval', + 'text', + wg_get_endpoint_resolve_interval(), + ['placeholder' => wg_get_endpoint_resolve_interval()] +))->setHelp("Interval (in seconds) for re-resolving endpoint host/domain names.
+ Note: The default is {$wgg['default_resolve_interval']} seconds (0 to disable)."); + +$group->add(new Form_Checkbox( + 'resolve_interval_track', + null, + gettext('Track System Resolve Interval'), + ($pconfig['resolve_interval_track'] == 'yes') +))->setHelp("Tracks the system 'Aliases Hostnames Resolve Interval' setting.
+ Note: See System / Advanced / Firewall & NAT"); + +$section->add($group); + $form->add($section); -$section = new Form_Section("User Interface Settings"); +$section = new Form_Section('User Interface Settings'); $section->addInput(new Form_Checkbox( 'hide_secrets', 'Hide Secrets', gettext('Enable'), $pconfig['hide_secrets'] == 'yes' -))->setHelp('Note: ' - . 'With \'Hide Secrets\' enabled, all secrets (private and pre-shared keys) are hidden in the user interface.'); +))->setHelp("Note: + With 'Hide Secrets' enabled, all secrets (private and pre-shared keys) are hidden in the user interface."); $form->add($section); @@ -189,7 +207,7 @@ @@ -204,6 +222,20 @@ }); + $('#resolve_interval_track').click(function () { + + updateResolveInterval(this.checked); + + }); + + function updateResolveInterval(state) { + + $('#resolve_interval').prop( "disabled", state); + + } + + updateResolveInterval($('#resolve_interval_track').prop('checked')); + }); //]]> diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels.php b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels.php index f34a035e..269256f5 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels.php +++ b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels.php @@ -53,8 +53,7 @@ $tunnels_to_apply = wg_apply_list_get('tunnels'); - // TODO: Make extra services restart (true) a package setting - $sync_status = wg_tunnel_sync($tunnels_to_apply, true); + $sync_status = wg_tunnel_sync($tunnels_to_apply, true, true); $ret_code |= $sync_status['ret_code']; @@ -180,9 +179,9 @@ foreach ($wgg['tunnels'] as $tunnel): - $peers = wg_get_tunnel_peers($tunnel['name']); + $peers = wg_tunnel_get_peers_config($tunnel['name']); ?> - + diff --git a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels_edit.php b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels_edit.php index ecd47740..c0a5bbe9 100644 --- a/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels_edit.php +++ b/net/pfSense-pkg-WireGuard/files/usr/local/www/wg/vpn_wg_tunnels_edit.php @@ -64,8 +64,7 @@ $tunnels_to_apply = wg_apply_list_get('tunnels'); - // TODO: Make extra services restart (true) a package setting - $sync_status = wg_tunnel_sync($tunnels_to_apply, true); + $sync_status = wg_tunnel_sync($tunnels_to_apply, true, true); $ret_code |= $sync_status['ret_code']; @@ -450,51 +449,51 @@
-

+

@@ -177,7 +176,7 @@ - " href=""> + "> " usepost>
- - - + + + + - + - ';" class=""> + ';" class=""> - + + - " class="btn btn-success btn-sm"> - +
- " href=""> - - " usepost> + "> + + " usepost>
- + +