FreeRADIUS部署实战:从环境准备到动态VLAN分配
1. 这不是“装个软件就完事”的活FreeRADIUS部署的真实门槛在哪里很多人看到“10分钟搭建”这个标题第一反应是点开就抄命令、复制粘贴完事——结果十有八九卡在第3步radiusd -X启动失败日志里满屏红色报错或者认证测试时radtest返回Access-Reject却死活找不到原因更常见的是服务器跑起来了但连上WiFi后设备反复弹登录框后台日志却安静得像没这回事。我带过三届网络工程方向的实习生几乎每届都有人把FreeRADIUS当成“Linux版Windows认证服务”照着某篇博客敲完命令以为万事大吉结果在客户现场调试到凌晨三点才发现根本没配对数据库字段或者EAP-TLS证书链压根没导入客户端。FreeRADIUS不是Apache或Nginx那种“开箱即用”的Web服务器。它是一套可编程的认证策略引擎核心价值不在于“能运行”而在于“能精准控制谁、在什么条件下、以什么方式、访问哪类资源”。它的配置文件不是INI格式的参数堆砌而是类似轻量级脚本语言的策略描述if (User-Name ~ /^guest-./)是条件判断update reply { Reply-Message : Welcome, guest! }是动作执行post-auth { exec: /usr/local/bin/log_access.sh %u %t }是外部钩子调用。你看到的/etc/freeradius/3.0/sites-enabled/default本质上是一张状态机流程图的文字化表达——从接收请求、校验用户名密码、查数据库、执行授权逻辑到最终返回Accept/Reject每一步都可能被重写、跳过或注入自定义逻辑。所以“10分钟”指的从来不是“从零到全功能上线”而是从环境就绪到第一个成功认证请求落地的端到端耗时。这背后隐含了三个硬性前提第一操作系统已预装必要依赖OpenSSL、libssl-dev、python3-dev等第二你已明确认证模式PAPMS-CHAPv2EAP-TTLS并准备好对应凭证明文密码库LDAP连接信息PKI证书第三你手边有一台能发RADIUS请求的测试设备比如一台装了radtest的Linux笔记本或支持802.1X的无线AP。没有这三个前提“10分钟”就是空中楼阁。本文要带你走通的正是这条被无数人跳过的“前提验证链”——不讲虚的架构图只拆解真实机房里拧螺丝时手心冒汗的那几个关键节点。2. 环境准备为什么Ubuntu 22.04 LTS是当前最稳的选择FreeRADIUS官方文档明确支持Debian系和RHEL系发行版但实际生产环境中我经手的73个部署案例里68个选的是Ubuntu 22.04 LTS。这不是跟风而是三个具体痛点被它一并解决第一OpenSSL版本兼容性。FreeRADIUS 3.2.x当前稳定主线深度依赖OpenSSL 3.0的API尤其是EAP-TLS握手阶段的密钥派生函数。CentOS 7默认带OpenSSL 1.0.2强行升级会破坏系统基础库RHEL 8虽预装3.0但其FIPS模式常与FreeRADIUS的随机数生成器冲突导致radiusd -X启动时卡在Initializing OpenSSL library。Ubuntu 22.04原生搭载OpenSSL 3.0.2且默认关闭FIPS省去90%的加密层报错排查。第二包管理器的可靠性。FreeRADIUS主程序、MySQL模块、LDAP模块、Python策略模块在apt仓库中版本严格对齐。比如freeradius-mysql包会自动安装匹配的libmysqlclient21而手动编译时若MySQL头文件版本与运行时库不一致rlm_sql_mysql.so加载必失败。我曾在一个客户现场花4小时定位问题最后发现是mysql_config --version返回8.0.33但dpkg -l | grep mysql显示安装的是8.0.28的二进制包——这种版本漂移在源码编译中极难察觉apt却通过Depends:字段强制约束。第三SELinux/AppArmor的零干扰。RHEL/CentOS默认启用SELinuxFreeRADIUS进程受限于radiusd_t域读取自定义证书路径如/opt/certs/需手动写策略模块Ubuntu默认用AppArmor其/etc/apparmor.d/usr.sbin.radiusd模板已预置对/etc/freeradius/**和/var/log/freeradius/**的宽松权限新增/opt/certs/只需一行/opt/certs/** r,即可生效5秒搞定。提示本文所有操作基于Ubuntu 22.04.3 LTS内核6.2.0-36-generic使用root用户执行。若用普通用户请在所有命令前加sudo并确保该用户在sudoers中免密执行systemctl和freeradius命令。现在开始实操。先更新系统并安装核心组件apt update apt upgrade -y apt install -y freeradius freeradius-mysql freeradius-ldap freeradius-utils python3-pip注意这里没装freeradius-postgresql——因为MySQL模块在企业网管系统中兼容性更好如Zabbix、LibreNMS对接RADIUS日志时MySQL驱动比PostgreSQL更成熟。freeradius-utils包含radtest、raddebug等诊断工具python3-pip为后续自定义策略预留扩展能力。安装完成后检查基础服务状态systemctl is-active freeradius # 应返回 inactive未启动 freeradius -v # 应输出版本号 FreeRADIUS Version 3.2.3如果freeradius -v报错command not found说明/usr/sbin不在PATH中某些最小化安装镜像会删PATH执行export PATH/usr/sbin:$PATH并加入~/.bashrc。这是新手最容易忽略的“环境变量陷阱”看似简单却能让后续所有调试命令失效。3. 配置骨架从default站点切入理解RADIUS请求的完整生命周期FreeRADIUS的配置目录结构常让新人晕头转向/etc/freeradius/3.0/下有mods-available/、mods-enabled/、sites-available/、sites-enabled/、policy.d/等多个子目录。别急着改文件先理解设计哲学——它采用模块化策略分层mods-available是功能模块仓库如sql、ldap、papmods-enabled是启用的模块软链接sites-available是业务场景模板如default处理常规认证inner-tunnel处理EAP内部隧道sites-enabled是激活的场景软链接policy.d/存放可复用的策略片段如密码强度校验规则。我们直接编辑/etc/freeradius/3.0/sites-enabled/default——这是所有认证请求的入口。打开后你会看到大量被#注释的段落别被吓住。RADIUS协议本身只有三个核心阶段接收Receive→ 处理Process→ 响应Reply。FreeRADIUS用authorize、authenticate、post-auth三个区块映射这三阶段而default站点的骨架就是围绕这三个区块构建的。3.1 authorize区块不是“鉴权”而是“请求预处理”很多人误以为authorize是检查用户权限其实它是请求合法性审查。比如检查NAS-IP-Address是否在白名单内防伪造请求解析User-Name字段剥离域名部分userdomain.com→user根据Calling-Station-IDMAC地址匹配设备类型触发不同策略看一段真实配置authorize { # 第一步预处理用户名去掉符号后的域名 if (User-Name ~ //) { update request { Stripped-User-Name : %{User-Name:-none} User-Name : %{Stripped-User-Name} } } # 第二步检查NAS来源只允许指定IP段的设备发请求 if (Client-IP-Address !~ /^10\.10\.0\.[0-9]$/) { reject } # 第三步调用SQL模块查询用户是否存在 sql }这段代码执行顺序是严格的先做字符串处理再做IP校验最后查数据库。如果IP校验失败reject指令会立即终止流程sql模块根本不会执行。这就是FreeRADIUS的“短路逻辑”——每个模块返回ok、notfound、fail等状态上层根据状态决定是否继续。注意sql模块在此处只是“查询用户是否存在”不涉及密码校验。密码比对由后续的authenticate区块完成。这是新手最大误区——总想在authorize里写if (User-Password 123)但FreeRADIUS禁止明文密码比较必须走PAP/CHAP等标准协议栈。3.2 authenticate区块密码校验的三种路径authenticate区块才是真正做密码验证的地方。FreeRADIUS支持三种主流方式选择取决于你的凭证存储形态认证方式适用场景配置要点典型错误PAP明文密码测试环境、LDAP直连Auth-Type PAP { pap }忘记在mods-enabled/pap中设置auto_header yes导致密码字段为空MS-CHAPv2Windows域集成Active Directory环境Auth-Type MS-CHAP { mschap }NAS设备未开启MS-CHAPv2支持返回Invalid RequestEAP-TTLS/PAP安全隧道公共WiFi、移动设备Auth-Type EAP { eap }eap { default_eap_type ttls }证书链不完整客户端提示“证书不受信任”我们以最简单的PAP为例在authenticate区块末尾添加authenticate { Auth-Type PAP { pap } }然后检查mods-enabled/pap文件确认以下两行未被注释pap { auto_header yes # 关键让PAP模块自动从Request中提取User-Password normalise yes }auto_header yes是生死线。没有它PAP模块收不到密码永远返回Invalid password。这个参数在FreeRADIUS 3.0.20之后才成为默认值但很多旧教程仍按老版本写法导致新装环境必踩坑。3.3 post-auth区块认证后的“善后工作”post-auth在认证成功Access-Accept或失败Access-Reject后触发是埋点监控、日志审计、动态VLAN分配的核心位置。比如post-auth { # 认证成功时记录详细日志 if (reply:Reply-Message) { update reply { Reply-Message : Authenticated via FreeRADIUS } } # 调用外部脚本记录用户上线时间 exec { wait yes program /usr/local/bin/radius_log.sh %{User-Name} %{Acct-Session-Time:-0} %{reply:Reply-Message} input_pairs request } # 动态分配VLAN需NAS支持Vendor-Specific属性 if (reply:Reply-Message Authenticated via FreeRADIUS) { update reply { Tunnel-Type : VLAN Tunnel-Medium-Type : IEEE-802 Tunnel-Private-Group-ID : 100 # 分配到VLAN 100 } } }这段配置展示了FreeRADIUS的“策略即代码”本质用条件判断if、变量赋值update、外部调用exec组合出复杂业务逻辑。Tunnel-Private-Group-ID是Cisco私有属性值100会被NAS设备识别为VLAN ID无需额外开发。4. 数据源对接MySQL实战——从建库到密码哈希的完整链路FreeRADIUS本身不存用户数据它像一个“认证中间件”把认证请求转发给后端数据源。MySQL是最常用选择因其与现有网管系统如PHPMyAdmin、Grafana生态无缝衔接。但直接存明文密码是严重安全违规必须用强哈希。FreeRADIUS原生支持crypt、md5、sha256、sha512四种哈希算法其中sha512是当前推荐标准crypt仅用于兼容旧系统。4.1 创建数据库与用户表登录MySQL假设root密码为admin123mysql -u root -p执行建库建表语句CREATE DATABASE radius; GRANT ALL PRIVILEGES ON radius.* TO radiuslocalhost IDENTIFIED BY radius123; FLUSH PRIVILEGES; USE radius; CREATE TABLE radcheck ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(64) NOT NULL DEFAULT , attribute VARCHAR(64) NOT NULL DEFAULT , op CHAR(2) NOT NULL DEFAULT , value VARCHAR(253) NOT NULL DEFAULT , PRIMARY KEY (id), KEY username (username(32)) ); -- 插入测试用户testuser密码为sha512哈希值password INSERT INTO radcheck (username, attribute, op, value) VALUES (testuser, Cleartext-Password, :, password), (admin, MD5-Password, :, 5f4dcc3b5aa765d61d8327deb882cf99);注意radcheck表的设计逻辑attribute字段指定密码类型Cleartext-Password表示明文MD5-Password表示MD5哈希op字段:表示赋值value存实际凭证。FreeRADIUS会根据attribute值自动选择校验算法。4.2 配置SQL模块连接MySQL编辑/etc/freeradius/3.0/mods-enabled/sql修改以下关键参数sql { driver rlm_sql_mysql dialect mysql server localhost port 3306 login radius password radius123 radius_db radius # 关键指定密码查询SQL语句 read_groups false read_profiles false delete_stale_sessions true # 密码查询语句——必须精确匹配radcheck表结构 queries { authorize_check_query SELECT id, username, attribute, value, op FROM radcheck WHERE username %{SQL-User-Name} ORDER BY id } }authorize_check_query是灵魂所在。它告诉FreeRADIUS“当用户testuser来认证时去radcheck表查所有usernametestuser的记录按id排序”。返回结果中attributeCleartext-Password的记录会被PAP模块捕获valuepassword作为明文密码参与校验。4.3 密码哈希实战用Python生成sha512密码明文存库是自杀行为。生产环境必须用哈希。FreeRADIUS自带radmin工具可生成但更推荐用Python——可控性强易集成到自动化脚本import hashlib import base64 import os def generate_sha512_password(password): # 生成16字节随机盐值 salt os.urandom(16) # sha512(密码盐) hash_obj hashlib.sha512(password.encode() salt) # Base64编码hashsalt result base64.b64encode(hash_obj.digest() salt).decode() return result print(generate_sha512_password(mysecurepass)) # 输出类似KQJqYz...64位Base64字符串将生成的哈希值存入数据库INSERT INTO radcheck (username, attribute, op, value) VALUES (secureuser, SHA512-Password, :, KQJqYz...);此时attribute改为SHA512-PasswordFreeRADIUS会自动调用sha512校验逻辑。整个过程无需修改任何FreeRADIUS配置只改数据库字段——这就是模块化设计的优势。5. 实战测试用radtest模拟NAS定位90%的配置错误部署成败5分钟见分晓。radtest是FreeRADIUS自带的RADIUS客户端它模拟NAS设备向服务器发送认证请求是调试的黄金工具。别用浏览器或手机APP测试——它们封装太深错误信息不透明。5.1 启动FreeRADIUS调试模式先停掉系统服务用前台调试模式启动systemctl stop freeradius freeradius -X-X参数让FreeRADIUS以前台方式运行并输出超详细日志包括每个模块的输入输出、变量值、SQL查询语句。这是唯一能看到“内部世界”的窗口。5.2 发送测试请求新开一个终端执行radtest testuser password 127.0.0.1 0 testing123参数解析testuser用户名对应radcheck.usernamepassword明文密码对应radcheck.value中Cleartext-Password的值127.0.0.1RADIUS服务器地址0RADIUS端口认证端口默认1812testing123共享密钥/etc/freeradius/3.0/clients.conf中定义如果配置正确freeradius -X终端会滚动大量日志最终出现(0) Finished request Waking up in 4.9 seconds. (0) Sending Access-Accept of id 15 to 127.0.0.1 port 33212同时radtest终端显示Sending Access-Request of id 15 to 127.0.0.1 port 1812 ... Received Access-Accept Id 15 from 127.0.0.1:1812 to 127.0.0.1:33212 length 205.3 三类高频错误的日志特征与修复错误1No reply from server日志中无任何Sending或Received记录freeradius -X也无响应。→ 原因clients.conf中客户端IP未配置或共享密钥不匹配。→ 修复检查/etc/freeradius/3.0/clients.conf确保有client localhost { ipaddr 127.0.0.1 secret testing123 # 必须与radtest最后参数完全一致 require_message_authenticator no }错误2Invalid user或User not found日志中出现sql: Executing query: SELECT ...但返回空结果。→ 原因radcheck表中无该用户名或username字段有空格/大小写不匹配。→ 修复登录MySQL执行SELECT * FROM radcheck WHERE usernametestuser;确认记录存在且无隐藏字符。错误3Invalid password日志显示pap: Login attempt with password password但后续报错pap: Password verification failed。→ 原因mods-enabled/pap中auto_header no或数据库中attribute写成Password而非Cleartext-Password。→ 修复检查pap模块配置确认attribute值与数据库严格一致。经验每次修改配置后务必用freeradius -C做语法检查-C是config check它会扫描所有.conf文件报告缺失模块、未闭合括号等硬错误。比直接-X启动快10倍避免浪费时间在语法错误上。6. 安全加固生产环境不可跳过的五道防线FreeRADIUS默认配置面向开发测试生产环境必须加固。这不是“锦上添花”而是防止服务器沦为DDoS反射放大器或密码爆破靶机。6.1 限制监听地址与端口默认/etc/freeradius/3.0/sites-enabled/default监听所有IP的1812端口。生产环境应绑定到内网IP编辑/etc/freeradius/3.0/sites-enabled/default找到listen区块listen { type auth ipaddr 10.10.0.100 # 改为你的内网管理IP port 1812 }同时在/etc/freeradius/3.0/clients.conf中删除client default块只为每个NAS设备单独配置client ap1 { ipaddr 10.10.1.10 secret ap1_secret_key } client ap2 { ipaddr 10.10.1.11 secret ap2_secret_key }这样只有10.10.1.10和10.10.1.11能发请求其他IP连接直接被拒绝。6.2 启用速率限制防暴力破解FreeRADIUS内置rlm_attr_filter模块可实现IP级限速。编辑/etc/freeradius/3.0/mods-enabled/attr_filter取消注释并修改attr_filter rate_limit { attrsfile ${modconfdir}/${.:name}/attrs.rate_limit key %{Client-IP-Address} }创建/etc/freeradius/3.0/mods-config/attr_filter/rate_limit文件DEFAULT Framed-Protocol PPP, Service-Type Outbound-User, Simultaneous-Use 1 DEFAULT NAS-IP-Address 10.10.1.10, Max-All-Session-Time 3600再在sites-enabled/default的authorize区块开头加入authorize { # 限速每IP每分钟最多5次认证请求 if (%{Client-IP-Address} ~ /^10\.10\.1\./) { attr_filter rate_limit } ... }此配置对10.10.1.0/24网段的NAS设备启用限速超过阈值的请求直接reject。6.3 日志审计与异常告警FreeRADIUS日志默认写入/var/log/freeradius/radius.log但原始日志无结构化字段难以分析。我们用rsyslog将其转为JSON格式接入ELK在/etc/rsyslog.d/50-freeradius.conf中添加module(loadimfile) input(typeimfile File/var/log/freeradius/radius.log Tagfreeradius Severityinfo Facilitylocal7) template(namejson typelist) { constant(value{) constant(value\timestamp\:\) property(nametimereported dateFormatrfc3339) constant(value\,\level\:\) property(namesyslogseverity-text) constant(value\,\message\:\) property(namemsg formatjson) constant(value\}) } if $programname freeradius then { action(typeomfwd protocoltcp target10.10.0.200 port5044 templatejson) }重启服务systemctl restart rsyslog freeradius。此后所有认证日志以JSON格式发送到10.10.0.200:5044可在Kibana中创建看板实时监控Access-Reject率突增可能遭遇爆破、单IP高频请求需封禁等异常。6.4 TLS证书强制启用EAP场景若使用EAP-TLS必须禁用不安全的TLS版本。编辑/etc/freeradius/3.0/mods-enabled/eapeap { default_eap_type tls timer_expire 60 ignore_unknown_eap_types no tls { private_key_file ${certdir}/server.key certificate_file ${certdir}/server.pem ca_file ${cadir}/ca.pem dh_file ${certdir}/dh # 强制TLS 1.2禁用SSLv3/TLS1.0 cipher_list HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4:!SHA1 tls_min_version 1.2 tls_max_version 1.3 } }cipher_list中!SHA1是关键——SHA1签名已被证实不安全现代客户端iOS 15、Android 12默认拒绝SHA1证书。6.5 文件权限最小化原则FreeRADIUS进程以freerad用户运行但配置文件默认属主是root。攻击者若获得freerad权限可读取clients.conf中的共享密钥。必须收紧权限chown -R root:freerad /etc/freeradius/3.0/ chmod -R 750 /etc/freeradius/3.0/ chmod 640 /etc/freeradius/3.0/clients.conf chmod 600 /etc/freeradius/3.0/certs/*/etc/freeradius/3.0/certs/目录必须600权限否则FreeRADIUS启动时报Permission denied——这是证书私钥的安全红线。7. 故障排查全景图从radtest失败到Access-Accept的完整决策树当radtest返回No response或Access-Reject不要盲目改配置。按以下决策树逐层排查90%的问题能在5分钟内定位7.1 网络层确认请求是否抵达服务器在FreeRADIUS服务器上执行tcpdump -i any port 1812 -nn -vv然后运行radtest。如果tcpdump无任何输出说明请求未到达服务器——检查客户端防火墙ufw status确保1812端口开放服务器防火墙iptables -L INPUT | grep 1812网络路由ping 10.10.0.100服务器内网IP是否通7.2 协议层确认共享密钥与客户端匹配tcpdump若捕获到UDP包但freeradius -X无日志大概率是共享密钥错误。FreeRADIUS收到请求后先用密钥解密密钥错误则直接丢弃不记录日志。验证方法# 查看clients.conf中对应客户端的secret grep -A 5 client ap1 /etc/freeradius/3.0/clients.conf # 确保radtest命令最后参数与此secret完全一致区分大小写、空格 radtest testuser pass 10.10.0.100 0 ap1_secret_key7.3 配置层确认模块加载与路径正确freeradius -X启动时首屏会列出加载的模块。搜索关键词sql若无此行说明mods-enabled/sql未正确软链接pap若无此行检查mods-enabled/pap是否被注释Listening on auth address 10.10.0.100 port 1812确认监听IP正确7.4 数据层确认SQL查询返回预期结果在freeradius -X日志中搜索sql: Executing query复制完整的SQL语句在MySQL中手动执行SELECT id, username, attribute, value, op FROM radcheck WHERE username testuser ORDER BY id;检查返回结果是否包含attributeCleartext-Password且value值正确。若返回空检查username字段是否有不可见字符用SELECT HEX(username) FROM radcheck;查看十六进制编码。7.5 密码层确认哈希算法与存储格式匹配若SQL返回密码记录但日志显示pap: Password verification failed检查mods-enabled/pap中auto_header yes是否启用数据库中attribute值是否为Cleartext-Password非Password或User-Password若用哈希密码attribute是否为SHA512-Password且value是Base64编码的哈希盐这张决策树不是理论模型而是我过去三年在27个客户现场手写在笔记本上的真实排查路径。每一次Access-Reject背后都是某个环节的微小偏差——可能是clients.conf里多了一个空格可能是radcheck表中用户名末尾有换行符也可能是NAS设备固件Bug导致发送的User-Name字段被截断。FreeRADIUS的威力恰恰在于它把所有这些“黑盒”细节全部摊开在日志里供你审视。8. 进阶场景如何让FreeRADIUS成为网络准入的智能中枢FreeRADIUS的价值远不止“用户名密码登录”。当它与现有IT设施深度集成就能演变为网络准入控制NAC的智能中枢。以下是三个已在生产环境验证的进阶方案8.1 动态VLAN分配基于用户角色自动划分广播域传统VLAN需在交换机端口静态配置运维成本高。FreeRADIUS可通过RADIUS属性动态下发VLAN ID。关键配置在post-auth区块post-auth { # 根据用户组分配VLAN if (control:Tmp-String-0 guest) { update reply { Tunnel-Type : VLAN Tunnel-Medium-Type : IEEE-802 Tunnel-Private-Group-ID : 200 # Guest VLAN } } elsif (control:Tmp-String-0 employee) { update reply { Tunnel-Type : VLAN Tunnel-Medium-Type : IEEE-802 Tunnel-Private-Group-ID : 100 # Employee VLAN } } }control:Tmp-String-0的值从哪里来在authorize区块中查询数据库authorize { # 查询radusergroup表获取用户组 sql if (sql:group) { update control { Tmp-String-0 : %{sql:group} } } }radusergroup表结构CREATE TABLE radusergroup ( username VARCHAR(64) NOT NULL DEFAULT , groupname VARCHAR(64) NOT NULL DEFAULT , priority INT(11) NOT NULL DEFAULT 1 ); INSERT INTO radusergroup VALUES (testuser, guest, 1);当用户testuser认证时FreeRADIUS先查radusergroup得groupnameguest再在post-auth中下发VLAN 200。思科/华为交换机收到此属性后自动将该用户流量打上VLAN 200标签全程无需人工干预。8.2 与LDAP/Active Directory联动复用企业身份源企业已有AD域无需重复维护用户。FreeRADIUS通过rlm_ldap模块直连AD。配置要点编辑/etc/freeradius/3.0/mods-enabled/ldapldap { server dc1.corp.local identity CNradius-svc,OUService Accounts,DCcorp,DClocal password svc_password base_dn DCcorp,DClocal user { base_dn ${..base_dn} filter (sAMAccountName%{%{Stripped-User-Name}:-%{User-Name}}) password_attribute unicodePwd } }filter中sAMAccountName是AD用户登录名字段unicodePwd是AD密码属性需SSL连接。测试命令radtest testuser ad_password 127.0.0.1 0 testing123FreeRADIUS会向AD发起LDAP Bind请求成功即返回Access-Accept。此方案将认证源头从MySQL切换为AD用户增删改在AD中操作FreeRADIUS自动同步彻底消除账号不一致风险。8.3 自定义Python策略实现复杂业务逻辑FreeRADIUS支持Python模块执行任意逻辑。例如检测用户是否在黑名单Redis缓存或调用Webhook通知ITSM系统。创建/etc/freeradius/3.0/mods-config/python/custom.py