假设某服务器有一个接口 eth0,并配置了三个 IP 地址 10.0.0.4, 10.0.0.5, 10.0.0.6。需求就是,如何知道这一台服务器每个 IP 的历史出网流量。

思路由 @a350063 提供,很简单,活用 iptables 的计数器就 OK 了。这种方法非常灵活,可以对任何能用 iptables 规则描述的流量数据进行统计,比如统计某个源地址指定端口的进出流量。首先我们使用 iptables 增加一些规则:

# 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

执行完成之后,就可以直接使用命令 iptables -nvxL OUTPUT 看到每个 IP 的出网流量了:

# 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

接下来就是想办法记录这些数据了。由于我比较懒,不想用什么杂七杂八的东西来完成这个工作,直接用 shell 脚本。至于数据存到哪儿,肯定是数据库了,省事儿嘛。先建个表:

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
);

然后写个获取数据,插入数据库并重置计数器的脚本:

#!/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"

脚本内容很简单,构造 SQL,清空 iptables 计数器并执行 SQL。可能有人已经发现了这个脚本存在的问题:构造 SQL 和清空计数器之间的流量信息可能会丢失。比较好的方案应该是不清空计数器,而是将上一次的数据与本次的数据做差,将这个差值存入数据库。但是我懒,不想管了(

让这个脚本每小时执行一次,就完事儿了。创建文件 /etc/systemd/system/record_traffic.service

[Unit]
Description=record traffic

[Service]
Type=oneshot
ExecStart=/bin/bash /root/record_traffic.sh

并创建 /etc/systemd/system/record_traffic.timer

[Unit]
Description=record traffic hourly

[Timer]
OnCalendar=*-*-* *:00:00
Persistent=true

[Install]
WantedBy=timers.target

最后 enable && start 这个 timer 即可。运行了一段时间之后,数据库内就有了大概这样的数据:

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)

这个 timer 很不准啊