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
|
?>
|