1#! /usr/bin/perl
2# $OpenBSD: flow.pl,v 1.6 2017/03/03 21:34:14 bluhm Exp $
3
4# Copyright (c) 2013 Florian Obser <florian@openbsd.org>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18
19use strict;
20use warnings;
21use 5.010;
22use Config;
23
24use Data::Dumper;
25use IO::Socket::INET;
26use Net::Flow;
27
28my $port = 9996;
29
30{
31	my $id2name = {
32		  1 => 'octetDeltaCount',
33		  2 => 'packetDeltaCount',
34		  4 => 'protocolIdentifier',
35		  5 => 'ipClassOfService',
36		  7 => 'sourceTransportPort',
37		  8 => 'sourceIPv4Address',
38		 10 => 'ingressInterface',
39		 11 => 'destinationTransportPort',
40		 12 => 'destinationIPv4Address',
41		 14 => 'egressInterface',
42		 21 => 'flowEndSysUpTime',
43		 22 => 'flowStartSysUpTime',
44		 27 => 'sourceIPv6Address',
45		 28 => 'destinationIPv6Address',
46		150 => 'flowStartSeconds',
47		151 => 'flowEndSeconds',
48		152 => 'flowStartMilliseconds',
49		153 => 'flowEndMilliseconds',
50	};
51	my $name2id = {reverse %$id2name};
52	sub id2name { return $id2name->{$_[0]} || $_[0]; }
53	sub name2id { return $name2id->{$_[0]} || $_[0]; }
54}
55
56sub get_ifs
57{
58	my (@ifs, $prog);
59	open($prog, 'ifconfig |') or die $!;
60	while(<$prog>) {
61		chomp;
62		push(@ifs, $1) if(/^(\w+):/);
63	}
64	close($prog) or die $!;
65	return(grep({$_ ne 'lo0'} @ifs));
66}
67
68sub gen_pf_conf
69{
70	my @ifs = @_;
71	my $skip = 'set skip on {'.join(' ', @ifs).'}';
72	return <<END;
73$skip
74pass on lo0 no state
75pass on lo0 proto tcp from port 12345 to port 12346 keep state (pflow)
76END
77}
78
79if (scalar(@ARGV) != 2 || ($ARGV[0] != 9 && $ARGV[0]!=10)) {
80	print STDERR "usage: $0 [9|10] [4|6]\n";
81	exit(1);
82}
83
84if (scalar(@ARGV) != 2 || ($ARGV[1] != 4 && $ARGV[1]!=6)) {
85	print STDERR "usage: $0 [9|10] [4|6]\n";
86	exit(1);
87}
88
89
90my @v94_elem_names = qw (sourceIPv4Address
91    destinationIPv4Address
92    ingressInterface
93    egressInterface
94    packetDeltaCount
95    octetDeltaCount
96    flowStartSysUpTime
97    flowEndSysUpTime
98    sourceTransportPort
99    destinationTransportPort
100    ipClassOfService
101    protocolIdentifier);
102
103my @v96_elem_names = qw (sourceIPv6Address
104    destinationIPv6Address
105    ingressInterface
106    egressInterface
107    packetDeltaCount
108    octetDeltaCount
109    flowStartSysUpTime
110    flowEndSysUpTime
111    sourceTransportPort
112    destinationTransportPort
113    ipClassOfService
114    protocolIdentifier);
115
116my @v104_elem_names = qw (sourceIPv4Address
117    destinationIPv4Address
118    ingressInterface
119    egressInterface
120    packetDeltaCount
121    octetDeltaCount
122    flowStartMilliseconds
123    flowEndMilliseconds
124    sourceTransportPort
125    destinationTransportPort
126    ipClassOfService
127    protocolIdentifier);
128
129my @v106_elem_names = qw (sourceIPv6Address
130    destinationIPv6Address
131    ingressInterface
132    egressInterface
133    packetDeltaCount
134    octetDeltaCount
135    flowStartMilliseconds
136    flowEndMilliseconds
137    sourceTransportPort
138    destinationTransportPort
139    ipClassOfService
140    protocolIdentifier);
141
142my ($name, $sock, $packet, $header_ref, $template_ref, $flow_ref, $flows_ref,
143    $error_ref, @elem_names, $prog, $line);
144
145system('ifconfig', 'lo0', 'inet', '10.11.12.13', 'alias');
146system('ifconfig', 'lo0', 'inet6', '2001:db8::13');
147
148open($prog, '|pfctl -f -') or die $!;
149print $prog gen_pf_conf(get_ifs());
150close($prog) or die $!;
151
152if (`ifconfig pflow0 2>&1` ne "pflow0: no such interface\n") {
153	system('ifconfig', 'pflow0', 'destroy');
154}
155
156system('ifconfig', 'pflow0', 'flowsrc', '127.0.0.1', 'flowdst',
157    '127.0.0.1:9996', 'pflowproto', $ARGV[0]);
158
159system('./gen_traffic '.$ARGV[1].' &');
160
161if ($ARGV[0] == 9 && $ARGV[1] == 4) {
162	@elem_names = @v94_elem_names;
163} elsif ($ARGV[0] == 9 && $ARGV[1] == 6) {
164	@elem_names = @v96_elem_names;
165} elsif ($ARGV[0] == 10 && $ARGV[1] == 4) {
166	@elem_names = @v104_elem_names;
167} elsif ($ARGV[0] == 10 && $ARGV[1] == 6) {
168	@elem_names = @v106_elem_names;
169}
170
171$sock = IO::Socket::INET->new(LocalPort =>$port, Proto => 'udp');
172while ($sock->recv($packet,1548)) {
173	($header_ref, $template_ref, $flows_ref, $error_ref) =
174		Net::Flow::decode(\$packet, $template_ref);
175	if (scalar(@$flows_ref) > 0) {
176		say scalar(@$flows_ref),' flows';
177		foreach $flow_ref (@$flows_ref) {
178			say scalar(keys %$flow_ref) - 1, ' elements';
179			say 'SetId: ', $flow_ref->{'SetId'};
180			my ($iif, $eif, $start, $end);
181
182			my $qpack = $Config{longsize} == 8 ? 'Q>' :
183			    $Config{byteorder} == 1234 ? 'L>xxxx' : 'xxxxL>';
184
185			foreach $name (@elem_names) {
186				if ($name eq 'ingressInterface') {
187					$iif = unpack('N',
188					    $flow_ref->{name2id($name)});
189				} elsif ($name eq 'egressInterface') {
190					$eif = unpack('N',
191					    $flow_ref->{name2id($name)});
192				} elsif ($name eq 'flowStartSysUpTime') {
193					$start = unpack('N',
194					    $flow_ref->{name2id($name)})/1000;
195				} elsif ($name eq 'flowEndSysUpTime') {
196					$end = unpack('N',
197					    $flow_ref->{name2id($name)})/1000;
198				} elsif ($name eq 'flowStartSeconds') {
199					$start = unpack('N',
200					    $flow_ref->{name2id($name)});
201				} elsif ($name eq 'flowEndSeconds') {
202					$end = unpack('N',
203					    $flow_ref->{name2id($name)});
204				} elsif ($name eq 'flowStartMilliseconds') {
205					$start = unpack($qpack,
206					    $flow_ref->{name2id($name)})/1000;
207				} elsif ($name eq 'flowEndMilliseconds') {
208					$end = unpack($qpack,
209					    $flow_ref->{name2id($name)})/1000;
210				} else {
211					say $name,': ', unpack('H*',
212					    $flow_ref->{name2id($name)});
213				}
214			}
215
216			say 'ingressInterface == egressInterface && '.
217			    'egressInterface > 0: ', ($iif == $eif && $eif > 0);
218		}
219		last;
220	}
221}
222
223END {
224	system('ifconfig', 'pflow0', 'destroy');
225	system('ifconfig', 'lo0', 'inet', '10.11.12.13', 'delete');
226	system('ifconfig', 'lo0', 'inet6', '2001:db8::13', 'delete');
227}
228