Passed
Push — develop ( 47fa61...73a184 )
by Портнов
05:13
created

Network::udhcpcConfigureRenewBoundSystemCtl()   B

Complexity

Conditions 7
Paths 48

Size

Total Lines 60
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 60
rs 8.3466
c 0
b 0
f 0
cc 7
nc 48
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 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, PbxSettings};
23
use MikoPBX\Core\System\Configs\IptablesConf;
24
use MikoPBX\Core\Utilities\SubnetCalculator;
25
use Phalcon\Di\Injectable;
26
use Phalcon\Mvc\Model;
27
use Throwable;
28
29
/**
30
 * Class Network
31
 */
32
class Network extends Injectable
33
{
34
35
    public static function startSipDump(): void
36
    {
37
        $config = new MikoPBXConfig();
38
        $use    = $config->getGeneralSettings('USE_PCAP_SIP_DUMP');
39
        if ($use !== '1') {
40
            return;
41
        }
42
43
        Processes::killByName('pcapsipdump');
44
        $log_dir = System::getLogDir() . '/pcapsipdump';
45
        Util::mwMkdir($log_dir);
46
47
        $network         = new Network();
48
        $arr_eth         = $network->getInterfacesNames();
49
        $pcapsipdumpPath = Util::which('pcapsipdump');
50
        foreach ($arr_eth as $eth) {
51
            $pid_file = "/var/run/pcapsipdump_{$eth}.pid";
52
            Processes::mwExecBg(
53
                $pcapsipdumpPath . ' -T 120 -P ' . $pid_file . ' -i ' . $eth . ' -m \'^(INVITE|REGISTER)$\' -L ' . $log_dir . '/dump.db'
54
            );
55
        }
56
    }
57
58
    /**
59
     * Имена всех подключенных сетевых интерфейсов.
60
     */
61
    public function getInterfacesNames()
62
    {
63
        // Универсальная команда для получения всех PCI сетевых интерфейсов.
64
        $lsPath   = Util::which('ls');
65
        $grepPath = Util::which('grep');
66
        $awkPath  = Util::which('awk');
67
        Processes::mwExec("{$lsPath} -l /sys/class/net | {$grepPath} devices | {$grepPath} -v virtual | {$awkPath} '{ print $9 }'", $names);
68
69
        return $names;
70
    }
71
72
    /**
73
     * Up loopback.
74
     **/
75
    public function loConfigure():void
76
    {
77
        if (Util::isSystemctl()) {
78
            return;
79
        }
80
        $busyboxPath  = Util::which('busybox');
81
        $ifconfigPath = Util::which('ifconfig');
82
        Processes::mwExec("{$busyboxPath} {$ifconfigPath} lo 127.0.0.1");
83
    }
84
85
    /**
86
     * Generates resolv.conf
87
     **/
88
    public function resolvConfGenerate(): void
89
    {
90
        if(Util::isDocker()){
91
            return;
92
        }
93
        $resolv_conf   = '';
94
        $data_hostname = self::getHostName();
95
        if (trim($data_hostname['domain']) !== '') {
96
            $resolv_conf .= "domain {$data_hostname['domain']}\n";
97
        }
98
99
        $resolv_conf .= "nameserver 127.0.0.1\n";
100
101
        $named_dns = [];
102
        $dns       = $this->getHostDNS();
103
        foreach ($dns as $ns) {
104
            if (trim($ns) === '') {
105
                continue;
106
            }
107
            $named_dns[] = $ns;
108
            $resolv_conf .= "nameserver {$ns}\n";
109
        }
110
        if (count($dns) === 0) {
111
            $resolv_conf .= "nameserver 4.4.4.4\n";
112
            $named_dns[] .= "8.8.8.8";
113
        }
114
115
        if (Util::isSystemctl()) {
116
            $s_resolv_conf = "[Resolve]\n"
117
                . "DNS=127.0.0.1\n";
118
            if (trim($data_hostname['domain']) !== '') {
119
                $s_resolv_conf .= "Domains={$data_hostname['domain']}\n";
120
            }
121
            file_put_contents('/etc/systemd/resolved.conf', $s_resolv_conf);
122
            $systemctlPath = Util::which('systemctl');
123
            Processes::mwExec("{$systemctlPath} restart systemd-resolved");
124
        } else {
125
            file_put_contents('/etc/resolv.conf', $resolv_conf);
126
        }
127
128
        $this->generatePdnsdConfig($named_dns);
129
    }
130
131
    /**
132
     * Возвращает имя сервера в виде ассоциативного массива.
133
     *
134
     * @return array
135
     */
136
    public static function getHostName(): array
137
    {
138
        $data = [
139
            'hostname' => 'mikopbx',
140
            'domain'   => '',
141
        ];
142
        /** @var LanInterfaces $res */
143
        $res = LanInterfaces::findFirst("internet = '1'");
144
        if (null !== $res) {
145
            $data['hostname'] = $res->hostname;
146
            $data['domain']   = $res->domain;
147
        }
148
        $data['hostname'] = (empty($data['hostname'])) ? 'mikopbx' : $data['hostname'];
149
150
        return $data;
151
    }
152
153
    /**
154
     * Возвращает массив DNS серверов из настроек.
155
     *
156
     * @return array
157
     */
158
    public function getHostDNS(): array
159
    {
160
        $dns = [];
161
        /** @var LanInterfaces $res */
162
        $res = LanInterfaces::findFirst("internet = '1'");
163
        if (null !== $res) {
164
            if ( ! empty($res->primarydns) && '127.0.0.1' !== $res->primarydns) {
165
                $dns[] = $res->primarydns;
166
            }
167
            if ( ! empty($res->secondarydns) && '127.0.0.1' !== $res->secondarydns) {
168
                $dns[] = $res->secondarydns;
169
            }
170
        }
171
172
        return $dns;
173
    }
174
175
    /**
176
     * Настройка кэширующего DNS сервера.
177
     *
178
     * @param $named_dns
179
     */
180
    public function generatePdnsdConfig($named_dns): void
181
    {
182
        $tempDir   = $this->di->getShared('config')->path('core.tempDir');
183
        $cache_dir = $tempDir . '/pdnsd/cache';
184
        Util::mwMkdir($cache_dir);
185
186
        $conf = 'global {' . "\n" .
187
            '	perm_cache=10240;' . "\n" .
188
            '	cache_dir="' . $cache_dir . '";' . "\n" .
189
            '	pid_file = /var/run/pdnsd.pid;' . "\n" .
190
            '	run_as="nobody";' . "\n" .
191
            '	server_ip = 127.0.0.1;' . "\n" .
192
            '	status_ctl = on;' . "\n" .
193
            '	query_method=udp_tcp;' . "\n" .
194
            '	min_ttl=15m;' . "\n" .
195
            '	max_ttl=1w;' . "\n" .
196
            '	timeout=10;' . "\n" .
197
            '	neg_domain_pol=on;' . "\n" .
198
            '	run_as=root;' . "\n" .
199
            '	daemon=on;' . "\n" .
200
            '}' . "\n" .
201
            'server {' . "\n" .
202
            '	label = "main";' . "\n" .
203
            '	ip = ' . implode(', ', $named_dns) . ';' . "\n" .
204
            '	interface=lo;' . "\n" .
205
            '	uptest=if;' . "\n" .
206
            '	interval=10m;' . "\n" .
207
            '	purge_cache=off;' . "\n" .
208
            '}';
209
210
        $pdnsdConfFile  = '/etc/pdnsd.conf';
211
        $savedConf = '';
212
        if(file_exists($pdnsdConfFile)){
213
            $savedConf = file_get_contents($pdnsdConfFile);
214
        }
215
        if($savedConf !== $conf){
216
            file_put_contents($pdnsdConfFile, $conf);
217
        }
218
        $pdnsdPath = Util::which('pdnsd');
219
        $pid       = Processes::getPidOfProcess($pdnsdPath);
220
        if (!empty($pid) && $savedConf === $conf) {
221
            // Выполним дополнительную проверку, работает ли сервер.
222
            $resultResolve = gethostbynamel('lic.miko.ru');
223
            if($resultResolve !== false){
224
                // Ничего делать не нужно. Конфиг не изменился. Рестарт не требуется.
225
                return;
226
            }
227
            // Выполним reload сервера DNS.
228
        }
229
        if (!empty($pid)) {
230
            // Завершаем процесс.
231
            $busyboxPath = Util::which('busybox');
232
            $killPath = Util::which('kill');
233
            Processes::mwExec("{$busyboxPath} {$killPath} '$pid'");
234
        }
235
        Processes::mwExec("{$pdnsdPath} -c /etc/pdnsd.conf -4");
236
    }
237
238
    /**
239
     * Configures LAN interface
240
     *
241
     * @return int
242
     */
243
    public function lanConfigure(): int
244
    {
245
        if(Util::isDocker()){
246
            // Обновляем список интерфейсов.
247
            $this->getGeneralNetSettings();
248
            return 0;
249
        }
250
251
        $busyboxPath = Util::which('busybox');
252
        $vconfigPath = Util::which('vconfig');
253
        $killallPath = Util::which('killall');
254
255
        $networks     = $this->getGeneralNetSettings();
256
        $arr_commands = [];
257
        $arr_commands[] = "{$killallPath} udhcpc";
258
        $eth_mtu = [];
259
        foreach ($networks as $if_data) {
260
            if ($if_data['disabled'] === '1') {
261
                continue;
262
            }
263
264
            $if_name = $if_data['interface'];
265
            $if_name = escapeshellcmd(trim($if_name));
266
            if (empty($if_name)) {
267
                continue;
268
            }
269
270
            $data_hostname = self::getHostName();
271
            $hostname      = $data_hostname['hostname'];
272
273
            if ($if_data['vlanid'] > 0) {
274
                // Переопределяем имя интерфейса.
275
                $arr_commands[] = "{$vconfigPath} set_name_type VLAN_PLUS_VID_NO_PAD";
276
                // Добавляем новый интерфейс.
277
                $arr_commands[] = "{$vconfigPath} add {$if_data['interface_orign']} {$if_data['vlanid']}";
278
            }
279
            // Отключаем интерфейс.
280
            $arr_commands[] = "{$busyboxPath} ifconfig $if_name down";
281
            $arr_commands[] = "{$busyboxPath} ifconfig $if_name 0.0.0.0";
282
283
            $gw_param = '';
284
            if (trim($if_data['dhcp']) === '1') {
285
                /*
286
                 * -t - количество попыток.
287
                 * -T - таймаут попытки.
288
                 * -v - включить отладку.
289
                 * -S - логи в syslog.
290
                 * -q - Exit after obtaining lease
291
                 * -n - Exit if lease is not obtained
292
                 */
293
                $pid_file = "/var/run/udhcpc_{$if_name}";
294
                $pid_pcc  = Processes::getPidOfProcess($pid_file);
295
                if ( ! empty($pid_pcc) && file_exists($pid_file)) {
296
                    // Завершаем старый процесс.
297
                    $killPath = Util::which('kill');
298
                    $catPath  = Util::which('cat');
299
                    system("{$killPath} `{$catPath} {$pid_file}` {$pid_pcc}");
300
                }
301
                $udhcpcPath = Util::which('udhcpc');
302
                $nohupPath  = Util::which('nohup');
303
304
                // Получаем IP и дожидаемся завершения процесса.
305
                $workerPath     = '/etc/rc/udhcpc.configure';
306
                $options        = '-t 6 -T 5 -q -n';
307
                $arr_commands[] = "{$udhcpcPath} {$options} -i {$if_name} -x hostname:{$hostname} -s {$workerPath}";
308
                // Старутем новый процесс udhcpc в  фоне.
309
                $options        = '-t 6 -T 5 -S -b -n';
310
                $arr_commands[] = "{$nohupPath} {$udhcpcPath} {$options} -p {$pid_file} -i {$if_name} -x hostname:{$hostname} -s {$workerPath} 2>&1 &";
311
                /*
312
                    udhcpc  - утилита произведет настройку интерфейса
313
                               - произведет конфигурацию /etc/resolv.conf
314
                    Дальнейшая настройка маршрутов будет произволиться в udhcpcConfigureRenewBound();
315
                    и в udhcpcConfigureDeconfig(). Эти методы будут вызваны скриптом WorkerUdhcpcConfigure.php.
316
                    // man udhcp
317
                    // http://pwet.fr/man/linux/administration_systeme/udhcpc/
318
319
                */
320
            } else {
321
                $ipaddr  = trim($if_data['ipaddr']);
322
                $subnet  = trim($if_data['subnet']);
323
                $gateway = trim($if_data['gateway']);
324
                if (empty($ipaddr)) {
325
                    continue;
326
                }
327
                // Это короткое представление маск /24 /32.
328
                try {
329
                    $calc_subnet = new SubnetCalculator($ipaddr, $subnet);
330
                    $subnet      = $calc_subnet->getSubnetMask();
331
                } catch (Throwable $e) {
332
                    echo "Caught exception: $ipaddr $subnet", $e->getMessage(), "\n";
333
                    continue;
334
                }
335
336
                $ifconfigPath   = Util::which('ifconfig');
337
                $arr_commands[] = "{$busyboxPath} {$ifconfigPath} $if_name $ipaddr netmask $subnet";
338
339
                if ("" !== trim($gateway)) {
340
                    $gw_param = "gw $gateway";
341
                }
342
343
                $routePath      = Util::which('route');
344
                $arr_commands[] = "{$busyboxPath} {$routePath} del default $if_name";
345
346
                /** @var LanInterfaces $if_data */
347
                $if_data = LanInterfaces::findFirst("id = '{$if_data['id']}'");
348
                $is_inet = ($if_data !== null) ? (string)$if_data->internet : '0';
349
                // Добавляем маршруты по умолчанию.
350
                if ($is_inet === '1') {
351
                    // ТОЛЬКО, если этот интерфейс для интернет, создаем дефолтный маршрут.
352
                    $arr_commands[] = "{$busyboxPath} {$routePath} add default $gw_param dev $if_name";
353
                }
354
                // Поднимаем интерфейс.
355
                $arr_commands[] = "{$busyboxPath} {$ifconfigPath} $if_name up";
356
357
                $eth_mtu[] = $if_name;
358
            }
359
        }
360
        $out = null;
361
        Processes::mwExecCommands($arr_commands, $out, 'net');
362
        $this->hostsGenerate();
363
364
        foreach ($eth_mtu as $eth) {
365
            Processes::mwExecBg("/etc/rc/networking.set.mtu '{$eth}'");
366
        }
367
368
        // Дополнительные "ручные" маршруты.
369
        Util::fileWriteContent('/etc/static-routes', '');
370
        $arr_commands = [];
371
        $out          = [];
372
        $grepPath     = Util::which('grep');
373
        $awkPath      = Util::which('awk');
374
        $catPath      = Util::which('cat');
375
        Processes::mwExec(
376
            "{$catPath} /etc/static-routes | {$grepPath} '^rout' | {$busyboxPath} {$awkPath} -F ';' '{print $1}'",
377
            $arr_commands
378
        );
379
        Processes::mwExecCommands($arr_commands, $out, 'rout');
380
381
        $this->openVpnConfigure();
382
        return 0;
383
    }
384
385
    /**
386
     * Получение настроек интерфейсов LAN.
387
     *
388
     * @return array
389
     */
390
    public function getGeneralNetSettings(): array
391
    {
392
        // Массив сетевых интерфейсов, которые видит ОС.
393
        $src_array_eth = $this->getInterfacesNames();
394
        // Создаем копию массива сетевых интерфейсов.
395
        $array_eth = $src_array_eth;
396
        $res       = LanInterfaces::find(['order' => 'interface,vlanid']);
397
        $networks  = $res->toArray();
398
        if (count($networks) > 0) {
399
            // Дополнительная обработка данных.
400
            foreach ($networks as &$if_data) {
401
                $if_data['interface_orign'] = $if_data['interface'];
402
                $if_data['interface']       = ($if_data['vlanid'] > 0) ? "vlan{$if_data['vlanid']}" : $if_data['interface'];
403
                $if_data['dhcp']            = ($if_data['vlanid'] > 0) ? 0 : $if_data['dhcp'];
404
405
                if (Verify::isIpAddress($if_data['subnet'])) {
406
                    $if_data['subnet'] = $this->netMaskToCidr($if_data['subnet']);
407
                }
408
409
                $key = array_search($if_data['interface_orign'], $src_array_eth, true);
410
                if ($key !== false) {
411
                    // Интерфейс найден.
412
                    // Удаляем элемент массива, если это не VLAN.
413
                    if ($if_data['vlanid'] === '0') {
414
                        unset($array_eth[$key]);
415
                        $this->enableLanInterface($if_data['interface_orign']);
416
                    }
417
                } else {
418
                    // Интерфейс не существует.
419
                    $this->disableLanInterface($if_data['interface_orign']);
420
                    // Отключаем интерфейс.
421
                    $if_data['disabled'] = 1;
422
                }
423
            }
424
            unset($if_data);
425
        } elseif (count($array_eth) > 0) {
426
            $networks = [];
427
            // Настраиваем основной интерфейс.
428
            $networks[] = $this->addLanInterface($array_eth[0], true);
429
            unset($array_eth[0]);
430
        }
431
        // $array_eth - в массиве останутся только те элементы,
432
        // по которым нет настроек в базе дынных.
433
        // Следует добавить настройки "по умолчанию".
434
        foreach ($array_eth as $eth) {
435
            // Добавляем. Все интерфейсы, отключаем.
436
            $networks[] = $this->addLanInterface($eth, false);
437
        }
438
        $res = LanInterfaces::findFirst("internet = '1' AND disabled='0'");
439
        if (null === $res) {
440
            /** @var LanInterfaces $eth_settings */
441
            $eth_settings = LanInterfaces::findFirst("disabled='0'");
442
            if ($eth_settings !== null) {
443
                $eth_settings->internet = 1;
444
                $eth_settings->save();
445
            }
446
        }
447
448
        return $networks;
449
    }
450
451
    /**
452
     * Сбор информации о доступных адресах web интерфейса.
453
     * @return string
454
     */
455
    public static function getInfoMessage():string
456
    {
457
        $addresses = [
458
            'local' => [],
459
            'external' => []
460
        ];
461
        /** @var LanInterfaces $interface */
462
        $interfaces = LanInterfaces::find("disabled='0'");
463
        foreach ($interfaces as $interface){
464
            if(!empty($interface->ipaddr)){
465
                $addresses['local'][] = $interface->ipaddr;
466
            }
467
            if(!empty($interface->exthostname) && !in_array($interface->exthostname, $addresses['local'], true)){
468
                $addresses['external'][] = explode(':', $interface->exthostname)[0]??'';
469
            }
470
            if(!empty($interface->extipaddr) && !in_array($interface->extipaddr, $addresses['local'], true) ){
471
                $addresses['external'][] = explode(':', $interface->extipaddr)[0]??'';
472
            }
473
        }
474
        unset($interfaces);
475
        $port = PbxSettings::getValueByKey('WEBHTTPSPort');
476
        $info = PHP_EOL."   The web interface is available at the addresses:".PHP_EOL.PHP_EOL;
477
        foreach ($addresses['local'] as $address){
478
            if(empty($address)){
479
                continue;
480
            }
481
            $info.= "    - https://$address:$port".PHP_EOL;
482
        }
483
        $info.=PHP_EOL;
484
        $info.= "   The web interface is available at the external addresses:".PHP_EOL.PHP_EOL;
485
        foreach ($addresses['external'] as $address){
486
            if(empty($address)){
487
                continue;
488
            }
489
            $info.= "    - https://$address:$port".PHP_EOL;
490
        }
491
        $info.=PHP_EOL;
492
493
        return $info;
494
    }
495
496
497
    /**
498
     * Преобразует сетевую маску в CIDR представление.
499
     *
500
     * @param $net_mask
501
     *
502
     * @return int
503
     */
504
    public function netMaskToCidr($net_mask): int
505
    {
506
        $bits     = 0;
507
        $net_mask = explode(".", $net_mask);
508
509
        foreach ($net_mask as $oct_ect) {
510
            $bits += strlen(str_replace("0", "", decbin((int)$oct_ect)));
511
        }
512
513
        return $bits;
514
    }
515
516
    /**
517
     * Включаем интерфейс по его имени.
518
     *
519
     * @param $name
520
     */
521
    public function enableLanInterface($name): void
522
    {
523
        $parameters = [
524
            'conditions' => 'interface = :ifName: and disabled = :disabled:',
525
            'bind'       => [
526
                'ifName'   => $name,
527
                'disabled' => 1,
528
            ],
529
        ];
530
531
        $if_data = LanInterfaces::findFirst($parameters);
532
        if ($if_data !== null) {
533
            $if_data->disabled = 0;
534
            $if_data->update();
535
        }
536
    }
537
538
    /**
539
     * Удаляем интерфейс по его имени.
540
     *
541
     * @param $name
542
     */
543
    public function disableLanInterface($name): void
544
    {
545
        $if_data = LanInterfaces::findFirst("interface = '{$name}'");
546
        if ($if_data !== null) {
547
            $if_data->internet = 0;
548
            $if_data->disabled = 1;
549
            $if_data->update();
550
        }
551
    }
552
553
    /**
554
     * Добавляем в базу данных сведения о новом интерфейсе.
555
     *
556
     * @param      $name
557
     * @param bool $general
558
     *
559
     * @return mixed
560
     */
561
    private function addLanInterface($name, $general = false)
562
    {
563
        $data = new LanInterfaces();
564
        $data->name      = $name;
565
        $data->interface = $name;
566
        $data->dhcp      = '1';
567
        $data->internet  = ($general === true) ? '1' : '0';
568
        $data->disabled  = '0';
569
        $data->vlanid    = '0';
570
        $data->hostname  = 'mikopbx';
571
        $data->domain    = '';
572
        $data->topology  = 'private';
573
        $data->primarydns= '';
574
        $data->save();
575
576
        return $data->toArray();
577
    }
578
579
    /**
580
     * Настройка hosts
581
     */
582
    public function hostsGenerate(): void
583
    {
584
        $this->hostnameConfigure();
585
    }
586
587
    /**
588
     *  Setup hostname
589
     **/
590
    public function hostnameConfigure(): void
591
    {
592
        $data       = self::getHostName();
593
        $hosts_conf = "127.0.0.1 localhost\n" .
594
            "127.0.0.1 {$data['hostname']}\n";
595
        if ( ! empty($data['domain'])) {
596
            $hosts_conf .= "127.0.0.1 {$data['hostname']}.{$data['domain']}\n";
597
        }
598
        Util::fileWriteContent('/etc/hosts', $hosts_conf);
599
600
        $hostnamePath = Util::which('hostname');
601
        Processes::mwExec($hostnamePath . ' ' . escapeshellarg($data['hostname']));
602
    }
603
604
    /**
605
     * Настройка OpenVPN. Если в кастомизации системных файлов определн конфиг, то сеть поднимется.
606
     */
607
    public function openVpnConfigure():void
608
    {
609
        $confFile = '/etc/openvpn.ovpn';
610
        Util::fileWriteContent($confFile, '');
611
        $data = file_get_contents($confFile);
612
613
        $pidFile = '/var/run/openvpn.pid';
614
        $pid     = Processes::getPidOfProcess('openvpn');
615
        if ( ! empty($pid)) {
616
            // Завершаем процесс.
617
            $busyboxPath = Util::which('busybox');
618
            Processes::mwExec("{$busyboxPath} kill '$pid'");
619
        }
620
        if ( ! empty($data)) {
621
            $openvpnPath = Util::which('openvpn');
622
            Processes::mwExecBg("{$openvpnPath} --config /etc/openvpn.ovpn --writepid {$pidFile}", '/dev/null', 5);
623
        }
624
    }
625
626
    /**
627
     * Configures LAN interface FROM udhcpc (renew_bound)
628
     */
629
    public function udhcpcConfigureRenewBound(): void
630
    {
631
        if(Util::isDocker()){
632
            return;
633
        }
634
        if (Util::isSystemctl()) {
635
            $this->udhcpcConfigureRenewBoundSystemCtl();
636
            return;
637
        }
638
        // Инициализация массива переменных.
639
        $env_vars = [
640
            'broadcast' => '',
641
            'interface' => '',
642
            'ip'        => '',
643
            'router'    => '',
644
            'timesvr'   => '',
645
            'namesvr'   => '',
646
            'dns'       => '',
647
            'hostname'  => '',
648
            'subnet'    => '',
649
            'serverid'  => '',
650
            'ipttl'     => '',
651
            'lease'     => '',
652
            'domain'    => '',
653
        ];
654
655
        $debugMode = $this->di->getShared('config')->path('core.debugMode');
656
        // Получаем значения переменных окружения.
657
        foreach ($env_vars as $key => $value) {
658
            $env_vars[$key] = trim(getenv($key));
659
        }
660
        $BROADCAST = ($env_vars['broadcast'] === '') ? "" : "broadcast {$env_vars['broadcast']}";
661
        if($env_vars['subnet'] === '255.255.255.255' || $env_vars['subnet'] === ''){
662
            // support /32 address assignment
663
            // https://forummikrotik.ru/viewtopic.php?f=3&t=6246&start=40
664
            $NET_MASK = '';
665
        }else{
666
            $NET_MASK = "netmask {$env_vars['subnet']}";
667
        }
668
        // Настраиваем интерфейс.
669
        $busyboxPath = Util::which('busybox');
670
        Processes::mwExec("{$busyboxPath} ifconfig {$env_vars['interface']} {$env_vars['ip']} $BROADCAST $NET_MASK");
671
672
        // Удаляем старые маршруты по умолчанию.
673
        while (true) {
674
            $out = [];
675
            Processes::mwExec("route del default gw 0.0.0.0 dev {$env_vars['interface']}", $out);
676
            if (trim(implode('', $out)) !== '') {
677
                // Произошла ошибка, значит все маршруты очищены.
678
                break;
679
            }
680
            if ($debugMode) {
681
                break;
682
            } // Иначе бесконечный цикл.
683
        }
684
        // Добавляем маршруты по умолчанию.
685
        /** @var LanInterfaces $if_data */
686
        $if_data = LanInterfaces::findFirst("interface = '{$env_vars['interface']}'");
687
        $is_inet = ($if_data !== null) ? (int)$if_data->internet : 0;
688
        if ('' !== $env_vars['router'] && $is_inet === 1) {
689
            // ТОЛЬКО, если этот интерфейс для интернет, создаем дефолтный маршрут.
690
            $routers = explode(' ', $env_vars['router']);
691
            foreach ($routers as $router) {
692
                Processes::mwExec("route add default gw {$router} dev {$env_vars['interface']}");
693
            }
694
        }
695
        // Добавляем пользовательские маршруты.
696
        if (file_exists('/etc/static-routes')) {
697
            $busyboxPath = Util::which('busybox');
698
            $grepPath    = Util::which('grep');
699
            $awkPath     = Util::which('awk');
700
            $catPath     = Util::which('cat');
701
            $shPath      = Util::which('sh');
702
            Processes::mwExec(
703
                "{$catPath} /etc/static-routes | {$grepPath} '^rout' | {$busyboxPath} {$awkPath} -F ';' '{print $1}' | {$grepPath} '{$env_vars['interface']}' | {$shPath}"
704
            );
705
        }
706
        $named_dns = [];
707
        if ('' !== $env_vars['dns']) {
708
            $named_dns = explode(' ', $env_vars['dns']);
709
        }
710
        if ($is_inet === 1) {
711
            // ТОЛЬКО, если этот интерфейс для интернет, правим resolv.conf.
712
            // Прописываем основные DNS.
713
            $this->generatePdnsdConfig($named_dns);
714
        }
715
716
        // Сохрании информацию в базу данных.
717
        $data = [
718
            'subnet'  => $env_vars['subnet'],
719
            'ipaddr'  => $env_vars['ip'],
720
            'gateway' => $env_vars['router'],
721
        ];
722
        if (Verify::isIpAddress($env_vars['ip'])) {
723
            $data['subnet'] = $this->netMaskToCidr($env_vars['subnet']);
724
        } else {
725
            $data['subnet'] = '';
726
        }
727
        $this->updateIfSettings($data, $env_vars['interface']);
728
729
        $data = [
730
            'primarydns'   => $named_dns[0] ?? '',
731
            'secondarydns' => $named_dns[1] ?? '',
732
        ];
733
        $this->updateDnsSettings($data, $env_vars['interface']);
734
735
        Processes::mwExecBg("/etc/rc/networking.set.mtu '{$env_vars['interface']}'");
736
    }
737
738
    /**
739
     * For OS systemctl (Debian).
740
     * Configures LAN interface FROM dhcpc (renew_bound).
741
     */
742
    public function udhcpcConfigureRenewBoundSystemCtl(): void
743
    {
744
        // Инициализация массива переменных.
745
        $prefix   = "new_";
746
        $env_vars = [
747
            'broadcast' => 'broadcast_address',
748
            'interface' => 'interface',
749
            'ip'        => 'ip_address',
750
            'router'    => 'routers',
751
            'timesvr'   => '',
752
            'namesvr'   => 'netbios_name_servers',
753
            'dns'       => 'domain_name_servers',
754
            'hostname'  => 'host_name',
755
            'subnet'    => 'subnet_mask',
756
            'serverid'  => '',
757
            'ipttl'     => '',
758
            'lease'     => 'new_dhcp_lease_time',
759
            'domain'    => 'domain_name',
760
        ];
761
762
        // Получаем значения переменных окружения.
763
        foreach ($env_vars as $key => $value) {
764
            $var_name = "{$prefix}{$value}";
765
            if (empty($var_name)) {
766
                continue;
767
            }
768
            $env_vars[$key] = trim(getenv("{$prefix}{$value}"));
769
        }
770
771
        // Добавляем маршруты по умолчанию.
772
        /** @var LanInterfaces $if_data */
773
        $if_data = LanInterfaces::findFirst("interface = '{$env_vars['interface']}'");
774
        $is_inet = ($if_data !== null) ? (string)$if_data->internet : '0';
775
776
        $named_dns = [];
777
        if ('' !== $env_vars['dns']) {
778
            $named_dns = explode(' ', $env_vars['dns']);
779
        }
780
        if ($is_inet === '1') {
781
            // ТОЛЬКО, если этот интерфейс для интернет, правим resolv.conf.
782
            // Прописываем основные DNS.
783
            $this->generatePdnsdConfig($named_dns);
784
        }
785
        // Сохрании информацию в базу данных.
786
        $data = [
787
            'subnet'  => $env_vars['subnet'],
788
            'ipaddr'  => $env_vars['ip'],
789
            'gateway' => $env_vars['router'],
790
        ];
791
        if (Verify::isIpAddress($env_vars['ip'])) {
792
            $data['subnet'] = $this->netMaskToCidr($env_vars['subnet']);
793
        } else {
794
            $data['subnet'] = '';
795
        }
796
        $this->updateIfSettings($data, $env_vars['interface']);
797
        $data = [
798
            'primarydns'   => $named_dns[0] ?? '',
799
            'secondarydns' => $named_dns[1] ?? '',
800
        ];
801
        $this->updateDnsSettings($data, $env_vars['interface']);
802
    }
803
804
    /**
805
     * Сохранение настроек сетевого интерфейса.
806
     *
807
     * @param $data
808
     * @param $name
809
     */
810
    public function updateIfSettings($data, $name): void
811
    {
812
        /** @var LanInterfaces $res */
813
        $res = LanInterfaces::findFirst("interface = '$name' AND vlanid=0");
814
        if ($res === null || !$this->settingsIsChange($data, $res->toArray()) ) {
815
            return;
816
        }
817
        foreach ($data as $key => $value) {
818
            $res->writeAttribute($key, $value);
819
        }
820
        $res->save();
821
    }
822
823
    /**
824
     * Сохранение DNS настроек сетевого интерфейса.
825
     *
826
     * @param $data
827
     * @param $name
828
     */
829
    public function updateDnsSettings($data, $name): void
830
    {
831
        /** @var LanInterfaces $res */
832
        $res = LanInterfaces::findFirst("interface = '$name' AND vlanid=0");
833
        if ($res === null || !$this->settingsIsChange($data, $res->toArray())) {
834
            return;
835
        }
836
        foreach ($data as $key => $value) {
837
            $res->writeAttribute($key, $value);
838
        }
839
        if (empty($res->primarydns) && !empty($res->secondarydns)) {
840
            $res->primarydns    = $res->secondarydns;
841
            $res->secondarydns  = '';
842
        }
843
        $res->save();
844
    }
845
846
    /**
847
     * Compares two array
848
     * @param array $data
849
     * @param array $dbData
850
     * @return bool
851
     */
852
    private function settingsIsChange(array $data, array $dbData):bool{
853
        $isChange = false;
854
        foreach ($dbData as $key => $value){
855
            if(!isset($data[$key]) || (string)$value === (string)$data[$key]){
856
                continue;
857
            } 
858
            Util::sysLogMsg(__METHOD__, "Find new network settings: {$key} changed {$value}=>{$data[$key]}");
859
            $isChange = true;
860
        }
861
        return $isChange;
862
    }
863
864
    /**
865
     * Возвращает имя интерфейса по его id.
866
     *
867
     * @param $id_net
868
     *
869
     * @return string
870
     */
871
    public function getInterfaceNameById($id_net): string
872
    {
873
        $res = LanInterfaces::findFirstById($id_net);
874
        if ($res !== null && $res->interface !== null) {
875
            return $res->interface;
876
        }
877
878
        return '';
879
    }
880
881
    /**
882
     * Возвращает список включеннх веб интерейсов
883
     *
884
     * @return array
885
     */
886
    public function getEnabledLanInterfaces(): array
887
    {
888
        /** @var LanInterfaces $res */
889
        $res = LanInterfaces::find('disabled=0');
890
891
        return $res->toArray();
892
    }
893
894
    /**
895
     * Configures LAN interface FROM udhcpc (deconfig)
896
     */
897
    public function udhcpcConfigureDeconfig(): void
898
    {
899
        // Настройка по умолчанию.
900
        if ( Util::isSystemctl()) {
901
            return;
902
        }
903
        $interface = trim(getenv('interface'));
904
        // Для MIKO LFS Edition.
905
        $busyboxPath = Util::which('busybox');
906
        Processes::mwExec("{$busyboxPath} ifconfig {$interface} up");
907
        Processes::mwExec("{$busyboxPath} ifconfig {$interface} 192.168.2.1 netmask 255.255.255.0");
908
    }
909
    /**
910
     * Сохранение настроек сетевого интерфейса.
911
     *
912
     * @param $data
913
     */
914
    public function updateNetSettings($data): void
915
    {
916
        $res         = LanInterfaces::findFirst("internet = '1'");
917
        $update_inet = false;
918
        if ($res === null) {
919
            $res         = LanInterfaces::findFirst();
920
            $update_inet = true;
921
        }
922
923
        if ($res !== null) {
924
            foreach ($data as $key => $value) {
925
                $res->$key = $value;
926
            }
927
            if ($update_inet === true) {
928
                $res->internet = 1;
929
            }
930
            $res->save();
931
        }
932
    }
933
934
    /**
935
     * Возвращает массив с информацией по сетевым интерфейсам.
936
     *
937
     * @return array
938
     */
939
    public function getInterfaces(): array
940
    {
941
        // Получим все имена PCI интерфейсов (сеть).
942
        $i_names = $this->getInterfacesNames();
943
        $if_list = [];
944
        foreach ($i_names as $i) {
945
            $if_list[$i] = $this->getInterface($i);
946
        }
947
948
        return $if_list;
949
    }
950
951
    /**
952
     * Сбор информации по сетевому интерфейсу.
953
     *
954
     * @param $name
955
     *
956
     * @return array
957
     */
958
    public function getInterface($name): array
959
    {
960
        $interface = [];
961
962
        // Получаем ifconfig's для interface $name.
963
        $busyboxPath = Util::which('busybox');
964
        Processes::mwExec("{$busyboxPath} ifconfig $name 2>/dev/null", $output);
965
        $output = implode(" ", $output);
966
967
        // Парсим mac
968
        preg_match("/HWaddr (\S+)/", $output, $matches);
969
        $interface['mac'] = (count($matches) > 0) ? $matches[1] : '';
970
971
        // Парсим ip.
972
        preg_match("/inet addr:(\S+)/", $output, $matches);
973
        $interface['ipaddr'] = (count($matches) > 0) ? $matches[1] : '';
974
975
        // Парсим подсеть.
976
        preg_match("/Mask:(\S+)/", $output, $matches);
977
        $subnet              = (count($matches) > 0) ? $this->netMaskToCidr($matches[1]) : '';
978
        $interface['subnet'] = $subnet;
979
980
        // Поднят ли интерфейс?
981
        preg_match("/\s+(UP)\s+/", $output, $matches);
982
        $status = (count($matches) > 0) ? $matches[1] : '';
983
        if ($status === "UP") {
984
            $interface['up'] = true;
985
        } else {
986
            $interface['up'] = false;
987
        }
988
        $busyboxPath = Util::which('busybox');
989
        $grepPath    = Util::which('grep');
990
        $cutPath     = Util::which('cut');
991
        $routePath   = Util::which('route');
992
993
        Processes::mwExec(
994
            "{$busyboxPath} {$routePath} -n | {$grepPath} {$name} | {$grepPath} \"^0.0.0.0\" | {$cutPath} -d ' ' -f 10",
995
            $matches
996
        );
997
        $gw = (count($matches) > 0) ? $matches[0] : '';
998
        if (Verify::isIpAddress($gw)) {
999
            $interface['gateway'] = $gw;
1000
        }
1001
        $catPath = Util::which('cat');
1002
        Processes::mwExec("{$catPath} /etc/resolv.conf | {$grepPath} nameserver | {$cutPath} -d ' ' -f 2", $dnsout);
1003
1004
        $dnsSrv = [];
1005
        foreach ($dnsout as $line) {
1006
            if (Verify::isIpAddress($line)) {
1007
                $dnsSrv[] = $line;
1008
            }
1009
        }
1010
        $interface['dns'] = $dnsSrv;
1011
1012
        return $interface;
1013
    }
1014
}