Passed
Push — develop ( c1eaed...deed70 )
by Портнов
11:55 queued 13s
created

Network::getInfoMessage()   B

Complexity

Conditions 11
Paths 81

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 39
rs 7.3166
cc 11
nc 81
nop 0

How to fix   Complexity   

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
            return 0;
247
        }
248
        if (Util::isSystemctl()) {
249
            $this->lanConfigureSystemCtl();
250
            $this->openVpnConfigure();
251
            return 0;
252
        }
253
        $busyboxPath = Util::which('busybox');
254
        $vconfigPath = Util::which('vconfig');
255
        $killallPath = Util::which('killall');
256
257
        $networks     = $this->getGeneralNetSettings();
258
        $arr_commands = [];
259
        $arr_commands[] = "{$killallPath} udhcpc";
260
        $eth_mtu = [];
261
        foreach ($networks as $if_data) {
262
            if ($if_data['disabled'] === '1') {
263
                continue;
264
            }
265
266
            $if_name = $if_data['interface'];
267
            $if_name = escapeshellcmd(trim($if_name));
268
            if (empty($if_name)) {
269
                continue;
270
            }
271
272
            $data_hostname = self::getHostName();
273
            $hostname      = $data_hostname['hostname'];
274
275
            if ($if_data['vlanid'] > 0) {
276
                // Переопределяем имя интерфейса.
277
                $arr_commands[] = "{$vconfigPath} set_name_type VLAN_PLUS_VID_NO_PAD";
278
                // Добавляем новый интерфейс.
279
                $arr_commands[] = "{$vconfigPath} add {$if_data['interface_orign']} {$if_data['vlanid']}";
280
            }
281
            // Отключаем интерфейс.
282
            $arr_commands[] = "{$busyboxPath} ifconfig $if_name down";
283
            $arr_commands[] = "{$busyboxPath} ifconfig $if_name 0.0.0.0";
284
285
            $gw_param = '';
286
            if (trim($if_data['dhcp']) === '1') {
287
                /*
288
                 * -t - количество попыток.
289
                 * -T - таймаут попытки.
290
                 * -v - включить отладку.
291
                 * -S - логи в syslog.
292
                 * -q - Exit after obtaining lease
293
                 * -n - Exit if lease is not obtained
294
                 */
295
                $pid_file = "/var/run/udhcpc_{$if_name}";
296
                $pid_pcc  = Processes::getPidOfProcess($pid_file);
297
                if ( ! empty($pid_pcc) && file_exists($pid_file)) {
298
                    // Завершаем старый процесс.
299
                    $killPath = Util::which('kill');
300
                    $catPath  = Util::which('cat');
301
                    system("{$killPath} `{$catPath} {$pid_file}` {$pid_pcc}");
302
                }
303
                $udhcpcPath = Util::which('udhcpc');
304
                $nohupPath  = Util::which('nohup');
305
306
                // Получаем IP и дожидаемся завершения процесса.
307
                $workerPath     = '/etc/rc/udhcpc.configure';
308
                $options        = '-t 6 -T 5 -q -n';
309
                $arr_commands[] = "{$udhcpcPath} {$options} -i {$if_name} -x hostname:{$hostname} -s {$workerPath}";
310
                // Старутем новый процесс udhcpc в  фоне.
311
                $options        = '-t 6 -T 5 -S -b -n';
312
                $arr_commands[] = "{$nohupPath} {$udhcpcPath} {$options} -p {$pid_file} -i {$if_name} -x hostname:{$hostname} -s {$workerPath} 2>&1 &";
313
                /*
314
                    udhcpc  - утилита произведет настройку интерфейса
315
                               - произведет конфигурацию /etc/resolv.conf
316
                    Дальнейшая настройка маршрутов будет произволиться в udhcpcConfigureRenewBound();
317
                    и в udhcpcConfigureDeconfig(). Эти методы будут вызваны скриптом WorkerUdhcpcConfigure.php.
318
                    // man udhcp
319
                    // http://pwet.fr/man/linux/administration_systeme/udhcpc/
320
321
                */
322
            } else {
323
                $ipaddr  = trim($if_data['ipaddr']);
324
                $subnet  = trim($if_data['subnet']);
325
                $gateway = trim($if_data['gateway']);
326
                if (empty($ipaddr)) {
327
                    continue;
328
                }
329
                // Это короткое представление маск /24 /32.
330
                try {
331
                    $calc_subnet = new SubnetCalculator($ipaddr, $subnet);
332
                    $subnet      = $calc_subnet->getSubnetMask();
333
                } catch (Throwable $e) {
334
                    echo "Caught exception: $ipaddr $subnet", $e->getMessage(), "\n";
335
                    continue;
336
                }
337
338
                $ifconfigPath   = Util::which('ifconfig');
339
                $arr_commands[] = "{$busyboxPath} {$ifconfigPath} $if_name $ipaddr netmask $subnet";
340
341
                if ("" !== trim($gateway)) {
342
                    $gw_param = "gw $gateway";
343
                }
344
345
                $routePath      = Util::which('route');
346
                $arr_commands[] = "{$busyboxPath} {$routePath} del default $if_name";
347
348
                /** @var LanInterfaces $if_data */
349
                $if_data = LanInterfaces::findFirst("id = '{$if_data['id']}'");
350
                $is_inet = ($if_data !== null) ? (string)$if_data->internet : '0';
351
                // Добавляем маршруты по умолчанию.
352
                if ($is_inet === '1') {
353
                    // ТОЛЬКО, если этот интерфейс для интернет, создаем дефолтный маршрут.
354
                    $arr_commands[] = "{$busyboxPath} {$routePath} add default $gw_param dev $if_name";
355
                }
356
                // Поднимаем интерфейс.
357
                $arr_commands[] = "{$busyboxPath} {$ifconfigPath} $if_name up";
358
359
                $eth_mtu[] = $if_name;
360
            }
361
        }
