Passed
Push — develop ( 49e3f5...56ecd0 )
by Nikolay
04:35
created

Udhcpc::configure()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 30
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
dl 0
loc 30
rs 8.4444
c 1
b 0
f 0
cc 8
nc 6
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2024 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
24
/**
25
 * Class Udhcpc
26
 *
27
 * @package MikoPBX\Core\System
28
 */
29
class Udhcpc extends Network
30
{
31
    /**
32
     * @param string $action
33
     * @return void
34
     */
35
    public function configure(string $action):void
36
    {
37
        /**
38
         * Check if running inside a Docker container.
39
         * If true, skip the action and exit the script.
40
         */
41
        if(Util::isDocker()){
42
            Util::sysLogMsg(basename(__FILE__), "Skipped action {$action}... because of docker", LOG_DEBUG);
43
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
44
        } else {
45
            Util::sysLogMsg(basename(__FILE__), "Starting action {$action}...", LOG_DEBUG);
46
        }
47
48
        if ($action === 'deconfig' && Util::isT2SdeLinux()) {
49
            /**
50
             * Perform deconfiguration for T2SDE Linux.
51
             */
52
53
            $this->udhcpcConfigureDeconfig();
54
        } elseif ('bound' === $action || 'renew' === $action) {
55
            if (Util::isSystemctl()) {
56
                /**
57
                 * Perform configuration renewal and bound actions using systemctl (systemd-based systems).
58
                 */
59
                $this->configureRenewBoundSystemCtl();
60
            } elseif (Util::isT2SdeLinux()) {
61
                /**
62
                 * Perform configuration renewal and bound actions for T2SDE Linux.
63
                 */
64
                $this->configureRenewBound();
65
            }
66
        }
67
    }
68
69
    /**
70
     * Performs deconfiguration of the udhcpc configuration.
71
     */
72
    private function udhcpcConfigureDeconfig(): void
73
    {
74
        $interface = trim(getenv('interface'));
75
76
        // For MIKO LFS Edition.
77
        $busyboxPath = Util::which('busybox');
78
79
        // Bring the interface up.
80
        Processes::mwExec("{$busyboxPath} ifconfig {$interface} up");
81
82
        // Set a default IP configuration for the interface.
83
        Processes::mwExec("{$busyboxPath} ifconfig {$interface} 192.168.2.1 netmask 255.255.255.0");
84
    }
85
86
    /**
87
     * Renews and configures the network settings after successful DHCP negotiation using systemd environment variables.
88
     * For OS systemctl (Debian).
89
     *  Configures LAN interface FROM dhcpc (renew_bound).
90
     * @return void
91
     */
92
    public function configureRenewBoundSystemCtl(): void
93
    {
94
        $prefix = "new_";
95
96
        // Initialize the environment variables array.
97
        $env_vars = [
98
            'broadcast' => 'broadcast_address',
99
            'interface' => 'interface',
100
            'ip' => 'ip_address',
101
            'router' => 'routers',
102
            'timesvr' => '',
103
            'namesvr' => 'netbios_name_servers',
104
            'dns' => 'domain_name_servers',
105
            'hostname' => 'host_name',
106
            'subnet' => 'subnet_mask',
107
            'serverid' => '',
108
            'ipttl' => '',
109
            'lease' => 'new_dhcp_lease_time',
110
            'domain' => 'domain_name',
111
        ];
112
113
        // Get the values of environment variables.
114
        foreach ($env_vars as $key => $value) {
115
            $env_vars[$key] = trim(getenv("{$prefix}{$value}"));
116
        }
117
118
        /** @var LanInterfaces $if_data */
119
        $if_data = LanInterfaces::findFirst("interface = '{$env_vars['interface']}'");
120
        $is_inet = ($if_data !== null) ? (string)$if_data->internet : '0';
121
122
        $named_dns = [];
123
        if ('' !== $env_vars['dns']) {
124
            $named_dns = explode(' ', $env_vars['dns']);
125
        }
126
        if ($is_inet === '1') {
127
            // Only generate pdnsd config if this interface is for internet.
128
            $this->generatePdnsdConfig($named_dns);
129
        }
130
131
        // Save information to the database.
132
        $data = [
133
            'subnet' => '',
134
            'ipaddr' => $env_vars['ip'],
135
            'gateway' => $env_vars['router'],
136
        ];
137
        if (Verify::isIpAddress($env_vars['ip'])) {
138
            $data['subnet'] = $this->netMaskToCidr($env_vars['subnet']);
139
        }
140
        $this->updateIfSettings($data, $env_vars['interface']);
141
        $data = [
142
            'primarydns' => $named_dns[0] ?? '',
143
            'secondarydns' => $named_dns[1] ?? '',
144
        ];
145
        $this->updateDnsSettings($data, $env_vars['interface']);
146
    }
147
148
    /**
149
     * Processes DHCP renewal and binding for network interfaces.
150
     *
151
     * This function configures the network interface based on DHCP lease information.
152
     * It sets up the interface IP, subnet mask, default gateway, and static routes.
153
     * It also handles interface configuration deinitialization, DNS settings update, and MTU settings.
154
     *
155
     * @return void
156
     */
157
    public function configureRenewBound(): void
158
    {
159
        // Initialize an array to store environment variables related to network configuration.
160
        $env_vars = [
161
            'broadcast' => '', // 10.0.0.255
162
            'interface' => '', // eth0
163
            'ip' => '', // 10.0.0.249
164
            'router' => '', // 10.0.0.1
165
            'timesvr' => '',
166
            'namesvr' => '',
167
            'dns' => '', // 10.0.0.254
168
            'hostname' => '', // bad
169
            'subnet' => '', // 255.255.255.0
170
            'serverid' => '', // 10.0.0.1
171
            'ipttl' => '',
172
            'lease' => '', // 86400
173
            'domain' => '', // bad
174
            'mtu'=>'' , // 1500
175
            'staticroutes'=> '', // 0.0.0.0/0 10.0.0.1 169.254.169.254/32 10.0.0.65 0.0.0.0/0 10.0.0.1
176
            'mask' => '', // 24
177
        ];
178
179
        // Check for debug mode to enable logging.
180
        $debugMode = $this->di->getShared('config')->path('core.debugMode');
181
182
        // Retrieve and trim the values of the required environment variables.
183
        foreach ($env_vars as $key => $value) {
184
            $env_vars[$key] = trim(getenv($key));
185
        }
186
        unset($value);
187
188
        // Configure broadcast address if provided, otherwise leave it blank.
189
        $BROADCAST = !empty($env_vars['broadcast']) ? "broadcast {$env_vars['broadcast']}" : "";
190
191
        // Handle subnet mask for /32 assignments and other cases.
192
        $NET_MASK = (!empty($env_vars['subnet']) && $env_vars['subnet'] !== '255.255.255.255') ? "netmask {$env_vars['subnet']}" : "";
193
194
        // Configure the network interface with the provided IP, broadcast, and subnet mask.
195
        $busyboxPath = Util::which('busybox');
196
        Processes::mwExec("{$busyboxPath} ifconfig {$env_vars['interface']} {$env_vars['ip']} $BROADCAST $NET_MASK");
197
198
199
        // Remove any existing default gateway routes associated with this interface.
200
        while (true) {
201
            $out = [];
202
            Processes::mwExec("route del default gw 0.0.0.0 dev {$env_vars['interface']}", $out);
203
            if (trim(implode('', $out)) !== '') {
204
                // An error occurred, indicating that all routes have been cleared.
205
                break;
206
            }
207
            if ($debugMode) {
208
                break;
209
            } // Otherwise, it will be an infinite loop.
210
        }
211
212
        // Add a default gateway route if a router address is provided and the interface is for the internet.
213
        $if_data = LanInterfaces::findFirst("interface = '{$env_vars['interface']}'");
214
        $is_inet = ($if_data !== null) ? (int)$if_data->internet : 0;
215
        if (!empty($env_vars['router']) && $is_inet === 1) {
216
            // Only add the default route if this interface is for the internet.
217
            $routers = explode(' ', $env_vars['router']);
218
            foreach ($routers as $router) {
219
                Processes::mwExec("route add default gw {$router} dev {$env_vars['interface']}");
220
            }
221
        }
222
223
        // Add custom static routes if any are provided.
224
        $this->addStaticRoutes($env_vars['staticroutes'], $env_vars['interface']);
225
226
        // Add custom routes.
227
        $this->addCustomStaticRoutes($env_vars['interface']);
228
229
230
        // Setup DNS.
231
        $named_dns = [];
232
        if ('' !== $env_vars['dns']) {
233
            $named_dns = explode(' ', $env_vars['dns']);
234
        }
235
        if ($is_inet === 1) {
236
            // Only generate pdnsd config if this interface is for internet.
237
            $this->generatePdnsdConfig($named_dns);
238
        }
239
240
        // Save information to the database.
241
        $data = [
242
            'subnet' => '',
243
            'ipaddr' => $env_vars['ip'],
244
            'gateway' => $env_vars['router'],
245
        ];
246
        if (Verify::isIpAddress($env_vars['ip'])) {
247
            $data['subnet'] = $this->netMaskToCidr($env_vars['subnet']);
248
        }
249
250
        $this->updateIfSettings($data, $env_vars['interface']);
251
252
        $data = [
253
            'primarydns' => $named_dns[0] ?? '',
254
            'secondarydns' => $named_dns[1] ?? '',
255
        ];
256
        $this->updateDnsSettings($data, $env_vars['interface']);
257
258
        Processes::mwExecBg("/etc/rc/networking.set.mtu '{$env_vars['interface']}'");
259
    }
260
261
    /**
262
     * Add static routes based on DHCP provided static routes information.
263
     * Parses the `staticroutes` environment variable and adds each route to the system.
264
     *
265
     * @param string $staticRoutes The static routes string from DHCP, format: "destination gateway"
266
     * @param string $interface The network interface to add routes to, e.g., eth0
267
     * @return void
268
     */
269
    private function addStaticRoutes(string $staticRoutes, string $interface): void
270
    {
271
        if (empty($staticRoutes)) {
272
            return;
273
        }
274
275
        // Split the static routes string into individual routes.
276
        $routes = explode(' ', $staticRoutes);
277
        $processedRoutes = []; // To keep track of processed routes and avoid duplicates.
278
279
        $busyboxPath = Util::which('busybox');
280
281
        // Iterate through the routes, adding each to the system.
282
        $countRoutes = count($routes);
283
        for ($i = 0; $i < $countRoutes; $i += 2) {
284
            $destination = $routes[$i];
285
            $gateway = $routes[$i + 1] ?? '';
286
287
            // Check if the route has already been processed to prevent duplicates.
288
            if (!empty($destination) && !empty($gateway) && !in_array($destination, $processedRoutes)) {
289
                Processes::mwExec("$busyboxPath ip route add $destination via $gateway dev $interface");
290
                $processedRoutes[] = $destination; // Mark this route as processed.
291
            }
292
        }
293
    }
294
295
    /**
296
     * Add custom static routes based on the `/etc/static-routes` file.
297
     *
298
     * @param string $interface The network interface to add routes to, e.g., eth0
299
     * @return void
300
     */
301
    private function addCustomStaticRoutes(string $interface): void
302
    {
303
        if (file_exists('/etc/static-routes')) {
304
            $busyboxPath = Util::which('busybox');
305
            $grepPath = Util::which('grep');
306
            $awkPath = Util::which('awk');
307
            $catPath = Util::which('cat');
308
            $shPath = Util::which('sh');
309
            Processes::mwExec(
310
                "{$catPath} /etc/static-routes | {$grepPath} '^rout' | {$busyboxPath} {$awkPath} -F ';' '{print $1}' | {$grepPath} '{$interface}' | {$shPath}"
311
            );
312
        }
313
    }
314
}