ocserv 配合 freeradius 进行流量统计


ocserv 配合 FreeRADIUS 实现用户流量统计是一种常见的配置方式,通过 FreeRADIUS 的 radacct 表记录用户流量数据(包括上行流量、下行流量和总流量),并可进一步扩展为流量限制。

1. 安装和配置 FreeRADIUS

1.1 安装 FreeRADIUS

在服务器上安装 FreeRADIUS 和数据库支持(这里全部都是自己编译,因为需要支持MySQL):

pacman -U libmysqlclient* mysql-clients* freeradius* radcli* ocserv-allinone* 

1.2 配置数据库支持

1.2.0. 初始化数据库

mkdir -p /srv/mysql/{data,inno}
chown -R mysql:mysql /srv/mysql

nano -w /etc/mysql/my.cnf
innodb_data_home_dir = /srv/mysql/inno
innodb_log_group_home_dir = /srv/mysql/inno
basedir = /usr
datadir = /srv/mysql/data


mysqld --initialize --user=mysql --basedir=/usr --datadir=/srv/mysql/data --innodb_data_home_dir=/srv/mysql/inno --innodb_log_group_home_dir=/srv/mysql/inno

ALTER USER USER() IDENTIFIED BY 'password';

CREATE USER 'root'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

1.2.1. 创建数据库

登录数据库并创建 FreeRADIUS 的数据库和用户:

CREATE DATABASE radius;
CREATE USER 'radius'@'localhost' IDENTIFIED BY 'radius';
GRANT ALL PRIVILEGES ON radius.* TO 'radius'@'localhost';
FLUSH PRIVILEGES;

1.2.2. 导入 FreeRADIUS 数据库结构

mysql -u radius -p radius < /etc/raddb/mods-config/sql/main/mysql/schema.sql

修改表结构,方便统计操作

在 radcheck 和 radreply 表中添加 Domain 字段:

ALTER TABLE radcheck ADD COLUMN domain VARCHAR(64);
ALTER TABLE radreply ADD COLUMN domain VARCHAR(64);

创建用户

INSERT INTO radcheck (username, attribute, op, value, domain) 
VALUES ('test@domain.com', 'Cleartext-Password', ':=', 'password', 'domain.com');
  • username:用户名, @后的是主机名。
  • attribute:认证属性,Cleartext-Password 用于明文密码。
  • op:操作符,:= 表示赋值。
  • value:密码。
  • domain: 服务器的域名, 用于分开不同的服务器

添加应答属性(可选)
在 radreply 表中为用户配置会话的应答属性。
示例:VIP用户可以自定义路由

INSERT INTO radreply (username, attribute, op, value) 
VALUES ('user@domain.com', 'Framed-Route', ':=', '41.57.120.0/22');
  • attribute:应答属性,Framed-Route 表示分配的 IP 地址。
  • value:具体的值,这里是 41.57.120.0/22。

创建用户组

INSERT INTO radgroupcheck (groupname, attribute, op, value) VALUES ('free', 'Auth-Type', ':=', 'Accept');
INSERT INTO radgroupcheck (groupname, attribute, op, value) VALUES ('user', 'Auth-Type', ':=', 'Accept');
INSERT INTO radgroupcheck (groupname, attribute, op, value) VALUES ('vip', 'Auth-Type', ':=', 'Accept');
INSERT INTO radgroupcheck (groupname, attribute, op, value) VALUES ('admin', 'Auth-Type', ':=', 'Accept');
  • groupname:组的名称。
  • attribute:检查条件属性(如 Auth-Type)。
  • op:操作符,通常为 :=。
  • value:属性值(如 Accept)。

上面的配置了会导致计费功能失效,请不要设置,这里只是演示而已

将用户分配到组
在 radusergroup 表中,将用户加入指定组。
示例:将用户加入user 组, 注意,这里只是中间组, 由radius管理的, 实际的组需要在响应里面配置

INSERT INTO radusergroup (username, groupname, priority)
VALUES ('test@domain.com', 'vip', 1);
  • username:用户名。
  • groupname:组名(如 admin、user)。
  • priority:优先级,数值越小优先级越高。


配置组的返回属性
在 radgroupreply 表中,为组配置应答属性。

基础组匹配信息