362
        $out = null;
363
        Processes::mwExecCommands($arr_commands, $out, 'net');
364
        $this->hostsGenerate();
365
366
        foreach ($eth_mtu as $eth) {
367
            Processes::mwExecBg("/etc/rc/networking.set.mtu '{$eth}'");
368
        }
369
370
        // Дополнительные "ручные" маршруты.
371
        Util::fileWriteContent('/etc/static-routes', '');
372
        $arr_commands = [];
373
        $out          = [];
374
        $grepPath     = Util::which('grep');
375
        $awkPath      = Util::which('awk');
376
        $catPath      = Util::which('cat');
377
        Processes::mwExec(
378
            "{$catPath} /etc/static-routes | {$grepPath} '^rout' | {$busyboxPath} {$awkPath} -F ';' '{print $1}'",
379
            $arr_commands
380
        );
381
        Processes::mwExecCommands($arr_commands, $out, 'rout');
382
383
        $this->openVpnConfigure();
384
        return 0;
385
    }
386
387
    /**
388
     * For OS systemctl (Debian).
389
     * Configures LAN interface
390
     */
391
    public function lanConfigureSystemCtl(): void
392
    {
393
        $networks      = $this->getGeneralNetSettings();
394
        $busyboxPath   = Util::which('busybox');
395
        $grepPath      = Util::which('grep');
396
        $awkPath       = Util::which('awk');
397
        $catPath       = Util::which('cat');
398
        $systemctlPath = Util::which('systemctl');
399
        $modprobePath  = Util::which('modprobe');
400
        Processes::mwExec("{$systemctlPath} stop networking");
401
        Processes::mwExec("{$modprobePath} 8021q");
402
        foreach ($networks as $if_data) {
403
            if($if_data['disabled'] === 1){
404
                continue;
405
            }
406
            $if_name = trim($if_data['interface']);
407
            if ('' === $if_name) {
408
                continue;
409
            }
410
            $conf_file = "/etc/network/interfaces.d/{$if_name}";
411
            if ($if_data['disabled'] === '1') {
412
                $ifdownPath = Util::which('ifdown');
413
                Processes::mwExec("{$ifdownPath} eth0");
414
                if (file_exists($if_name)) {
415
                    unlink($conf_file);
416
                }
417
                continue;
418
            }
419
            $subnet  = trim($if_data['subnet']);
420
            $ipaddr  = trim($if_data['ipaddr']);
421
            $gateway = trim($if_data['gateway']);
422
423
            $result = [''];
424
            if (file_exists('/etc/static-routes')) {
425
                $command = "{$catPath} /etc/static-routes " .
426
                    "| {$grepPath} '^rout' " .
427
                    "| {$busyboxPath} awk -F ';' '{print $1}' " .
428
                    "| {$grepPath} '{$if_name}\$' " .
429
                    "| {$awkPath} -F 'dev {$if_name}' '{ print $1 }'";
430
                Processes::mwExec($command, $result);
431
            }
432
            $routs_add = ltrim(implode("\npost-up ", $result));
433
            $routs_rem = ltrim(implode("\npre-down ", $result));
434
435
436
            if ($if_data['vlanid'] > 0) {
437
                // Пока только статика.
438
                $lan_config = "auto {$if_data['interface_orign']}.{$if_data['vlanid']}\n" .
439
                    "iface {$if_data['interface_orign']}.{$if_data['vlanid']} inet static \n" .
440
                    "address {$ipaddr}\n" .
441
                    "netmask {$subnet}\n" .
442
                    "gateway {$gateway}\n" .
443
                    "dns-nameservers 127.0.0.1\n" .
444
                    "vlan_raw_device {$if_data['interface_orign']}\n" .
445
                    "{$routs_add}\n" .
446
                    "{$routs_rem}\n";
447
            } elseif (trim($if_data['dhcp']) === '1') {
448
                $lan_config = "auto {$if_name}\n" .
449
                    "iface {$if_name} inet dhcp\n" .
450
                    "{$routs_add}\n" .
451
                    "{$routs_rem}\n";
452
            } else {
453
                if (empty($ipaddr)) {
454
                    continue;
455
                }
456
                try {
457
                    $calc_subnet = new SubnetCalculator($ipaddr, $subnet);
458
                    $subnet      = $calc_subnet->getSubnetMask();
459
                } catch (Throwable $e) {
460
                    echo "Caught exception: $ipaddr $subnet", $e->getMessage(), "\n";
461
                    continue;
462
                }
463
                $lan_config = "auto {$if_name}\n" .
464
                    "iface {$if_name} inet static\n" .
465
                    "address {$ipaddr}\n" .
466
                    "netmask {$subnet}\n" .
467
                    "gateway {$gateway}\n" .
468
                    "dns-nameservers 127.0.0.1\n" .
469
                    "{$routs_add}\n" .
470
                    "{$routs_rem}\n";
471
            }
472
            file_put_contents("/etc/network/interfaces.d/{$if_name}", $lan_config);
473
        }
474
        $systemctlPath = Util::which('systemctl');
475
        Processes::mwExec("{$systemctlPath} start networking");
476
        $this->hostsGenerate();
477
478
        $firewall = new IptablesConf();
479
        $firewall->applyConfig();
480
    }
