Make connection tracking list sortable.
[people/teissler/ipfire-2.x.git] / html / cgi-bin / connections.cgi
1 #!/usr/bin/perl
2 ###############################################################################
3 # #
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2007-2012 IPFire Team <info@ipfire.org> #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 use strict;
23
24 use Net::IPv4Addr qw( :all );
25 use Switch;
26
27 # enable only the following on debugging purpose
28 #use warnings;
29 #use CGI::Carp 'fatalsToBrowser';
30
31 require '/var/ipfire/general-functions.pl';
32 require "${General::swroot}/lang.pl";
33 require "${General::swroot}/header.pl";
34
35 my $colour_multicast = "#A0A0A0";
36
37 # sort arguments for connection tracking table
38 # the sort field. eg. 1=src IP, 2=dst IP, 3=src port, 4=dst port
39 my $SORT_FIELD = 0;
40 # the sort order. (a)scending orr (d)escending
41 my $SORT_ORDER = 0;
42 # cgi query arguments
43 my %cgiin;
44 # debug mode
45 my $debug = 0;
46
47 # retrieve query arguments
48 # note: let a-z A-Z and 0-9 pass as value only
49 if (length ($ENV{'QUERY_STRING'}) > 0){
50 my $name;
51 my $value;
52 my $buffer = $ENV{'QUERY_STRING'};
53 my @pairs = split(/&/, $buffer);
54 foreach my $pair (@pairs){
55 ($name, $value) = split(/=/, $pair);
56 $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; # e.g. "%20" => " "
57 $value =~ s/[^a-zA-Z0-9]*//g; # a-Z 0-9 will pass
58 $cgiin{$name} = $value;
59 }
60 }
61
62 &Header::showhttpheaders();
63
64 my @network=();
65 my @masklen=();
66 my @colour=();
67
68 my %netsettings=();
69 &General::readhash("${General::swroot}/ethernet/settings", \%netsettings);
70
71 # output cgi query arrguments to browser on debug
72 if ( $debug ){
73 &Header::openbox('100%', 'center', 'DEBUG');
74 my $debugCount = 0;
75 foreach my $line (sort keys %cgiin) {
76 print "$line = '$cgiin{$line}'<br />\n";
77 $debugCount++;
78 }
79 print "&nbsp;Count: $debugCount\n";
80 &Header::closebox();
81 }
82
83 #workaround to suppress a warning when a variable is used only once
84 my @dummy = ( ${Header::table1colour} );
85 undef (@dummy);
86
87 # check sorting arguments
88 if ( $cgiin{'sort_field'} ~~ [ '1','2','3','4','5','6','7','8','9' ] ) {
89 $SORT_FIELD = $cgiin{'sort_field'};
90
91 if ( $cgiin{'sort_order'} ~~ [ 'a','d','A','D' ] ) {
92 $SORT_ORDER = lc($cgiin{'sort_order'});
93 }
94 }
95
96 # Read and sort the connection tracking table
97 # do sorting
98 if ($SORT_FIELD and $SORT_ORDER) {
99 # field sorting when sorting arguments are sane
100 open(CONNTRACK, "/usr/local/bin/getconntracktable | /usr/local/bin/consort.sh $SORT_FIELD $SORT_ORDER |") or die "Unable to read conntrack table";
101 } else {
102 # default sorting with no query arguments
103 open(CONNTRACK, "/usr/local/bin/getconntracktable | sort -k 5,5 --numeric-sort --reverse |") or die "Unable to read conntrack table";
104 }
105 my @conntrack = <CONNTRACK>;
106 close(CONNTRACK);
107
108 # Collect data for the @network array.
109
110 # Add Firewall Localhost 127.0.0.1
111 push(@network, '127.0.0.1');
112 push(@masklen, '255.255.255.255');
113 push(@colour, ${Header::colourfw});
114
115 if (open(IP, "${General::swroot}/red/local-ipaddress")) {
116 my $redip = <IP>;
117 close(IP);
118
119 chomp $redip;
120 push(@network, $redip);
121 push(@masklen, '255.255.255.255');
122 push(@colour, ${Header::colourfw});
123 }
124
125 # Add STATIC RED aliases
126 if ($netsettings{'RED_DEV'}) {
127 my $aliasfile = "${General::swroot}/ethernet/aliases";
128 open(ALIASES, $aliasfile) or die 'Unable to open aliases file.';
129 my @aliases = <ALIASES>;
130 close(ALIASES);
131
132 # We have a RED eth iface
133 if ($netsettings{'RED_TYPE'} eq 'STATIC') {
134 # We have a STATIC RED eth iface
135 foreach my $line (@aliases) {
136 chomp($line);
137 my @temp = split(/\,/,$line);
138 if ($temp[0]) {
139 push(@network, $temp[0]);
140 push(@masklen, $netsettings{'RED_NETMASK'} );
141 push(@colour, ${Header::colourfw} );
142 }
143 }
144 }
145 }
146
147 # Add Green Firewall Interface
148 push(@network, $netsettings{'GREEN_ADDRESS'});
149 push(@masklen, "255.255.255.255" );
150 push(@colour, ${Header::colourfw} );
151
152 # Add Green Network to Array
153 push(@network, $netsettings{'GREEN_NETADDRESS'});
154 push(@masklen, $netsettings{'GREEN_NETMASK'} );
155 push(@colour, ${Header::colourgreen} );
156
157 # Add Green Routes to Array
158 my @routes = `/sbin/route -n | /bin/grep $netsettings{'GREEN_DEV'}`;
159 foreach my $route (@routes) {
160 chomp($route);
161 my @temp = split(/[\t ]+/, $route);
162 push(@network, $temp[0]);
163 push(@masklen, $temp[2]);
164 push(@colour, ${Header::colourgreen} );
165 }
166
167 # Add Blue Firewall Interface
168 push(@network, $netsettings{'BLUE_ADDRESS'});
169 push(@masklen, "255.255.255.255" );
170 push(@colour, ${Header::colourfw} );
171
172 # Add Blue Network
173 if ($netsettings{'BLUE_DEV'}) {
174 push(@network, $netsettings{'BLUE_NETADDRESS'});
175 push(@masklen, $netsettings{'BLUE_NETMASK'} );
176 push(@colour, ${Header::colourblue} );
177
178 # Add Blue Routes to Array
179 @routes = `/sbin/route -n | /bin/grep $netsettings{'BLUE_DEV'}`;
180 foreach my $route (@routes) {
181 chomp($route);
182 my @temp = split(/[\t ]+/, $route);
183 push(@network, $temp[0]);
184 push(@masklen, $temp[2]);
185 push(@colour, ${Header::colourblue} );
186 }
187 }
188
189 # Add Orange Firewall Interface
190 push(@network, $netsettings{'ORANGE_ADDRESS'});
191 push(@masklen, "255.255.255.255" );
192 push(@colour, ${Header::colourfw} );
193
194 # Add Orange Network
195 if ($netsettings{'ORANGE_DEV'}) {
196 push(@network, $netsettings{'ORANGE_NETADDRESS'});
197 push(@masklen, $netsettings{'ORANGE_NETMASK'} );
198 push(@colour, ${Header::colourorange} );
199 # Add Orange Routes to Array
200 @routes = `/sbin/route -n | /bin/grep $netsettings{'ORANGE_DEV'}`;
201 foreach my $route (@routes) {
202 chomp($route);
203 my @temp = split(/[\t ]+/, $route);
204 push(@network, $temp[0]);
205 push(@masklen, $temp[2]);
206 push(@colour, ${Header::colourorange} );
207 }
208 }
209
210 # Highlight multicast connections.
211 push(@network, "224.0.0.0");
212 push(@masklen, "239.0.0.0");
213 push(@colour, $colour_multicast);
214
215 # Add OpenVPN net and RED/BLUE/ORANGE entry (when appropriate)
216 if (-e "${General::swroot}/ovpn/settings") {
217 my %ovpnsettings = ();
218 &General::readhash("${General::swroot}/ovpn/settings", \%ovpnsettings);
219 my @tempovpnsubnet = split("\/",$ovpnsettings{'DOVPN_SUBNET'});
220
221 # add OpenVPN net
222 push(@network, $tempovpnsubnet[0]);
223 push(@masklen, $tempovpnsubnet[1]);
224 push(@colour, ${Header::colourovpn} );
225
226 # add BLUE:port / proto
227 if (($ovpnsettings{'ENABLED_BLUE'} eq 'on') && $netsettings{'BLUE_DEV'}) {
228 push(@network, $netsettings{'BLUE_ADDRESS'} );
229 push(@masklen, '255.255.255.255' );
230 push(@colour, ${Header::colourovpn});
231 }
232
233 # add ORANGE:port / proto
234 if (($ovpnsettings{'ENABLED_ORANGE'} eq 'on') && $netsettings{'ORANGE_DEV'}) {
235 push(@network, $netsettings{'ORANGE_ADDRESS'} );
236 push(@masklen, '255.255.255.255' );
237 push(@colour, ${Header::colourovpn} );
238 }
239 }
240
241 open(IPSEC, "${General::swroot}/vpn/config");
242 my @ipsec = <IPSEC>;
243 close(IPSEC);
244
245 foreach my $line (@ipsec) {
246 my @vpn = split(',', $line);
247 my ($network, $mask) = split("/", $vpn[12]);
248
249 if (!&General::validip($mask)) {
250 $mask = ipv4_cidr2msk($mask);
251 }
252
253 push(@network, $network);
254 push(@masklen, $mask);
255 push(@colour, ${Header::colourvpn});
256 }
257
258 if (-e "${General::swroot}/ovpn/n2nconf") {
259 open(OVPNN2N, "${General::swroot}/ovpn/ovpnconfig");
260 my @ovpnn2n = <OVPNN2N>;
261 close(OVPNN2N);
262
263 foreach my $line (@ovpnn2n) {
264 my @ovpn = split(',', $line);
265 next if ($ovpn[4] ne 'net');
266
267 my ($network, $mask) = split("/", $ovpn[12]);
268 if (!&General::validip($mask)) {
269 $mask = ipv4_cidr2msk($mask);
270 }
271
272 push(@network, $network);
273 push(@masklen, $mask);
274 push(@colour, ${Header::colourovpn});
275 }
276 }
277
278 # Show the page.
279 &Header::openpage($Lang::tr{'connections'}, 1, '');
280 &Header::openbigbox('100%', 'left');
281 &Header::openbox('100%', 'left', $Lang::tr{'connection tracking'});
282
283 # Print legend.
284 print <<END;
285 <table width='100%'>
286 <tr>
287 <td align='center'>
288 <b>$Lang::tr{'legend'} : </b>
289 </td>
290 <td align='center' bgcolor='${Header::colourgreen}'>
291 <b><font color='#FFFFFF'>$Lang::tr{'lan'}</font></b>
292 </td>
293 <td align='center' bgcolor='${Header::colourred}'>
294 <b><font color='#FFFFFF'>$Lang::tr{'internet'}</font></b>
295 </td>
296 <td align='center' bgcolor='${Header::colourorange}'>
297 <b><font color='#FFFFFF'>$Lang::tr{'dmz'}</font></b>
298 </td>
299 <td align='center' bgcolor='${Header::colourblue}'>
300 <b><font color='#FFFFFF'>$Lang::tr{'wireless'}</font></b>
301 </td>
302 <td align='center' bgcolor='${Header::colourfw}'>
303 <b><font color='#FFFFFF'>IPFire</font></b>
304 </td>
305 <td align='center' bgcolor='${Header::colourvpn}'>
306 <b><font color='#FFFFFF'>$Lang::tr{'vpn'}</font></b>
307 </td>
308 <td align='center' bgcolor='${Header::colourovpn}'>
309 <b><font color='#FFFFFF'>$Lang::tr{'OpenVPN'}</font></b>
310 </td>
311 <td align='center' bgcolor='$colour_multicast'>
312 <b><font color='#FFFFFF'>Multicast</font></b>
313 </td>
314 </tr>
315 </table>
316 <br>
317 END
318
319 if ($SORT_FIELD and $SORT_ORDER) {
320 my @sort_field_name = (
321 $Lang::tr{'source ip'},
322 $Lang::tr{'destination ip'},
323 $Lang::tr{'source port'},
324 $Lang::tr{'destination port'},
325 $Lang::tr{'protocol'},
326 $Lang::tr{'connection'}.' '.$Lang::tr{'status'},
327 $Lang::tr{'expires'}.' ('.$Lang::tr{'seconds'}.')',
328 $Lang::tr{'download'},
329 $Lang::tr{'upload'}
330 );
331 my $sort_order_name;
332 if (lc($SORT_ORDER) eq "a") {
333 $sort_order_name = $Lang::tr{'sort ascending'};
334 } else {
335 $sort_order_name = $Lang::tr{'sort descending'};
336 }
337
338 print <<END
339 <div style="font-weight:bold;margin:10px;font-size: 70%">
340 $sort_order_name: $sort_field_name[$SORT_FIELD-1]
341 </div>
342 END
343 ;
344 }
345
346 # Print table header.
347 print <<END;
348 <table width='100%'>
349 <tr valign="top"">
350 <th align='center'>
351 <a href="?sort_field=5&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
352 <a href="?sort_field=5&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
353 </th>
354 <th align='center' colspan="2">
355 <a href="?sort_field=1&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
356 <a href="?sort_field=1&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
357 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
358 <a href="?sort_field=3&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
359 <a href="?sort_field=3&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
360 </th>
361 <th align='center' colspan="2">
362 <a href="?sort_field=2&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
363 <a href="?sort_field=2&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
364 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
365 <a href="?sort_field=4&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
366 <a href="?sort_field=4&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
367 </th>
368 <th align='center'>
369 <a href="?sort_field=8&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
370 <a href="?sort_field=8&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
371 &nbsp;&nbsp;&nbsp;&nbsp;
372 <a href="?sort_field=9&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
373 <a href="?sort_field=9&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
374 </th>
375 <th align='center'>
376 <a href="?sort_field=6&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
377 <a href="?sort_field=6&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
378 </th>
379 <th align='center'>
380 <a href="?sort_field=7&sort_order=d"><img style="width:10px" src="/images/up.gif"></a>
381 <a href="?sort_field=7&sort_order=a"><img style="width:10px" src="/images/down.gif"></a>
382 </th>
383 </tr>
384 <tr valign="top"">
385 <th align='center'>
386 $Lang::tr{'protocol'}
387 </th>
388 <th align='center' colspan="2">
389 $Lang::tr{'source ip and port'}
390 </th>
391 <th align='center' colspan="2">
392 $Lang::tr{'dest ip and port'}
393 </th>
394 <th align='center'>
395 $Lang::tr{'download'} /
396 <br>$Lang::tr{'upload'}
397 </th>
398 <th align='center'>
399 $Lang::tr{'connection'}<br>$Lang::tr{'status'}
400 </th>
401 <th align='center'>
402 $Lang::tr{'expires'}<br>($Lang::tr{'seconds'})
403 </th>
404 </tr>
405 END
406
407 foreach my $line (@conntrack) {
408 my @conn = split(' ', $line);
409
410 # The first bit is the l3 protocol.
411 my $l3proto = $conn[0];
412
413 # Skip everything that is not IPv4.
414 if ($l3proto ne 'ipv4') {
415 next;
416 }
417
418 # L4 protocol (tcp, udp, ...).
419 my $l4proto = $conn[2];
420
421 # Translate unknown protocols.
422 if ($l4proto eq 'unknown') {
423 my $l4protonum = $conn[3];
424 if ($l4protonum eq '2') {
425 $l4proto = 'IGMP';
426 } elsif ($l4protonum eq '4') {
427 $l4proto = 'IPv4 Encap';
428 } elsif ($l4protonum eq '33') {
429 $l4proto = 'DCCP';
430 } elsif ($l4protonum eq '41') {
431 $l4proto = 'IPv6 Encap';
432 } elsif ($l4protonum eq '50') {
433 $l4proto = 'ESP';
434 } elsif ($l4protonum eq '51') {
435 $l4proto = 'AH';
436 } elsif ($l4protonum eq '132') {
437 $l4proto = 'SCTP';
438 } else {
439 $l4proto = $l4protonum;
440 }
441 } else {
442 $l4proto = uc($l4proto);
443 }
444
445 # Source and destination.
446 my $sip;
447 my $sip_ret;
448 my $dip;
449 my $dip_ret;
450 my $sport;
451 my $sport_ret;
452 my $dport;
453 my $dport_ret;
454 my @packets;
455 my @bytes;
456
457 my $ttl = $conn[4];
458 my $state;
459 if ($l4proto eq 'TCP') {
460 $state = $conn[5];
461 }
462
463 # Kick out everything that is not IPv4.
464 foreach my $item (@conn) {
465 my ($key, $val) = split('=', $item);
466
467 switch ($key) {
468 case "src" {
469 if ($sip == "") {
470 $sip = $val;
471 } else {
472 $dip_ret = $val;
473 }
474 }
475 case "dst" {
476 if ($dip == "") {
477 $dip = $val;
478 } else {
479 $sip_ret = $val;
480 }
481 }
482 case "sport" {
483 if ($sport == "") {
484 $sport = $val;
485 } else {
486 $dport_ret = $val;
487 }
488 }
489 case "dport" {
490 if ($dport == "") {
491 $dport = $val;
492 } else {
493 $sport_ret = $val;
494 }
495 }
496 case "packets" {
497 push(@packets, $val);
498 }
499 case "bytes" {
500 push(@bytes, $val);
501 }
502 }
503 }
504
505 my $sip_colour = ipcolour($sip);
506 my $dip_colour = ipcolour($dip);
507
508 my $sserv = '';
509 if ($sport < 1024) {
510 $sserv = uc(getservbyport($sport, lc($l4proto)));
511 }
512
513 my $dserv = '';
514 if ($dport < 1024) {
515 $dserv = uc(getservbyport($dport, lc($l4proto)));
516 }
517
518 my $bytes_in = format_bytes($bytes[0]);
519 my $bytes_out = format_bytes($bytes[1]);
520
521 # Format TTL
522 $ttl = format_time($ttl);
523
524 my $sip_extra;
525 if ($sip ne $sip_ret) {
526 $sip_extra = "<font color='#FFFFFF'>&gt;</font> ";
527 $sip_extra .= "<a href='/cgi-bin/ipinfo.cgi?ip=$sip_ret'>";
528 $sip_extra .= " <font color='#FFFFFF'>$sip_ret</font>";
529 $sip_extra .= "</a>";
530 }
531
532 my $dip_extra;
533 if ($dip ne $dip_ret) {
534 $dip_extra = "<font color='#FFFFFF'>&gt;</font> ";
535 $dip_extra .= "<a href='/cgi-bin/ipinfo.cgi?ip=$dip_ret'>";
536 $dip_extra .= " <font color='#FFFFFF'>$dip_ret</font>";
537 $dip_extra .= "</a>";
538 }
539
540
541 my $sport_extra;
542 if ($sport ne $sport_ret) {
543 my $sserv_ret = '';
544 if ($sport_ret < 1024) {
545 $sserv_ret = uc(getservbyport($sport_ret, lc($l4proto)));
546 }
547
548 $sport_extra = "<font color='#FFFFFF'>&gt;</font> ";
549 $sport_extra .= "<a href='http://isc.sans.org/port_details.php?port=$sport_ret' target='top' title='$sserv_ret'>";
550 $sport_extra .= " <font color='#FFFFFF'>$sport_ret</font>";
551 $sport_extra .= "</a>";
552 }
553
554 my $dport_extra;
555 if ($dport ne $dport_ret) {
556 my $dserv_ret = '';
557 if ($dport_ret < 1024) {
558 $dserv_ret = uc(getservbyport($dport_ret, lc($l4proto)));
559 }
560
561 $dport_extra = "<font color='#FFFFFF'>&gt;</font> ";
562 $dport_extra .= "<a href='http://isc.sans.org/port_details.php?port=$dport_ret' target='top' title='$dserv_ret'>";
563 $dport_extra .= " <font color='#FFFFFF'>$dport_ret</font>";
564 $dport_extra .= "</a>";
565 }
566
567 print <<END;
568 <tr>
569 <td align='center'>$l4proto</td>
570 <td align='center' bgcolor='$sip_colour'>
571 <a href='/cgi-bin/ipinfo.cgi?ip=$sip'>
572 <font color='#FFFFFF'>$sip</font>
573 </a>
574 $sip_extra
575 </td>
576 <td align='center' bgcolor='$sip_colour'>
577 <a href='http://isc.sans.org/port_details.php?port=$sport' target='top' title='$sserv'>
578 <font color='#FFFFFF'>$sport</font>
579 </a>
580 $sport_extra
581 </td>
582 <td align='center' bgcolor='$dip_colour'>
583 <a href='/cgi-bin/ipinfo.cgi?ip=$dip'>
584 <font color='#FFFFFF'>$dip</font>
585 </a>
586 $dip_extra
587 </td>
588 <td align='center' bgcolor='$dip_colour'>
589 <a href='http://isc.sans.org/port_details.php?port=$dport' target='top' title='$dserv'>
590 <font color='#FFFFFF'>$dport</font>
591 </a>
592 $dport_extra
593 </td>
594 <td align='center'>
595 $bytes_in / $bytes_out
596 </td>
597 <td align='center'>$state</td>
598 <td align='center'>$ttl</td>
599 </tr>
600 END
601 }
602
603 # Close the main table.
604 print "</table>";
605
606 &Header::closebox();
607 &Header::closebigbox();
608 &Header::closepage();
609
610 sub format_bytes($) {
611 my $bytes = shift;
612 my @units = ("B", "k", "M", "G", "T");
613
614 foreach my $unit (@units) {
615 if ($bytes < 1024) {
616 return sprintf("%d%s", $bytes, $unit);
617 }
618
619 $bytes /= 1024;
620 }
621
622 return sprintf("%d%s", $bytes, $units[$#units]);
623 }
624
625 sub format_time($) {
626 my $time = shift;
627
628 my $seconds = $time % 60;
629 my $minutes = $time / 60;
630
631 my $hours = 0;
632 if ($minutes >= 60) {
633 $hours = $minutes / 60;
634 $minutes %= 60;
635 }
636
637 return sprintf("%3d:%02d:%02d", $hours, $minutes, $seconds);
638 }
639
640 sub ipcolour($) {
641 my $id = 0;
642 my $colour = ${Header::colourred};
643 my ($ip) = $_[0];
644 my $found = 0;
645
646 foreach my $line (@network) {
647 if ($network[$id] eq '') {
648 $id++;
649 } else {
650 if (!$found && ipv4_in_network($network[$id], $masklen[$id], $ip) ) {
651 $found = 1;
652 $colour = $colour[$id];
653 }
654 $id++;
655 }
656 }
657
658 return $colour;
659 }
660
661 1;