INSERT INTO radgroupreply (groupname, attribute, op, value)
VALUES ('free', 'Class', '=', 'optimize');
INSERT INTO radgroupreply (groupname, attribute, op, value)
VALUES ('user', 'Class', '=', 'optimize');
INSERT INTO radgroupreply (groupname, attribute, op, value)
VALUES ('user', 'Class', '=', 'cnnoroute');
INSERT INTO radgroupreply (groupname, attribute, op, value)
VALUES ('vip', 'Class', '=', 'optimize');
INSERT INTO radgroupreply (groupname, attribute, op, value)
VALUES ('vip', 'Class', '=', 'cnnoroute');
INSERT INTO radgroupreply (groupname, attribute, op, value)
VALUES ('vip', 'Class', '=', 'transmitted');
  • groupname:组的名称。
  • attribute:返回的 RADIUS 属性(如 Class 表示组名)。
  • op:操作符, =为追加,:=为替换。
  • value:返回值(如组的标识 transmitted)。

1.2.3. 配置 FreeRADIUS 使用数据库

nano -w /etc/raddb/mods-available/sql

dialect = "mysql"
driver = "rlm_sql_${dialect}"

mysql {
        # If any of the files below are set, TLS encryption is enabled
        tls {
                #ca_file = "/etc/ssl/certs/my_ca.crt"
                #ca_path = "/etc/ssl/certs/"
                #certificate_file = "/etc/ssl/certs/private/client.crt"
                #private_key_file = "/etc/ssl/certs/private/client.key"
                #cipher = "DHE-RSA-AES256-SHA:AES128-SHA"

                tls_required = no 
                tls_check_cert = no
                tls_check_cert_cn = no
        }

        # If yes, (or auto and libmysqlclient reports warnings are
        # available), will retrieve and log additional warnings from
        # the server if an error has occured. Defaults to 'auto'
        warnings = auto
}

server = "localhost"
port = 3306
login = "radius"
password = "radius"
radius_db = "radius"

read_clients = yes
read_groups = yes          # 启用组功能


启用 SQL 模块

ln -s /etc/raddb/mods-available/sql /etc/raddb/mods-enabled/

1.2.4. 在 FreeRADIUS 服务器中配置

nano -w /etc/raddb/clients.conf , 也可在localhost里面直接改密码

client localhost {
    ipaddr = 127.0.0.1           # ocserv 的服务器地址
    secret = testing123          # 共享密钥
    shortname = ocserv           # 客户端的名称
    require_message_authenticator = yes
                                 # 发送包含 Message-Authenticator 的请求
    nas_type = other             # NAS 类型,可选值:other、cisco、nokia 等
}
  • ipaddr:客户端的 IP 地址(可以是单个 IP 或子网)
  • secret:共享密钥,需与客户端配置一致
  • shortname:客户端的标识名称(可选,用于日志和管理)

1.2.5 生成自签名证书

nano -w /etc/raddb/certs/ca.cnf
nano -w /etc/raddb/certs/server.cnf

[ req_distinguished_name ]
commonName = radius.example.com  # 将 "radius.example.com" 替换为主机名
cd /etc/raddb/certs
nano -w client.cnf         替换为主机名
./bootstrap

该脚本会生成以下文件:

  • server.pem:服务器证书
  • server.key:服务器私钥
  • ca.pem:CA 证书(客户端需要)

2. 配置 ocserv 使用 FreeRADIUS

2.1 安装 FreeRADIUS 客户端工具

先测试一下

[root@ocserv certs]# radtest test@domain.com password 127.0.0.1 0 testing123
Sent Access-Request Id 58 from 0.0.0.0:44492 to 127.0.0.1:1812 length 74
        User-Name = "test"
        User-Password = "password"
        NAS-IP-Address = 172.16.1.187
        NAS-Port = 0
        Message-Authenticator = 0x00
        Cleartext-Password = "password"
Received Access-Accept Id 34 from 127.0.0.1:1812 to 127.0.0.1:44805 length 83
        Message-Authenticator = 0x52136e587e33a7d07e8ee9c2a620ca1a
        Framed-IP-Address = 172.16.200.100
        Class = 0x6f7074696d697a65
        Framed-Route = "41.57.120.0/22 172.16.200.1"

2.2 配置 ocserv

nano -w /etc/ocserv/ocserv.conf

#auth = "radius[config=/etc/radiusclient.default/radiusclient.conf]"
auth = "radius[config=/etc/radcli/radiusclient.conf]"

注意不要覆盖本地组配置的组配置属性