481
482
    /**
483
     * Получение настроек интерфейсов LAN.
484
     *
485
     * @return array
486
     */
487
    public function getGeneralNetSettings(): array
488
    {
489
        // Массив сетевых интерфейсов, которые видит ОС.
490
        $src_array_eth = $this->getInterfacesNames();
491
        // Создаем копию массива сетевых интерфейсов.
492
        $array_eth = $src_array_eth;
493
        $res       = LanInterfaces::find(['order' => 'interface,vlanid']);
494
        $networks  = $res->toArray();
495
        if (count($networks) > 0) {
496
            // Дополнительная обработка данных.
497
            foreach ($networks as &$if_data) {
498
                $if_data['interface_orign'] = $if_data['interface'];
499
                $if_data['interface']       = ($if_data['vlanid'] > 0) ? "vlan{$if_data['vlanid']}" : $if_data['interface'];
500
                $if_data['dhcp']            = ($if_data['vlanid'] > 0) ? 0 : $if_data['dhcp'];
501
502
                if (Verify::isIpAddress($if_data['subnet'])) {
503
                    $if_data['subnet'] = $this->netMaskToCidr($if_data['subnet']);
504
                }
505
506
                $key = array_search($if_data['interface_orign'], $src_array_eth, true);
507
                if ($key !== false) {
508
                    // Интерфейс найден.
509
                    // Удаляем элемент массива, если это не VLAN.
510
                    if ($if_data['vlanid'] === '0') {
511
                        unset($array_eth[$key]);
512
                        $this->enableLanInterface($if_data['interface_orign']);
513
                    }
514
                } else {
515
                    // Интерфейс не существует.
516
                    $this->disableLanInterface($if_data['interface_orign']);
517
                    // Отключаем интерфейс.
518
                    $if_data['disabled'] = 1;
519
                }
520
            }
521
            unset($if_data);
522
        } elseif (count($array_eth) > 0) {
523
            $networks = [];
524
            // Настраиваем основной интерфейс.
525
            $networks[] = $this->addLanInterface($array_eth[0], true);
526
            unset($array_eth[0]);
527
        }
528
        // $array_eth - в массиве останутся только те элементы,
529
        // по которым нет настроек в базе дынных.
530
        // Следует добавить настройки "по умолчанию".
531
        foreach ($array_eth as $eth) {
532
            // Добавляем. Все интерфейсы, отключаем.
533
            $networks[] = $this->addLanInterface($eth, false);
534
        }
535
        $res = LanInterfaces::findFirst("internet = '1' AND disabled='0'");
536
        if (null === $res) {
537
            /** @var LanInterfaces $eth_settings */
538
            $eth_settings = LanInterfaces::findFirst("disabled='0'");
539
            if ($eth_settings !== null) {
540
                $eth_settings->internet = 1;
541
                $eth_settings->save();
542
            }
543
        }
544
545
        return $networks;
546
    }
