Passed
Push — develop ( b3fca4...078ece )
by Nikolay
04:51
created

Network::cliAction()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 47
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 47
rs 8.8333
c 0
b 0
f 0
cc 7
nc 7
nop 1
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\Sysinfo\GetExternalIpInfoAction;
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';
0 ignored issues
show
Deprecated Code introduced by
The function MikoPBX\Core\System\System::getLogDir() has been deprecated: use Directories::getDir(Directories::CORE_LOGS_DIR); ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

51
        $log_dir = /** @scrutinizer ignore-deprecated */ System::getLogDir() . '/pcapsipdump';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

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