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

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

    
22

    
23
// pfSense includes
24
require_once('config.inc');
25
require_once('globals.inc');
26
require_once('gwlb.inc');
27
require_once('util.inc');
28
require_once('services.inc');
29
require_once('service-utils.inc');
30

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

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

    
38
declare(ticks=1);
39
register_tick_function(function(){
40
    global $rcbLoghandle;
41
    $backtrace = debug_backtrace();
42
    $line = $backtrace[0]['line'] - 1;
43
    $file = $backtrace[0]['file'];
44

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

    
47
    static $fp, $cur, $buf;
48
    if (!isset($fp[$file])) {
49
        $fp[$file] = fopen($file, 'r');
50
        $cur[$file] = 0;
51
    }
52

    
53
    if (isset($buf[$file][$line])) {
54
        $code = $buf[$file][$line];
55
    } else {
56
        do {
57
            $code = fgets($fp[$file]);
58
            $buf[$file][$cur[$file]] = $code;
59
        } while (++$cur[$file] <= $line);
60
    }
61

    
62
    $line++;
63

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

    
68
});
69

    
70
global $wgg;
71

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

    
75
	ignore_user_abort(true);
76

    
77
	set_time_limit(0);
78

    
79
	pcntl_async_signals(true);
80

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

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

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

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

    
99
		case 'stop':
100
			$ret_code = wg_service_cli_stop($serialize);
101
			break;
102

    
103
		case 'restart':
104
			$ret_code = wg_service_cli_restart($serialize);
105
			break;
106

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

    
113
	// We are done...
114
	exit($ret_code);
115
}
116

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

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

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

    
126
	if ($unserialize_output = unserialize($exec_output[0])) {
127
		if (is_array($unserialize_output)) {
128
			$ret_output = $unserialize_output;
129
		}
130
	}
131

    
132
	// Consumers expect an array...
133
	return $ret_output;
134
}
135

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

    
139
	$ret_code = 0;
140

    
141
	if (wg_is_service_running()) {
142
		$ret_code = wg_service_cli_stop($serialize);
143

    
144
		if ($ret_code <> 0) {
145
			return $ret_code;
146
		}
147
	}
148

    
149
	$ret_code = wg_service_cli_start($serialize);
150

    
151
	return $ret_code;
152
}
153

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

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

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

    
163
	if ($unserialize_output = unserialize($exec_output[0])) {
164
		if (is_array($unserialize_output)) {
165
			$ret_output = $unserialize_output;
166
		}
167
	}
168

    
169
	// Consumers expect an array...
170
	return $ret_output;
171
}
172

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

    
177
	$ret_code = 0;
178

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

    
182
		wg_service_parse_errors($ret_code, $serialize);
183

    
184
		// Terminate early...
185
		return $ret_code;
186
	}
187

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

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

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

    
200
	return $ret_code;
201
}
202

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

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

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

    
212
	// Catch unserialization results before returning
213
	if ($unserialize_output = unserialize($exec_output[0])) {
214
		if (is_array($unserialize_output)) {
215
			$ret_output = $unserialize_output;
216
		}
217
	}
218

    
219
	// Consumers expect an array...
220
	return $ret_output;
221
}
222

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

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

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

    
232
	$ret_code = 0;
233

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

    
237
		wg_service_parse_errors($ret_code, $serialize);
238

    
239
		return $ret_code;
240
	}
241

    
242
	if (wg_is_service_running()) {
243
		$ret_code |= WG_ERROR_SVC_RUNNING;
244

    
245
		wg_service_parse_errors($ret_code, $serialize);
246

    
247
		return $ret_code;
248
	}
249

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

    
253
		wg_service_parse_errors($ret_code, $serialize);
254

    
255
		return $ret_code;
256
	}
257

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

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

    
265
		// Supresses ifconfig spew
266
		mute_kernel_msgs();
267
	}
268

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

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

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

    
279
	if (platform_booting()) {
280
		unmute_kernel_msgs();
281

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

    
284
		return $ret_code;
285
	}
286

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

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

    
293
		wg_destroy_tunnels();
294

    
295
		wg_service_parse_errors($ret_code, $serialize);
296

    
297
		return $ret_code;