547
548
    /**
549
     * Сбор информации о доступных адресах web интерфейса.
550
     * @return string
551
     */
552
    public static function getInfoMessage():string
553
    {
554
        $addresses = [
555
            'local' => [],
556
            'external' => []
557
        ];
558
        /** @var LanInterfaces $interface */
559
        $interfaces = LanInterfaces::find("disabled='0'");
560
        foreach ($interfaces as $interface){
561
            if(!empty($interface->ipaddr)){
562
                $addresses['local'][] = $interface->ipaddr;
563
            }
564
            if(!empty($interface->exthostname) && !in_array($interface->exthostname, $addresses['local'], true)){
565
                $addresses['external'][] = explode(':', $interface->exthostname)[0]??'';
566
            }
567
            if(!empty($interface->extipaddr) && !in_array($interface->extipaddr, $addresses['local'], true) ){
568
                $addresses['external'][] = explode(':', $interface->extipaddr)[0]??'';
569
            }
570
        }
571
        unset($interfaces);
572
        $port = PbxSettings::getValueByKey('WEBHTTPSPort');
573
        $info = PHP_EOL."   The web interface is available at the addresses:".PHP_EOL.PHP_EOL;
574
        foreach ($addresses['local'] as $address){
575
            if(empty($address)){
576
                continue;
577
            }
578
            $info.= "    - https://$address:$port".PHP_EOL;
579
        }
580
        $info.=PHP_EOL;
581
        $info.= "   The web interface is available at the external addresses:".PHP_EOL.PHP_EOL;
582
        foreach ($addresses['external'] as $address){
583
            if(empty($address)){
584
                continue;
585
            }
586
            $info.= "    - https://$address:$port".PHP_EOL;
587
        }
588
        $info.=PHP_EOL;
589
590
        return $info;
591
    }
592
593
594
    /**
595
     * Преобразует сетевую маску в CIDR представление.
596
     *
597
     * @param $net_mask
598
     *
599
     * @return int
600
     */
601
    public function netMaskToCidr($net_mask): int
602
    {
603
        $bits     = 0;
604
        $net_mask = explode(".", $net_mask);
605
606
        foreach ($net_mask as $oct_ect) {
607
            $bits += strlen(str_replace("0", "", decbin((int)$oct_ect)));
608
        }
609
610
        return $bits;
611
    }
612
613
    /**
614
     * Включаем интерфейс по его имени.
615
     *
616
     * @param $name
617
     */
618
    public function enableLanInterface($name): void
619
    {
620
        $parameters = [
621
            'conditions' => 'interface = :ifName: and disabled = :disabled:',
622
            'bind'       => [
623
                'ifName'   => $name,
624
                'disabled' => 1,
625
            ],
626
        ];
627
628
        $if_data = LanInterfaces::findFirst($parameters);
629
        if ($if_data !== null) {
630
            $if_data->disabled = 0;
631
            $if_data->update();
632
        }
633
    }
634
635
    /**
636
     * Удаляем интерфейс по его имени.
637
     *
638
     * @param $name
639
     */
640
    public function disableLanInterface($name): void
641
    {
642
        $if_data = LanInterfaces::findFirst("interface = '{$name}'");
643
        if ($if_data !== null) {
644
            $if_data->internet = 0;
645
            $if_data->disabled = 1;
646
            $if_data->update();
647
        }
648
    }
649
650
    /**
651
     * Добавляем в базу данных сведения о новом интерфейсе.
652
     *
653
     * @param      $name
654
     * @param bool $general
655
     *
656
     * @return mixed
657
     */
