Project

General

Profile

Bug #15435 » wg_service.inc

hack to enable tracing of PHP code with time stamping - Patrik Stahlman, 04/24/2024 07:53 PM

 
<?php
/*
* wg_service.inc
*
* part of pfSense (https://www.pfsense.org)
* Copyright (c) 2021 R. Christian McDonald (https://github.com/rcmcdonald91)
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


// pfSense includes
require_once('config.inc');
require_once('globals.inc');
require_once('gwlb.inc');
require_once('util.inc');
require_once('services.inc');
require_once('service-utils.inc');

// WireGuard includes
require_once('wireguard/includes/wg.inc');

global $rcbLoghandle;
$rcbLoghandle = fopen('/root/wireguard.bootup.log', 'ab');
fwrite($rcbLoghandle, 'Logging ' . __FILE__ . ' ' . $argv[1] . ' platform_booting(' . platform_booting()==1 . ')' ."\n");

declare(ticks=1);
register_tick_function(function(){
global $rcbLoghandle;
$backtrace = debug_backtrace();
$line = $backtrace[0]['line'] - 1;
$file = $backtrace[0]['file'];

//if ($backtrace[0]['function'] == 'ticker') return;

static $fp, $cur, $buf;
if (!isset($fp[$file])) {
$fp[$file] = fopen($file, 'r');
$cur[$file] = 0;
}

if (isset($buf[$file][$line])) {
$code = $buf[$file][$line];
} else {
do {
$code = fgets($fp[$file]);
$buf[$file][$cur[$file]] = $code;
} while (++$cur[$file] <= $line);
}

$line++;

$date = date('d/m/Y H:i:s');
$output = $date . ' ' . $file . ':' . $line . $code . "\n";
fwrite($rcbLoghandle, $output);

});

global $wgg;

if (isset($argv[1])) {
$ret_code = 0;

ignore_user_abort(true);

set_time_limit(0);

pcntl_async_signals(true);

if (!wg_is_cli()) {
// Bail out if we aren't in the CLI...
die("FATAL: This script can only be started through the CLI.\n");
}

if (PHP_BINARY != $wgg['php_wg']) {
// Bail out if we aren't running under under the correct executable...
die("FATAL: This script can only be executed by {$wgg['php_wg']}.\n");
}

// Should we serialize the return output?
$serialize = (isset($argv[2]) && strtolower($argv[2]) == 'serialize');

switch (strtolower($argv[1])) {
case 'start':
$ret_code = wg_service_cli_start($serialize);
break;

case 'stop':
$ret_code = wg_service_cli_stop($serialize);
break;

case 'restart':
$ret_code = wg_service_cli_restart($serialize);
break;

default:
fwrite(STDOUT, "Usage: {$wgg['wg_daemon']} {START|STOP|RESTART} [SERIALIZE]");
$ret_code = 1;
break;
}

// We are done...
exit($ret_code);
}

// This is a wrapper for safely calling from web frontend
function wg_service_fpm_restart() {
global $wgg;

$exec_output = $unserialize_output = $ret_output = array();

// Invokes service restart with serialization flag so we can cleanly report back to the web frontend.
exec("{$wgg['wg_daemon']} restart serialize", $exec_output, $ret_code);

if ($unserialize_output = unserialize($exec_output[0])) {
if (is_array($unserialize_output)) {
$ret_output = $unserialize_output;
}
}

// Consumers expect an array...
return $ret_output;
}

function wg_service_cli_restart($serialize = true) {
global $wgg;

$ret_code = 0;

if (wg_is_service_running()) {
$ret_code = wg_service_cli_stop($serialize);

if ($ret_code <> 0) {
return $ret_code;
}
}

$ret_code = wg_service_cli_start($serialize);

return $ret_code;
}

// This is a wrapper for safely calling from web frontend
function wg_service_fpm_stop() {
global $wgg;

$exec_output = $unserialize_output = $ret_output = array();

// Invokes service stop with serialization flag, so we can cleanly report back to the web frontend.
exec("{$wgg['wg_daemon']} stop serialize", $exec_output, $ret_code);

if ($unserialize_output = unserialize($exec_output[0])) {
if (is_array($unserialize_output)) {
$ret_output = $unserialize_output;
}
}

// Consumers expect an array...
return $ret_output;
}

// Only for calling service stop from the CLI
function wg_service_cli_stop($serialize = true) {
global $wgg;

$ret_code = 0;

if (!wg_is_cli()) {
$ret_code |= WG_ERROR_SVC_STOP;

wg_service_parse_errors($ret_code, $serialize);

// Terminate early...
return $ret_code;
}

// Need to wait here for just a second...
if (killbypid($wgg['pid_path'], 1) <> 0) {
$ret_code |= WG_ERROR_SVC_STOP;
wg_service_parse_errors($ret_code, $serialize);
}

// Disable any WireGuard gateways configured on the system.
wg_gateways_set_enable(false);

// Now we restart any additional services
$ret_code |= wg_restart_extra_services();

return $ret_code;
}

// This is a wrapper for safely calling from the web frontend
function wg_service_fpm_start() {
global $wgg;

$exec_output = $unserialize_output = $ret_output = array();

// Invokes service start with serialization flag so we can cleanly report back to the web frontend
exec("{$wgg['wg_daemon']} start serialize", $exec_output, $ret_code);

// Catch unserialization results before returning
if ($unserialize_output = unserialize($exec_output[0])) {
if (is_array($unserialize_output)) {
$ret_output = $unserialize_output;
}
}

// Consumers expect an array...
return $ret_output;
}

// Only for calling service start from the CLI
function wg_service_cli_start($serialize = true) {
global $g, $wgg;

$s = fn($x) => $x;

// Set the process name
cli_set_process_title('WireGuard service');

$ret_code = 0;

if (!wg_is_service_enabled()) {
$ret_code |= WG_ERROR_SVC_DISABLED;

wg_service_parse_errors($ret_code, $serialize);

return $ret_code;
}

if (wg_is_service_running()) {
$ret_code |= WG_ERROR_SVC_RUNNING;

wg_service_parse_errors($ret_code, $serialize);

return $ret_code;
}

if (!wg_is_cli()) {
$ret_code |= WG_ERROR_SVC_START;

wg_service_parse_errors($ret_code, $serialize);

return $ret_code;
}

// Register the service environment and lock early to ensure singletons
wg_register_service_env(false);

if (platform_booting()) {
// Output during booting must be to STDERR for some reason
fwrite(STDERR, gettext('Configuring WireGuard tunnels...'));

// Supresses ifconfig spew
mute_kernel_msgs();
}

// Get the tunnel interfaces to build
$ifs_to_build = wg_get_configured_ifs();

// Now build them...
$sync_status = wg_tunnel_sync($ifs_to_build, false, false);

if ($sync_status['ret_code'] <> 0 ) {
$ret_code |= WG_ERROR_SVC_CREATE;
}

if (platform_booting()) {
unmute_kernel_msgs();

fwrite(STDERR, "{$s(gettext('done.'))}\n");

return $ret_code;
}

// Now, the initial fork...
$newpid = pcntl_fork();

if ($newpid === -1) {
$ret_code |= WG_ERROR_SVC_START;

wg_destroy_tunnels();

wg_service_parse_errors($ret_code, $serialize);

return $ret_code;
} elseif ($newpid) {
wg_service_parse_errors($ret_code, $serialize);

return $ret_code;
}

// Now become the session leader
if (posix_setsid() < 0) {
wg_destroy_tunnels();

return 1;
}

// The second fork...
$newpid = pcntl_fork();

if ($newpid === -1) {
wg_destroy_tunnels();

return 1;
} elseif ($newpid) {
// Reap the child process below...
pcntl_waitpid($newpid, $status);

// Boilerplate if we want to trap service ret codes and halt...
$ret_code = pcntl_wexitstatus($status);

if ($ret_code <> 0) {
return $ret_code;
}

// Move on to the actual daemon
wg_service_daemon();

// We shouldn't be here...
return 0;
} else {
// Now we can enable any WireGuard gateways
wg_gateways_set_enable(true);

// Now we restart any additional services
$ret_code = wg_restart_extra_services();

return $ret_code;
}

// We shouldn't be here...
return 1;
}

// This is where we restart any extra services
function wg_restart_extra_services($force = false) {
if (platform_booting() && !$force) {
return false;
}

// dpinger
setup_gateways_monitor();

// unbound
services_unbound_configure();

// reconfigure static routes
system_staticroutes_configure();

// TODO: This is where we will add facilities for users to pick what services to restart

return true;
}

// Main WireGuard service loop
function wg_service_daemon() {
global $wgg;

$esa = fn($s) => escapeshellarg($s);

// Re-register the service environment
wg_register_service_env(true);

// Now that we are properly daemonized, register the service signal handlers
wg_register_daemon_sig_handler();

// Attempt to load the kmod, required to run the service without any tunnels configured
wg_kld_loadunload(true);

// Now we resolve endpoint hostnames here...
$last_update_time = wg_resolve_endpoints();

// Main WireGuard service loop
while (true) {
// The whole point of this daemon...
if (!is_module_loaded($wgg['kmod'])) {
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);
}

// Execute SIGTERM handler to exit gracefully
wg_daemon_sig_handler(SIGTERM);
}

function wg_deregister_service_env() {
global $h_lock, $wgg;

if (!is_null($h_lock)) {
// Attempt to release exclusive lock
@flock($h_lock, LOCK_UN);

// Attempt to close file handler
@fclose($h_lock);
}

// Attempt to delete PID file
unlink_if_exists($wgg['pid_path']);
}

function wg_register_service_env($close_fdio = false) {
global $h_lock, $wgg;

wg_deregister_service_env();

if ($h_lock = fopen($wgg['pid_path'], 'a+')) {
flock($h_lock, LOCK_EX);
ftruncate($h_lock, 0);
fseek($h_lock, 0, 0);
fwrite($h_lock, getmypid());
fflush($h_lock);
}

if ($close_fdio) {
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
}
}

function wg_register_daemon_sig_handler() {
pcntl_signal(SIGTERM, 'wg_daemon_sig_handler');
}

function wg_daemon_sig_handler($signo) {
switch ($signo) {
case SIGTERM:
// Teardown any tunnels and unload the module
wg_destroy_tunnels();

// Cleanup the service environment
wg_deregister_service_env();

// We are done...
exit(0);
break;

default:
break;
}
}

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) {
$errors[$error_mask] = $error_text;

if (!$serialize_output) {
// Send each error to STDERR as it is found
fwrite(STDERR, "{$error_text}\n");
}
}
}

if ($serialize_output) {
$res = array('ret_code' => $ret_code, 'errors' => $errors);

$res = is_array($extras) ? array_merge($res, $extras) : $res;

$res_serialized = serialize($res);

fwrite(STDOUT, "{$res_serialized}\n");
}

return;
}

// Check if we are in CLI or not
function wg_is_cli() {
return (PHP_SAPI === 'cli');
}

// Checks if the service is running
function wg_is_service_running() {
global $wgg;

if (!($h_lock = @fopen($wgg['pid_path'], 'r')) || !file_exists($wgg['pid_path'])) {
return false;
}

$not_running = flock($h_lock, LOCK_EX | LOCK_NB, $wouldblock);

if ($not_running) {
flock($h_lock, LOCK_UN);
}

$pid = fgets($h_lock);

fclose($h_lock);

// Another trick to test if a process is running
$sig_test = posix_kill($pid, 0);

return (!$not_running || $wouldblock || $sig_test);
}

?>
(2-2/4)