Подсчет трафика с помощью ng_netflow

Понадобилось мне как-то реализовать подсчет трафика на шлюзе, с установленной на нем ОС FreeBSD 7.3. Для подсчета трафика я решил использовать модуль ng_netflow подсистемы netgraph. Статистика будет складываться в БД и через веб морду просматриваться. В качестве СУБД буду использовать PostgreSQL, но ничто не мешает использовать другую СУБД. В качестве коллектора и анализатора статистики я использовал пакет flow-tools. Для начала нужно создать соответствующие узлы в netgraph. Для простоты развертывания на других машинах был написан следующий скрипт:
  1. #!/bin/sh
  2.  
  3. # PROVIDE: ngnetflow
  4. # REQUIRE: netif
  5. # KEYWORD: nojail shutdown
  6.  
  7. . /etc/rc.subr
  8.  
  9. name="ngnetflow"
  10. rcvar=`set_rcvar`
  11. start_cmd="${name}_start"
  12. stop_cmd=":"
  13.  
  14. load_rc_config $name
  15. eval "${rcvar}=\${${rcvar}:-'NO'}"
  16. ngnetflow_ifaces=${ngnetflow_ifaces:-""}
  17. ngnetflow_export=${ngnetflow_export:-""}
  18.  
  19. ngnetflow_start()
  20. {
  21.         if [ -z "${ngnetflow_ifaces}" ]; then
  22.                 echo "ngnetflow_ifaces is not set"
  23.                 return 1
  24.         fi
  25.  
  26.         if [ -z "${ngnetflow_export}" ]; then
  27.                 echo "ngnetflow_export is not set"
  28.                 return 1
  29.         fi
  30.  
  31.         count_if="0"
  32.         for iface in ${ngnetflow_ifaces}; do
  33.                 ngctl mkpeer ${iface}: tee lower left
  34.                 ngctl name ${iface}:lower ${iface}_tee
  35.                 ngctl connect ${iface}: ${iface}_tee: upper right
  36.  
  37.                 ngctl mkpeer ${iface}_tee: one2many left2right many0
  38.                 ngctl name ${iface}_tee:left2right ${iface}_tee_o2m
  39.                 ngctl connect ${iface}_tee: ${iface}_tee_o2m: right2left many1
  40.  
  41.                 if [ "${count_if}" -eq "0" ]; then
  42.                         ngctl mkpeer ${iface}_tee_o2m: netflow one iface0
  43.                         ngctl name ${iface}_tee_o2m:one netflow
  44.                 else
  45.                         ngctl connect ${iface}_tee_o2m: netflow: one iface${count_if}
  46.                 fi
  47.                 count_if=`echo ${count_if} | awk '{ $0++; print $0; }'`
  48.         done
  49.  
  50.         ngctl mkpeer netflow: ksocket export inet/dgram/udp
  51.         ngctl name netflow:export nf_ksocket
  52.         ngctl msg nf_ksocket: connect inet/${ngnetflow_export}
  53.  
  54.         echo "Netflow system started..."
  55.  
  56.         return 0
  57. }
  58.  
  59. run_rc_command "$1"
Скрипт прост в использовании - его нужно скопировать в /usr/local/etc/rc.d, дать права на исполнение и сделать соответствующие записи в /etc/rc.conf.
  1. ngnetflow_enable="YES"     # разрешаем запуск скрипта при старте системы
  2. ngnetflow_export="127.0.0.1:4444" # экспорт статистики на указанный здесь адрес
  3. ngnetflow_ifaces="if0 if1" # интерфейсы, на которых нужно считать трафик
Далее нужно установить пакет flow-tools.
  1. # cd /usr/ports/net-mgmt/flow-tools
  2. # make install clean
После завершения установки добавляем следующие строки в /etc/rc.conf и запускаем коллектор:
  1. flow_capture_enable="YES"
  2. flow_capture_port="4444"
  3. flow_capture_flags="-E 16M"
  1. # /usr/local/etc/rc.d/flow_capture start
Проверим запустился ли сервис:
  1. # sockstat -4 -l | grep flow
  2. flowtools flow-captu 1008  1  udp4   *:4444                *:*
Если в выводе команды вы видите что-то подобное, то запуск прошел успешно. Теперь запустим скрипт, который создаст соответствующие узлы в подсистеме netgraph.
  1. # /usr/local/etc/rc.d/ngnetflow start