658
    private function addLanInterface($name, $general = false)
659
    {
660
        $data = new LanInterfaces();
661
        $data->name      = $name;
662
        $data->interface = $name;
663
        $data->dhcp      = '1';
664
        $data->internet  = ($general === true) ? '1' : '0';
665
        $data->disabled  = '0';
666
        $data->vlanid    = '0';
667
        $data->hostname  = 'mikopbx';
668
        $data->domain    = '';
669
        $data->topology  = 'private';
670
        $data->primarydns= '';
671
        $data->save();
672
673
        return $data->toArray();
674
    }
675
676
    /**
677
     * Настройка hosts
678
     */
679
    public function hostsGenerate(): void
680
    {
681
        $this->hostnameConfigure();
682
    }
683
684
    /**
685
     *  Setup hostname
686
     **/
687
    public function hostnameConfigure(): void
688
    {
689
        $data       = self::getHostName();
690
        $hosts_conf = "127.0.0.1 localhost\n" .
691
            "127.0.0.1 {$data['hostname']}\n";
692
        if ( ! empty($data['domain'])) {
693
            $hosts_conf .= "127.0.0.1 {$data['hostname']}.{$data['domain']}\n";
694
        }
695
        Util::fileWriteContent('/etc/hosts', $hosts_conf);
696
697
        $hostnamePath = Util::which('hostname');
698
        Processes::mwExec($hostnamePath . ' ' . escapeshellarg($data['hostname']));
699
    }
700
701
    /**
702
     * Настройка OpenVPN. Если в кастомизации системных файлов определн конфиг, то сеть поднимется.
703
     */
704
    public function openVpnConfigure():void
705
    {
706
        $confFile = '/etc/openvpn.ovpn';
707
        Util::fileWriteContent($confFile, '');
708
        $data = file_get_contents($confFile);
709
710
        $pidFile = '/var/run/openvpn.pid';
711
        $pid     = Processes::getPidOfProcess('openvpn');
712
        if ( ! empty($pid)) {
713
            // Завершаем процесс.
714
            $busyboxPath = Util::which('busybox');
715
            Processes::mwExec("{$busyboxPath} kill '$pid'");
716
        }
717
        if ( ! empty($data)) {
718
            $openvpnPath = Util::which('openvpn');
719
            Processes::mwExecBg("{$openvpnPath} --config /etc/openvpn.ovpn --writepid {$pidFile}", '/dev/null', 5);
720
        }
721
    }
722
723
    /**
724
     * Configures LAN interface FROM udhcpc (renew_bound)
725
     */
726
    public function udhcpcConfigureRenewBound(): void