2.3 配置 radiusclient.conf

#nano -w /etc/radiusclient.default/radiusclient.conf
nano -w /etc/radcli/radiusclient.conf

authserver 127.0.0.1
acctserver 127.0.0.1:1813

#servers                /etc/radiusclient/servers
servers                /etc/radiusclient.default/servers

#dictionary     /etc/radiusclient/dictionary
#dictionary      /etc/radiusclient.default/dictionary
dictionary     /usr/share/radcli/dictionary

#nano -w /etc/radiusclient.default/servers
nano -w /etc/radcli/servers

localhost/localhost                            testing123

3. 配置 FreeRADIUS 记录用户流量

3.1 启用会计记录

确保 FreeRADIUS 的 acct 模块启用,记录用户会话流量。

nano -w /etc/raddb/sites-available/default

authorize {
    ...
    -sql
    # 检查用户是否超出流量限制
    if ("%{sql:SELECT disabled FROM user_limits WHERE username = SUBSTRING_INDEX('%{SQL-User-Name}', '@', 1)}" == 1) {
        reject  # 立即拒绝认证
        update reply {
            Reply-Message := "Your account has been disabled due to exceeding the traffic limit."
        }
    }
    ...
}

accounting {
    ...
    -sql
    ...
}

session {
    ...
    sql
    ...
}

3.2 检查会计记录

• 用户连接后,FreeRADIUS 会在 radacct 表中记录用户流量信息。

• 可以通过以下 SQL 查询查看用户流量:

SELECT UserName, AcctInputOctets, AcctOutputOctets, AcctSessionTime FROM radacct;

4. 扩展:流量限制,自动禁用超出的用户

4.1 流量限制表

创建一个用于存储用户流量限制和状态的表:

CREATE TABLE user_limits (
username VARCHAR(64) PRIMARY KEY, — 用户名,统一限制流量,domain可以不同
maxtraffic BIGINT NOT NULL, — 最大流量限制(字节)
currenttraffic BIGINT DEFAULT 0, — 当前已用流量(字节)
disabled TINYINT(1) DEFAULT 0 — 用户是否被禁用(0: 启用, 1: 禁用)
);

4.2 为用户添加初始限额

连接后,可以通过以下 SQL 查询验证用户流量是否被正确记录:

INSERT INTO user_limits (username, maxtraffic) VALUES
('test', 21474836480);  -- 20 GB

5.3 显示用户每个月的流量

当用户流量超出 Max-Monthly-Traffic 值时,FreeRADIUS 会自动断开用户的 VPN 连接。

CREATE VIEW `radius`.`radtraffic` AS SELECT username,
       SUM(acctinputoctets + acctoutputoctets) AS totaltraffic
FROM radacct
WHERE acctstarttime >= DATE_FORMAT(NOW() ,'%Y-%m-01 00:00:00')
  AND acctstarttime < DATE_FORMAT(NOW() + INTERVAL 1 MONTH,'%Y-%m-01 00:00:00')
GROUP BY username;

5.4 通过 SQL 更新用户的流量数据到 user_limits 表

UPDATE user_limits ul
JOIN (
    SELECT 
        username,
        SUM(acctinputoctets) +
        SUM(acctoutputoctets) AS totaltraffic
    FROM radacct
    GROUP BY username
) rt ON ul.username = rt.username
SET ul.currenttraffic = rt.totaltraffic;

5.5 禁用超出流量限制的用户

UPDATE user_limits
SET disabled = 1
WHERE currenttraffic > maxtraffic;

5.6 禁用超出流量限制的用户

创建时间15分钟执行事件

CREATE EVENT `radius`.`radlimit`
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL '15' MINUTE
DO UPDATE user_limits ul
JOIN (
    SELECT 
        username,
        SUM(acctinputoctets) +
        SUM(acctoutputoctets) AS totaltraffic
    FROM radacct
    GROUP BY username
) rt ON ul.username = rt.username
SET ul.currenttraffic = rt.totaltraffic;

UPDATE user_limits
SET disabled = 1
WHERE currenttraffic > maxtraffic;

关于Zeno Chen

本人涉及的领域较多,杂而不精 程序设计语言: Perl, Java, PHP, Python; 数据库系统: MySQL,Oracle; 偶尔做做电路板的开发,主攻STM32单片机
此条目发表在Linux分类目录。将固定链接加入收藏夹。