collector.pl 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. #!/usr/bin/perl -w
  2. # This is a Cisco NetFlow datagram collector
  3. # Netflow protocol reference:
  4. # http://www.cisco.com/en/US/products/sw/netmgtsw/ps1964/products_implementation_design_guide09186a00800d6a11.html
  5. # XXX Doesn't support NetFlow 9
  6. my $af;
  7. BEGIN {
  8. use strict;
  9. use warnings;
  10. use IO qw(Socket);
  11. use Socket;
  12. use Carp;
  13. use POSIX qw(strftime);
  14. use Getopt::Long;
  15. eval "use IO::Socket::INET6;";
  16. eval "use Socket6;";
  17. }
  18. ############################################################################
  19. sub timestamp()
  20. {
  21. return strftime "%Y-%m-%dT%H:%M:%S", localtime;
  22. }
  23. sub fuptime($)
  24. {
  25. my $t = shift;
  26. my $r = "";
  27. my $tmp;
  28. # Milliseconds
  29. $tmp = $t % 1000;
  30. $r = sprintf ".%03u%s", $tmp, $r;
  31. # Seconds
  32. $t = int($t / 1000);
  33. $tmp = $t % 60;
  34. $r = "${tmp}s${r}";
  35. # Minutes
  36. $t = int($t / 60);
  37. $tmp = $t % 60;
  38. $r = "${tmp}m${r}" if $tmp;
  39. # Hours
  40. $t = int($t / 60);
  41. $tmp = $t % 24;
  42. $r = "${tmp}h${r}" if $tmp;
  43. # Days
  44. $t = int($t / 24);
  45. $tmp = $t % 7;
  46. $r = "${tmp}d${r}" if $tmp;
  47. # Weeks
  48. $t = int($t / 7);
  49. $tmp = $t % 52;
  50. $r = "${tmp}w${r}" if $tmp;
  51. # Years
  52. $t = int($t / 52);
  53. $r = "${tmp}y${r}" if $tmp;
  54. return $r;
  55. }
  56. sub do_listen($$)
  57. {
  58. my $port = shift
  59. or confess "No UDP port specified";
  60. my $socket;
  61. if ($af == 4) {
  62. $socket = IO::Socket::INET->new(Proto=>'udp', LocalPort=>$port)
  63. or croak "Couldn't open UDP socket: $!";
  64. } elsif ($af == 6) {
  65. $socket = IO::Socket::INET6->new(Proto=>'udp', LocalPort=>$port)
  66. or croak "Couldn't open UDP socket: $!";
  67. } else {
  68. croak "Unsupported AF";
  69. }
  70. return $socket;
  71. }
  72. sub process_nf_v1($$)
  73. {
  74. my $sender = shift;
  75. my $pkt = shift;
  76. my %header;
  77. my %flow;
  78. my $sender_s;
  79. %header = qw();
  80. $sender_s = inet_ntoa($sender) if $af == 4;
  81. $sender_s = inet_ntop(AF_INET6, $sender) if $af == 6;
  82. ($header{ver}, $header{flows}, $header{uptime}, $header{secs},
  83. $header{nsecs}) = unpack("nnNNN", $pkt);
  84. if (length($pkt) < (16 + (48 * $header{flows}))) {
  85. printf STDERR timestamp()." Short Netflow v.1 packet: %d < %d\n",
  86. length($pkt), 16 + (48 * $header{flows});
  87. return;
  88. }
  89. printf timestamp() . " HEADER v.%u (%u flow%s)\n", $header{ver},
  90. $header{flows}, $header{flows} == 1 ? "" : "s";
  91. for(my $i = 0; $i < $header{flows}; $i++) {
  92. my $off = 16 + (48 * $i);
  93. my $ptr = substr($pkt, $off, 52);
  94. %flow = qw();
  95. (my $src1, my $src2, my $src3, my $src4,
  96. my $dst1, my $dst2, my $dst3, my $dst4,
  97. my $nxt1, my $nxt2, my $nxt3, my $nxt4,
  98. $flow{in_ndx}, $flow{out_ndx}, $flow{pkts}, $flow{bytes},
  99. $flow{start}, $flow{finish}, $flow{src_port}, $flow{dst_port},
  100. my $pad1, $flow{protocol}, $flow{tos}, $flow{tcp_flags}) =
  101. unpack("CCCCCCCCCCCCnnNNNNnnnCCC", $ptr);
  102. $flow{src} = sprintf "%u.%u.%u.%u", $src1, $src2, $src3, $src4;
  103. $flow{dst} = sprintf "%u.%u.%u.%u", $dst1, $dst2, $dst3, $dst4;
  104. $flow{nxt} = sprintf "%u.%u.%u.%u", $nxt1, $nxt2, $nxt3, $nxt4;
  105. printf timestamp() . " " .
  106. "from %s started %s finish %s proto %u %s:%u > %s:%u %u " .
  107. "packets %u octets\n",
  108. $sender_s,
  109. fuptime($flow{start}), fuptime($flow{finish}),
  110. $flow{protocol},
  111. $flow{src}, $flow{src_port}, $flow{dst}, $flow{dst_port},
  112. $flow{pkts}, $flow{bytes};
  113. }
  114. }
  115. sub process_nf_v5($$)
  116. {
  117. my $sender = shift;
  118. my $pkt = shift;
  119. my %header;
  120. my %flow;
  121. my $sender_s;
  122. %header = qw();
  123. $sender_s = inet_ntoa($sender) if $af == 4;
  124. $sender_s = inet_ntop(AF_INET6, $sender) if $af == 6;
  125. ($header{ver}, $header{flows}, $header{uptime}, $header{secs},
  126. $header{nsecs}, $header{flow_seq}, ) = unpack("nnNNNN", $pkt);
  127. if (length($pkt) < (24 + (48 * $header{flows}))) {
  128. printf STDERR timestamp()." Short Netflow v.1 packet: %d < %d\n",
  129. length($pkt), 24 + (48 * $header{flows});
  130. return;
  131. }
  132. printf timestamp() . " HEADER v.%u (%u flow%s) seq %u\n", $header{ver},
  133. $header{flows}, $header{flows} == 1 ? "" : "s", $header{flow_seq};
  134. for(my $i = 0; $i < $header{flows}; $i++) {
  135. my $off = 24 + (48 * $i);
  136. my $ptr = substr($pkt, $off, 52);
  137. %flow = qw();
  138. (my $src1, my $src2, my $src3, my $src4,
  139. my $dst1, my $dst2, my $dst3, my $dst4,
  140. my $nxt1, my $nxt2, my $nxt3, my $nxt4,
  141. $flow{in_ndx}, $flow{out_ndx}, $flow{pkts}, $flow{bytes},
  142. $flow{start}, $flow{finish}, $flow{src_port}, $flow{dst_port},
  143. my $pad1, $flow{tcp_flags}, $flow{protocol}, $flow{tos},
  144. $flow{src_as}, $flow{dst_as},
  145. $flow{src_mask}, $flow{dst_mask}) =
  146. unpack("CCCCCCCCCCCCnnNNNNnnCCCCnnCC", $ptr);
  147. $flow{src} = sprintf "%u.%u.%u.%u", $src1, $src2, $src3, $src4;
  148. $flow{dst} = sprintf "%u.%u.%u.%u", $dst1, $dst2, $dst3, $dst4;
  149. $flow{nxt} = sprintf "%u.%u.%u.%u", $nxt1, $nxt2, $nxt3, $nxt4;
  150. printf timestamp() . " " .
  151. "from %s started %s finish %s proto %u %s:%u > %s:%u %u " .
  152. "packets %u octets\n",
  153. $sender_s,
  154. fuptime($flow{start}), fuptime($flow{finish}),
  155. $flow{protocol},
  156. $flow{src}, $flow{src_port}, $flow{dst}, $flow{dst_port},
  157. $flow{pkts}, $flow{bytes};
  158. }
  159. }
  160. ############################################################################
  161. # Commandline options
  162. my $debug = 0;
  163. my $af4 = 0;
  164. my $af6 = 0;
  165. my $port;
  166. # Long option Short option
  167. GetOptions( 'debug+' => \$debug, 'd+' => \$debug,
  168. '4+' => \$af4,
  169. '6+' => \$af6,
  170. 'port=i' => \$port, 'p=i' => \$port);
  171. # Unbuffer output
  172. $| = 1;
  173. die "The -4 and -6 are mutually exclusive\n" if $af4 && $af6;
  174. die "You must specify a port (collector.pl -p XXX).\n" unless $port;
  175. $af4 = $af = 4 if $af4 || (!$af4 && !$af6);
  176. $af6 = $af = 6 if $af6;
  177. # These modules aren't standard everywhere, so load them only if necessary
  178. # Main loop - receive and process a packet
  179. for (;;) {
  180. my $socket;
  181. my $from;
  182. my $payload;
  183. my $ver;
  184. my $failcount = 0;
  185. my $netflow;
  186. my $junk;
  187. my $sender;
  188. # Open the listening port if we haven't already
  189. $socket = do_listen($port, $af) unless defined $socket;
  190. # Fetch a packet
  191. $from = $socket->recv($payload, 8192, 0);
  192. ($junk, $sender) = unpack_sockaddr_in($from) if $af4;
  193. ($junk, $sender) = unpack_sockaddr_in6($from) if $af6;
  194. # Reopen listening socket on error
  195. if (!defined $from) {
  196. $socket->close;
  197. undef $socket;
  198. $failcount++;
  199. die "Couldn't recv: $!\n" if ($failcount > 5);
  200. next; # Socket will be reopened at start of loop
  201. }
  202. if (length($payload) < 16) {
  203. printf STDERR timestamp()." Short packet recevied: %d < 16\n",
  204. length($payload);
  205. next;
  206. }
  207. # The version is always the first 16 bits of the packet
  208. ($ver) = unpack("n", $payload);
  209. if ($ver == 1) { process_nf_v1($sender, $payload); }
  210. elsif ($ver == 5) { process_nf_v5($sender, $payload); }
  211. else {
  212. printf STDERR timestamp()." Unsupported netflow version %d\n",
  213. $ver;
  214. next;
  215. }
  216. undef $payload;
  217. next;
  218. }
  219. exit 0;