727
    {
728
        if(Util::isDocker()){
729
            return;
730
        }
731
        if (Util::isSystemctl()) {
732
            $this->udhcpcConfigureRenewBoundSystemCtl();
733
            return;
734
        }
735
        // Инициализация массива переменных.
736
        $env_vars = [
737
            'broadcast' => '',
738
            'interface' => '',
739
            'ip'        => '',
740
            'router'    => '',
741
            'timesvr'   => '',
742
            'namesvr'   => '',
743
            'dns'       => '',
744
            'hostname'  => '',
745
            'subnet'    => '',
746
            'serverid'  => '',
747
            'ipttl'     => '',
748
            'lease'     => '',
749
            'domain'    => '',
750
        ];
751
752
        $debugMode = $this->di->getShared('config')->path('core.debugMode');
753
        // Получаем значения переменных окружения.
754
        foreach ($env_vars as $key => $value) {
755
            $env_vars[$key] = trim(getenv($key));
756
        }
757
        $BROADCAST = ($env_vars['broadcast'] === '') ? "" : "broadcast {$env_vars['broadcast']}";
758
        if($env_vars['subnet'] === '255.255.255.255' || $env_vars['subnet'] === ''){
759
            // support /32 address assignment
760
            // https://forummikrotik.ru/viewtopic.php?f=3&t=6246&start=40
761
            $NET_MASK = '';
762
        }else{
763
            $NET_MASK = "netmask {$env_vars['subnet']}";
764
        }
765
        // Настраиваем интерфейс.
766
        $busyboxPath = Util::which('busybox');
767
        Processes::mwExec("{$busyboxPath} ifconfig {$env_vars['interface']} {$env_vars['ip']} $BROADCAST $NET_MASK");
768
769
        // Удаляем старые маршруты по умолчанию.
770
        while (true) {
771
            $out = [];
772
            Processes::mwExec("route del default gw 0.0.0.0 dev {$env_vars['interface']}", $out);
773
            if (trim(implode('', $out)) !== '') {
774
                // Произошла ошибка, значит все маршруты очищены.
775
                break;
776
            }
777
            if ($debugMode) {
778
                break;
779
            } // Иначе бесконечный цикл.
780
        }
781
        // Добавляем маршруты по умолчанию.
782
        /** @var LanInterfaces $if_data */
783
        $if_data = LanInterfaces::findFirst("interface = '{$env_vars['interface']}'");
784
        $is_inet = ($if_data !== null) ? (int)$if_data->internet : 0;
785
        if ('' !== $env_vars['router'] && $is_inet === 1) {
786
            // ТОЛЬКО, если этот интерфейс для интернет, создаем дефолтный маршрут.
787
            $routers = explode(' ', $env_vars['router']);
788
            foreach ($routers as $router) {
789
                Processes::mwExec("route add default gw {$router} dev {$env_vars['interface']}");
790
            }
791
        }
792
        // Добавляем пользовательские маршруты.
793
        if (file_exists('/etc/static-routes')) {
794
            $busyboxPath = Util::which('busybox');
795
            $grepPath    = Util::which('grep');
796
            $awkPath     = Util::which('awk');
797
            $catPath     = Util::which('cat');
798
            $shPath      = Util::which('sh');
799
            Processes::mwExec(
800
                "{$catPath} /etc/static-routes | {$grepPath} '^rout' | {$busyboxPath} {$awkPath} -F ';' '{print $1}' | {$grepPath} '{$env_vars['interface']}' | {$shPath}"
801
            );
802
        }
803
        $named_dns = [];
804
        if ('' !== $env_vars['dns']) {
805
            $named_dns = explode(' ', $env_vars['dns']);
806
        }
807
        if ($is_inet === 1) {
808
            // ТОЛЬКО, если этот интерфейс для интернет, правим resolv.conf.
809
            // Прописываем основные DNS.
810
            $this->generatePdnsdConfig($named_dns);
811
        }
812
813
        // Сохрании информацию в базу данных.
814
        $data = [
815
            'subnet'  => $env_vars['subnet'],
816
            'ipaddr'  => $env_vars['ip'],
817
            'gateway' => $env_vars['router'],
818
        ];
819
        if (Verify::isIpAddress($env_vars['ip'])) {
820
            $data['subnet'] = $this->netMaskToCidr($env_vars['subnet']);
821
        } else {
822
            $data['subnet'] = '';
823
        }
824
        $this->updateIfSettings($data, $env_vars['interface']);
825
826
        $data = [
827
            'primarydns'   => $named_dns[0] ?? '',
828
            'secondarydns' => $named_dns[1] ?? '',
829
        ];
830
        $this->updateDnsSettings($data, $env_vars['interface']);
831
832
        Processes::mwExecBg("/etc/rc/networking.set.mtu '{$env_vars['interface']}'");
833
    }
834
835
    /**
836
     * For OS systemctl (Debian).
837
     * Configures LAN interface FROM dhcpc (renew_bound).
838
     */
839
    public function udhcpcConfigureRenewBoundSystemCtl(): void