Если скрипт не выдал никаких ошибок, значит запуск прошел успешно. Для перегона статистики в БД я написал два скрипта. Первый запускается каждый день и запихивает в БД статистику за прошлый день. Второй запускается раз в месяц и создает новую таблицу в БД. Первый скрипт (nf_daily.sh):
  1. #!/bin/sh
  2.  
  3. # Скрипт экспортирует статистику за день в БД
  4.  
  5. REP_Y=`date -v-1d "+%Y"` # report year
  6. REP_M=`date -v-1d "+%m"` # report month
  7. REP_D=`date -v-1d "+%d"` # report day
  8.  
  9. WORKDIR="/root/netflow"
  10. TMPDIR="/tmp"
  11. FLOWS_DIR="/var/db/flows"
  12. EXP_FORMAT="DPKTS,DOCTETS,SRCADDR,DSTADDR,NEXTHOP,INPUT,OUTPUT,SRCPORT,DSTPORT,PROT,TOS,TCP_FLAGS,SRC_MASK,DST_MASK"
  13.  
  14. DB_HOST="192.168.7.253"
  15. DB_PORT="5432"
  16. DB_USER="netflow"
  17. DB_PASS="1234"
  18. DB_NAME="netflow"
  19. DB_TABLE="${REP_Y}-${REP_M}"
  20. DB_COLUMNS="\
  21. \"dpkts\",\
  22. \"doctets\",\
  23. \"srcaddr\",\
  24. \"dstaddr\",\
  25. \"nexthop\",\
  26. \"input\",\
  27. \"output\",\
  28. \"srcport\",\
  29. \"dstport\",\
  30. \"proto\",\
  31. \"tos\",\
  32. \"tcp_flags\""
  33.  
  34.  
  35. EXP_FILE="${TMPDIR}/netflow.csv"
  36. SQL_FILE="${TMPDIR}/netflow.sql"
  37. LOG_FILE="${WORKDIR}/nf_daily.log"
  38.  
  39. flow-cat "${FLOWS_DIR}/${REP_Y}/${REP_Y}-${REP_M}/${REP_Y}-${REP_M}-${REP_D}" | \
  40. flow-export -f2 -m ${EXP_FORMAT} 2> /dev/null | \
  41. egrep -v "^#" > ${EXP_FILE}
  42.  
  43. if [ ! -f ${EXP_FILE} ]; then
  44.   echo "Export file not found"
  45.   exit 1
  46. fi
  47.  
  48. if [ -f ${SQL_FILE} ]; then
  49.   rm -f ${SQL_FILE}
  50. fi
  51.  
  52. for line in `cat ${EXP_FILE}`; do
  53.   data=`echo ${line} | awk '{ split($0, str, ",");
  54.                             printf("%u, %u, $$%s$$, $$%s$$, $$%s$$, %d, %d, %d, %d, %d, %d, %d",
  55.                                     str[1], str[2], str[3], str[4],
  56.                                     str[5], str[6], str[7], str[8],
  57.                                     str[9], str[10], str[11], str[12]);
  58.                             }'`
  59.   echo "INSERT INTO \"${DB_TABLE}\"(${DB_COLUMNS}) VALUES (${data});" >> $SQL_FILE
  60. done
  61.  
  62. psql -q -d ${DB_NAME} -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER}  -f $SQL_FILE
  63.  
  64. rm -f ${EXP_FILE}
  65. rm -f ${SQL_FILE}
  66.  
  67. exit 0
Второй скрипт (nf_month.sh):
  1. #!/bin/sh
  2.  
  3. DB_HOST="192.168.7.253"
  4. DB_PORT="5432"
  5. DB_USER="netflow"
  6. DB_PASS="1234"
  7. DB_NAME="netflow"
  8. DB_TABLE=`date "+%Y-%m"`
  9.  
  10. SQL_QUERY="CREATE TABLE \"${DB_TABLE}\" (\
  11.   \"id\" bigserial NOT NULL UNIQUE PRIMARY KEY,\
  12.   \"dpkts\" bigint,\
  13.   \"doctets\" bigint,\
  14.   \"srcaddr\" cidr,\
  15.   \"dstaddr\" cidr,\
  16.   \"nexthop\" cidr,\
  17.   \"input\" smallint,\
  18.   \"output\" smallint,\
  19.   \"srcport\" int,\
  20.   \"dstport\" int,\
  21.   \"proto\" smallint,\
  22.   \"tos\" smallint,\
  23.   \"tcp_flags\" smallint\
  24. );"
  25.  
  26. psql -q -d ${DB_NAME} -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER} -c "${SQL_QUERY}" > /dev/null 2>&1
  27.  
  28. exit 0
Поправьте параметры подключения к БД на свои и не забудте дать права на запуск скриптам. Теперь добавим их в крон:
  1. 0       1       1       *       *       /root/netflow/nf_month.sh
  2. 30      0       *       *       *       /root/netflow/nf_daily.sh
