Passed
Push — develop ( db74f4...3f7c1e )
by Nikolay
04:30
created

Network::hostnameConfigure()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 15
rs 9.9
cc 3
nc 4
nop 0
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\Core\System;
21
22
use MikoPBX\Common\Models\LanInterfaces;
23
use MikoPBX\Core\Utilities\SubnetCalculator;
24
use MikoPBX\PBXCoreREST\Lib\SysinfoManagementProcessor;
25
use Phalcon\Di\Injectable;
26
use Throwable;
27
28
/**
29
 * Class Network
30
 *
31
 *
32
 *
33
 * @package MikoPBX\Core\System
34
 */
35
class Network extends Injectable
36
{
37
    public const INTERNET_FLAG_FILE = '/var/etc/internet_flag';
38
39
    /**
40
     * Starts the SIP dump process.
41
     */
42
    public static function startSipDump(): void
43
    {
44
        $config = new MikoPBXConfig();
45
        $use = $config->getGeneralSettings('USE_PCAP_SIP_DUMP');
46
        if ($use !== '1') {
47
            return;
48
        }
49
50
        Processes::killByName('pcapsipdump');
51
        $log_dir = System::getLogDir() . '/pcapsipdump';
52
        Util::mwMkdir($log_dir);
53
54
        $network = new Network();
55
        $arr_eth = $network->getInterfacesNames();
56
        $pcapsipdumpPath = Util::which('pcapsipdump');
57
        foreach ($arr_eth as $eth) {
58
            $pid_file = "/var/run/pcapsipdump_{$eth}.pid";
59
            Processes::mwExecBg(
60
                $pcapsipdumpPath . ' -T 120 -P ' . $pid_file . ' -i ' . $eth . ' -m \'^(INVITE|REGISTER)$\' -L ' . $log_dir . '/dump.db'
61
            );
62
        }
63
    }
64
65
    /**
66
     * Retrieves the names of all PCI network interfaces.
67
     *
68
     * @return array An array containing the names of the network interfaces.
69
     */
70
    public function getInterfacesNames(): array
71
    {
72
        $grepPath = Util::which('grep');
73
        $awkPath = Util::which('awk');
74
        if (Util::isDocker()) {
75
            $ifconfigPath = Util::which('ifconfig');
76
            $command = "{$ifconfigPath} | {$grepPath} -o -E '^[a-zA-Z0-9]+' | {$grepPath} -v 'lo'";
77
        } else {
78
            // Universal command to retrieve all PCI network interfaces.
79
            $lsPath = Util::which('ls');
80
            $command = "{$lsPath} -l /sys/class/net | {$grepPath} devices | {$grepPath} -v virtual | {$awkPath} '{ print $9 }'";
81
        }
82
        Processes::mwExec($command, $names);
83
        return $names;
84
    }
85
86
    /**
87
     * Configures the loopback interface (lo) with the IP address 127.0.0.1.
88
     *
89
     * @return void
90
     */
91
    public function loConfigure(): void
92
    {
93
        $busyboxPath = Util::which('busybox');
94
        $ifconfigPath = Util::which('ifconfig');
95
        Processes::mwExec("{$busyboxPath} {$ifconfigPath} lo 127.0.0.1");
96
    }
97
98
    /**
99
     * Generates the resolv.conf file based on system configuration.
100
     */
101
    public function resolvConfGenerate(): void
102
    {
103
        if (Util::isDocker()) {
104
            return;
105
        }
106
107
        // Initialize resolv.conf content
108
        $resolv_conf = '';
109
110
        // Get hostname information
111
        $data_hostname = self::getHostName();
112
113
        // Append domain to resolv.conf if it is not empty
114
        if (trim($data_hostname['domain']) !== '') {
115
            $resolv_conf .= "domain {$data_hostname['domain']}\n";
116
        }
117
118
        // Append local nameserver to resolv.conf
119
        $resolv_conf .= "nameserver 127.0.0.1\n";
120
121
        // Initialize an array to store named DNS servers
122
        $named_dns = [];
123
124
        // Retrieve host DNS settings
125
        $dns = $this->getHostDNS();
126
127
        // Iterate over each DNS server
128
        foreach ($dns as $ns) {
129
            // Skip empty DNS servers
130
            if (trim($ns) === '') {
131
                continue;
132
            }
133
            // Add the DNS server to the named_dns array
134
            $named_dns[] = $ns;
135
136
            // Append the DNS server to resolv.conf
137
            $resolv_conf .= "nameserver {$ns}\n";
138
        }
139
140
        // If no DNS servers were found, use default ones and add them to named_dns
141
        if (count($dns) === 0) {
142
            $resolv_conf .= "nameserver 4.4.4.4\n";
143
            $named_dns[] .= "8.8.8.8";
144
        }
145
146
        // Check if systemctl is available
147
        if (Util::isSystemctl()) {
148
149
            // Generate resolved.conf content for systemd-resolved
150
            $s_resolv_conf = "[Resolve]\n"
151
                . "DNS=127.0.0.1\n";
152
153
            // Append domain to resolved.conf if it is not empty
154
            if (trim($data_hostname['domain']) !== '') {
155
                $s_resolv_conf .= "Domains={$data_hostname['domain']}\n";
156
            }
157
158
            // Write resolved.conf content to the file
159
            file_put_contents('/etc/systemd/resolved.conf', $s_resolv_conf);
160
161
            // Restart systemd-resolved service
162
            $systemctlPath = Util::which('systemctl');
163
            Processes::mwExec("{$systemctlPath} restart systemd-resolved");
164
        } else {
165
            // Write resolv.conf content to the file
166
            file_put_contents('/etc/resolv.conf', $resolv_conf);
167
        }
168
169
        // Generate pdnsd configuration using named_dns
170
        $this->generatePdnsdConfig($named_dns);
171
    }
172
173
    /**
174
     * Retrieves the hostname and domain information.
175
     *
176
     * @return array An array containing the hostname and domain.
177
     */
178
    public static function getHostName(): array
179
    {
180
        // Initialize default hostname and domain
181
        $data = [
182
            'hostname' => 'mikopbx',
183
            'domain' => '',
184
        ];
185
186
        // Find the first LanInterfaces record with internet set to '1'
187
        /** @var LanInterfaces $res */
188
        $res = LanInterfaces::findFirst("internet = '1'");
189
190
        // If a matching record is found, update the hostname and domain
191
        if (null !== $res) {
192
            $data['hostname'] = $res->hostname;
193
            $data['domain'] = $res->domain;
194
        }
195
196
        // If the hostname is empty, set it to the default value 'mikopbx'
197
        $data['hostname'] = (empty($data['hostname'])) ? 'mikopbx' : $data['hostname'];
198
199
        return $data;
200
    }
201
202
    /**
203
     * Retrieves the DNS servers configured for the host.
204
     *
205
     * @return array An array containing the DNS servers.
206
     */
207
    public function getHostDNS(): array
208
    {
209
        $dns = [];
210
211
        // Find the first LanInterfaces record with internet set to '1'
212
        /** @var LanInterfaces $res */
213
        $res = LanInterfaces::findFirst("internet = '1'");
214
215
        // If a matching record is found, check and add primary and secondary DNS servers
216
        if (null !== $res) {
217
            // Check and add primary DNS server if not empty and not '127.0.0.1'
218
            if (!empty($res->primarydns) && '127.0.0.1' !== $res->primarydns) {
219
                $dns[] = $res->primarydns;
220
            }
221
            // Check and add secondary DNS server if not empty and not '127.0.0.1'
222
            if (!empty($res->secondarydns) && '127.0.0.1' !== $res->secondarydns) {
223
                $dns[] = $res->secondarydns;
224
            }
225
        }
226
227
        return $dns;
228
    }
229
230
    /**
231
     * Generates the pdnsd configuration file and restarts the pdnsd service if necessary.
232
     *
233
     * @param array $named_dns An array of named DNS servers.
234
     */
235
    public function generatePdnsdConfig(array $named_dns): void
236
    {
237
        $tempDir = $this->di->getShared('config')->path('core.tempDir');
238
        $cache_dir = $tempDir . '/pdnsd/cache';
239
        Util::mwMkdir($cache_dir);
240
241
        $conf = 'global {' . "\n" .
242
            '	perm_cache=10240;' . "\n" .
243
            '	cache_dir="' . $cache_dir . '";' . "\n" .
244
            '	pid_file = /var/run/pdnsd.pid;' . "\n" .
245
            '	run_as="nobody";' . "\n" .
246
            '	server_ip = 127.0.0.1;' . "\n" .
247
            '	status_ctl = on;' . "\n" .
248
            '	query_method=udp_tcp;' . "\n" .
249
            '	min_ttl=15m;' . "\n" .
250
            '	max_ttl=1w;' . "\n" .
251
            '	timeout=10;' . "\n" .
252
            '	neg_domain_pol=on;' . "\n" .
253
            '	run_as=root;' . "\n" .
254
            '	daemon=on;' . "\n" .
255
            '}' . "\n" .
256
            'server {' . "\n" .
257
            '	label = "main";' . "\n" .
258
            '	ip = ' . implode(', ', $named_dns) . ';' . "\n" .
259
            '	interface=lo;' . "\n" .
260
            '	uptest=if;' . "\n" .
261
            '	interval=10m;' . "\n" .
262
            '	purge_cache=off;' . "\n" .
263
            '}';
264
265
        $pdnsdConfFile = '/etc/pdnsd.conf';
266
267
        // Update the pdnsd.conf file if it has changed
268
        $savedConf = '';
269
        if (file_exists($pdnsdConfFile)) {
270
            $savedConf = file_get_contents($pdnsdConfFile);
271
        }
272
        if ($savedConf !== $conf) {
273
            file_put_contents($pdnsdConfFile, $conf);
274
        }
275
        $pdnsdPath = Util::which('pdnsd');
276
        $pid = Processes::getPidOfProcess($pdnsdPath);
277
278
        // Check if pdnsd process is running and the configuration has not changed
279
        if (!empty($pid) && $savedConf === $conf) {
280
281
            // Perform additional check if the DNS server is working
282
            $resultResolve = gethostbynamel('lic.miko.ru');
283
            if ($resultResolve !== false) {
284
                // Configuration has not changed and the DNS server is working,
285
                // no need to restart or reload the service
286
                return;
287
            }
288
            // Perform a reload of the DNS server
289
        }
290
291
        // If pdnsd process is running, terminate the process
292
        if (!empty($pid)) {
293
            $busyboxPath = Util::which('busybox');
294
            $killPath = Util::which('kill');
295
            Processes::mwExec("{$busyboxPath} {$killPath} '$pid'");
296
        }
297
298
        // Start the pdnsd service with the updated configuration
299
        Processes::mwExec("{$pdnsdPath} -c /etc/pdnsd.conf -4");
300
    }
301
302
    /**
303
     * Configures the LAN interfaces and performs related network operations.
304
     *
305
     * @return int The result of the configuration process.
306
     */
307
    public function lanConfigure(): int
308
    {
309
        if (Util::isDocker()) {
310
            return 0;
311
        }
312
313
        // Retrieve the network settings
314
        $networks = $this->getGeneralNetSettings();
315
316
        // Retrieve the paths of required commands
317
        $busyboxPath = Util::which('busybox');
318
        $vconfigPath = Util::which('vconfig');
319
        $killallPath = Util::which('killall');
320
321
        $arr_commands = [];
322
        $arr_commands[] = "{$killallPath} udhcpc";
323
        $eth_mtu = [];
324
        foreach ($networks as $if_data) {
325
            if ($if_data['disabled'] === '1') {
326
                continue;
327
            }
328
329
            $if_name = $if_data['interface'];
330
            $if_name = escapeshellcmd(trim($if_name));
331
            if (empty($if_name)) {
332
                continue;
333
            }
334
335
            $data_hostname = self::getHostName();
336
            $hostname = $data_hostname['hostname'];
337
338
            if ($if_data['vlanid'] > 0) {
339
                // Override the interface name for VLAN interfaces
340
                $arr_commands[] = "{$vconfigPath} set_name_type VLAN_PLUS_VID_NO_PAD";
341
                // Add the new VLAN interface
342
                $arr_commands[] = "{$vconfigPath} add {$if_data['interface_orign']} {$if_data['vlanid']}";
343
            }
344
            // Disable and reset the interface
345
            $arr_commands[] = "{$busyboxPath} ifconfig $if_name down";
346
            $arr_commands[] = "{$busyboxPath} ifconfig $if_name 0.0.0.0";
347
348
            $gw_param = '';
349
            if (trim($if_data['dhcp']) === '1') {
350
                // DHCP configuration
351
                /*
352
                 * -t - number of attempts.
353
                 * -T - timeout for each attempt.
354
                 * -v - enable debugging.
355
                 * -S - log messages to syslog.
356
                 * -q - exit after obtaining lease.
357
                 * -n - exit if lease is not obtained.
358
                 */
359
                $pid_file = "/var/run/udhcpc_{$if_name}";
360
                $pid_pcc = Processes::getPidOfProcess($pid_file);
361
                if (!empty($pid_pcc) && file_exists($pid_file)) {
362
                    // Terminate the old udhcpc process
363
                    $killPath = Util::which('kill');
364
                    $catPath = Util::which('cat');
365
                    system("{$killPath} `{$catPath} {$pid_file}` {$pid_pcc}");
366
                }
367
                $udhcpcPath = Util::which('udhcpc');
368
                $nohupPath = Util::which('nohup');
369
370
                // Obtain IP and wait for the process to finish
371
                $workerPath = '/etc/rc/udhcpc_configure';
372
                $options = '-t 6 -T 5 -q -n';
373
                $arr_commands[] = "{$udhcpcPath} {$options} -i {$if_name} -x hostname:{$hostname} -s {$workerPath}";
374
                // Start a new udhcpc process in the background
375
                $options = '-t 6 -T 5 -S -b -n';
376
                $arr_commands[] = "{$nohupPath} {$udhcpcPath} {$options} -p {$pid_file} -i {$if_name} -x hostname:{$hostname} -s {$workerPath} 2>&1 &";
377
                /*
378
                   udhcpc - utility for configuring the interface
379
                               - configures /etc/resolv.conf
380
                    Further route configuration will be performed in udhcpcConfigureRenewBound();
381
                    and udhcpcConfigureDeconfig(). These methods will be called by the script WorkerUdhcpcConfigure.php.
382
                    // man udhcp
383
                    // http://pwet.fr/man/linux/administration_systeme/udhcpc/
384
385
                */
386
            } else {
387
                // Static IP configuration
388
                $ipaddr = trim($if_data['ipaddr']);
389
                $subnet = trim($if_data['subnet']);
390
                $gateway = trim($if_data['gateway']);
391
                if (empty($ipaddr)) {
392
                    continue;
393
                }
394
                try {
395
                    // Calculate the short subnet mask
396
                    $calc_subnet = new SubnetCalculator($ipaddr, $subnet);
397
                    $subnet = $calc_subnet->getSubnetMask();
398
                } catch (Throwable $e) {
399
                    echo "Caught exception: $ipaddr $subnet", $e->getMessage(), "\n";
400
                    continue;
401
                }
402
403
                $ifconfigPath = Util::which('ifconfig');
404
                $arr_commands[] = "{$busyboxPath} {$ifconfigPath} $if_name $ipaddr netmask $subnet";
405
406
                if ("" !== trim($gateway)) {
407
                    $gw_param = "gw $gateway";
408
                }
409
410
                $routePath = Util::which('route');
411
                $arr_commands[] = "{$busyboxPath} {$routePath} del default $if_name";
412
413
                /** @var LanInterfaces $if_data */
414
                $if_data = LanInterfaces::findFirst("id = '{$if_data['id']}'");
415
                $is_inet = ($if_data !== null) ? (string)$if_data->internet : '0';
416
417
                if ($is_inet === '1') {
418
                    // Create default route only if the interface is for internet
419
                    $arr_commands[] = "{$busyboxPath} {$routePath} add default $gw_param dev $if_name";
420
                }
421
                // Bring up the interface
422
                $arr_commands[] = "{$busyboxPath} {$ifconfigPath} $if_name up";
423
424
                $eth_mtu[] = $if_name;
425
            }
426
        }
427
        $out = null;
428
        Processes::mwExecCommands($arr_commands, $out, 'net');
429
        $this->hostsGenerate();
430
431
        foreach ($eth_mtu as $eth) {
432
            Processes::mwExecBg("/etc/rc/networking_set_mtu '{$eth}'");
433
        }
434
435
        // Additional "manual" routes
436
        Util::fileWriteContent('/etc/static-routes', '');
437
        $arr_commands = [];
438
        $out = [];
439
        $grepPath = Util::which('grep');
440
        $awkPath = Util::which('awk');
441
        $catPath = Util::which('cat');
442
        Processes::mwExec(
443
            "{$catPath} /etc/static-routes | {$grepPath} '^rout' | {$busyboxPath} {$awkPath} -F ';' '{print $1}'",
444
            $arr_commands
445
        );
446
        Processes::mwExecCommands($arr_commands, $out, 'rout');
447
448
        $this->openVpnConfigure();
449
        return 0;
450
    }
451
452
    /**
453
     * Retrieves the general network settings and performs additional processing.
454
     *
455
     * @return array An array of network interfaces and their settings.
456
     */
457
    public function getGeneralNetSettings(): array
458
    {
459
        // Get the list of network interfaces visible to the operating system
460
        $src_array_eth = $this->getInterfacesNames();
461
462
        // Create a copy of the network interfaces array
463
        $array_eth = $src_array_eth;
464
465
        // Retrieve the LAN interface settings from the database
466
        $res = LanInterfaces::find(['order' => 'interface,vlanid']);
467
        $networks = $res->toArray();
468
469
        if (count($networks) > 0) {
470
            // Additional data processing
471
            foreach ($networks as &$if_data) {
472
                $if_data['interface_orign'] = $if_data['interface'];
473
                $if_data['interface'] = ($if_data['vlanid'] > 0) ? "vlan{$if_data['vlanid']}" : $if_data['interface'];
474
                $if_data['dhcp'] = ($if_data['vlanid'] > 0) ? 0 : $if_data['dhcp'];
475
476
                if (Verify::isIpAddress($if_data['subnet'])) {
477
                    $if_data['subnet'] = $this->netMaskToCidr($if_data['subnet']);
478
                }
479
480
                $key = array_search($if_data['interface_orign'], $src_array_eth, true);
481
                if ($key !== false) {
482
                    // Interface found
483
                    // Remove the array element if it's not a VLAN
484
                    if ($if_data['vlanid'] === '0') {
485
                        unset($array_eth[$key]);
486
                        $this->enableLanInterface($if_data['interface_orign']);
487
                    }
488
                } else {
489
                    // Interface does not exist
490
                    $this->disableLanInterface($if_data['interface_orign']);
491
                    // Disable the interface
492
                    $if_data['disabled'] = 1;
493
                }
494
            }
495
            unset($if_data);
496
        } elseif (count($array_eth) > 0) {
497
            $networks = [];
498
            // Configure the main interface
499
            $networks[] = $this->addLanInterface($array_eth[0], true);
500
            unset($array_eth[0]);
501
        }
502
        // $array_eth will contain only the elements without settings in the database
503
        // Add the "default" settings for these interfaces
504
        foreach ($array_eth as $eth) {
505
            // Add the interface and disable it
506
            $networks[] = $this->addLanInterface($eth, false);
507
        }
508
509
        // Check if there is an active internet interface, if not, set the first available interface as internet
510
        $res = LanInterfaces::findFirst("internet = '1' AND disabled='0'");
511
        if (null === $res) {
512
            /** @var LanInterfaces $eth_settings */
513
            $eth_settings = LanInterfaces::findFirst("disabled='0'");
514
            if ($eth_settings !== null) {
515
                $eth_settings->internet = 1;
516
                $eth_settings->save();
517
            }
518
        }
519
520
        return $networks;
521
    }
522
523
    /**
524
     * Converts a net mask to CIDR notation.
525
     *
526
     * @param string $net_mask The net mask to convert.
527
     * @return int The CIDR notation.
528
     */
529
    public function netMaskToCidr(string $net_mask): int
530
    {
531
        $bits = 0;
532
        $net_mask = explode(".", $net_mask);
533
534
        foreach ($net_mask as $oct_ect) {
535
            $bits += strlen(str_replace("0", "", decbin((int)$oct_ect)));
536
        }
537
538
        return $bits;
539
    }
540
541
    /**
542
     * Enables a LAN interface
543
     *
544
     * @param string $name The name of the interface to enable.
545
     * @return void
546
     */
547
    public function enableLanInterface(string $name): void
548
    {
549
        $parameters = [
550
            'conditions' => 'interface = :ifName: and disabled = :disabled:',
551
            'bind' => [
552
                'ifName' => $name,
553
                'disabled' => 1,
554
            ],
555
        ];
556
557
        $if_data = LanInterfaces::findFirst($parameters);
558
        if ($if_data !== null) {
559
            $if_data->disabled = 0;
560
            $if_data->update();
561
        }
562
    }
563
564
    /**
565
     * Disables a LAN interface by setting its internet flag to 0 and disabled flag to 1.
566
     *
567
     * @param string $name The name of the interface to disable.
568
     * @return void
569
     */
570
    public function disableLanInterface(string $name): void
571
    {
572
        $if_data = LanInterfaces::findFirst("interface = '{$name}'");
573
        if ($if_data !== null) {
574
            $if_data->internet = 0;
575
            $if_data->disabled = 1;
576
            $if_data->update();
577
        }
578
    }
579
580
    /**
581
     * Adds a LAN interface with the specified name and settings.
582
     *
583
     * @param string $name The name of the interface.
584
     * @param bool $general Flag indicating if the interface is a general interface.
585
     * @return array The array representation of the added interface.
586
     */
587
    private function addLanInterface(string $name, bool $general = false): array
588
    {
589
        $data = new LanInterfaces();
590
        $data->name = $name;
591
        $data->interface = $name;
592
        $data->dhcp = '1';
593
        $data->internet = ($general === true) ? '1' : '0';
594
        $data->disabled = '0';
595
        $data->vlanid = '0';
596
        $data->hostname = 'MikoPBX';
597
        $data->domain = '';
598
        $data->topology = LanInterfaces::TOPOLOGY_PRIVATE;
599
        $data->autoUpdateExtIp = ($general === true) ? '1' : '0';;
600
        $data->primarydns = '';
601
        $data->save();
602
603
        return $data->toArray();
604
    }
605
606
    /**
607
     * Generates the hosts configuration.
608
     *
609
     * @return void
610
     */
611
    public function hostsGenerate(): void
612
    {
613
        $this->hostnameConfigure();
614
    }
615
616
    /**
617
     * Configures the hostname and hosts file.
618
     *
619
     * @return void
620
     */
621
    public function hostnameConfigure(): void
622
    {
623
        $data = self::getHostName();
624
        $hosts_conf = "127.0.0.1 localhost\n" .
625
            "127.0.0.1 {$data['hostname']}\n";
626
        if (!empty($data['domain'])) {
627
            $hosts_conf .= "127.0.0.1 {$data['hostname']}.{$data['domain']}\n";
628
        }
629
        $hostnamePath = Util::which('hostname');
630
        if (Util::isDocker()) {
631
            $realHostName = shell_exec($hostnamePath);
632
            $hosts_conf .= "127.0.0.1 $realHostName\n";
633
        }
634
        Util::fileWriteContent('/etc/hosts', $hosts_conf);
635
        Processes::mwExec($hostnamePath . ' ' . escapeshellarg($data['hostname']));
636
    }
637
638
    /**
639
     * Configuring OpenVPN. If a custom configuration file is specified in the system file customization, the network will be brought up.
640
     */
641
    public function openVpnConfigure(): void
642
    {
643
        $confFile = '/etc/openvpn.ovpn';
644
        Util::fileWriteContent($confFile, '');
645
        $data = file_get_contents($confFile);
646
647
        $pidFile = '/var/run/openvpn.pid';
648
        $pid = Processes::getPidOfProcess('openvpn');
649
        if (!empty($pid)) {
650
            // Terminate the process.
651
            $busyboxPath = Util::which('busybox');
652
            Processes::mwExec("{$busyboxPath} kill '$pid'");
653
        }
654
        if (!empty($data)) {
655
            $openvpnPath = Util::which('openvpn');
656
            Processes::mwExecBg("{$openvpnPath} --config /etc/openvpn.ovpn --writepid {$pidFile}", '/dev/null', 5);
657
        }
658
    }
659
660
    /**
661
     * Configures the LAN settings inside the Docker container.
662
     *
663
     * If the environment is not Docker, this method does nothing.
664
     *
665
     * @return void
666
     */
667
    public function configureLanInDocker(): void
668
    {
669
        // Check if the environment is Docker
670
        if (!Util::isDocker()) {
671
            return;
672
        }
673
674
        // Find the path to the busybox binary
675
        $busyboxPath = Util::which('busybox');
676
677
        // Retrieve the network settings
678
        $networks = $this->getGeneralNetSettings();
679
680
        foreach ($networks as $if_data) {
681
682
            $if_name = $if_data['interface'];
683
            $if_name = escapeshellcmd(trim($if_name));
684
685
            $commands = [
686
                'subnet' => $busyboxPath . ' ifconfig eth0 | awk \'/Mask:/ {sub("Mask:", "", $NF); print $NF}\'',
687
                'ipaddr' => $busyboxPath . ' ifconfig eth0 | awk \'/inet / {sub("addr:", "", $2); print $2}\'',
688
                'gateway' => $busyboxPath . ' route -n | awk \'/^0.0.0.0/ {print $2}\'',
689
                'hostname' => $busyboxPath . ' hostname',
690
            ];
691
            $data = [];
692
            foreach ($commands as $key => $command) {
693
                $output = [];
694
                if (Processes::MWExec($command, $output) === 0) {
695
                    $value = implode("", $output);
696
                    if ($key === 'subnet') {
697
                        $data[$key] = $this->netMaskToCidr($value);
698
                    } else {
699
                        $data[$key] = $value;
700
                    }
701
                }
702
            }
703
704
            // Save information to the database.
705
            $this->updateIfSettings($data, $if_name);
706
        }
707
708
    }
709
710
    /**
711
     * Updates the interface settings with the provided data.
712
     *
713
     * @param array $data The data to update the interface settings with.
714
     * @param string $name The name of the interface.
715
     *
716
     * @return void;
717
     */
718
    public function updateIfSettings(array $data, string $name): void
719
    {
720
        /** @var LanInterfaces $res */
721
        $res = LanInterfaces::findFirst("interface = '$name' AND vlanid=0");
722
        if ($res === null || !$this->settingsIsChange($data, $res->toArray())) {
723
            return;
724
        }
725
        foreach ($data as $key => $value) {
726
            $res->writeAttribute($key, $value);
727
        }
728
        $res->save();
729
    }
730
731
    /**
732
     * Updates the DNS settings with the provided data.
733
     *
734
     * @param array $data The data to update the DNS settings with.
735
     * @param string $name The name of the interface.
736
     *
737
     * @return void
738
     */
739
    public function updateDnsSettings(array $data, string $name): void
740
    {
741
        /** @var LanInterfaces $res */
742
        $res = LanInterfaces::findFirst("interface = '$name' AND vlanid=0");
743
        if ($res === null || !$this->settingsIsChange($data, $res->toArray())) {
744
            return;
745
        }
746
        foreach ($data as $key => $value) {
747
            $res->writeAttribute($key, $value);
748
        }
749
        if (empty($res->primarydns) && !empty($res->secondarydns)) {
750
            // Swap primary and secondary DNS if primary is empty
751
            $res->primarydns = $res->secondarydns;
752
            $res->secondarydns = '';
753
        }
754
        $res->save();
755
    }
756
757
    /**
758
     * Checks if the network settings have changed.
759
     *
760
     * @param array $data The new network settings.
761
     * @param array $dbData The existing network settings from the database.
762
     *
763
     * @return bool  Returns true if the settings have changed, false otherwise.
764
     */
765
    public function settingsIsChange(array $data, array $dbData): bool
766
    {
767
        $isChange = false;
768
        foreach ($dbData as $key => $value) {
769
            if (!isset($data[$key]) || (string)$value === (string)$data[$key]) {
770
                continue;
771
            }
772
            SystemMessages::sysLogMsg(__METHOD__, "Find new network settings: {$key} changed {$value}=>{$data[$key]}");
773
            $isChange = true;
774
        }
775
        return $isChange;
776
    }
777
778
    /**
779
     * Retrieves the interface name by its ID.
780
     *
781
     * @param string $id_net The ID of the network interface.
782
     *
783
     * @return string  The interface name.
784
     */
785
    public function getInterfaceNameById(string $id_net): string
786
    {
787
        $res = LanInterfaces::findFirstById($id_net);
788
        if ($res !== null && $res->interface !== null) {
789
            return $res->interface;
790
        }
791
792
        return '';
793
    }
794
795
    /**
796
     * Retrieves the enabled LAN interfaces.
797
     *
798
     * @return array  An array of enabled LAN interfaces.
799
     */
800
    public function getEnabledLanInterfaces(): array
801
    {
802
        /** @var LanInterfaces $res */
803
        $res = LanInterfaces::find('disabled=0');
804
805
        return $res->toArray();
806
    }
807
808
809
810
    /**
811
     * Updates the network settings with the provided data.
812
     * @param array $data The network settings data to update.
813
     */
814
    public function updateNetSettings(array $data): void
815
    {
816
        $res = LanInterfaces::findFirst("internet = '1'");
817
        $update_inet = false;
818
        if ($res === null) {
819
            // If no interface with internet connection is found, get the first interface.
820
            $res = LanInterfaces::findFirst();
821
            $update_inet = true;
822
        }
823
824
        if ($res !== null) {
825
            foreach ($data as $key => $value) {
826
                $res->$key = $value;
827
            }
828
            if ($update_inet === true) {
829
                $res->internet = 1;
830
            }
831
            $res->save();
832
        }
833
    }
834
835
    /**
836
     * Retrieves information about all network interfaces.
837
     * @return array An array of network interfaces with their respective information.
838
     */
839
    public function getInterfaces(): array
840
    {
841
        // Get all PCI interface names (network interfaces).
842
        $i_names = $this->getInterfacesNames();
843
        $if_list = [];
844
        foreach ($i_names as $i) {
845
            $if_list[$i] = $this->getInterface($i);
846
        }
847
848
        return $if_list;
849
    }
850
851
    /**
852
     * Retrieves information about a specific network interface.
853
     * @param string $name The name of the network interface.
854
     * @return array An array containing the interface information.
855
     */
856
    public function getInterface(string $name): array
857
    {
858
        $interface = [];
859
860
        // Get ifconfig's output for the specified interface.
861
        $busyboxPath = Util::which('busybox');
862
        Processes::mwExec("{$busyboxPath} ifconfig $name 2>/dev/null", $output);
863
        $output = implode(" ", $output);
864
865
        // Parse MAC address.
866
        preg_match("/HWaddr (\S+)/", $output, $matches);
867
        $interface['mac'] = (count($matches) > 0) ? $matches[1] : '';
868
869
        // Parse IP address.
870
        preg_match("/inet addr:(\S+)/", $output, $matches);
871
        $interface['ipaddr'] = (count($matches) > 0) ? $matches[1] : '';
872
873
        // Parse subnet mask.
874
        preg_match("/Mask:(\S+)/", $output, $matches);
875
        $subnet = (count($matches) > 0) ? $this->netMaskToCidr($matches[1]) : '';
876
        $interface['subnet'] = $subnet;
877
878
        // Check if the interface is up.
879
        preg_match("/\s+(UP)\s+/", $output, $matches);
880
        $status = (count($matches) > 0) ? $matches[1] : '';
881
        if ($status === "UP") {
882
            $interface['up'] = true;
883
        } else {
884
            $interface['up'] = false;
885
        }
886
        $busyboxPath = Util::which('busybox');
887
888
        // Get the default gateway.
889
        $grepPath = Util::which('grep');
890
        $cutPath = Util::which('cut');
891
        $routePath = Util::which('route');
892
893
        Processes::mwExec(
894
            "{$busyboxPath} {$routePath} -n | {$grepPath} {$name} | {$grepPath} \"^0.0.0.0\" | {$cutPath} -d ' ' -f 10",
895
            $matches
896
        );
897
        $gw = (count($matches) > 0) ? $matches[0] : '';
898
        if (Verify::isIpAddress($gw)) {
899
            $interface['gateway'] = $gw;
900
        }
901
902
        // Get DNS servers.
903
        $catPath = Util::which('cat');
904
        Processes::mwExec("{$catPath} /etc/resolv.conf | {$grepPath} nameserver | {$cutPath} -d ' ' -f 2", $dnsout);
905
906
        $dnsSrv = [];
907
        foreach ($dnsout as $line) {
908
            if (Verify::isIpAddress($line)) {
909
                $dnsSrv[] = $line;
910
            }
911
        }
912
        $interface['dns'] = $dnsSrv;
913
914
        return $interface;
915
    }
916
917
    /**
918
     * Update external IP address
919
     */
920
    public function updateExternalIp(): void
921
    {
922
        $ipInfoResult = SysinfoManagementProcessor::getExternalIpInfo();
923
        if ($ipInfoResult->success && isset($ipInfoResult->data['ip'])) {
924
            $currentIP = $ipInfoResult->data['ip'];
925
            $lanData = LanInterfaces::find('autoUpdateExtIp=1');
926
            foreach ($lanData as $lan) {
927
                $oldExtIp = $lan->extipaddr;
928
                $parts = explode(':', $oldExtIp);
929
                $oldIP = $parts[0]; // Only IP part of the address
930
                $port = isset($parts[1]) ? ':' . $parts[1] : '';
931
                if ($oldIP !== $currentIP) {
932
                    $newExtIp = $currentIP . $port;
933
                    $lan->extipaddr = $newExtIp;
934
                    if ($lan->save()) {
935
                        SystemMessages::sysLogMsg(__METHOD__, "External IP address updated for interface {$lan->interface}");
936
                    }
937
                }
938
            }
939
        }
940
    }
941
942
    /**
943
     * Execute cli command to set up network
944
     * @param string $action Action to perform (start or stop)
945
     * @return void
946
     */
947
    public function cliAction(string $action): void
948
    {
949
        /**
950
         * If running inside a Docker container, exit the script.
951
         */
952
        if (Util::isDocker()) {
953
            return;
954
        }
955
956
        if ('start' === $action) {
957
958
            /**
959
             * Generate the resolv.conf file for DNS configuration.
960
             */
961
            $this->resolvConfGenerate();
962
            if (Util::isT2SdeLinux()) {
963
                /**
964
                 * Configure the loopback interface for T2SDE Linux.
965
                 */
966
                $this->loConfigure();
967
            }
968
            /**
969
             * Configure the LAN interfaces.
970
             */
971
            $this->lanConfigure();
972
        } elseif ('stop' === $action) {
973
            if (Util::isSystemctl()) {
974
                /**
975
                 * Stop networking using systemctl (systemd-based systems).
976
                 */
977
                $systemctlPath = Util::which('systemctl');
978
                Processes::mwExec("{$systemctlPath} stop networking");
979
            } else {
980
                /**
981
                 * Stop networking on T2SDE (non-systemd) systems.
982
                 */
983
                $if_list      = $this->getInterfaces();
984
                $arr_commands = [];
985
                $ifconfigPath = Util::which('ifconfig');
986
                foreach ($if_list as $if_name => $data) {
987
                    $arr_commands[] = "{$ifconfigPath} $if_name down";
988
                }
989
990
                /**
991
                 * Execute the stop commands for each interface.
992
                 */
993
                Processes::mwExecCommands($arr_commands, $out, 'net_stop');
994
            }
995
        }
996
    }
997
}