840
    {
841
        // Инициализация массива переменных.
842
        $prefix   = "new_";
843
        $env_vars = [
844
            'broadcast' => 'broadcast_address',
845
            'interface' => 'interface',
846
            'ip'        => 'ip_address',
847
            'router'    => 'routers',
848
            'timesvr'   => '',
849
            'namesvr'   => 'netbios_name_servers',
850
            'dns'       => 'domain_name_servers',
851
            'hostname'  => 'host_name',
852
            'subnet'    => 'subnet_mask',
853
            'serverid'  => '',
854
            'ipttl'     => '',
855
            'lease'     => 'new_dhcp_lease_time',
856
            'domain'    => 'domain_name',
857
        ];
858
859
        // Получаем значения переменных окружения.
860
        foreach ($env_vars as $key => $value) {
861
            $var_name = "{$prefix}{$value}";
862
            if (empty($var_name)) {
863
                continue;
864
            }
865
            $env_vars[$key] = trim(getenv("{$prefix}{$value}"));
866
        }
867
868
        // Добавляем маршруты по умолчанию.
869
        /** @var LanInterfaces $if_data */
870
        $if_data = LanInterfaces::findFirst("interface = '{$env_vars['interface']}'");
871
        $is_inet = ($if_data !== null) ? (string)$if_data->internet : '0';
872
873
        $named_dns = [];
874
        if ('' !== $env_vars['dns']) {
875
            $named_dns = explode(' ', $env_vars['dns']);
876
        }
877
        if ($is_inet === '1') {
878
            // ТОЛЬКО, если этот интерфейс для интернет, правим resolv.conf.
879
            // Прописываем основные DNS.
880
            $this->generatePdnsdConfig($named_dns);
881
        }
882
        // Сохрании информацию в базу данных.
883
        $data = [
884
            'subnet'  => $env_vars['subnet'],
885
            'ipaddr'  => $env_vars['ip'],
886
            'gateway' => $env_vars['router'],
887
        ];
888
        if (Verify::isIpAddress($env_vars['ip'])) {
889
            $data['subnet'] = $this->netMaskToCidr($env_vars['subnet']);
890
        } else {
891
            $data['subnet'] = '';
892
        }
893
        $this->updateIfSettings($data, $env_vars['interface']);
894
        $data = [
895
            'primarydns'   => $named_dns[0] ?? '',
896
            'secondarydns' => $named_dns[1] ?? '',
897
        ];
898
        $this->updateDnsSettings($data, $env_vars['interface']);
899
    }
900
901
    /**
902
     * Сохранение настроек сетевого интерфейса.
903
     *
904
     * @param $data
905
     * @param $name
906
     */
907
    public function updateIfSettings($data, $name): void
908
    {
909
        /** @var LanInterfaces $res */
910
        $res = LanInterfaces::findFirst("interface = '$name' AND vlanid=0");
911
        if ($res === null || !$this->settingsIsChange($data, $res->toArray()) ) {
912
            return;
913
        }
914
        foreach ($data as $key => $value) {
915
            $res->writeAttribute($key, $value);
916
        }
917
        $res->save();
918
    }
919
920
    /**
921
     * Сохранение DNS настроек сетевого интерфейса.
922
     *
923
     * @param $data
924
     * @param $name
925
     */
926
    public function updateDnsSettings($data, $name): void
927
    {
928
        /** @var LanInterfaces $res */
929
        $res = LanInterfaces::findFirst("interface = '$name' AND vlanid=0");
930
        if ($res === null || !$this->settingsIsChange($data, $res->toArray())) {
931
            return;
932
        }
933
        foreach ($data as $key => $value) {
934
            $res->writeAttribute($key, $value);
935
        }
936
        if (empty($res->primarydns) && !empty($res->secondarydns)) {
937
            $res->primarydns    = $res->secondarydns;
938
            $res->secondarydns  = '';
939
        }
940
        $res->save();
941
    }
942
943
    /**
944
     * Compares two array
945
     * @param array $data
946
     * @param array $dbData
947
     * @return bool
948
     */
949
    private function settingsIsChange(array $data, array $dbData):bool{
950
        $isChange = false;
951
        foreach ($dbData as $key => $value){
952
            if(!isset($data[$key]) || (string)$value === (string)$data[$key]){
953
                continue;
954
            } 
955
            Util::sysLogMsg(__METHOD__, "Find new network settings: {$key} changed {$value}=>{$data[$key]}");
956
            $isChange = true;
957
        }
958
        return $isChange;
959
    }
960
961
    /**
962
     * Возвращает имя интерфейса по его id.
963
     *
964
     * @param $id_net
965
     *
966
     * @return string
967
     */
968
    public function getInterfaceNameById($id_net): string
969
    {
970
        $res = LanInterfaces::findFirstById($id_net);
971
        if ($res !== null && $res->interface !== null) {
972
            return $res->interface;
973
        }
974
975
        return '';
976
    }
977
978
    /**
979
     * Возвращает список включеннх веб интерейсов
980
     *
981
     * @return array
982
     */
983
    public function getEnabledLanInterfaces(): array
984
    {
985
        /** @var LanInterfaces $res */
986
        $res = LanInterfaces::find('disabled=0');
987
988
        return $res->toArray();
989
    }
990
991
    /**
992
     * Configures LAN interface FROM udhcpc (deconfig)
993
     */
994
    public function udhcpcConfigureDeconfig(): void