Осталось только создать таблицу за текущий месяц, чтобы скрипту nf_daily.sh было куда складывать статистику, для этого можно просто запустить скрипт nf_month.sh. Вот еще веб морда, написанная на перле, для просмотра собранной статистики.
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5.  
  6. my $DB_HOST = 'localhost';
  7. my $DB_PORT = '5432';
  8. my $DB_NAME = 'netflow';
  9. my $DB_USER = 'netflow';
  10. my $DB_PASS = '1234';
  11.  
  12. my $stat_month = '';
  13. my $query_menu = '
  14. SELECT
  15. 	"tablename"
  16. FROM "pg_tables"
  17. WHERE "tableowner" = $$#owner#$$;';
  18. my $query_content = '
  19. SELECT
  20. 	SUM("dpkts") AS "packets",
  21. 	(SUM("doctets")/1048576)::bigint AS "size",
  22. 	host("dstaddr") FROM "#tbl_name#"
  23. WHERE "dstaddr" << $$192.168.7.0/24$$
  24. GROUP BY "dstaddr"
  25. ORDER BY "dstaddr";';
  26.  
  27. if ($ENV{'REQUEST_METHOD'} && ($ENV{'REQUEST_METHOD'} eq 'GET')){
  28. 	my $param = $ENV{'QUERY_STRING'};
  29. 	foreach	(split(/&/, $param)){
  30. 		my @str = split(/=/);
  31. 		if ($str[0] eq 'month'){
  32. 			$stat_month = $str[1];
  33. 			$stat_month =~ s/'|"|\$|\(|\)//;
  34. 			last;
  35. 		}
  36. 	}
  37. }
  38.  
  39. print("Content-type: text/html\n\n");
  40.  
  41. $query_menu =~ s/#owner#/$DB_USER/;
  42. system("psql -h $DB_HOST -p $DB_PORT -d $DB_NAME -U $DB_USER -t -c '$query_menu' -o /tmp/db_menu.txt");
  43. if (length($stat_month) > 0){
  44. 	$query_content =~ s/#tbl_name#/$stat_month/;
  45. 	system("psql -h $DB_HOST -p $DB_PORT -d $DB_NAME -U $DB_USER -t -c '$query_content' -o /tmp/db_table.txt");
  46. }
  47.  
  48.  
  49. print('<html>',
  50. 			'<head>',
  51. 			'<title>Statistics</title>',
  52. 			'<style type="text/css">',
  53. 			'table { border-collapse: collapse; }',
  54. 			'.header { background-color: lightblue; }',
  55. 			'.content { background-color: orange; }',
  56. 			'</style>',
  57. 			'</head>',
  58. 			'<body>'
  59. );
  60.  
  61. sub get_menu(){
  62. 	my $text = '';
  63.  
  64. 	if (!open(FIN, "< /tmp/db_menu.txt")){
  65. 		return '<h3>Empty!!!</h3>'
  66. 	}
  67.  
  68. 	$text .= join('', '<table align="center" border="1" width="100%">',
  69. 				'<tr><td align="center" class="header">Menu</td></tr>'
  70. 	);
  71. 	while (<FIN>){
  72. 		chomp;
  73. 		next if length == 0;
  74. 		s/\s|\t//g;
  75. 		$text .= join('', '<tr><td align="center" class="content"><a href="?month=',
  76. 					$_,
  77. 					'">',
  78. 					$_,
  79. 					'</a></td></tr>'
  80. 		);
  81. 	}
  82. 	$text .= '</table>';
  83.  
  84. 	close(FIN);
  85.  
  86. 	return $text;
  87. }
  88.  
  89. sub get_content(){
  90. 	my $text = '';
  91.  
  92. 	if (!open(FIN, "< /tmp/db_table.txt")){
  93. 		return '<h3>Empty!!!</h3>';
  94. 	}
  95.  
  96. 	$text .= join('', '<table border="1">',
  97. 				'<tr><td class="header">packets</td>',
  98. 				'<td class="header">megabytes</td>',
  99. 				'<td class="header">IP</td></tr>'
  100. 	);
  101. 	while (<FIN>){
  102. 		chomp;
  103. 		next if length == 0;
  104. 		$text .= '<tr>';
  105. 		foreach (split(/\|/)){
  106. 			chomp;
  107. 			s/\s|\t//g;
  108. 			$text .= join('', '<td class="content">', $_, '</td>');
  109. 		}
  110. 		$text .= '</tr>';
  111. 	}
  112. 	$text .= '</table>';
  113.  
  114. 	close(FIN);
  115.  
  116. 	return $text;
  117. }
  118.  
  119. print('<table width="100%">',
  120. 			'<tr><td align="center" valign="top" width="15%">',
  121. 			get_menu(),
  122. 			'</td>'
  123. );
  124.  
  125. print('<td align="left" valign="top" width="85%">',
  126. 			get_content(),
  127. 			'</td></tr></table>',
  128. 			'</body></html>'
  129. );
  130.  
  131. system("rm -f /tmp/db_menu.txt /tmp/db_table.txt");
Ну на этом все, можно пользоваться.
ВложениеРазмер
Иконка простого текстового файла ngnetflow1.33 КБ
Иконка простого текстового файла nf_daily.sh2.82 КБ
Иконка простого текстового файла nf_month.sh583 байта

Добавить комментарий

Filtered text

CAPTCHA
Этот вопрос предназначен для предотвращения автоматизированной обработки форм.
Fill in the blank.