Я не сомневаюсь в том, что эту задачу можно решить средствами готовых биллинговых систем - из свободных первым в голову приходит NeTAMS. Но слишком уж все они монсторобразны для такой простой задачи. Попробуем решить ее на коленке с помощью connexion по мотивам следующего примера.
Устанавливаем все необходимое:
# apt-get install python-module-cxnet python-module-MySQLdb MySQL-server # service mysqld startСоздаем базу данных и таблицу, в которой будем хранить билеты, выписываем первый билет:
$ mysql -u root
...
mysql> create database cx;
Query OK, 1 row affected (0.00 sec)
mysql> use cx;
Database changed
mysql> create table tickets (
-> id serial,
-> host varchar(50),
-> bytes bigint,
-> max_bytes bigint,
-> enabled boolean
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> insert into tickets(host, bytes, max_bytes, enabled) values ('192.168.100.103', 0, 100, true);
Query OK, 1 row affected (0.00 sec)
mysql> select * from tickets;
+----+-----------------+-------+-----------+---------+
| id | host | bytes | max_bytes | enabled |
+----+-----------------+-------+-----------+---------+
| 1 | 192.168.100.103 | 0| 100 | 1 |
+----+-----------------+-------+-----------+---------+
1 row in set (0.00 sec)
Входящие пакеты для хоста 192.168.100.103 завернем в userspace следующим образом:# modprobe ip_queue # iptables -A FORWARD -p icmp -d 192.168.100.103 -j QUEUEА обрабатывать эти пакеты и решать, пропустить или нет, мы будем следующей программой, использующей один из компонентов connexion - библиотеку cxnet:
#!/usr/bin/python
from cxnet.netlink.ipq import *
from cxnet.ip4 import *
from cxnet.utils import *
import MySQLdb
db = MySQLdb.connect(host="localhost", user="root", passwd="", db="cx")
cursor = db.cursor()
ipqs = ipq_socket()
while True:
(l,msg) = ipqs.recv()
header = iphdr.from_address(addressof(msg.data.payload))
if header.daddr>0:
host = int_to_dqn(header.daddr)
size = header.tot_len
print "packet: %s:%s" % (host,size)
cursor.execute("update tickets set bytes = bytes+%s where enabled = true and host = %s", (size, host))
cursor.execute("update tickets set enabled = false where enabled = true and bytes >= max_bytes")
cursor.execute("select count(*) from tickets where enabled = true and host = %s", host)
for record in cursor.fetchall():
print "count: %s" % record[0]
if record[0] > 0:
ipqs.verdict(msg.data.packet_id, NF_ACCEPT)
continue
ipqs.verdict(msg.data.packet_id, NF_DROP)
Теперь отправляем первый пинг с хоста 192.168.100.103 наружу:$ ping -c 1 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.12 ms --- 192.168.1.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.121/1.121/1.121/0.000 msПри этом билет изменится следующим образом:
mysql> select * from tickets; +----+-----------------+-------+-----------+---------+ | id | host | bytes | max_bytes | enabled | +----+-----------------+-------+-----------+---------+ | 1 | 192.168.100.103 | 84 | 100 | 1 | +----+-----------------+-------+-----------+---------+ 1 row in set (0.00 sec)Отправим следующий пинг:
$ ping -c 1 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. --- 192.168.1.1 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0msПри этом билет будет заблокирован, поскольку число принятых байт превысило лимит:
mysql> select * from tickets; +----+-----------------+-------+-----------+---------+ | id | host | bytes | max_bytes | enabled | +----+-----------------+-------+-----------+---------+ | 1 | 192.168.100.103 | 168 | 100 | 0 | +----+-----------------+-------+-----------+---------+ 1 row in set (0.00 sec)Работает! Правда, тормозить должно жутко, ибо задавать кучу вопросов СУБД (пусть даже и MySQL с дефолтным хранилищем MyISAM, которое и транзакций-то не умеет) на каждый входящий пакет - удовольствие не дешевое. Но о бенчмарках в следующий раз ...