995
    {
996
        // Настройка по умолчанию.
997
        if ( Util::isSystemctl()) {
998
            return;
999
        }
1000
        $interface = trim(getenv('interface'));
1001
        // Для MIKO LFS Edition.
1002
        $busyboxPath = Util::which('busybox');
1003
        Processes::mwExec("{$busyboxPath} ifconfig {$interface} up");
1004
        Processes::mwExec("{$busyboxPath} ifconfig {$interface} 192.168.2.1 netmask 255.255.255.0");
1005
    }
1006
    /**
1007
     * Сохранение настроек сетевого интерфейса.
1008
     *
1009
     * @param $data
1010
     */
1011
    public function updateNetSettings($data): void
1012
    {
1013
        $res         = LanInterfaces::findFirst("internet = '1'");
1014
        $update_inet = false;
1015
        if ($res === null) {
1016
            $res         = LanInterfaces::findFirst();
1017
            $update_inet = true;
1018
        }
1019
1020
        if ($res !== null) {
1021
            foreach ($data as $key => $value) {
1022
                $res->$key = $value;
1023
            }
1024
            if ($update_inet === true) {
1025
                $res->internet = 1;
1026
            }
1027
            $res->save();
1028
        }
1029
    }
1030
1031
    /**
1032
     * Возвращает массив с информацией по сетевым интерфейсам.
1033
     *
1034
     * @return array
1035
     */
1036
    public function getInterfaces(): array
1037
    {
1038
        // Получим все имена PCI интерфейсов (сеть).
1039
        $i_names = $this->getInterfacesNames();
1040
        $if_list = [];
1041
        foreach ($i_names as $i) {
1042
            $if_list[$i] = $this->getInterface($i);
1043
        }
1044
1045
        return $if_list;
1046
    }
1047
1048
    /**
1049
     * Сбор информации по сетевому интерфейсу.
1050
     *
1051
     * @param $name
1052
     *
1053
     * @return array
1054
     */
1055
    public function getInterface($name): array
1056
    {
1057
        $interface = [];
1058
1059
        // Получаем ifconfig's для interface $name.
1060
        $busyboxPath = Util::which('busybox');
1061
        Processes::mwExec("{$busyboxPath} ifconfig $name 2>/dev/null", $output);
1062
        $output = implode(" ", $output);
1063
1064
        // Парсим mac
1065
        preg_match("/HWaddr (\S+)/", $output, $matches);
1066
        $interface['mac'] = (count($matches) > 0) ? $matches[1] : '';
1067
1068
        // Парсим ip.
1069
        preg_match("/inet addr:(\S+)/", $output, $matches);
1070
        $interface['ipaddr'] = (count($matches) > 0) ? $matches[1] : '';
1071
1072
        // Парсим подсеть.
1073
        preg_match("/Mask:(\S+)/", $output, $matches);
1074
        $subnet              = (count($matches) > 0) ? $this->netMaskToCidr($matches[1]) : '';
1075
        $interface['subnet'] = $subnet;
1076
1077
        // Поднят ли интерфейс?
1078
        preg_match("/\s+(UP)\s+/", $output, $matches);
1079
        $status = (count($matches) > 0) ? $matches[1] : '';
1080
        if ($status === "UP") {
1081
            $interface['up'] = true;
1082
        } else {
1083
            $interface['up'] = false;
1084
        }
1085
        $busyboxPath = Util::which('busybox');
1086
        $grepPath    = Util::which('grep');
1087
        $cutPath     = Util::which('cut');
1088
        $routePath   = Util::which('route');
1089
1090
        Processes::mwExec(
1091
            "{$busyboxPath} {$routePath} -n | {$grepPath} {$name} | {$grepPath} \"^0.0.0.0\" | {$cutPath} -d ' ' -f 10",
1092
            $matches
1093
        );
1094
        $gw = (count($matches) > 0) ? $matches[0] : '';
1095
        if (Verify::isIpAddress($gw)) {
1096
            $interface['gateway'] = $gw;
1097
        }
1098
        $catPath = Util::which('cat');
1099
        Processes::mwExec("{$catPath} /etc/resolv.conf | {$grepPath} nameserver | {$cutPath} -d ' ' -f 2", $dnsout);
1100
1101
        $dnsSrv = [];
1102
        foreach ($dnsout as $line) {
1103
            if (Verify::isIpAddress($line)) {
1104
                $dnsSrv[] = $line;
1105
            }
1106
        }
1107
        $interface['dns'] = $dnsSrv;
1108
1109
        return $interface;
1110
    }
1111
}