298
	} elseif ($newpid) {
299
		wg_service_parse_errors($ret_code, $serialize);
300

    
301
		return $ret_code;
302
	}
303

    
304
	// Now become the session leader
305
	if (posix_setsid() < 0) {
306
		wg_destroy_tunnels();
307

    
308
		return 1;
309
	}
310

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

    
314
	if ($newpid === -1) {
315
		wg_destroy_tunnels();
316

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

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

    
325
		if ($ret_code <> 0) {
326
			return $ret_code;
327
		}
328

    
329
		// Move on to the actual daemon
330
		wg_service_daemon();
331

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

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

    
341
		return $ret_code;
342
	}
343

    
344
	// We shouldn't be here...
345
	return 1;
346
}
347

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

    
354
	// dpinger
355
	setup_gateways_monitor();
356

    
357
	// unbound
358
	services_unbound_configure();
359

    
360
	// reconfigure static routes
361
	system_staticroutes_configure();
362

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

    
365
	return true;
366
}
367

    
368
// Main WireGuard service loop
369
function wg_service_daemon() {
370
	global $wgg;
371

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

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

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

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

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

    
386
	// Main WireGuard service loop
387
	while (true) {
388
		// The whole point of this daemon...
389
		if (!is_module_loaded($wgg['kmod'])) {
390
			break;
391
		}
392

    
393
		// Check if we should reresolve hostnames
394
		if (wg_should_reresolve_endpoints($last_update_time)) {
395
			// Reresolve endpoint hostnames again
396
			$last_update_time = wg_resolve_endpoints();
397
		}
398

    
399
		// Wait a bit before trying again
400
		sleep(1);
401
	}
402

    
403
	// Execute SIGTERM handler to exit gracefully
404
	wg_daemon_sig_handler(SIGTERM);
405
}
406

    
407
function wg_deregister_service_env() {
408
	global $h_lock, $wgg;
409

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

    
414
		// Attempt to close file handler
415
		@fclose($h_lock);
416
	}
417

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

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

    
425
	wg_deregister_service_env();
426

    
427
	if ($h_lock = fopen($wgg['pid_path'], 'a+')) {
428
		flock($h_lock, LOCK_EX);
429
		ftruncate($h_lock, 0);
430
		fseek($h_lock, 0, 0);
431
		fwrite($h_lock, getmypid());
432
		fflush($h_lock);
433
	}
434

    
435
	if ($close_fdio) {
436
		fclose(STDIN);
437
		fclose(STDOUT);
438
		fclose(STDERR);
439
	}
440
}
441

    
442
function wg_register_daemon_sig_handler() {
443
	pcntl_signal(SIGTERM, 'wg_daemon_sig_handler');
444
}
445

    
446
function wg_daemon_sig_handler($signo) {
447
	switch ($signo) {
448
		case SIGTERM:
449
			// Teardown any tunnels and unload the module
450
			wg_destroy_tunnels();
451

    
452
			// Cleanup the service environment
453
			wg_deregister_service_env();
454

    
455
			// We are done...
456
			exit(0);
457
			break;
458

    
459
		default:
460
			break;
461
	}
462
}
463

    
464
function wg_service_parse_errors($ret_code, $serialize_output = true, $extras = null) {
465
	global $wgg;
466

    
467
	$errors = array();
468

    
469
	// Collect any errors
470
	foreach ($wgg['error_flags']['service'] as $error_mask => $error_text) {
471
		if (($ret_code & $error_mask) > 0) {
472
			$errors[$error_mask] = $error_text;
473

    
474
			if (!$serialize_output) {
475
				// Send each error to STDERR as it is found
476
				fwrite(STDERR, "{$error_text}\n");
477
			}
478
		}
479
	}
480

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

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

    
486
		$res_serialized = serialize($res);
487

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

    
491
	return;
492
}
493

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

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

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

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

    
509
	if ($not_running) {
510
		flock($h_lock, LOCK_UN);
511
	}
512

    
513
	$pid = fgets($h_lock);
514

    
515
	fclose($h_lock);
516

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

    
520
	return (!$not_running || $wouldblock || $sig_test);
521
}
522

    
523
?>
(2-2/4)