]> git.ipfire.org Git - ipfire-2.x.git/blob - config/cfgroot/network-functions.pl
7bd6466e0c91e3b6ece894e1cae9b71a1f7cf3ed
[ipfire-2.x.git] / config / cfgroot / network-functions.pl
1 #!/usr/bin/perl -w
2 ############################################################################
3 # #
4 # This file is part of the IPFire Firewall. #
5 # #
6 # IPFire 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 2 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # IPFire 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 #
17 # along with IPFire; if not, write to the Free Software #
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
19 # #
20 # Copyright (C) 2014 IPFire Team <info@ipfire.org>. #
21 # #
22 ############################################################################
23
24 package Network;
25
26 require "/var/ipfire/general-functions.pl";
27
28 use Socket;
29
30 # System ethernet configuration
31 our %ethernet_settings = ();
32 &General::readhash("${General::swroot}/ethernet/settings", \%ethernet_settings);
33
34 # List of all possible network zones that can be configured
35 our @known_network_zones = ("red", "green", "orange", "blue");
36
37 # IPv4 netmask CIDR to dotted decimal notation conversion table
38 my %PREFIX2NETMASK = (
39 32 => "255.255.255.255",
40 31 => "255.255.255.254",
41 30 => "255.255.255.252",
42 29 => "255.255.255.248",
43 28 => "255.255.255.240",
44 27 => "255.255.255.224",
45 26 => "255.255.255.192",
46 25 => "255.255.255.128",
47 24 => "255.255.255.0",
48 23 => "255.255.254.0",
49 22 => "255.255.252.0",
50 21 => "255.255.248.0",
51 20 => "255.255.240.0",
52 19 => "255.255.224.0",
53 18 => "255.255.192.0",
54 17 => "255.255.128.0",
55 16 => "255.255.0.0",
56 15 => "255.254.0.0",
57 14 => "255.252.0.0",
58 13 => "255.248.0.0",
59 12 => "255.240.0.0",
60 11 => "255.224.0.0",
61 10 => "255.192.0.0",
62 9 => "255.128.0.0",
63 8 => "255.0.0.0",
64 7 => "254.0.0.0",
65 6 => "252.0.0.0",
66 5 => "248.0.0.0",
67 4 => "240.0.0.0",
68 3 => "224.0.0.0",
69 2 => "192.0.0.0",
70 1 => "128.0.0.0",
71 0 => "0.0.0.0"
72 );
73
74 my %NETMASK2PREFIX = reverse(%PREFIX2NETMASK);
75
76 # Takes an IP address in dotted decimal notation and
77 # returns a 32 bit integer representing that IP addresss.
78 # Will return undef for invalid inputs.
79 sub ip2bin($) {
80 my $address = shift;
81
82 # This function returns undef for undefined input.
83 if (!defined $address) {
84 return undef;
85 }
86
87 my $address_bin = &Socket::inet_pton(AF_INET, $address);
88 if ($address_bin) {
89 $address_bin = unpack('N', $address_bin);
90 }
91
92 return $address_bin;
93 }
94
95 # Does the reverse of ip2bin().
96 # Will return undef for invalid inputs.
97 sub bin2ip($) {
98 my $address_bin = shift;
99
100 # This function returns undef for undefined input.
101 if (!defined $address_bin) {
102 return undef;
103 }
104
105 my $address = pack('N', $address_bin);
106 if ($address) {
107 $address = &Socket::inet_ntop(AF_INET, $address);
108 }
109
110 return $address;
111 }
112
113 # Takes two network addresses, compares them against each other
114 # and returns true if equal or false if not
115 sub network_equal {
116 my $network1 = shift;
117 my $network2 = shift;
118
119 my @bin1 = &network2bin($network1);
120 my @bin2 = &network2bin($network2);
121
122 if (!defined $bin1 || !defined $bin2) {
123 return undef;
124 }
125
126 if ($bin1[0] == $bin2[0] && $bin1[1] == $bin2[1]) {
127 return 1;
128 }
129
130 return 0;
131 }
132
133 # Takes a network in either a.b.c.d/a.b.c.d or a.b.c.d/e notation
134 # and will return an 32 bit integer representing the start
135 # address and an other one representing the network mask.
136 sub network2bin($) {
137 my $network = shift;
138
139 my ($address, $netmask) = split(/\//, $network, 2);
140
141 if (&check_prefix($netmask)) {
142 $netmask = &convert_prefix2netmask($netmask);
143 }
144
145 my $address_bin = &ip2bin($address);
146 my $netmask_bin = &ip2bin($netmask);
147
148 if (!defined $address_bin || !defined $netmask_bin) {
149 return undef;
150 }
151
152 my $network_start = $address_bin & $netmask_bin;
153
154 return ($network_start, $netmask_bin);
155 }
156
157 # Deletes leading zeros in ip address
158 sub ip_remove_zero{
159 my $address = shift;
160 my @ip = split (/\./, $address);
161
162 foreach my $octet (@ip) {
163 $octet = int($octet);
164 }
165
166 $address = join (".", @ip);
167
168 return $address;
169 }
170 # Returns True for all valid IP addresses
171 sub check_ip_address($) {
172 my $address = shift;
173
174 # Normalise the IP address and compare the result with
175 # the input - which should obviously the same.
176 my $normalised_address = &_normalise_ip_address($address);
177
178 return ((defined $normalised_address) && ($address eq $normalised_address));
179 }
180
181 # Returns True for all valid prefixes.
182 sub check_prefix($) {
183 my $prefix = shift;
184
185 return (exists $PREFIX2NETMASK{$prefix});
186 }
187
188 # Returns True for all valid subnet masks.
189 sub check_netmask($) {
190 my $netmask = shift;
191
192 return (exists $NETMASK2PREFIX{$netmask});
193 }
194
195 # Returns True for all valid inputs like a.b.c.d/a.b.c.d.
196 sub check_ip_address_and_netmask($$) {
197 my $network = shift;
198
199 my ($address, $netmask) = split(/\//, $network, 2);
200
201 # Check if the IP address is fine.
202 #
203 my $result = &check_ip_address($address);
204 unless ($result) {
205 return $result;
206 }
207
208 return &check_netmask($netmask);
209 }
210
211 # Returns True for all valid subnets like a.b.c.d/e or a.b.c.d/a.b.c.d
212 sub check_subnet($) {
213 my $subnet = shift;
214
215 my ($address, $network) = split(/\//, $subnet, 2);
216
217 # Check if the IP address is fine.
218 my $result = &check_ip_address($address);
219 unless ($result) {
220 return $result;
221 }
222
223 return &check_prefix($network) || &check_netmask($network);
224 }
225
226 # For internal use only. Will take an IP address and
227 # return it in a normalised style. Like 8.8.8.010 -> 8.8.8.8.
228 sub _normalise_ip_address($) {
229 my $address = shift;
230
231 my $address_bin = &ip2bin($address);
232 if (!defined $address_bin) {
233 return undef;
234 }
235
236 return &bin2ip($address_bin);
237 }
238
239 # Returns the prefix for the given subnet mask.
240 sub convert_netmask2prefix($) {
241 my $netmask = shift;
242
243 if (exists $NETMASK2PREFIX{$netmask}) {
244 return $NETMASK2PREFIX{$netmask};
245 }
246
247 return undef;
248 }
249
250 # Returns the subnet mask for the given prefix.
251 sub convert_prefix2netmask($) {
252 my $prefix = shift;
253
254 if (exists $PREFIX2NETMASK{$prefix}) {
255 return $PREFIX2NETMASK{$prefix};
256 }
257
258 return undef;
259 }
260
261 # Takes an IP address and an offset and
262 # will return the offset'th IP address.
263 sub find_next_ip_address($$) {
264 my $address = shift;
265 my $offset = shift;
266
267 my $address_bin = &ip2bin($address);
268 $address_bin += $offset;
269
270 return &bin2ip($address_bin);
271 }
272
273 # Returns the network address of the given network.
274 sub get_netaddress($) {
275 my $network = shift;
276 my ($network_bin, $netmask_bin) = &network2bin($network);
277
278 if (defined $network_bin) {
279 return &bin2ip($network_bin);
280 }
281
282 return undef;
283 }
284
285 # Returns the broadcast of the given network.
286 sub get_broadcast($) {
287 my $network = shift;
288 my ($network_bin, $netmask_bin) = &network2bin($network);
289
290 return &bin2ip($network_bin ^ ~$netmask_bin);
291 }
292
293 # Returns True if $address is in $network.
294 sub ip_address_in_network($$) {
295 my $address = shift;
296 my $network = shift;
297
298 my $address_bin = &ip2bin($address);
299 return undef unless (defined $address_bin);
300
301 my ($network_bin, $netmask_bin) = &network2bin($network);
302
303 # Find end address
304 my $broadcast_bin = $network_bin ^ (~$netmask_bin % 2 ** 32);
305
306 return (($address_bin >= $network_bin) && ($address_bin <= $broadcast_bin));
307 }
308
309 sub setup_upstream_proxy() {
310 my %proxysettings = ();
311 &General::readhash("${General::swroot}/proxy/settings", \%proxysettings);
312
313 if ($proxysettings{'UPSTREAM_PROXY'}) {
314 my $credentials = "";
315
316 if ($proxysettings{'UPSTREAM_USER'}) {
317 $credentials = $proxysettings{'UPSTREAM_USER'};
318
319 if ($proxysettings{'UPSTREAM_PASSWORD'}) {
320 $credentials .= ":" . $proxysettings{'UPSTREAM_PASSWORD'};
321 }
322
323 $credentials .= "@";
324 }
325
326 my $proxy = "http://" . $credentials . $proxysettings{'UPSTREAM_PROXY'};
327
328 $ENV{'http_proxy'} = $proxy;
329 $ENV{'https_proxy'} = $proxy;
330 $ENV{'ftp_proxy'} = $proxy;
331 }
332 }
333
334 my %wireless_status = ();
335
336 sub _get_wireless_status($) {
337 my $intf = shift;
338
339 if (!$wireless_status{$intf}) {
340 $wireless_status{$intf} = `iwconfig $intf`;
341 }
342
343 return $wireless_status{$intf};
344 }
345
346 sub wifi_get_essid($) {
347 my $status = &_get_wireless_status(shift);
348
349 my ($essid) = $status =~ /ESSID:\"(.*)\"/;
350
351 return $essid;
352 }
353
354 sub wifi_get_frequency($) {
355 my $status = &_get_wireless_status(shift);
356
357 my ($frequency) = $status =~ /Frequency:(\d+\.\d+ GHz)/;
358
359 return $frequency;
360 }
361
362 sub wifi_get_access_point($) {
363 my $status = &_get_wireless_status(shift);
364
365 my ($access_point) = $status =~ /Access Point: ([0-9A-F:]+)/;
366
367 return $access_point;
368 }
369
370 sub wifi_get_bit_rate($) {
371 my $status = &_get_wireless_status(shift);
372
373 my ($bit_rate) = $status =~ /Bit Rate=(\d+ [GM]b\/s)/;
374
375 return $bit_rate;
376 }
377
378 sub wifi_get_link_quality($) {
379 my $status = &_get_wireless_status(shift);
380
381 my ($cur, $max) = $status =~ /Link Quality=(\d+)\/(\d+)/;
382
383 return $cur * 100 / $max;
384 }
385
386 sub wifi_get_signal_level($) {
387 my $status = &_get_wireless_status(shift);
388
389 my ($signal_level) = $status =~ /Signal level=(\-\d+ dBm)/;
390
391 return $signal_level;
392 }
393
394 sub get_hardware_address($) {
395 my $ip_address = shift;
396 my $ret;
397
398 open(FILE, "/proc/net/arp") or die("Could not read ARP table");
399
400 while (<FILE>) {
401 my ($ip_addr, $hwtype, $flags, $hwaddr, $mask, $device) = split(/\s+/, $_);
402 if ($ip_addr eq $ip_address) {
403 $ret = $hwaddr;
404 last;
405 }
406 }
407
408 close(FILE);
409
410 return $ret;
411 }
412
413 sub get_nic_property {
414 my $nicname = shift;
415 my $property = shift;
416 my $result;
417
418 open(FILE, "/sys/class/net/$nicname/$property") or die("Could not read property");
419 $result = <FILE>;
420 close(FILE);
421
422 chomp($result);
423
424 return $result;
425 }
426
427 sub valid_mac($) {
428 my $mac = shift;
429
430 return $mac =~ /^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$/;
431 }
432
433 sub random_mac {
434 my $address = "02";
435
436 for my $i (0 .. 4) {
437 $address = sprintf("$address:%02x", int(rand(255)));
438 }
439
440 return $address;
441 }
442
443 sub get_mac_by_name($) {
444 my $mac = shift;
445
446 if ((!&valid_mac($mac)) && ($mac ne "")) {
447 if (-e "/sys/class/net/$mac/") {
448 $mac = get_nic_property($mac, "address");
449 }
450 }
451
452 return $mac;
453 }
454
455 #
456 ## Function to get a list of all available network zones.
457 #
458 sub get_available_network_zones () {
459 # Obtain the configuration type from the netsettings hash.
460 my $config_type = $ethernet_settings{'CONFIG_TYPE'};
461
462 # Hash which contains the conversation from the config mode
463 # to the existing network interface names. They are stored like
464 # an array.
465 #
466 # Mode "0" red is a modem and green
467 # Mode "1" red is a netdev and green
468 # Mode "2" red, green and orange
469 # Mode "3" red, green and blue
470 # Mode "4" red, green, blue, orange
471 my %config_type_to_interfaces = (
472 "0" => [ "red", "green" ],
473 "1" => [ "red", "green" ],
474 "2" => [ "red", "green", "orange" ],
475 "3" => [ "red", "green", "blue" ],
476 "4" => [ "red", "green", "blue", "orange" ]
477 );
478
479 # Obtain and dereference the corresponding network interaces based on the read
480 # network config type.
481 my @network_zones = @{ $config_type_to_interfaces{$config_type} };
482
483 # Return them.
484 return @network_zones;
485 }
486
487 #
488 ## Function to check if a network zone is available in the current configuration
489 #
490 sub is_zone_available() {
491 my $zone = lc shift;
492
493 # Make sure the zone is valid
494 die("Unknown network zone '$zone'") unless ($zone ~~ @known_network_zones);
495
496 # Get available zones and return result
497 my @available_zones = get_available_network_zones();
498 return ($zone ~~ @available_zones);
499 }
500
501 #
502 ## Function to determine if the RED zone is in standard IP (or modem, PPP, VDSL, ...) mode
503 #
504 sub is_red_mode_ip() {
505 # Obtain the settings from the netsettings hash
506 my $config_type = $ethernet_settings{'CONFIG_TYPE'};
507 my $red_type = $ethernet_settings{'RED_TYPE'};
508
509 # RED must be a network device (configuration 1-4) with dynamic or static IP
510 return (($config_type ~~ [1..4]) && ($red_type ~~ ["DHCP", "STATIC"]));
511 }
512
513 1;
514
515 # Remove the next line to enable the testsuite
516 __END__
517
518 sub assert($$) {
519 my $tst = shift;
520 my $ret = shift;
521
522 if ($ret) {
523 return;
524 }
525
526 print "ASSERTION ERROR - $tst\n";
527 exit(1);
528 }
529
530 sub testsuite() {
531 my $result;
532
533 my $address1 = &ip2bin("8.8.8.8");
534 assert('ip2bin("8.8.8.8")', $address1 == 134744072);
535
536 my $address2 = &bin2ip($address1);
537 assert("bin2ip($address1)", $address2 eq "8.8.8.8");
538
539 # Check if valid IP addresses are correctly recognised.
540 foreach my $address ("1.2.3.4", "192.168.180.1", "127.0.0.1") {
541 if (!&check_ip_address($address)) {
542 print "$address is not correctly recognised as a valid IP address!\n";
543 exit 1;
544 };
545 }
546
547 # Check if invalid IP addresses are correctly found.
548 foreach my $address ("456.2.3.4", "192.768.180.1", "127.1", "1", "a.b.c.d", "1.2.3.4.5", "1.2.3.4/12") {
549 if (&check_ip_address($address)) {
550 print "$address is recognised as a valid IP address!\n";
551 exit 1;
552 };
553 }
554
555 $result = &check_ip_address_and_netmask("192.168.180.0/255.255.255.0");
556 assert('check_ip_address_and_netmask("192.168.180.0/255.255.255.0")', $result);
557
558 $result = &convert_netmask2prefix("255.255.254.0");
559 assert('convert_netmask2prefix("255.255.254.0")', $result == 23);
560
561 $result = &convert_prefix2netmask(8);
562 assert('convert_prefix2netmask(8)', $result eq "255.0.0.0");
563
564 $result = &find_next_ip_address("1.2.3.4", 2);
565 assert('find_next_ip_address("1.2.3.4", 2)', $result eq "1.2.3.6");
566
567 $result = &network_equal("192.168.0.0/24", "192.168.0.0/255.255.255.0");
568 assert('network_equal("192.168.0.0/24", "192.168.0.0/255.255.255.0")', $result);
569
570 $result = &network_equal("192.168.0.0/24", "192.168.0.0/25");
571 assert('network_equal("192.168.0.0/24", "192.168.0.0/25")', !$result);
572
573 $result = &network_equal("192.168.0.0/24", "192.168.0.128/25");
574 assert('network_equal("192.168.0.0/24", "192.168.0.128/25")', !$result);
575
576 $result = &network_equal("192.168.0.1/24", "192.168.0.XXX/24");
577 assert('network_equal("192.168.0.1/24", "192.168.0.XXX/24")', !$result);
578
579 $result = &ip_address_in_network("10.0.1.4", "10.0.0.0/8");
580 assert('ip_address_in_network("10.0.1.4", "10.0.0.0/8"', $result);
581
582 $result = &ip_address_in_network("192.168.30.11", "192.168.30.0/255.255.255.0");
583 assert('ip_address_in_network("192.168.30.11", "192.168.30.0/255.255.255.0")', $result);
584
585 $result = &ip_address_in_network("192.168.30.11", "0.0.0.0/8");
586 assert('ip_address_in_network("192.168.30.11", "0.0.0.0/8")', !$result);
587
588 print "Testsuite completed successfully!\n";
589
590 return 0;
591 }
592
593 &testsuite();