Notice
This is an old article and only available in Chinese. If you need a translation, please leave a comment and I will do my best to provide it as soon as possible.
假设某服务器有一个接口 eth0
,并配置了三个 IP 地址 10.0.0.4
, 10.0.0.5
, 10.0.0.6
。需求就是,如何知道这一台服务器每个 IP 的历史出网流量。
思路由 @a350063 提供,很简单,活用 iptables
的计数器就 OK 了。这种方法非常灵活,可以对任何能用 iptables 规则描述的流量数据进行统计,比如统计某个源地址指定端口的进出流量。首先我们使用 iptables
增加一些规则:
1
2
3
# iptables -I OUTPUT -s 10.0.0.4/32 -j ACCEPT
# iptables -I OUTPUT -s 10.0.0.5/32 -j ACCEPT
# iptables -I OUTPUT -s 10.0.0.6/32 -j ACCEPT
Copy 执行完成之后,就可以直接使用命令 iptables -nvxL OUTPUT
看到每个 IP 的出网流量了:
1
2
3
4
5
6
# iptables -nvxL OUTPUT
Chain OUTPUT (policy ACCEPT 3856 packets, 1914416 bytes)
pkts bytes target prot opt in out source destination
12880 11794276 ACCEPT all -- * * 10.0.0.4 0.0.0.0/0
6850 5801012 ACCEPT all -- * * 10.0.0.5 0.0.0.0/0
110 9315 ACCEPT all -- * * 10.0.0.6 0.0.0.0/0
Copy
接下来就是想办法记录这些数据了。由于我比较懒,不想用什么杂七杂八的东西来完成这个工作,直接用 shell 脚本。至于数据存到哪儿,肯定是数据库了,省事儿嘛。先建个表:
1
2
3
4
5
6
7
8
9
10
DROP DATABASE IF EXISTS traffic_collection ;
CREATE DATABASE traffic_collection ;
USE traffic_collection ;
CREATE TABLE traffic_log (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT ,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,
ip_address VARCHAR ( 40 ) NOT NULL ,
outgoing_bytes INTEGER NOT NULL
);
Copy 然后写个获取数据,插入数据库并重置计数器的脚本:
1
2
3
4
5
6
7
#!/bin/bash
set -e
RECORD_SQL = "INSERT INTO traffic_log(ip_address, outgoing_bytes) VALUES " ` iptables -nvxL OUTPUT | grep -P '10\.0\.0\.[4-6]' | awk '{ print "(\""$8"\","$2")," }' ORS = '' | sed s/,$/\; /`
iptables -Z OUTPUT
mysql -u ****** --password= ****** -D traffic_collection -e " $RECORD_SQL "
Copy 脚本内容很简单,构造 SQL,清空 iptables 计数器并执行 SQL。可能有人已经发现了这个脚本存在的问题:构造 SQL 和清空计数器之间的流量信息可能会丢失。比较好的方案应该是不清空计数器,而是将上一次的数据与本次的数据做差,将这个差值存入数据库。但是我懒,不想管了(
让这个脚本每小时执行一次,就完事儿了。创建文件 /etc/systemd/system/record_traffic.service
:
1
2
3
4
5
6
[Unit]
Description=record traffic
[Service]
Type=oneshot
ExecStart=/bin/bash /root/record_traffic.sh
Copy 并创建 /etc/systemd/system/record_traffic.timer
:
1
2
3
4
5
6
7
8
9
[Unit]
Description=record traffic hourly
[Timer]
OnCalendar=*-*-* *:00:00
Persistent=true
[Install]
WantedBy=timers.target
Copy 最后 enable && start 这个 timer 即可。运行了一段时间之后,数据库内就有了大概这样的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MariaDB [traffic_collection]> select * from traffic_log limit 50 offset 2200;
+------+---------------------+------------+----------------+
| id | timestamp | ip_address | outgoing_bytes |
+------+---------------------+------------+----------------+
| 2201 | 2019-06-05 06:00:03 | 10.0.0.5 | 1649373 |
| 2202 | 2019-06-05 06:00:03 | 10.0.0.6 | 58858 |
| 2203 | 2019-06-05 07:00:01 | 10.0.0.4 | 16317328 |
| 2204 | 2019-06-05 07:00:01 | 10.0.0.5 | 2898066 |
| 2205 | 2019-06-05 07:00:01 | 10.0.0.6 | 45341 |
| 2206 | 2019-06-05 08:00:04 | 10.0.0.4 | 22077543 |
| 2207 | 2019-06-05 08:00:04 | 10.0.0.5 | 3361343 |
| 2208 | 2019-06-05 08:00:04 | 10.0.0.6 | 53434 |
| 2209 | 2019-06-05 09:00:04 | 10.0.0.4 | 34954806 |
| 2210 | 2019-06-05 09:00:04 | 10.0.0.5 | 22881420 |
| 2211 | 2019-06-05 09:00:04 | 10.0.0.6 | 179481 |
| 2212 | 2019-06-05 10:00:05 | 10.0.0.4 | 79252475 |
| 2213 | 2019-06-05 10:00:05 | 10.0.0.5 | 47689049 |
| 2214 | 2019-06-05 10:00:05 | 10.0.0.6 | 979906 |
+------+---------------------+------------+----------------+
14 rows in set (0.001 sec)
Copy 这个 timer 很不准啊