ToughRADIUS Handbook
Welcome to the ToughRADIUS Handbook — the canonical, in-repository, bilingual
documentation site for the ToughRADIUS
RADIUS server. It is built with mdBook and
lives in the repository under docs-site/, so the documentation is versioned,
reviewed, and validated by CI together with the code it describes.
The handbook is organized into two mirrored language sections. Pick a language to begin:
Each chapter exists in both languages with a matching structure, and the pages cross-link to their counterparts so you can switch language at any point.
Relationship to the existing GitBook site. This handbook is published to GitHub Pages at its custom domain https://www.toughradius.net/, while GitBook renders the same sources at
docs.toughradius.net. The mdBook handbook coexists with GitBook from a single source of truth rather than replacing it; see mdbook & GitBook Coexistence / mdbook 与 GitBook 并存 for the single-source-of-truth policy and the boundary between the two pipelines.
Build it locally
# Install mdBook (https://rust-lang.github.io/mdBook/guide/installation.html)
cargo install mdbook # or: brew install mdbook
# Build the static site into docs-site/book/
mdbook build docs-site
# Or serve with live reload at http://localhost:3000
mdbook serve docs-site
ToughRADIUS 使用手册
欢迎来到 ToughRADIUS 使用手册 —— 这是
ToughRADIUS RADIUS 服务器
随仓库维护的中英文双语文档站点。它基于
mdBook 构建,位于仓库的 docs-site/
目录下,因此文档与代码一起被版本化、评审,并由 CI 校验。
手册分为结构一一对应的两个语言区。请选择语言开始阅读:
每个章节都提供中英文两个版本且结构对应,页面之间相互交叉链接,方便随时切换语言。
与现有 GitBook 站点的关系。 本手册部署到 GitHub Pages,使用自定义域名 https://www.toughradius.net/ 对外服务;GitBook 则在
docs.toughradius.net渲染同一份源文件。本 mdBook 手册基于单一事实来源与 GitBook 并存而非替代; 单一事实来源策略与两套管线的边界详见 mdbook 与 GitBook 并存 / mdbook & GitBook Coexistence。
本地构建
# 安装 mdBook(https://rust-lang.github.io/mdBook/guide/installation.html)
cargo install mdbook # 或:brew install mdbook
# 将静态站点构建到 docs-site/book/
mdbook build docs-site
# 或开启热重载预览,访问 http://localhost:3000
mdbook serve docs-site
Overview
中文版本:概述
ToughRADIUS is a powerful, open-source RADIUS server written in Go, designed for ISPs, enterprise networks, and carriers. It implements the standard RADIUS protocols together with RadSec (RADIUS over TLS) and ships with a modern React Admin web management interface.
Core capabilities
- Standard RADIUS — full RFC 2865 (authentication) and RFC 2866 (accounting) support.
- RadSec — TLS-encrypted RADIUS over TCP (RFC 6614).
- Dynamic authorization — CoA and Disconnect messages (RFC 5176).
- EAP / 802.1X suite — challenge methods (EAP-MD5, EAP-MSCHAPv2) and tunneled methods: EAP-TLS (RFC 5216, certificate-based), PEAPv0/EAP-MSCHAPv2 (Windows / AD compatibility), and EAP-TTLS (RFC 5281, inner PAP / MS-CHAPv2). MS-CHAPv2-based methods are compatibility-oriented and carry an NTLMv1-like attack surface; prefer EAP-TLS where you control client certificates. TLS 1.3 (RFC 9190), TEAP, and EAP-PWD are tracked on the roadmap.
- Multi-vendor support — compatible with Cisco, MikroTik, Huawei, and other major network devices through vendor-specific attributes (VSAs).
- Modern management UI — a React Admin dashboard for users, profiles, online sessions, accounting, and audit logs.
- Multi-database — PostgreSQL (default) and SQLite (pure Go, no CGO).
Service model
The server runs several independent services concurrently; if any one fails, the process exits so a supervisor can restart it cleanly.
| Service | Protocol / Port | Purpose |
|---|---|---|
| Web / Admin API | HTTP, TCP 1816 | Management UI and REST API |
| RADIUS Auth | UDP 1812 | Authentication |
| RADIUS Acct | UDP 1813 | Accounting |
| RadSec | TLS over TCP 2083 | Encrypted RADIUS transport |
Where to go next
- Concepts & Terminology — AAA vocabulary, the request flow, and how each concept maps to the product.
- Quick Start — install, initialize, create a user, and
verify with
radtestin about ten minutes. - Vendor Integration Guide — case studies for MikroTik, Huawei, Cisco, H3C, ZTE, iKuai, and standard devices.
- Admin UI Manual — every page of the management console.
- Operations Guide — production configuration, certificates, monitoring, backup, and CLI tools.
- FAQ — answers to the questions everyone asks.
- Documentation Map — find the existing README, agent guide, security policy, feature checklist, roadmap, and RFC index.
- mdbook & GitBook Coexistence — how this handbook relates to the GitBook site and the single-source-of-truth policy.
Concepts & Terminology
中文版本:核心术语与概念
This chapter explains the core AAA terminology used throughout ToughRADIUS and maps each concept to where it lives in the product. For the authoritative list of protocol specifications and how each one maps to the code, see the Protocol & RFC Reference.
AAA in one paragraph
RADIUS (Remote Authentication Dial In User Service) is the protocol that lets a
network device ask a central server three questions: who is this user
(Authentication), what are they allowed to do (Authorization), and
what did they consume (Accounting). ToughRADIUS answers all three: it
validates credentials, returns authorization attributes (IP address, bandwidth,
VLAN, session limits) in the Access-Accept, and records usage from accounting
packets.
Core terms
| Term | Meaning in ToughRADIUS |
|---|---|
| NAS (Network Access Server) | The network device — a router, switch, BRAS, or wireless controller — that sends RADIUS requests. Each NAS is registered under NAS Devices in the admin UI with its IP address, shared secret, and vendor code. Requests from unregistered addresses are dropped. |
| Shared secret | A per-NAS password that authenticates RADIUS packets themselves (RFC 2865 §3). It must match on both the NAS and the ToughRADIUS NAS record. |
| Subscriber / RADIUS user | An account under RADIUS Users: username, password, expiration, optional static IPv4/IPv6 addresses, MAC/VLAN bindings, and a billing profile. |
Billing profile (RadiusProfile) | A reusable template under Billing Profiles: concurrent-session limit (active_num), upload/download rate in Kbps, address pool, IPv6 prefix, domain, and MAC/VLAN binding switches. A user inherits profile values for fields it leaves empty. |
| VSA (Vendor-Specific Attribute) | RADIUS attribute 26 (RFC 2865 §5.26) — a container that lets each vendor define private attributes. ToughRADIUS ships attribute dictionaries for 15+ vendors and emits vendor-specific authorization attributes based on the NAS vendor code. See the Vendor Integration Guide. |
| Vendor code | The IANA Private Enterprise Number that selects vendor behavior, e.g. 9 Cisco, 2011 Huawei, 14988 MikroTik, 25506 H3C, 3902 ZTE, 10055 iKuai, 0 standard. Set per NAS record; it controls which request parser and which Access-Accept attributes are used. |
| CoA / Disconnect (RFC 5176) | Dynamic Authorization: server-initiated messages that change a live session (CoA-Request, e.g. new Session-Timeout or Filter-Id) or terminate it (Disconnect-Request). Sent from the Online Sessions page to the NAS on UDP port 3799 (overridable per NAS). |
| RadSec (RFC 6614) | RADIUS over TLS on TCP port 2083. Wraps RADIUS in a TLS tunnel so packets can safely cross untrusted networks; plain UDP RADIUS relies only on the shared secret. |
| EAP (RFC 3748) | The Extensible Authentication Protocol used by 802.1X networks. ToughRADIUS implements EAP-MD5, EAP-MSCHAPv2, EAP-TLS, PEAPv0/EAP-MSCHAPv2, and EAP-TTLS; the active method and certificates are chosen in System Config → RADIUS. |
| Accounting session | The lifecycle reported by the NAS via Accounting-Request packets: Start → Interim-Update(s) → Stop (RFC 2866). Live sessions appear under Online Sessions (radius_online table); history under Accounting (radius_accounting). |
| Acct-Interim-Interval | How often (seconds) the NAS should send interim accounting updates. Returned in every Access-Accept from the radius.AcctInterimInterval setting. |
| Session-Timeout | Maximum remaining session time in seconds. ToughRADIUS sets it to the time left until the user’s expiration date, so a session never outlives the account. |
Address pool (Framed-Pool) | A named IP pool configured on the NAS. ToughRADIUS only returns the pool name; the NAS allocates the actual address. Static addresses, by contrast, are returned directly as Framed-IP-Address / Framed-IPv6-Address. |
| MAC binding | When a profile enables bind_mac, the first calling MAC seen is stored on the user and later requests must match it. |
| VLAN binding | When a profile enables bind_vlan, the inner/outer VLAN IDs parsed from NAS-Port-Id are stored and enforced the same way. Requires a vendor parser that extracts VLANs (Huawei, H3C, ZTE). |
How an authentication request flows
NAS ──Access-Request──▶ UDP 1812
│
▼
goroutine pool (ants, TOUGHRADIUS_RADIUS_POOL, default 1024)
│
▼
1. NAS lookup ─ source IP / identifier must match a registered NAS record
2. Vendor parser ─ extracts MAC (Calling-Station-Id) and VLANs (NAS-Port-Id)
· Huawei / H3C / ZTE parsers extract VLAN IDs
· all other vendors use the default parser (MAC only)
3. Credential validation ─ PAP / CHAP / MS-CHAPv2, or the EAP state machine
4. Checkers ─ account status, expiration, MAC bind, VLAN bind, concurrent
session count (active_num)
5. Accept enhancers ─ standard attributes (Session-Timeout, pools, static
IPs, IPv6) plus vendor attributes selected by the NAS vendor code
│
▼
NAS ◀─Access-Accept / Access-Reject──
Failed authentications are classified into Prometheus-style counters
(radus_reject_passwd_error, radus_reject_expire, …) that feed the
dashboard; see the Operations Guide.
A reject-delay guard slows brute-force attempts: after
radius.RejectDelayMaxRejects consecutive rejects (default 7) inside
radius.RejectDelayWindowSeconds (default 10 s), responses are delayed.
Password protocols at a glance
| Protocol | Where the password travels | Notes |
|---|---|---|
| PAP | In the request, XOR-protected by the shared secret (RFC 2865 §5.2) | Universally supported; pair with RadSec or a trusted network. |
| CHAP | Never — MD5 challenge/response (RFC 2865 §5.3) | Requires the server to know the cleartext password. |
| MS-CHAPv2 | Never — NT-hash challenge/response (RFC 2548) | Used by Windows; carries a well-known NTLMv1-like attack surface. |
| EAP (tunneled) | Inside a TLS tunnel (PEAP / EAP-TTLS) or replaced by certificates (EAP-TLS) | Preferred for Wi-Fi / 802.1X deployments. |
Related chapters
- Protocol & RFC Reference — every RFC cited above, mapped to code.
- Vendor Integration Guide — per-vendor attributes and formulas.
- Quick Start — see these concepts in action in 10 minutes.
Quick Start
中文版本:快速开始
This chapter takes you from nothing to a working RADIUS server with one test
user, then shows how to debug what the server is doing. Default ports: web UI
1816, RADIUS authentication UDP 1812, accounting UDP 1813, RadSec TCP
2083.
1. Install
Pick one of three paths.
Option A — pre-built binary
Download the binary for your platform from the
GitHub Releases page
(toughradius_linux_amd64, toughradius_linux_arm64, toughradius_darwin_arm64,
toughradius_windows_amd64.exe, …), then:
chmod +x toughradius_linux_amd64
sudo mv toughradius_linux_amd64 /usr/local/bin/toughradius
Option B — Docker
docker pull talkincode/toughradius:latest
docker run -d --name toughradius \
-p 1816:1816 -p 1812:1812/udp -p 1813:1813/udp -p 2083:2083 \
-v toughradius-data:/var/toughradius \
talkincode/toughradius:latest -c /etc/toughradius.yml
The image exposes 1816/tcp, 1812/udp, 1813/udp, and 2083/tcp. Mount a
volume at /var/toughradius (the default working directory) so the SQLite
database, logs, and certificates survive container restarts.
Option C — build from source
Requires Go 1.25+ and Node.js 20+ (the React Admin frontend is embedded into the binary):
git clone https://github.com/talkincode/toughradius.git
cd toughradius
make build # builds web/ then the Go binary → release/toughradius
For backend-only development with hot SQLite defaults:
make runs # CGO_ENABLED=0 go run main.go -c toughradius.yml
make runf # frontend dev server at http://localhost:3000/admin
2. Configure
ToughRADIUS looks for its configuration in this order: the -c <file> flag,
./toughradius.yml, /etc/toughradius.yml, and finally built-in defaults.
Environment variables override the file (see the
Operations Guide).
A minimal production-style configuration:
system:
appid: ToughRADIUS
location: Asia/Shanghai
workdir: /var/toughradius # data/logs/certs live here
debug: false
web:
host: 0.0.0.0
port: 1816
secret: change-me-to-a-long-random-string # JWT signing secret
database:
type: sqlite # or: postgres (+ host/port/user/passwd)
name: toughradius.db # stored under {workdir}/data/
radiusd:
enabled: true
host: 0.0.0.0
auth_port: 1812
acct_port: 1813
radsec_port: 2083
debug: true # log full packet dumps; disable in production
logger:
mode: production
file_enable: true
filename: /var/toughradius/toughradius.log
Change
web.secret. It signs admin login tokens. Likewise change the default admin password immediately after the first login.
3. Initialize the database and run
# FIRST TIME ONLY — drops and recreates all tables
toughradius -initdb -c /etc/toughradius.yml
# start the server
toughradius -c /etc/toughradius.yml
-initdb is destructive; on later upgrades just start the server — schema
migration runs automatically at startup. Other flags: -v prints the version,
-printcfg prints the merged configuration as JSON.
4. Log in to the admin UI
Open http://<server>:1816. The default administrator is:
- Username:
admin - Password:
toughradius
Change the password right away under Account Settings, or reset a lost one
with cmd/reset-password (see the FAQ).
5. Register a NAS and create a user
- Network Nodes → Create — make a node (a logical group), e.g.
default. - NAS Devices → Create — register your network device:
- IP address: the address the device sends RADIUS from.
- Secret: the shared secret, e.g.
testing123. - Vendor code: pick the device vendor (Standard / Cisco / Huawei / MikroTik / H3C / ZTE / iKuai) — this controls vendor-specific attributes; see the Vendor Integration Guide.
- CoA port: leave
3799unless your device uses another port.
- Billing Profiles → Create — e.g.
100M: concurrency1, up rate51200Kbps, down rate102400Kbps. - RADIUS Users → Create — username
test1, password111111, pick the profile and an expiration date.
6. Verify with radtest
The repository ships a small RADIUS client (defaults shown):
go run ./cmd/radtest auth \
-server 127.0.0.1 -secret testing123 \
-username test1 -password 111111
go run ./cmd/radtest flow ... # auth + acct-start + acct-stop in one run
A successful run prints Access-Accept with the returned attributes. The
session then appears under Online Sessions (for flow) and the dashboard
counters increase.
7. Debugging
| What you need | How |
|---|---|
| Full RADIUS packet dumps | radiusd.debug: true in the YAML (or env TOUGHRADIUS_RADIUS_DEBUG=true), or set System Config → RADIUS → Log Level to debug at runtime |
| Log file location | logger.filename, default {workdir}/toughradius.log; logger.mode: development for human-readable console output |
| Why a user is rejected | Reject reasons are counted per cause (wrong password, expired, bound MAC mismatch, …) and shown on the dashboard; the log carries the detail |
| Inspect effective config | toughradius -printcfg -c <file> |
| Load testing | go run ./cmd/benchmark — see the Operations Guide |
Next steps
- Vendor Integration Guide — configure Cisco, Huawei, MikroTik, H3C, ZTE, iKuai and standard devices.
- Admin UI Manual — every page of the management UI.
- Operations Guide — production deployment, TLS, EAP certificates, backups, and monitoring.
Vendor Integration Guide
中文版本:厂商对接指南
ToughRADIUS speaks standard RADIUS to every device and adds vendor-specific attributes (VSAs) for the vendors it knows. This chapter walks through the integration steps shared by all devices, then gives a case study per vendor: what ToughRADIUS sends, what it parses, and a reference configuration for the device side.
The vendor code on the NAS record decides everything. Attribute enhancement is selected by the Vendor field of the NAS device record in the admin UI — not by inspecting packets. If you leave a MikroTik registered as
Standard, it will authenticate fine but receive noMikrotik-Rate-Limit(no bandwidth enforcement). Pick the right vendor first.
📖 Want end-to-end operational examples (PPPoE speed tiers, Hotspot + MAC auth, CoA / forced disconnect)? See the Scenario Cookbook. This chapter is the attribute reference card; the cookbook is the playbook.
Portal boundary: ToughRADIUS is the RADIUS AAA backend. It does not host captive portal login pages or guest onboarding flows. See Portal / Hotspot Integration Boundary.
Integration steps for any device
- Register the NAS under NAS Devices → Create: source IP address (or identifier), shared secret, and the correct Vendor.
- Point the device at the server: authentication UDP
1812, accounting UDP1813, the same shared secret. - Optional CoA: ToughRADIUS sends CoA/Disconnect (RFC 5176) to the NAS on
UDP
3799by default; set the CoA port field on the NAS record if your device listens elsewhere. Each exchange waits up to 5 s and retransmits twice. - Create a billing profile and users, then test with
go run ./cmd/radtest auth …(Quick Start).
What every vendor receives (standard attributes)
Regardless of vendor, every Access-Accept may carry: Session-Timeout
(seconds until the account expires), Acct-Interim-Interval, Framed-Pool,
Framed-IP-Address (static IPv4), Framed-IPv6-Prefix / Framed-IPv6-Address
(RFC 6911), Framed-IPv6-Pool, Delegated-IPv6-Prefix (RFC 4818), and
Delegated-IPv6-Prefix-Pool — depending on which user/profile fields are set.
Rate-limit units
Profile rates are stored in Kbps. Each vendor enhancer converts:
| Vendor | Attributes | Value sent |
|---|---|---|
| Huawei (2011) | Huawei-Input/Output-Average-Rate, Huawei-Input/Output-Peak-Rate | average = rate_kbps × 1024 (bit/s); peak = × 4 further; clamped to Int32 max |
| MikroTik (14988) | Mikrotik-Rate-Limit | string "{up}k/{down}k", e.g. 51200k/102400k (rx/tx from the router’s view) |
| H3C (25506) | H3C-Input/Output-Average-Rate, peak variants | same ×1024 / ×4 scheme as Huawei |
| ZTE (3902) | ZTE-Rate-Ctrl-SCR-Up/Down | rate_kbps × 1024 |
| iKuai (10055) | RP-Upstream/Downstream-Speed-Limit | rate_kbps × 1024 × 8, clamped to Int32 max |
| Cisco (9), Standard (0) | — | standard attributes only; use device-side policy or Cisco-AVPair via custom integration |
Request parsing (MAC and VLAN)
The default parser reads the MAC address from Calling-Station-Id. Vendor
parsers add request-side VSA or NAS-Port-Id handling where the device family
has a stable encoding:
slot/subslot/port:vlan[.vlan2]— e.g.3/0/1:2814.727vlanid=<n>;vlanid2=<n>;— e.g.slot=2;...;vlanid=503;vlanid2=100;
| Vendor | MAC source | VLAN source | Notes |
|---|---|---|---|
| Huawei (2011) | Calling-Station-Id | NAS-Port-Id | Supports both encodings above. |
| H3C (25506) | Calling-Station-Id | NAS-Port-Id | Supports both encodings above. |
| ZTE (3902) | Calling-Station-Id | NAS-Port-Id | Supports both encodings above. |
| Radback (2352) | Mac-Addr VSA, falling back to Calling-Station-Id | Bind-Dot1q-Vlan-Tag-Id VSA, falling back to NAS-Port-Id | Request parser only; no Radback response enhancer is shipped. |
| Alcatel (3041) | AAT-User-MAC-Address VSA, falling back to Calling-Station-Id | NAS-Port-Id when present | Request parser only; response attributes remain deployment-specific. |
| Aruba/HPE (14823) | Calling-Station-Id | Aruba-User-Vlan VSA, falling back to NAS-Port-Id | Also has an Access-Accept enhancer; see below. |
| Juniper (2636) | Calling-Station-Id | Juniper-VoIP-Vlan VSA, falling back to NAS-Port-Id | Request parser only; response attributes remain deployment-specific. |
| MikroTik, iKuai, Cisco, Standard | Calling-Station-Id | — | No vendor-specific VLAN parser; use device-side policy or custom integration. |
MAC binding works with every vendor that sends a recognizable MAC address. VLAN binding requires one of the VLAN-aware parsers above and a matching request encoding from the NAS.
Device-side snippets below are reference examples — command syntax varies by model and OS version; always consult the vendor documentation.
MikroTik (RouterOS) — vendor code 14988
Best-known integration: PPPoE / Hotspot with Mikrotik-Rate-Limit.
ToughRADIUS sends Mikrotik-Rate-Limit = "{up}k/{down}k"; RouterOS applies it
as a dynamic simple queue (rx-rate/tx-rate from the router’s perspective, i.e.
subscriber upload first).
/radius add service=ppp,hotspot address=<TOUGHRADIUS_IP> secret=<SECRET> \
timeout=3s
/radius incoming set accept=yes port=3799
/ppp aaa set use-radius=yes accounting=yes interim-update=5m
radius incoming accept=yesenables CoA/Disconnect on UDP 3799.- For Hotspot: enable RADIUS in the hotspot server profile
(
/ip hotspot profile set ... use-radius=yes).
Huawei — vendor code 2011
Typical BRAS (ME60/NE) / aggregation deployments. ToughRADIUS sends the rate
quartet (Huawei-Input/Output-Average-Rate, peaks ×4), Huawei-Domain-Name
(when the user/profile has a domain), and Huawei-Framed-IPv6-Address for
static IPv6. The Huawei parser extracts VLANs from NAS-Port-Id, so MAC and
VLAN binding both work.
radius-server template tr_tpl
radius-server shared-key cipher <SECRET>
radius-server authentication <TOUGHRADIUS_IP> 1812
radius-server accounting <TOUGHRADIUS_IP> 1813
#
aaa
authentication-scheme auth_radius
authentication-mode radius
accounting-scheme acct_radius
accounting-mode radius
accounting interim interval 5
domain default
authentication-scheme auth_radius
accounting-scheme acct_radius
radius-server tr_tpl
For CoA/Disconnect, enable the RADIUS dynamic authorization extension
(radius-server authorization on the device) toward the server address.
Cisco — vendor code 9
ToughRADIUS authenticates Cisco devices with standard attributes (PAP / CHAP /
MS-CHAPv2 / EAP all work; sessions, accounting, CoA likewise). No
Cisco-specific attributes are emitted by default — apply bandwidth policy on
the device, or extend via the bundled Cisco-AVPair dictionary if you build a
custom integration.
aaa new-model
radius server TOUGHRADIUS
address ipv4 <TOUGHRADIUS_IP> auth-port 1812 acct-port 1813
key <SECRET>
aaa authentication ppp default group radius
aaa accounting network default start-stop group radius
aaa server radius dynamic-author
client <TOUGHRADIUS_IP> server-key <SECRET>
aaa server radius dynamic-author enables CoA/Disconnect (default port 3799).
H3C — vendor code 25506
Same rate semantics as Huawei (H3C-Input/Output-Average-Rate ×1024, peak ×4).
The H3C parser extracts VLANs, so VLAN binding is supported.
radius scheme tr_scheme
primary authentication <TOUGHRADIUS_IP> 1812
primary accounting <TOUGHRADIUS_IP> 1813
key authentication simple <SECRET>
key accounting simple <SECRET>
user-name-format without-domain
#
domain default enable system
authentication ppp radius-scheme tr_scheme
accounting ppp radius-scheme tr_scheme
ZTE — vendor code 3902
ToughRADIUS sends ZTE-Rate-Ctrl-SCR-Up/Down (rate ×1024) and parses VLANs
from NAS-Port-Id. Configuration on ZTE BRAS follows the same
radius-template + domain pattern as Huawei; bind the authentication/accounting
template to the server address, secret, and ports 1812/1813.
iKuai — vendor code 10055
Popular SMB gateway in China. ToughRADIUS sends
RP-Upstream-Speed-Limit / RP-Downstream-Speed-Limit (= rate_kbps × 8192,
clamped). On the iKuai web console: 认证计费 → RADIUS 计费 — set the server
address, ports 1812/1813, and the shared secret; enable RADIUS in the PPPoE
server settings.
Aruba/HPE — vendor code 14823
Aruba/HPE devices have both request parsing and Access-Accept enhancement.
The parser extracts VLAN information from Aruba-User-Vlan when the request
carries it, with the same NAS-Port-Id fallback used by other VLAN-aware
parsers.
On Access-Accept, ToughRADIUS sends:
| Attribute | Source | Boundary / no-op rule |
|---|---|---|
Aruba-User-Vlan | user or profile Vlanid1 | Sent only for VLAN IDs 1..4094; missing, 0, 4095, or negative values are omitted. Vlanid2 has no Aruba attribute mapping and is not sent. |
Aruba-User-Role | user or profile Domain | Sent when the inherited domain / role field is non-empty and not N/A. |
No Aruba VSAs are added for non-Aruba NAS records, and unset fields are left out instead of emitting placeholder values.
Standard / other devices — vendor code 0
Any RFC-compliant NAS (pfSense, strongSwan, FreeRADIUS clients, Wi-Fi
controllers, …) can authenticate against ToughRADIUS with vendor code
Standard: full credential validation, session control, accounting, IPv4/IPv6
attributes — but no proprietary rate attributes. Attribute dictionaries for
more vendors (Microsoft, F5, PfSense, Hillstone, …) ship in the codebase for
custom development. For Juniper, Alcatel, Aruba, and Radback, the shipped
capability is more specific than “dictionary only”: see the request parser and
Aruba enhancer boundaries above. A dictionary alone still does not parse
requests or enhance accepts unless a parser or enhancer is registered.
Troubleshooting an integration
| Symptom | Likely cause |
|---|---|
Device gets Access-Accept but no bandwidth limit | NAS record vendor is Standard, or the device ignores the VSA — check the vendor code first |
| All requests silently dropped | Source IP not registered as a NAS, or shared secret mismatch |
| VLAN binding never matches | NAS vendor is not one of the VLAN-aware parsers above, or the request uses an unexpected VSA / NAS-Port-Id format |
| CoA/Disconnect times out | Device CoA listener disabled, or non-default port — set CoA port on the NAS record |
More in the FAQ.
Portal / Hotspot Integration Boundary
This chapter defines the hard product boundary for captive portal and hotspot deployments.
Iron rule
ToughRADIUS does not provide, host, or operate a captive portal login page.
Portal server, guest onboarding, voucher issuance, SMS/WeChat login, payment flows, advertising pages, and captive network enforcement belong to a separate portal / gateway product. They are not part of the ToughRADIUS product scope.
ToughRADIUS stays a RADIUS AAA system:
- authentication on UDP
1812; - accounting on UDP
1813; - session policy and audit through RADIUS attributes, accounting records, and optional CoA / Disconnect;
- vendor-specific RADIUS attributes where they can be expressed safely inside the existing vendor parser / enhancer model.
Supported shape
The supported integration model is:
Client -> NAS / WLAN controller / gateway portal -> RADIUS -> ToughRADIUS
The NAS, WLAN controller, hotspot gateway, or external portal product owns:
- HTTP/HTTPS captive-portal redirection;
- the login page and user interaction;
- pre-auth / post-auth network enforcement;
- vendor portal callbacks or proprietary portal protocols;
- device-side session admission and release.
ToughRADIUS owns:
- user, profile, NAS, and policy data;
- Access-Accept / Access-Reject decisions;
- accounting records and online-session state;
- standard and vendor RADIUS attributes such as timeout, pool, rate, VLAN, role, or portal URL where a supported vendor enhancer emits them;
- CoA / Disconnect where the NAS supports it.
What this means for Hotspot
MikroTik Hotspot, Huawei/H3C/iKuai/Cisco WLAN controllers, Aruba captive portal flows, and similar devices can still integrate with ToughRADIUS when the device uses RADIUS as its backend. The device remains the portal implementation; this project remains the AAA backend.
Common supported cases:
- Hotspot / PPPoE / WLAN controller sends Access-Request to ToughRADIUS.
- MAC authentication admits known devices without showing a portal page.
- Accounting updates keep online-session and traffic data current.
- CoA / Disconnect removes or refreshes a session when supported by the NAS.
- Vendor attributes may steer device-side behavior, but only as RADIUS attributes. They do not make ToughRADIUS a portal server.
Explicit non-goals
Do not add these to ToughRADIUS:
- hosted login pages for guests or subscribers;
- voucher, coupon, QR-code, SMS, WeChat, OAuth, or payment onboarding flows;
- captive-portal JavaScript applications or customer self-service portals;
- proprietary portal-server callback protocols as first-class subsystems;
- per-vendor portal state machines that duplicate the NAS / controller role;
- generic campaign, advertisement, CRM, or visitor-management features.
If a deployment needs those functions, use a dedicated portal product in front of the NAS / controller and integrate it with ToughRADIUS through RADIUS.
Allowed narrow extensions
Portal-related work is acceptable only when it stays inside the existing RADIUS boundary:
- Add or fix vendor dictionaries, parsers, or enhancers for RADIUS attributes such as captive-portal URL, user role, filter ID, VLAN, session timeout, or rate limit.
- Document a specific NAS / controller configuration that uses ToughRADIUS as the RADIUS backend.
- Add tests for request parsing, response attributes, accounting, or CoA behavior.
Any request to build a hosted portal must be rejected or moved to another product before implementation starts.
Scenario Cookbook
中文版本:场景实战手册
The Vendor Integration Guide is a reference card — it tells you which attributes ToughRADIUS sends to / parses for a given vendor. This cookbook goes one step further: it is organized around real operational scenarios and translates a business need, end to end, into “server config + device config + verification + troubleshooting”.
The five-part shape of every scenario
So you can follow along and debug effectively, every scenario uses the same structure:
- Need / scenario — the problem in business language, no protocol detail.
- On the ToughRADIUS side — exactly what to configure in the admin UI, and which attributes are actually emitted after a successful auth, produced by which piece of code.
- On the device side — reference configuration for the NAS/router.
- Verification — how to confirm it really works (radtest, device commands, admin UI).
- Troubleshooting — the most common traps for that scenario, as “symptom → locate → fix”.
Reading conventions
- Every ToughRADIUS-side claim is anchored to code: emitted attributes come
from the enhancers in
internal/radiusd/plugins/auth/enhancers/; the accept/reject decisions come from the checkers ininternal/radiusd/plugins/auth/checkers/. This describes the system’s real behaviour, not an aspiration. - Device-side config is always a reference example: command syntax varies by model and OS version — defer to the vendor docs and your actual firmware.
- CoA / Disconnect port is 3799 (RFC 5176). The
1700you often see online is a client-side local port, not the destination port this system uses. - Rates are stored in Kbps in the rate profile (the UI labels the unit). See Vendor Integration Guide · Rate units for the per-vendor conversion.
Available cookbooks
- MikroTik RouterOS — PPPoE broadband ISP speed tiers, Hotspot + MAC authentication, CoA / forced disconnect and FUP.
- Huawei BRAS / NetEngine — broadband speed tiers with peak rate and AAA domain, line anti-fraud (MAC + VLAN binding) with dual-stack IPv6, CoA / forced disconnect and FUP.
- H3C, ZTE, iKuai & Cisco — the per-vendor diff (emitted rate attributes, unit multipliers, MAC / VLAN parsing) for the remaining mainstream vendors; the scenario mechanics follow the two flagship cookbooks.
Planned (roadmap M13.8 later batches): additional standard-attribute / Wi-Fi-controller scenarios as needed. For attribute-level details of any vendor, see the Vendor Integration Guide.
Related chapters
- Quick Start — install, first login,
radtestverification. - Vendor Integration Guide — per-vendor attribute reference.
- Admin UI Manual — users, rate profiles, online sessions, CoA.
- FAQ — cross-scenario troubleshooting Q&A.
Cookbook: MikroTik RouterOS
This chapter is part of the Scenario Cookbook and follows its five-part shape and reading conventions.
MikroTik RouterOS (vendor code 14988) is the most common integration target. ToughRADIUS registers a dedicated vendor enhancer for it; on a successful auth it emits:
Mikrotik-Rate-Limit = "{up}k/{down}k"— a string rate limit (produced bymikrotik_enhancer.go;rx/txis from the router’s perspective, so the first field is the subscriber’s upload).- Plus the standard attributes common to all devices:
Session-Timeout,Acct-Interim-Interval,Framed-Pool,Framed-IP-Address, etc. (produced bydefault_enhancer.go).
Prerequisite: register this router under NAS devices with vendor = MikroTik, the correct source IP and shared secret; ToughRADIUS must be reachable by the device (auth 1812, accounting 1813). If you register it as
Standard, auth still succeeds butMikrotik-Rate-Limitis not emitted.
Scenario A: PPPoE broadband ISP — speed tiers + address pool + expiry disconnect + concurrency
Need / scenario
A neighbourhood / small ISP serves Internet over PPPoE and needs: several speed tiers (e.g. Home = 30M down, Business = 100M down), accounts that disconnect on expiry and cannot redial, a concurrency cap per account (to stop credential sharing), and IPs assigned from a shared address pool.
On the ToughRADIUS side
- Create one rate profile per tier (Rate profiles → New):
- Up / down rate: the unit is Kbps. 30M down means
30720, not30; 10M up means10240. - Concurrency
active_num: e.g.1= at most one online session per account (0= unlimited). - Address pool: a pool name (e.g.
pppoe-pool) that must match the/ip poolname on the router.
- Up / down rate: the unit is Kbps. 30M down means
- Create a user (Users → New): username / password, the matching rate profile (rate, concurrency, pool are inherited from it), an expiry time, status = enabled. For a fixed IP, set a static IPv4 on the user (it overrides the pool).
- Accounting interval: the config item
radius.AcctInterimInterval(default120seconds) sets the emittedAcct-Interim-Interval.
After a successful auth, the Access-Accept actually carries (anchored to
code):
| Attribute | Value | Source |
|---|---|---|
Mikrotik-Rate-Limit | "{up_kbps}k/{down_kbps}k", e.g. 10240k/30720k | mikrotik_enhancer.go |
Session-Timeout | seconds remaining until expiry (drops the current session at the deadline) | default_enhancer.go |
Acct-Interim-Interval | radius.AcctInterimInterval (default 120) | default_enhancer.go |
Framed-Pool | the pool name from the profile / user (emitted only if set) | default_enhancer.go |
Framed-IP-Address | the user’s static IPv4 (emitted only if set) | default_enhancer.go |
The concurrency cap is not implemented by emitting an attribute: it is enforced by
online_count_checkerat auth time, comparingactive_numto the current online count — an over-limit new session is rejected (Access-Reject).
On the device side (RouterOS, reference example, verify on your firmware)
# Point at ToughRADIUS (same shared secret for auth/accounting)
/radius add service=ppp address=<TOUGHRADIUS_IP> secret=<SECRET> timeout=3s
/radius incoming set accept=yes port=3799
# Enable RADIUS auth + accounting + periodic interim updates
/ppp aaa set use-radius=yes accounting=yes interim-update=5m
# The pool name must match the emitted Framed-Pool
/ip pool add name=pppoe-pool ranges=10.10.0.2-10.10.255.254
# PPPoE service (let the RADIUS Framed-Pool/Framed-IP decide remote-address)
/ppp profile add name=radius-pppoe local-address=10.10.0.1
/interface pppoe-server server add service-name=isp interface=<bridge> \
default-profile=radius-pppoe disabled=no
No manual queue is needed for rate limiting: on receiving
Mikrotik-Rate-Limitthe router creates a dynamic simple queue automatically.
Verification
- radtest (server side):
On success it printsgo run ./cmd/radtest auth -server <TOUGHRADIUS_IP> -nas-ip <NAS_IP> \ -username <username> -password <password> -secret <SECRET>Access-Accept; you should seeMikrotik-Rate-Limit,Session-Timeout, and (if a pool is set)Framed-Pool. - Router side:
/ppp active print(online sessions),/queue simple print(the dynamic queue and its rate),/log print where topics~"radius". - Admin UI: the Online sessions page should list the session (Framed IP, duration, up/down traffic).
Troubleshooting (symptom → locate → fix)
- Rate limit has no effect at all → ① confirm the NAS is registered as
MikroTik (registered as Standard emits no VSA); ② wrong rate unit (
30instead of30720); ③ direction reversed (rx/txis the router’s view, the first field is the subscriber’s upload). - Dials up but gets no / wrong IP → the
Framed-Poolname does not match the/ip poolname, or no pool is set on the profile; align the names or set a static IP on the user. - Account still online after expiry →
Session-Timeoutonly drops the current session at the deadline; an already-online session waits for the timeout or a manual forced disconnect on the sessions page. A redial after expiry is rejected byexpire_checker(counted underuser expire). - The second connection is rejected → with
active_num=1,online_count_checkerrejects the concurrent session — this is expected; raise the profile’s concurrency to allow multiple dials. - Server slows down / stops replying after repeated auth failures →
reject_delay_guardintroduces a delay once a username is rejected more than a threshold (default 7) in a row, to throttle brute force; it recovers automatically.
Scenario B: Hotspot + MAC authentication
Need / scenario
In a public WiFi / Hotspot environment, you want certain enrolled devices (printers, IoT, long-term guest devices) to skip the portal and be admitted and rate-limited by MAC address.
On the ToughRADIUS side
ToughRADIUS decides a request is MAC authentication by this rule (anchored to
auth_stages.go / eap_helper.go):
When the MAC address parsed from the request equals the username, the request is treated as MAC auth; the password check then compares against the user record’s MAC address field rather than an ordinary password.
So configure it as follows:
- Create a user with username = the device MAC (the string must match exactly what the router sends), and put the same MAC in the user record’s MAC address field.
- Assign the user a rate profile (rate and concurrency apply as usual).
The biggest trap is MAC format: case and separators must exactly match the
User-Namethe RouterOS hotspot sends (ToughRADIUS compares strings exactly). RouterOS’s format is influenced by settings such asmac-auth-mode— what you store is what must be sent.
On the device side (RouterOS, reference example, verify on your firmware)
/radius add service=hotspot address=<TOUGHRADIUS_IP> secret=<SECRET> timeout=3s
# Enable RADIUS and MAC login on the hotspot server profile
/ip hotspot profile set <profile> use-radius=yes login-by=mac,http-chap
# mac-auth-mode / mac-auth-password depend on firmware
Verification
- radtest: use
-user <MAC> -pwd <MAC>to simulate a MAC auth and check forAccess-Accept. - Router side:
/ip hotspot active printshould list the device;/log printfor the auth log. - Admin UI: the session appears on the Online sessions page.
Troubleshooting (symptom → locate → fix)
- MAC auth always fails → the username / MAC field does not match the MAC
string the router actually sends (case, separators). Capture one request, read
the literal
User-Name, and recreate the user from that exact string. - It is being treated as an ordinary account → only “request MAC == username” takes the MAC-auth path; if the hotspot does not log in by MAC, the request goes through the portal username / password logic.
Scenario C: Live control — CoA, forced disconnect and FUP
Need / scenario
Control online users in real time: rate-limit after a quota is exceeded (FUP), force a re-authentication, or simply kick a session offline.
On the ToughRADIUS side
Select a session on the Online sessions page and run one of two actions
(anchored to session_actions.go and
Admin UI Manual · Online sessions):
- Change of Authorization (CoA-Request): currently carries only
Session-Timeout(#27) and / orFilter-Id(#11).- Use
Session-Timeoutto shorten the session’s life and force the client to re-authenticate sooner. - Use
Filter-Idto have RouterOS apply a pre-defined filter / address-list (e.g. a rate or site-restriction rule).
- Use
- Forced disconnect (Disconnect-Request): terminate the session directly (with a confirmation step).
The correct path to live FUP “speed change”: ToughRADIUS’s CoA does not rewrite
Mikrotik-Rate-Limit. To change a user’s speed live, the standard approach is — change the rate on the profile / user first, then force a disconnect; the client redials automatically and is re-authorized at the new speed. This path matches the system’s actual capability and works for any vendor.
Operator-initiated CoA / Disconnect uses a short timeout with one automatic retry, targeting the CoA port (default 3799) on the NAS record.
On the device side (RouterOS, reference example, verify on your firmware)
# Required, otherwise CoA/Disconnect is not received
/radius incoming set accept=yes port=3799
- The firewall must allow inbound UDP 3799 (from ToughRADIUS to the router).
- For the
Filter-Idapproach, pre-define a filter / queue / address-list of the same name on RouterOS (verify on your firmware).
Verification
- Click Change authorization / Forced disconnect on the sessions page; the result is reported as a notification.
- On the router,
/log printshows the incoming request; after a forced disconnect, the session disappears from/ppp active printand the client then redials automatically.
Troubleshooting (symptom → locate → fix)
- CoA / Disconnect does not respond or times out → ① RouterOS did not set
radius incoming accept=yes; ② the firewall blocks UDP 3799; ③ the NAS record’s CoA port differs from the device’sincoming port; ④ the shared secret differs. - Changed the rate but the online user’s speed did not change → expected: CoA does not change the rate; force a disconnect so the client redials.
- Port confusion → the CoA
1700you often see online is a client-side local port; this system and RFC 5176 use 3799.
Related chapters
- Vendor Integration Guide · MikroTik — attribute reference card.
- Admin UI Manual — users / rate profiles / online sessions / CoA forms.
- FAQ — more cross-scenario troubleshooting Q&A.
Cookbook: Huawei BRAS / NetEngine
This chapter is part of the Scenario Cookbook and follows its five-part shape and reading conventions.
Huawei (vendor code 2011) is the dominant broadband BRAS / enterprise gateway in Chinese carrier and enterprise networks (NetEngine / ME60 / older MA5200 lines). ToughRADIUS registers a dedicated vendor enhancer for it; on a successful auth it emits:
- A four-attribute rate quartet (produced by
huawei_enhancer.go):Huawei-Input-Average-Rate,Huawei-Input-Peak-Rate,Huawei-Output-Average-Rate,Huawei-Output-Peak-Rate. Huawei-Domain-Name— only when the user / profile has a domain set.Huawei-Framed-IPv6-Address— only when the user has a static IPv6.- Plus the standard attributes common to all devices (
Session-Timeout,Acct-Interim-Interval,Framed-Pool,Framed-IP-Address, …) produced bydefault_enhancer.go.
On the request side the Huawei parser (huawei_parser.go) extracts the MAC
from Calling-Station-Id (normalising - to :) and the inner / outer VLAN
IDs from NAS-Port-Id. That is what makes MAC binding and VLAN binding
possible for Huawei devices.
Prerequisite: register this BRAS under NAS devices with vendor = Huawei, the correct source IP and shared secret; ToughRADIUS must be reachable (auth 1812, accounting 1813). If you register it as
Standard, auth still succeeds but none of the Huawei VSAs above are emitted, and the VLAN IDs are not parsed.
Scenario A: PPPoE / IPoE broadband — speed tiers, peak rate and AAA domain
Need / scenario
A carrier or enterprise runs broadband on a Huawei BRAS and needs several speed tiers (e.g. Home = 30M down / 10M up, Business = 100M down). Huawei honours both an average and a peak (burst) rate, and groups subscribers into AAA domains so the BRAS applies the right domain policy.
On the ToughRADIUS side
- Create one rate profile per tier (Rate profiles → New):
- Up / down rate: the unit is Kbps. 30M down means
30720, not30; 10M up means10240. - Domain (optional): the Huawei AAA domain name (e.g.
isp), pushed asHuawei-Domain-Nameso the BRAS binds the session to that domain.
- Up / down rate: the unit is Kbps. 30M down means
- Create a user (Users → New): username / password, the matching rate profile, an expiry time, status = enabled. A per-user domain field overrides the profile domain.
How the stored Kbps rates become the four Huawei VSAs (anchored to
huawei_enhancer.go):
| Attribute | Value | Note |
|---|---|---|
Huawei-Input-Average-Rate | up_kbps × 1024 (bit/s) | “Input” = traffic into the BRAS = subscriber upload |
Huawei-Input-Peak-Rate | up_kbps × 1024 × 4 | average × 4 |
Huawei-Output-Average-Rate | down_kbps × 1024 (bit/s) | “Output” = traffic out to the subscriber = download |
Huawei-Output-Peak-Rate | down_kbps × 1024 × 4 | average × 4 |
Huawei-Domain-Name | the user / profile domain | emitted only if a domain is set |
Session-Timeout, Acct-Interim-Interval, Framed-Pool, Framed-IP-Address | standard | from default_enhancer.go |
Two traps unique to Huawei. ① Unit: the rate VSAs are in bit/s, not Kbps — ToughRADIUS multiplies the stored Kbps by 1024 (binary), and the peak is the average × 4. So a “30M down” tier is sent as
Output-Average-Rate = 30720 × 1024 = 31457280andOutput-Peak-Rate = 125829120. ② Direction naming: Huawei “Input” is the subscriber’s upload and “Output” is the download (BRAS perspective) — the opposite words from MikroTik’srx/tx, but the same physical meaning. All four values are clamped to the Int32 maximum.
On the device side (Huawei VRP, reference example, verify on your firmware)
# RADIUS server template
radius-server template tr-tmpl
radius-server shared-key cipher <SECRET>
radius-server authentication <TOUGHRADIUS_IP> 1812 weight 80
radius-server accounting <TOUGHRADIUS_IP> 1813 weight 80
#
# AAA domain bound to the template (matches Huawei-Domain-Name)
aaa
authentication-scheme radius-auth
authentication-mode radius
accounting-scheme radius-acct
accounting-mode radius
domain isp
authentication-scheme radius-auth
accounting-scheme radius-acct
radius-server tr-tmpl
The
Huawei-Output-Average-Rate/ peak values map to the BRAS’s per-user CAR / QoS; no manual per-user queue is required.
Verification
- radtest (server side):
On success it printsgo run ./cmd/radtest auth -server <TOUGHRADIUS_IP> -nas-ip <NAS_IP> \ -username <username> -password <password> -secret <SECRET>Access-Accept; you should see the four Huawei rate attributes and (if set)Huawei-Domain-Name. - BRAS side:
display access-user username <name>(online session, rate, domain),display aaa online-fail-recordfor failures. - Admin UI: the Online sessions page should list the session.
Troubleshooting (symptom → locate → fix)
- Rate limit has no effect → ① confirm the NAS is registered as Huawei
(registered as Standard emits no VSA); ② remember the value is bit/s
(
× 1024), so a tier that looks “1000× too small” usually means the unit was read as Kbps on the device side. - Speeds look swapped (upload capped at the download value) → the Input/Output direction confusion: Huawei Input = upload, Output = download; check which profile field feeds which.
- Domain policy not applied → the user / profile has no domain set (then
Huawei-Domain-Nameis not emitted), or the domain name does not match adomainconfigured on the BRAS. - Peak / burst seems too high → expected: peak is hard-coded as average × 4 in the enhancer; tune the average tier, not the peak.
Scenario B: Line anti-fraud — MAC + VLAN binding and dual-stack IPv6
Need / scenario
A broadband operator wants to stop account sharing / theft by binding each account to its access line — the subscriber’s MAC and / or the access VLAN (inner / outer, i.e. QinQ) — and to hand out a static IPv6 for dual-stack service.
On the ToughRADIUS side
Huawei encodes the access line in attributes the parser already reads:
- the MAC comes from
Calling-Station-Id; - the inner / outer VLAN come from
NAS-Port-Id.
Binding is then enforced by two checkers (anchored to mac_bind_checker.go /
vlan_bind_checker.go):
- MAC binding — enable bind MAC on the user / profile and store the
allowed MAC on the user. At auth time, if the request MAC differs from the
stored MAC, the session is rejected (
MacBindError). - VLAN binding — enable bind VLAN on the user / profile and store the
allowed VLAN ID 1 / VLAN ID 2 on the user. If a stored VLAN differs from
the parsed one, the session is rejected (
VlanBindError). - Static IPv6 — set the user’s IPv6 address; the enhancer then emits
Huawei-Framed-IPv6-Address(the prefix length, if written asaddr/len, is stripped before sending).
Binding only enforces what you have stored. Each checker is skipped when its toggle is off, and also when either side (stored value or request value) is empty / zero — it never silently auto-learns. So to bind a line you must (a) turn the toggle on and (b) fill in the MAC / VLAN on the user record (typically captured from the first successful login).
On the device side (Huawei VRP, reference example, verify on your firmware)
# IPoE / PPPoE access that reports the access line to RADIUS.
# Huawei carries the inner/outer VLAN inside NAS-Port-Id and the
# subscriber MAC inside Calling-Station-Id by default on BRAS access.
radius-server template tr-tmpl
radius-server shared-key cipher <SECRET>
radius-server authentication <TOUGHRADIUS_IP> 1812 weight 80
radius-server accounting <TOUGHRADIUS_IP> 1813 weight 80
#
# Enable IPv6 address/prefix delivery on the BRAS as required by your design
ipv6
Verification
- Capture one real
Access-Request(or read the auth log) and confirm theCalling-Station-IdandNAS-Port-Idvalues, then store exactly those on the user before turning binding on. - radtest with the matching MAC succeeds; a different MAC / VLAN is rejected.
- BRAS side:
display access-user username <name>shows the bound line and any delivered IPv6 address.
Troubleshooting (symptom → locate → fix)
- Binding rejects the legitimate user → the stored MAC / VLAN does not match
what the BRAS actually sends. Read the real
Calling-Station-Id/NAS-Port-Id, store those exact values, then re-enable binding. - VLAN binding never triggers → the parsed VLAN is
0(the BRAS does not put the VLAN inNAS-Port-Idin your topology), or bind VLAN is off, or the user’s stored VLAN is0; any of these skips the check. - No IPv6 delivered → the user has no static IPv6 address set, or the BRAS is not configured for IPv6 / dual-stack on that domain.
- Binding is silently ignored → the toggle is off or the stored value is empty; the checker only enforces when the toggle is on and both sides are present.
Scenario C: Live control — CoA, forced disconnect and FUP
Need / scenario
Control online users on a Huawei BRAS in real time: shorten a session, force re-authentication, rate-limit after a quota is exceeded (FUP), or kick a session offline.
On the ToughRADIUS side
Select a session on the Online sessions page and run one of two actions
(anchored to session_actions.go and
Admin UI Manual · Online sessions):
- Change of Authorization (CoA-Request): currently carries only
Session-Timeout(#27) and / orFilter-Id(#11) — it does not rewrite the Huawei rate VSAs. - Forced disconnect (Disconnect-Request): terminate the session directly.
The correct path to live FUP “speed change” on Huawei. Because CoA does not rewrite
Huawei-Output-Average-Rate, changing a user’s speed live follows the same vendor-agnostic path as everywhere else: change the rate on the profile / user first, then force a disconnect; the client redials and is re-authorized at the new rate quartet. (Some Huawei firmware also reacts to a vendor-specific CoA rate attribute, but ToughRADIUS does not emit one — so do not rely on it.)
Operator-initiated CoA / Disconnect uses a short timeout with one automatic retry, targeting the CoA port (default 3799) on the NAS record.
On the device side (Huawei VRP, reference example, verify on your firmware)
# The RADIUS template must accept dynamic authorization (CoA/DM).
radius-server template tr-tmpl
radius-server authorization <TOUGHRADIUS_IP> shared-key cipher <SECRET>
- The firewall must allow inbound UDP 3799 from ToughRADIUS to the BRAS.
- For the
Filter-Idapproach, pre-define a matching ACL / user-group of the same name on the BRAS (verify on your firmware).
Verification
- Click Change authorization / Forced disconnect on the sessions page; the result is reported as a notification.
- BRAS side: after a forced disconnect,
display access-user username <name>no longer lists the session, and the client redials automatically.
Troubleshooting (symptom → locate → fix)
- CoA / Disconnect does not respond or times out → ① the RADIUS template has
no
radius-server authorization; ② the firewall blocks UDP 3799; ③ the NAS record’s CoA port differs from the BRAS’s authorization port; ④ the shared secret differs. - Changed the rate but the online user’s speed did not change → expected: CoA does not change the rate; force a disconnect so the client redials.
- Port confusion → the CoA
1700you often see online is a client-side local port; this system and RFC 5176 use 3799.
Related chapters
- Vendor Integration Guide · Huawei — attribute reference card.
- Cookbook: MikroTik RouterOS — the same scenarios on RouterOS.
- Admin UI Manual — users / rate profiles / online sessions / CoA forms.
- FAQ — more cross-scenario troubleshooting Q&A.
Cookbook: H3C, ZTE, iKuai & Cisco
中文版本:实战手册:H3C / 中兴 / 爱快 / Cisco
This chapter is part of the Scenario Cookbook and follows its reading conventions.
These four vendors run the same operational scenarios as the two flagship
cookbooks — PPPoE / IPoE speed tiers, address pool, expiry disconnect,
per-account concurrency, MAC binding, CoA / forced disconnect and FUP. The
mechanics (how a tier, an expiry, a concurrency cap, or a CoA behaves) are
identical, because they come from the shared default_enhancer and the
checkers — not from the vendor.
So instead of repeating the full five-part playbook four times, this chapter is the per-vendor diff: for each device it states exactly (1) which rate attributes ToughRADIUS emits and the unit multiplier, (2) how the request MAC / VLAN is parsed (hence which bindings work), and (3) which flagship scenario to follow for the end-to-end steps.
Read this with a flagship chapter open. For the full step-by-step of any scenario, follow the matching section in the MikroTik or Huawei cookbook; only the emitted attributes below differ.
Which playbook applies
| You want to… | Follow | What changes per vendor (this chapter) |
|---|---|---|
| Speed tiers + address pool + expiry + concurrency | MikroTik Scenario A or Huawei Scenario A | the rate attribute(s) emitted + unit |
| Line anti-fraud (MAC / VLAN binding) | Huawei Scenario B | MAC parse format + whether VLAN binding is available |
| Live control (CoA / disconnect / FUP) | MikroTik Scenario C | nothing — CoA is vendor-agnostic (Session-Timeout + Filter-Id) |
The concurrency cap, expiry disconnect, address pool and CoA work the same for every vendor here — they are enforced by the shared checkers /
default_enhancer, not by a vendor VSA. The only real per-vendor variable is the rate-limit attribute and the MAC / VLAN parsing below.
H3C — vendor code 25506
Rate attributes (anchored to h3c_enhancer.go) — the same quartet shape as
Huawei:
| Attribute | Value |
|---|---|
H3C-Input-Average-Rate | up_kbps × 1024 (bit/s) — subscriber upload |
H3C-Input-Peak-Rate | up_kbps × 1024 × 4 |
H3C-Output-Average-Rate | down_kbps × 1024 (bit/s) — download |
H3C-Output-Peak-Rate | down_kbps × 1024 × 4 |
So a “30M down” tier (30720 Kbps) is sent as H3C-Output-Average-Rate = 31457280, peak 125829120; all values clamp to the Int32 max. Unlike Huawei,
H3C emits no domain or IPv6 VSA — only the rate quartet.
Request parsing (h3c_parser.go): MAC comes from H3C-IP-Host-Addr (the
last 17 characters, i.e. the trailing aa:bb:cc:dd:ee:ff) and falls back to
Calling-Station-Id (-→:) when that VSA is absent; the inner / outer VLAN
come from NAS-Port-Id. Both MAC and VLAN binding are supported.
Device side (H3C Comware, reference, verify on your firmware):
radius scheme tr_scheme
primary authentication <TOUGHRADIUS_IP> 1812
primary accounting <TOUGHRADIUS_IP> 1813
key authentication simple <SECRET>
key accounting simple <SECRET>
user-name-format without-domain
#
domain default enable system
authentication ppp radius-scheme tr_scheme
accounting ppp radius-scheme tr_scheme
Trap: register the NAS as H3C (not Huawei). Although the rate maths is identical, the VSA vendor IDs differ (25506 vs 2011); a Huawei-registered H3C device would receive Huawei VSAs it ignores, and the rate limit would not apply.
ZTE — vendor code 3902
Rate attributes (anchored to zte_enhancer.go) — two attributes, no peak:
| Attribute | Value |
|---|---|
ZTE-Rate-Ctrl-SCR-Up | up_kbps × 1024 (bit/s) |
ZTE-Rate-Ctrl-SCR-Down | down_kbps × 1024 (bit/s) |
A “30M down” tier is sent as ZTE-Rate-Ctrl-SCR-Down = 31457280. There is no
peak / burst attribute — the average rate is the cap.
Request parsing (zte_parser.go): MAC comes from Calling-Station-Id, but
ZTE sends it as a bare 12-hex-digit string; ToughRADIUS reformats it to
aa:bb:cc:dd:ee:ff. VLAN is parsed from NAS-Port-Id. Both MAC and VLAN
binding are supported — but for MAC binding, store the MAC in the
aa:bb:cc:dd:ee:ff form (that is what the parser produces).
Device side: ZTE BRAS uses the same radius-template + domain pattern as Huawei — bind the authentication / accounting template to the ToughRADIUS address, shared secret and ports 1812 / 1813 (verify on your firmware).
iKuai — vendor code 10055
A popular SMB / SOHO gateway in China.
Rate attributes (anchored to ikuai_enhancer.go) — two attributes, a
different multiplier:
| Attribute | Value |
|---|---|
RP-Upstream-Speed-Limit | up_kbps × 8192 (= × 1024 × 8) |
RP-Downstream-Speed-Limit | down_kbps × 8192 |
So a “30M down” tier (30720 Kbps) is sent as RP-Downstream-Speed-Limit = 251658240.
Trap — high tiers clamp. Because the multiplier is
× 8192and the value is clamped to the Int32 max (2147483647), any tier above roughly 256 Mbps (262144Kbps) overflows and is clamped — a 300M tier would be sent at the clamped ceiling, not 300M. For very high tiers, apply the policy on the iKuai device instead.
Request parsing: iKuai uses the default parser — MAC from
Calling-Station-Id (-→:), and VLAN is not parsed (always 0). So MAC
binding works, but VLAN binding does not (the VLAN check is always skipped).
Device side: on the iKuai web console, go to 认证计费 → RADIUS 计费, set the server address, ports 1812 / 1813 and the shared secret, then enable RADIUS in the PPPoE server settings.
Cisco — vendor code 9
Rate attributes: none. ToughRADIUS has no Cisco enhancer, so a Cisco
NAS receives only the standard attributes (Session-Timeout,
Acct-Interim-Interval, Framed-Pool, Framed-IP-Address, …) from
default_enhancer.go. Authentication (PAP / CHAP / MS-CHAPv2 / EAP), session
control, accounting and CoA all work normally — apply bandwidth policy on the
device, or build a custom integration using the bundled Cisco-AVPair
dictionary.
Request parsing: default parser — MAC from Calling-Station-Id, no VLAN. So
MAC binding works, VLAN binding does not.
Device side (Cisco IOS, reference, verify on your platform):
aaa new-model
radius server TOUGHRADIUS
address ipv4 <TOUGHRADIUS_IP> auth-port 1812 acct-port 1813
key <SECRET>
aaa authentication ppp default group radius
aaa accounting network default start-stop group radius
aaa server radius dynamic-author
client <TOUGHRADIUS_IP> server-key <SECRET>
aaa server radius dynamic-authorenables CoA / Disconnect (default port 3799). Do not expect a rate limit from RADIUS on Cisco — set it with a service-policy / QoS on the device.
Vendor capability matrix
| Vendor | Rate attribute(s) | Unit multiplier | Peak | MAC bind | VLAN bind |
|---|---|---|---|---|---|
| H3C (25506) | H3C-Input/Output-Average-Rate + peaks | × 1024 (bit/s) | ✅ avg × 4 | ✅ | ✅ |
| ZTE (3902) | ZTE-Rate-Ctrl-SCR-Up/Down | × 1024 (bit/s) | ❌ | ✅ | ✅ |
| iKuai (10055) | RP-Upstream/Downstream-Speed-Limit | × 8192 | ❌ | ✅ | ❌ |
| Cisco (9) | none (device-side QoS) | — | — | ✅ | ❌ |
(For comparison: MikroTik emits the Mikrotik-Rate-Limit string in Kbps;
Huawei emits the rate quartet × 1024 with peak × 4 plus domain / IPv6.)
Troubleshooting (symptom → locate → fix)
- Rate limit has no effect → ① the NAS is not registered with this exact
vendor (the VSA vendor ID must match — H3C ≠ Huawei even though the maths is
the same); ② Cisco has no RADIUS rate VSA by design — set QoS on the
device; ③ wrong unit on the device side (all values here are bit/s, except the
iKuai
× 8192). - iKuai high-tier speed is wrong / capped low → the
× 8192value overflowed Int32 and was clamped; apply high tiers on the device. - VLAN binding never triggers on iKuai / Cisco → expected: their parser does
not extract VLANs (always
0), so the VLAN check is always skipped. Use MAC binding instead, or a vendor that parses VLAN (Huawei / H3C / ZTE). - ZTE MAC binding never matches → store the MAC as
aa:bb:cc:dd:ee:ff(ZTE sends 12 bare hex digits; ToughRADIUS reformats to colon form before comparing).
Related chapters
- Cookbook: MikroTik RouterOS — full PPPoE / Hotspot / CoA playbook.
- Cookbook: Huawei BRAS / NetEngine — full speed-tier / line-binding / CoA playbook.
- Vendor Integration Guide — the attribute reference card for every vendor.
- FAQ — more cross-scenario troubleshooting Q&A.
Admin UI Manual
中文版本:管理系统用户手册
The management console runs on port 1816 (HTTP) and is built with React
Admin. It is bilingual (中文 default, English via the app-bar language menu) and
supports light/dark themes. This chapter walks through every page.
Logging in and accounts
Sign in at http://<server>:1816 with an operator account. The initial
administrator is admin / toughradius — change it immediately under
Account Settings (top-right avatar), which also edits your profile info.
Passwords must be at least 6 characters.
Operator roles (set under Operators):
| Level | Meaning |
|---|---|
super | Full access, including System Config and Operators |
admin | Same menu visibility as super |
operator | Day-to-day pages only — System Config and Operators are hidden |
Dashboard
The landing page summarizes the deployment:
- Stat cards — total users (with disabled/expired counts), online users, today’s authentication and accounting request counts, today’s upload/download traffic in GB.
- IPv6 strip — online sessions with IPv6, IPv6 address/prefix/delegated prefix counts, users with static IPv6 configuration.
- Charts — authentication trend, profile distribution pie, and a 24-hour upload/download traffic chart.
Counters are driven by in-memory metrics; see the Operations Guide.
Network Nodes
Logical groups (name, tags, remark) used to organize NAS devices. Create nodes first — each NAS belongs to one.
NAS Devices
Registry of network devices allowed to talk RADIUS to this server. Requests from unknown source addresses are dropped.
| Field | Notes |
|---|---|
| Name / Identifier | Free-form name; identifier matches the RADIUS NAS-Identifier |
| IP address / Hostname | The source address of RADIUS packets |
| Vendor | Decides vendor attribute behavior — Standard, Cisco, Huawei, MikroTik, H3C, ZTE, iKuai (see the Vendor Integration Guide) |
| Secret | RADIUS shared secret |
| CoA port | Where CoA/Disconnect are sent; default 3799 |
| Node / Status / Tags / Remark | Organization and lifecycle |
RADIUS Users
Subscriber accounts. List supports filtering by username, real name, email, mobile, and IP, plus CSV export and column sorting.
Key form fields: username/password, status (enabled/disabled), billing
profile (required — rates, concurrency, pools inherit from it), expire
time (drives Session-Timeout), static ip_addr / ipv6_addr, IPv6 prefix
pools and delegated prefixes (edit view), contact info, remark.
Batch import: the list toolbar’s import button accepts .xlsx, .csv, or
.json files and reports created/failed counts. Export the current list as a
template reference.
The show view groups everything including IPv6 details, MAC/VLAN binding values, and timestamps.
Billing Profiles
Reusable authorization templates:
| Field | Meaning |
|---|---|
active_num | Max concurrent sessions per user (0 = unlimited) |
up_rate / down_rate | Bandwidth in Kbps; converted per vendor (list shows Mbps for values ≥ 1024) |
addr_pool | Framed-Pool name the NAS allocates from |
ipv6_prefix / domain | IPv6 and Huawei domain authorization |
bind_mac / bind_vlan | Lock users to first-seen MAC / VLANs |
Online Sessions
Live sessions (radius_online). Columns include session ID, framed IP, NAS
address/port, start time, duration, timeout, and traffic counters. Filters
cover username, session ID, IPv4/IPv6 addresses and prefixes, NAS address, MAC,
and start-time ranges.
Per-row actions (this is where RFC 5176 dynamic authorization lives):
- 修改授权 / CoA — send a
CoA-Requestwith a new session timeout and/orFilter-Id. - 强制下线 / Disconnect — send a
Disconnect-Requestto terminate the session (with confirmation).
Both target the NAS CoA port and report success/failure as a notification.
Accounting
Historical accounting records (radius_accounting), read-only: session ID,
addresses, start/stop time, total input/output traffic. Same filter set as
Online Sessions plus stop-time data; CSV export supported.
System Config
Visible to super/admin only. Settings are schema-driven, grouped into
accordions (RADIUS group expanded by default), and live in the sys_config
table — changes apply without restart. The 13 RADIUS settings:
| Key | Default | Purpose |
|---|---|---|
EapMethod | eap-md5 | Active EAP method: eap-md5, eap-mschapv2, eap-tls, eap-peap, eap-ttls |
EapEnabledHandlers | * | Comma allow-list of permitted EAP handlers |
EapTlsCertFile / EapTlsKeyFile | empty | Server certificate/key for EAP-TLS/PEAP/TTLS; empty disables TLS-based EAP |
EapTlsCaFile | empty | CA bundle for client-certificate validation |
EapTlsMinVersion | 1.2 | Minimum TLS version (1.2/1.3) |
IgnorePassword | false | Skip password verification (testing only) |
AccountingHistoryDays | 90 | Accounting retention days (@daily cleanup; 0 disables) |
AcctInterimInterval | 300 | Seconds between NAS interim updates |
SessionTimeout | 3600 | Default session timeout seconds |
LogLevel | info | RADIUS log verbosity (debug/info/warn/error) |
RejectDelayMaxRejects | 7 | Consecutive rejects before delaying responses |
RejectDelayWindowSeconds | 10 | Window for the reject counter |
Toolbar: Save, Refresh, Reset (restore defaults, confirmed), and Backup / Restore — export or re-import a JSON snapshot of nodes, NAS, profiles, users, settings, and operators (see the Operations Guide for what it does not include).
Operators
Management of console accounts (super/admin only): username, password,
contact info, level, status. The operator action log is kept in the database
(sys_opr_log) and purged automatically after one year.
UI conveniences
- Language switcher (app bar) — 简体中文 / English, persisted per browser.
- Theme toggle — light/dark.
- CSV export on users, sessions, accounting, NAS, nodes, operators.
- Server-side pagination and active-filter chips on all list pages.
Operations Guide
中文版本:运维指南
Everything you need to run ToughRADIUS in production: configuration reference, environment variables, TLS/EAP certificates, storage, monitoring, backup, and the bundled command-line tools.
Process model
One static binary runs several services concurrently (web/admin API, RADIUS auth, RADIUS accounting, RadSec). If any service fails, the whole process exits so a supervisor can restart it — run it under systemd, Docker, or an equivalent.
# /etc/systemd/system/toughradius.service (reference)
[Unit]
Description=ToughRADIUS server
After=network-online.target
[Service]
ExecStart=/usr/local/bin/toughradius -c /etc/toughradius.yml
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Ports
| Port | Protocol | Service | Config key |
|---|---|---|---|
| 1816 | TCP HTTP | Admin UI + REST API | web.port |
| 1817 | TCP HTTPS | Admin UI over TLS (optional; start failure is non-fatal) | web.tls_port |
| 1812 | UDP | RADIUS authentication | radiusd.auth_port |
| 1813 | UDP | RADIUS accounting | radiusd.acct_port |
| 2083 | TCP TLS | RadSec (RFC 6614) | radiusd.radsec_port |
| 3799 | UDP (outbound) | CoA/Disconnect to the NAS | per-NAS CoA port field |
Configuration
Lookup order: -c <file> → ./toughradius.yml → /etc/toughradius.yml →
embedded defaults. Inspect the merged result with toughradius -printcfg.
system:
appid: ToughRADIUS
location: Asia/Shanghai # cron/timestamp timezone
workdir: /var/toughradius # default in production builds
debug: false
web:
host: 0.0.0.0
port: 1816
tls_port: 1817
secret: <random-string> # JWT signing secret — change it
database:
type: sqlite # sqlite | postgres
host: 127.0.0.1 # postgres only
port: 5432
name: toughradius.db # sqlite filename (under {workdir}/data/) or pg database
user: postgres
passwd: <password>
max_conn: 100
idle_conn: 10
debug: false
radiusd:
enabled: true
host: 0.0.0.0
auth_port: 1812
acct_port: 1813
radsec_port: 2083
radsec_worker: 100
radsec_ca_cert: private/ca.crt # relative paths resolve against workdir
radsec_cert: private/radsec.tls.crt
radsec_key: private/radsec.tls.key
debug: false # true = full packet dumps
logger:
mode: production # development | production
file_enable: true
filename: /var/toughradius/toughradius.log
Working directory layout
On startup ToughRADIUS creates under system.workdir:
/var/toughradius/
├── data/ # SQLite database, metrics data
├── logs/
├── private/ # TLS material (mode 0700)
├── public/
└── backup/ # server-side copies of configuration backups
Environment variables
Environment variables override the YAML file:
| Variable | Overrides |
|---|---|
TOUGHRADIUS_SYSTEM_WORKER_DIR | system.workdir |
TOUGHRADIUS_SYSTEM_DEBUG | system.debug |
TOUGHRADIUS_WEB_HOST / _WEB_PORT / _WEB_TLS_PORT / _WEB_SECRET | web.* |
TOUGHRADIUS_DB_TYPE / _DB_HOST / _DB_PORT / _DB_NAME / _DB_USER / _DB_PWD / _DB_DEBUG | database.* |
TOUGHRADIUS_RADIUS_ENABLED / _RADIUS_HOST / _RADIUS_AUTHPORT / _RADIUS_ACCTPORT / _RADIUS_DEBUG | radiusd.* |
TOUGHRADIUS_RADIUS_RADSEC_PORT / _RADIUS_RADSEC_WORKER / _RADIUS_RADSEC_CA_CERT / _RADIUS_RADSEC_CERT / _RADIUS_RADSEC_KEY | RadSec settings |
TOUGHRADIUS_LOGGER_MODE / _LOGGER_FILE_ENABLE | logger.* |
TOUGHRADIUS_RADIUS_POOL | RADIUS worker pool size (default 1024) |
CLI flags
| Flag | Effect |
|---|---|
-c <file> | Configuration file path |
-initdb | Drop and recreate all tables, then exit |
-printcfg | Print merged configuration as JSON, exit |
-v | Print version / build time / commit, exit |
-h | Usage |
Runtime RADIUS settings (EAP method, certificates, intervals, reject-delay…) live in the database and are edited in System Config — no restart needed. See the Admin UI Manual.
Database
- SQLite (default) — pure-Go driver, zero CGO, file at
{workdir}/data/<name>. Fine for small/medium deployments; back up the file. - PostgreSQL — set
database.type: postgresplus host/user/password. Recommended for production scale and concurrent accounting load.
Schema migration (GORM AutoMigrate) runs automatically at every startup, so
upgrades are: stop, replace the binary, start. -initdb is for first
installation only — it destroys all data.
Large tables to watch: radius_accounting (grows with every session) and
radius_online. The radius.AccountingHistoryDays setting (default 90, set to
0 to disable) defines the accounting retention window: a @daily job deletes
terminated radius_accounting rows older than that many days (active
sessions are untouched) and clears dangling radius_online rows that have
missed several interim updates. The operator action log (sys_opr_log) is
purged automatically after one year. For very high volumes, still consider
database-level archiving as part of your own ops.
TLS and certificates
Three independent certificate consumers:
| Consumer | Files | Notes |
|---|---|---|
| RadSec | radiusd.radsec_ca_cert / radsec_cert / radsec_key | TLS 1.2+; client certificates are verified if presented (VerifyClientCertIfGiven) |
| Web HTTPS | {workdir}/private/toughradius.tls.crt + .key (fixed paths) | Listens on web.tls_port; failure to load is logged, HTTP keeps running |
| EAP (TLS/PEAP/TTLS) | System Config → EapTlsCertFile, EapTlsKeyFile, EapTlsCaFile, EapTlsMinVersion | Empty cert/key disables TLS-based EAP methods |
Generate a complete CA/server/client set with the bundled tool:
go run ./cmd/certgen -type all -output /var/toughradius/private \
-server-cn radius.example.com -server-dns radius.example.com \
-days 3650
# then point radsec_cert/radsec_key (or the EAP settings) at the files
Logging
zap structured logging. logger.mode: development = human-readable console;
production = JSON. File output controlled by logger.file_enable +
logger.filename. RADIUS verbosity is additionally tunable at runtime via
System Config → LogLevel; radiusd.debug: true dumps full packets (keep
off in production).
Metrics
Counters are kept in memory and surfaced through the admin dashboard (there is
no Prometheus /metrics HTTP endpoint). RADIUS counters include: radus_accept,
radus_online/radus_offline, radus_accounting, radus_auth_drop /
radus_acct_drop, radus_radsec_saturated, and per-cause reject counters —
radus_reject_passwd_error, radus_reject_not_exists, radus_reject_expire,
radus_reject_disabled, radus_reject_limit, radus_reject_bind_error,
radus_reject_unauthorized, radus_reject_other. Accounting-Requests dropped
at ingress are classified by reason — radus_acct_drop_nas (unknown or
unauthorized NAS), radus_acct_drop_username (missing username), and
radus_acct_drop_secret (bad Request Authenticator) — while radus_acct_drop
remains the catch-all for back-pressure and response-write drops. System gauges
(CPU/memory, process CPU/memory) are sampled every 30 s.
For external monitoring, probe the service ports and watch the log file; treat process exit as the failure signal (the process model is fail-fast).
Backup and restore
System Config → Backup downloads a JSON snapshot (schema version 9.0) of:
nodes, NAS devices, profiles, users, system settings, operators. A copy is also
written to {workdir}/backup/. Restore re-imports such a file.
The snapshot does not include accounting history or online sessions. For a full disaster-recovery story, also back up the database itself (copy the SQLite file or use
pg_dump).
Command-line tools
All live under cmd/ and run with go run ./cmd/<tool>:
| Tool | Purpose |
|---|---|
radtest | Mini RADIUS client: auth, acct, flow (auth + start + stop). Flags: -server, -secret, -username, -password, -calling-station, -framed-ip, -session-id |
certgen | Generate CA / server / client certificates (see above) |
benchmark | Load tester: total requests -n, concurrency -c, auth/acct modes, CSV stats output |
reset-password | Reset a console operator password: go run ./cmd/reset-password -c <cfg> -u admin -p <new> |
demo-seed | Populate demo nodes/NAS/profiles/users/sessions for evaluation |
config-tool | Validate / summarize the settings schema JSON |
Production hardening checklist
- Change
web.secretand the defaultadminpassword. -
radiusd.debug: false,logger.mode: production. - Restrict UDP 1812/1813 and TCP 1816 to trusted networks (firewall).
- Use RadSec (2083) or a trusted L2/VPN path for RADIUS across untrusted networks.
- Unique, strong shared secret per NAS.
- EAP: prefer
eap-tls/eap-peap/eap-ttlswith real certificates; mind the MS-CHAPv2 caveats in the Security Policy. - Database backups scheduled (config snapshot + DB dump).
- Supervisor with restart policy; alert on process exit.
LDAP / AD Authentication Backend
中文版本:LDAP / AD 认证后端
ToughRADIUS can verify a user’s password against an external LDAP directory or Microsoft Active Directory by performing an LDAP bind, instead of (or in addition to) the password stored in its own database. This lets you reuse an existing corporate directory without copying or migrating passwords.
PAP-family only. The LDAP backend authenticates by binding with the cleartext password, so it serves only bare PAP and EAP-TTLS inner PAP (RFC 5281). Challenge/response methods — CHAP, MS-CHAP, MS-CHAPv2, EAP-MD5 and PEAP-MSCHAPv2 — cannot be served, because the server never holds the cleartext password it would need to recompute their responses. When LDAP is enabled those methods are deliberately rejected with a diagnostic reason; see How authentication flows below.
When to use it (and when not to)
Use the LDAP backend when you must serve users whose passwords already live in a directory — an OpenLDAP server, FreeIPA, or Active Directory — and you do not want to provision a certificate per client. It is the pragmatic bridge for mixed and legacy estates: protect the tunnel with a server certificate, then send the username and password inside it.
Be honest about the trade-off. EAP-TTLS/PAP transmits the cleartext password inside the TLS tunnel — it is protected only by that tunnel. The security of the deployment therefore rests on a strong, properly verified server certificate and TLS 1.2+. If every client can present a certificate, prefer EAP-TLS. If you need directory-backed passwords for legacy clients, LDAP + EAP-TTLS/PAP is the right tool — just size your expectations accordingly.
Deployment model
The LDAP backend replaces only the password check. It does not create
accounts or carry authorization data. For every user you still need a local
RadiusUser row, because authorization (profile/plan, rate limits, expiry,
concurrent-session limit, address pool, VLAN, MAC binding) is loaded from the
local database before authentication runs.
In other words:
- Authentication (is the password correct?) → the LDAP directory, by bind.
- Authorization (what is this user allowed to do?) → the local
RadiusUserrow, exactly as without LDAP.
A global ldap.Enabled switch turns the backend on or off for the whole server;
there is no per-user “authentication source” field. MAC-address authentication
always bypasses LDAP.
Configuration
Configure the backend on the System Configuration page of the admin UI, under the LDAP group. All items are also editable through the settings API. The backend re-reads its configuration on every authentication attempt, so changes take effect immediately without a restart.
| Key | Type | Default | Applies to | Description |
|---|---|---|---|---|
ldap.Enabled | bool | false | both | Turn the LDAP backend on. Off by default. |
ldap.ServerURL | string | (empty) | both | Directory URL, e.g. ldap://dc.example.com:389 or ldaps://dc.example.com:636. |
ldap.BindMode | enum | template | both | template or search (see below). |
ldap.BindDNTemplate | string | (empty) | template | DN template with a single %s for the username, e.g. uid=%s,ou=people,dc=example,dc=com or the AD UPN form %[email protected]. |
ldap.BaseDN | string | (empty) | search | Subtree base for the user lookup, e.g. dc=example,dc=com. |
ldap.UserFilter | string | (uid=%s) | search | Filter with a single %s for the username (escaped before substitution), e.g. (uid=%s) or AD (sAMAccountName=%s). |
ldap.SearchBindDN | string | (empty) | search | DN of the read-only service account used to find users, e.g. cn=svc-radius,ou=svc,dc=example,dc=com. |
ldap.SearchBindPassword | string | (empty) | search | Password for the service-account DN. |
ldap.StartTLS | bool | false | both | Upgrade an ldap:// connection to TLS with StartTLS (RFC 4513 §3) before binding. Leave off for ldaps://. |
ldap.TLSSkipVerify | bool | false | both | Skip TLS certificate verification. Insecure — lab / self-signed only. |
ldap.Timeout | int (s) | 5 | both | Dial and per-operation timeout in seconds (1–60). |
Bind modes
Template mode (ldap.BindMode = template) is the simplest: the username is
substituted into BindDNTemplate to form a DN, and the server binds directly as
that DN with the supplied password. Use it when every user’s DN follows a fixed
pattern.
BindMode = template
BindDNTemplate = uid=%s,ou=people,dc=example,dc=com
# Active Directory alternative:
# BindDNTemplate = %[email protected]
Search mode (ldap.BindMode = search) handles directories where the DN is
not predictable. The server first binds as a read-only service account
(SearchBindDN / SearchBindPassword), searches under BaseDN with
UserFilter to locate the user’s DN, and then re-binds as the user with the
supplied password to verify it.
BindMode = search
ServerURL = ldaps://dc.example.com:636
BaseDN = dc=example,dc=com
UserFilter = (sAMAccountName=%s) # Active Directory
SearchBindDN = cn=svc-radius,ou=svc,dc=example,dc=com
SearchBindPassword = ********
Transport security
- Use an
ldaps://URL for implicit TLS (port 636), or anldap://URL together withStartTLS = trueto upgrade the plaintext connection (RFC 4513 §3). Do not enable StartTLS on anldaps://URL. - Leave
TLSSkipVerify = falsein production. Enable it only against a lab or self-signed directory — it disables certificate verification and exposes the bind to interception.
How authentication flows
| Method | Behaviour when ldap.Enabled = true |
|---|---|
| Bare PAP | Authenticated by LDAP bind. |
| EAP-TTLS / inner PAP | Authenticated by LDAP bind; MS-MPPE keys are still derived for the tunnel. |
| CHAP / MS-CHAP / MS-CHAPv2 | Rejected with a diagnostic reason (no cleartext password to bind with). |
| EAP-MD5 / PEAP-MSCHAPv2 | Rejected for the same reason. |
| MAC authentication | Bypasses LDAP entirely. |
The rejection of challenge/response methods is deliberate and centralized at the
password-retrieval boundary, not forked at the protocol ingress. When LDAP is
active the local RadiusUser.Password is usually empty, and a challenge/response
method that derived its expected value from an empty secret could otherwise
falsely accept any user — so those methods are failed closed instead.
Security model
The backend is conservative by design:
- Empty username or password is rejected before any network operation. A bind with a DN and an empty password is treated as an anonymous bind by many servers and would otherwise succeed (RFC 4513 §5.1.2), so it is refused up front.
- Injection is neutralized. In search mode the username is escaped with LDAP filter escaping (RFC 4515 §3); in template mode it is DN-escaped before substitution.
- An ambiguous search is refused. If
UserFiltermatches more than one entry, the attempt is rejected rather than guessing. - A directory outage is never reported as a wrong password. An invalid- credentials result (LDAP code 49) maps to a password rejection; every other failure — unreachable directory, misconfiguration, service-account bind failure — maps to a backend-unavailable outcome. This distinction also drives the metrics below.
Observability
Rejections are counted by reason (see the Operations Guide for the metrics endpoint):
radus_reject_passwd_error— the password was wrong (bind returned invalid-credentials).radus_reject_ldap_error— the backend could not give an answer (directory unreachable, StartTLS failure, service-account bind failure, misconfiguration).
Keeping these separate means a directory outage shows up as
radus_reject_ldap_error, not as a spike of “wrong password” — so an alert on
radus_reject_ldap_error cleanly signals a directory problem rather than user
error.
Note.
radus_acceptincrements once per successful authentication — both the bare PAP/CHAP and the EAP flows converge on a single accept chokepoint — so it pairs with the reject counters above for a success-rate or SLO view. A directory outage still surfaces only asradus_reject_ldap_error, never as a falseradus_accept, keeping the success and failure signals separate.
Troubleshooting
| Symptom | Likely cause |
|---|---|
Every login fails, radus_reject_ldap_error climbing | ServerURL wrong/unreachable, TLS handshake failing, or (search mode) the service account cannot bind. |
| Windows/AD clients rejected, PAP users fine | The clients are using PEAP-MSCHAPv2 or MS-CHAPv2, which LDAP cannot serve — switch them to EAP-TTLS/PAP or use the local password backend. |
| Search mode finds no user | Check BaseDN and UserFilter; confirm the service account can read user entries. |
| Bind works in the lab but fails in production | TLSSkipVerify was masking an invalid certificate — install a trusted certificate and turn it off. |
| Passwords accepted but the session has no plan/limits | The local RadiusUser row is missing — LDAP only checks the password; create the local account for authorization. |
See also
- Operations Guide — EAP/TLS certificates, metrics, process model.
- Concepts & Terminology — PAP, EAP, EAP-TTLS.
- Protocol & RFC Reference — RFC 5281 (EAP-TTLS), RFC 4511/4513 (LDAP).
FAQ
中文版本:常见问题解答
Frequently asked questions, grouped by theme. If your question is not covered, search the GitHub issues or open a new one.
Installation & access
I forgot the admin password — how do I reset it?
Use the bundled tool against the same configuration file the server runs with:
go run ./cmd/reset-password -c /etc/toughradius.yml -u admin -p <new-password>
The default account after -initdb is admin / toughradius.
Which database should I choose, SQLite or PostgreSQL?
SQLite (the default) requires nothing extra — pure-Go driver, single file under
{workdir}/data/ — and suits labs and small deployments. Choose PostgreSQL for
production scale, high accounting volume, or when you need external backup
tooling (pg_dump, replication).
Can I run it on a port other than 1812/1813/1816?
Yes — every port is configurable (radiusd.auth_port, radiusd.acct_port,
web.port, …) via YAML or environment variables. See the
Operations Guide.
The HTTPS admin port (1817) doesn’t work
The web TLS listener needs {workdir}/private/toughradius.tls.crt and .key.
If they are missing or invalid the failure is logged and only the HTTPS
listener stops — plain HTTP on 1816 keeps serving. Generate certificates with
cmd/certgen or provide your own.
Is -initdb safe to run again?
No. It drops and recreates every table. Run it only at first installation. Regular upgrades need no manual schema step — migration runs automatically at startup.
Authentication
All requests are ignored / time out
The two most common causes:
- The request’s source IP is not registered as a NAS device — add it under NAS Devices (or fix NAT so the expected address is seen).
- Shared secret mismatch — RADIUS silently discards packets that fail authentication of the packet itself.
Turn on radiusd.debug: true or set LogLevel to debug to see what arrives.
Users authenticate but get no bandwidth limit
Rate attributes are sent only when the NAS record’s vendor is one that has
a rate VSA (Huawei, MikroTik, H3C, ZTE, iKuai). A NAS registered as Standard
or Cisco receives no proprietary rate attribute — see the
Vendor Integration Guide. Also check that
the user’s profile actually sets up_rate/down_rate (Kbps).
Dial-up authenticates but gets no IP / the address pool has no effect
Framed-Pool is sent only when a pool is set on the user or its rate
profile, and the emitted pool name must match the pool name on the NAS
(e.g. RouterOS /ip pool). A name mismatch — or no pool at all — leaves the
client without a (correct) address. For a fixed IP, set a static IPv4 on the
user (emits Framed-IP-Address, overriding the pool). See the end-to-end
Scenario Cookbook · MikroTik.
I have multiple NAS devices — must each be configured separately?
Yes. Every NAS must be registered individually under NAS Devices, each with its own source IP (or identifier) and its own shared secret. ToughRADIUS matches the packet’s source address (or NAS identifier) to the NAS record and its secret; requests from an unregistered source are logged and rejected as an unauthorized NAS. Different NAS devices may use different secrets — they need not be uniform.
Why was a specific user rejected?
Rejects are categorized (wrong password, user not found, expired, disabled,
session limit, MAC/VLAN binding mismatch, unauthorized NAS…) — the dashboard
shows per-cause counters and the log records the detail. The most surprising
one is the binding mismatch: with bind_mac/bind_vlan enabled, the first
seen MAC/VLAN is stored and later requests must match; clear the stored value
on the user after replacing hardware.
Repeated wrong passwords respond slowly — why?
That is the reject-delay brute-force guard: after
RejectDelayMaxRejects (default 7) consecutive rejects within
RejectDelayWindowSeconds (default 10 s), responses are delayed. Tune both in
System Config.
Does ToughRADIUS support 802.1X / Wi-Fi Enterprise?
Yes. Supported EAP methods: EAP-MD5, EAP-MSCHAPv2, EAP-TLS, PEAPv0/EAP-MSCHAPv2 and EAP-TTLS (inner PAP / MS-CHAP-V2). Select the method in System Config → EapMethod.
EAP-TLS / PEAP / EAP-TTLS doesn’t start
TLS-based EAP requires EapTlsCertFile + EapTlsKeyFile in System Config —
when they are empty the methods are disabled by design. Generate a server
certificate with cmd/certgen, set the paths, and retry. EapTlsCaFile is
needed only to validate client certificates (EAP-TLS).
What breaks if the server and device clocks drift apart?
Clock skew causes subtle problems: TLS-based EAP (EAP-TLS / PEAP / TTLS)
validates certificate validity windows (NotBefore / NotAfter), so a large
time gap can fail the handshake; accounting start/stop times and durations
become wrong; and the brute-force reject-delay window (default 10 s) is measured
on the server clock. Run NTP on both the server and the NAS to keep time in
sync.
Which EAP method should I pick?
- EAP-TLS — strongest (mutual certificates), needs client-cert rollout.
- PEAPv0/EAP-MSCHAPv2 — Windows/AD compatibility; mind the MS-CHAPv2 NTLMv1-like attack surface (see Security Policy).
- EAP-TTLS — legacy/LDAP backends via inner PAP, keeping passwords inside the TLS tunnel.
Sessions, CoA & accounting
Disconnect / CoA from Online Sessions has no effect
Check, in order: the device has dynamic authorization enabled (e.g.
radius incoming on RouterOS, aaa server radius dynamic-author on IOS); the
CoA port on the NAS record matches the device (default 3799); and the
device accepts requests from the server address. ToughRADIUS waits 5 s and
retries twice before reporting failure.
How do I change a user’s speed live (FUP / over-quota throttling)?
ToughRADIUS’s Change of Authorization (CoA) carries only Session-Timeout and
Filter-Id — it does not rewrite rate attributes like Mikrotik-Rate-Limit
live. The standard way to change speed in real time is to change the rate on the
profile / user first, then force a disconnect; the client redials and is
re-authorized at the new speed. Alternatively use Filter-Id to apply a
pre-defined rate rule on the device. See
Scenario Cookbook · MikroTik · Live control.
CoA / disconnect says the session was not found
An operator-initiated CoA / Disconnect first locates the session on the Online
Sessions page, then addresses the device using its NAS record and session
identity (e.g. Acct-Session-Id). If the online row is stale (the NAS never
sent an accounting stop) or the session already ended, there is nothing to
match — refresh the online list, confirm accounting works on the device, and
retry.
Online sessions show users that already disconnected
Online entries are created/refreshed by NAS accounting packets. If the NAS
stops sending (reboot, link loss) the row can linger. Ensure accounting and
interim updates are enabled on the device (Acct-Interim-Interval is sent in
every Access-Accept, default 300 s). Stale entries can also be cleared manually
from the UI with Disconnect/delete.
Accounting table grows forever — what is cleaned automatically?
Two @daily jobs purge data automatically: one deletes operation logs
(SysOprLog) older than one year; the other, SchedClearExpireData, deletes
terminated radius_accounting history older than AccountingHistoryDays
(default 90 days; active sessions are never purged) and removes dangling
radius_online rows that have missed several interim updates. Set
AccountingHistoryDays to 0 to disable accounting cleanup (keep history
indefinitely). For very high volumes you may still want database-level
archiving in your ops routine. Configuration backups do not include
accounting history — see Backup and restore.
Concurrent sessions are not limited
active_num in the billing profile is the per-user concurrency cap (0 means
unlimited). The check counts rows in radius_online, which requires working
accounting from the NAS — without accounting Start packets the server cannot
know who is online.
Operations
How do I monitor it with Prometheus?
There is currently no /metrics HTTP endpoint; counters are in-memory and
shown on the dashboard. For external monitoring probe the ports, watch the log
file, and alert on process exit (the process is fail-fast by design).
How do I upgrade safely?
Stop the service, replace the binary (or pull the new Docker tag), start. Schema migration is automatic. Take a configuration backup (System Config → Backup) and a database backup first.
Where are logs / data / certificates stored?
Everything lives under system.workdir (default /var/toughradius):
data/ (SQLite DB), logs/, private/ (TLS material), backup/
(config snapshots). See the
Operations Guide.
Protocol & RFC Reference
中文版本:协议与 RFC 索引
ToughRADIUS implements standard RADIUS, EAP, dynamic-authorization, and secure-transport protocols. This chapter is the curated, implementation-oriented index of the standards the project relies on, with each RFC mapped to where it is used in the code and on the roadmap.
The full RFC texts are archived under
docs/rfcs/;
the raw catalog of every file lives in
docs/rfcs/README.md.
Where the two differ, the citations in this chapter take precedence.
Implemented standards
RADIUS core
- RFC 2865 — RADIUS authentication; the base request/response protocol.
- RFC 2866 — RADIUS accounting; session start / interim-update / stop records.
RADIUS + EAP integration
- RFC 3579 — RADIUS support for EAP (EAP-Message and Message-Authenticator).
- RFC 3580 — IEEE 802.1X RADIUS usage guidelines.
EAP framework and methods
- RFC 3748 — Extensible Authentication Protocol (EAP). The framework itself; also defines EAP-MD5, the MD5-Challenge method (§5.4).
- RFC 5216 — EAP-TLS; certificate-based mutual authentication (milestone M1).
- RFC 5281 — EAP-TTLSv0; a TLS tunnel carrying inner PAP / MS-CHAPv2 (M9).
- RFC 2759 — MS-CHAP-V2, used as the inner method of EAP-MSCHAPv2 and PEAPv0 (M8). MS-MPPE session keys are derived per RFC 2548 / RFC 5705.
PEAPv0 has no standalone RFC; it follows the Microsoft/Cisco PEAP definition and carries inner EAP-MSCHAPv2. It is compatibility-oriented — MS-CHAPv2 exchanges carry an NTLMv1-like attack surface — so prefer EAP-TLS where you control client certificates.
Dynamic authorization
- RFC 5176 — CoA and Disconnect-Request, superseding RFC 3576 (milestone M2).
Secure transport
- RFC 6614 — RADIUS over TLS (RadSec).
- RFC 6613 — RADIUS over TCP.
Vendor-specific attributes
- RFC 2548 — Microsoft VSAs, including the MS-MPPE-Send/Recv-Key attributes used to carry EAP key material.
IPv6
- RFC 3162, RFC 4818, RFC 6911 — RADIUS IPv6 addressing and delegated-prefix attributes (milestone M3).
Roadmap standards
| RFC | Standard | Milestone |
|---|---|---|
| RFC 9190 (+ RFC 9427) | EAP-TLS 1.3 and TLS 1.3 key derivation | M10 |
| RFC 7170 / RFC 9930 | TEAP v1 — tunnel EAP, machine + user chaining | M11 |
| RFC 5931 | EAP-PWD — password-based, no per-client certificate | M12 |
Catalog accuracy note. In
docs/rfcs/, the filerfc7542-eap-pwd.txtis mislabeled: RFC 7542 is the Network Access Identifier (NAI), whereas EAP-PWD is RFC 5931. Likewise, EAP-MD5 is defined by RFC 3748 §5.4, not RFC 3851 (an S/MIME specification). This chapter uses the correct citations.
See also
- Overview — capability summary, including the EAP suite.
- Documentation Map — where every source document lives.
- RFC Editor — authoritative online RFC texts.
EAP Acceptance Reports
Weekly EAP acceptance runs validate ToughRADIUS with an external eapol_test supplicant and publish the latest retained reports here.
Latest verdict: ACCEPTED
Latest Scenario Summary
| Scenario | Method | Expected | Status | Duration | Detail |
|---|---|---|---|---|---|
| EAP-TLS valid client certificate | EAP-TLS | Access-Accept | passed | 146 ms | external supplicant received the expected Access-Accept |
| EAP-TLS untrusted client certificate | EAP-TLS | Access-Reject | passed | 19 ms | external supplicant was rejected as expected |
| PEAP/MSCHAPv2 valid credentials | PEAP/MSCHAPv2 | Access-Accept | skipped | 0 ms | Skipped intentionally: eapol_test currently exposes a PEAP inner-framing interop gap (server rejects the decrypted phase-2 payload as an invalid inner EAP message). The in-process PEAP/MSCHAPv2 integration test remains the current acceptance coverage. |
| PEAP/MSCHAPv2 wrong password | PEAP/MSCHAPv2 | Access-Reject | skipped | 0 ms | Skipped intentionally: eapol_test currently exposes a PEAP inner-framing interop gap (server rejects the decrypted phase-2 payload as an invalid inner EAP message). The in-process PEAP/MSCHAPv2 integration test remains the current acceptance coverage. |
| EAP-TTLS/PAP valid credentials | EAP-TTLS/PAP | Access-Accept | passed | 122 ms | external supplicant received the expected Access-Accept |
| EAP-TTLS/MSCHAPv2 valid credentials | EAP-TTLS/MSCHAPv2 | Access-Accept | passed | 122 ms | external supplicant received the expected Access-Accept |
| Malformed external EAP client config | tooling | documented skip | skipped | 0 ms | Skipped intentionally: eapol_test parser failures do not exercise ToughRADIUS over RADIUS/EAP. Negative server behavior is covered by untrusted certificate and wrong password scenarios. |
Retained Reports
Performance Benchmark Reports
Weekly benchmark runs record ToughRADIUS performance signals from existing Go Benchmark* functions. Reports are informational and do not fail on timing drift from GitHub hosted runners.
Latest verdict: RECORDED
Latest Summary
| Metric | Value |
|---|---|
| Benchmarks | 15 |
| Packages | 6 |
| Slowest | github.com/talkincode/toughradius/v9/pkg/excel / BenchmarkWriteToFile |
| Highest B/op | github.com/talkincode/toughradius/v9/pkg/excel / BenchmarkWriteToFile |
Retained Reports
Security Policy
中文版本:安全策略
This chapter is the canonical home for ToughRADIUS security advisories and the
guidance that goes with them. The repository’s
SECURITY.md
keeps a short pointer back to this chapter so there is a single source of truth.
Security advisories
XSS vulnerability fix (v8.0.8)
Version v8.0.8 addresses a critical cross-site scripting (XSS) vulnerability.
The issue was found in the errmsg parameter handling in the login endpoint.
| Item | Details |
|---|---|
| Vulnerability type | Cross-Site Scripting (XSS) |
| Severity | Critical |
| Affected versions | v8.0.1 – v8.0.7 |
| Fixed version | v8.0.8 |
| Affected component | Login endpoint (errmsg parameter) |
Recommended actions
We strongly recommend that all users update to the latest version immediately. See the Documentation Map for the README and build instructions you can follow to upgrade your deployment.
Agent Development Guide
中文版本:Agent 开发指南
This chapter is a contributor-oriented digest of how ToughRADIUS is built with AI coding agents. It summarizes the working rules, quality gates, and the auto-delegation loop so the workflow is discoverable from the handbook.
The canonical rules live in the repository’s
AGENT.md;
that file stays authoritative and is referenced directly by the agent tooling.
This chapter does not replace it — when in doubt, follow AGENT.md.
Product scope baseline
Development stays anchored to the feature checklist; it never drifts into unrelated product directions.
- The canonical scope baseline is
docs/feature-checklist.md(with an English copy atdocs/feature-checklist.en.md). - Every task, issue, PR, test, and review note maps to a feature ID such as
TR-F004. - If a request does not map to an existing ID, the checklist is updated first (scope, status, acceptance boundary, rationale) before any code changes.
- Non-goals
TR-N001–TR-N005(payment, CRM, generic monitoring stack, multi-tenancy, full rewrite) are out of scope unless the checklist is explicitly revised first.
Roadmap and skill library
Agent-driven development is organized around three artifacts:
docs/roadmap.md— long-term roadmap and milestones, each mapped toTR-FIDs. This is the task source for agent work..agents/skills/— reusable skill SOPs, one folder per skill (.agents/skills/<name>/SKILL.md)..agents/README.md— the delegation reference and shared guardrails.
A coordinator layer drives the loop, while the execution SOPs do the domain-specific work:
| Role | Skill | Purpose |
|---|---|---|
| Coordinator | orchestrate-roadmap | Entry role for “auto-delegate development”: selects the next unchecked subtask, picks the matching SOP, enforces gates, opens a PR |
| Gate | review-pr | Independent, CI-anchored review; requests changes via labels/comments and auto-merges only when approved and CI is green |
| Self-iteration | groom-roadmap | After each merge, checks off the delivered subtask and re-grooms the roadmap |
Execution SOPs include: add vendor VSA (add-radius-vendor), add EAP method
(add-eap-method), add Admin API (add-adminapi-endpoint), add React Admin
resource (add-react-admin-resource), add config schema (add-config-schema),
add acceptance test (add-acceptance-test), sync upstream radius
(sync-upstream-radius), reference RFC (reference-rfc), align checklist
(align-feature-checklist), write Go tests (write-go-tests), and document Go
APIs (document-go-apis). Pick the matching skill before starting a task type.
Agents run on your own host with your own agent/CLI, not via a CI workflow, so credentials never enter CI and the execution environment stays under your control.
Working guidelines
Understand existing code before editing
Never change code blindly. Locate the existing implementation, related tests, and documentation first, then mirror the project’s naming, error handling, and data flow. Trace the full execution path before fixing a bug, and map dependencies and side effects before refactoring.
Continuous verification
Do not wait until the end to run tests. Every logical change is followed by a test run so regressions surface immediately rather than at the end of a large batch.
Code is the best documentation
- All exported APIs carry comprehensive godoc comments — purpose,
parameters with constraints, return values, and error conditions (with usage
examples for complex APIs). See the
document-go-apisskill for the standard-library-style conventions. - Complex logic carries inline comments explaining the why, not the what.
- Vendor-specific code references the protocol spec (RFC numbers, VSA docs).
- No redundant standalone summary documents — information belongs in code comments and Git history.
Core development principles
Test-driven development (TDD)
Write the test first, then the code: define the expected behavior in a failing
test (red), write the minimum code to pass it (green), then refactor while the
tests stay green. If internal/radiusd/auth.go changes, the test lives in
internal/radiusd/auth_test.go. See the write-go-tests skill for the unified
conventions.
GitHub workflow
- Pull requests only — never push to
main; the protected branch rejects direct pushes. - Conventional commits —
<type>(<scope>): <subject>with types such asfeat,fix,test,docs,refactor,perf,chore. - Small, atomic changes over giant PRs.
Repository issue/PR automation
Several GitHub Actions add lightweight triage around issues and pull requests. They help maintainers scan the queue, but they do not replace reading the original issue, PR diff, and CI output.
| Workflow | Trigger and result | Maintainer notes |
|---|---|---|
| AI issue summary | .github/workflows/ai-issue-summary.yml runs when an issue is opened and posts a short GitHub Models summary as an issue comment. | Issue titles and bodies are untrusted input. Treat the generated summary as a convenience only; the issue text remains the source of truth. |
| Stale | .github/workflows/stale.yml runs daily at 04:24 UTC and by manual dispatch. After 60 days without activity it adds stale; after 14 more inactive days it closes the item. | Comment, push a commit, or remove stale to keep work open. Issues with pinned, security, help wanted, agent-roadmap, or needs-human are exempt; PRs with pinned, security, agent-roadmap, or needs-human are exempt; all milestones are exempt. |
| Labeler | .github/workflows/labeler.yml runs on pull_request_target and applies labels from .github/labeler.yml based on changed paths. | It labels go, javascript, github_actions, dependencies, and doc. The action reads the changed-file list and base-branch config; it does not check out or execute PR code. |
| Greetings | .github/workflows/greetings.yml runs when a contributor opens their first issue or PR and posts onboarding guidance. | The comment is informational. It does not change review requirements or issue priority. |
Minimum viable product (MVP)
Each change is delivered as a minimal, independently usable, rollback-safe unit that does not break existing behavior. Large efforts are broken into MVP increments (for example, vendor attribute parsing → auth integration → accounting → management UI) rather than landing in one oversized PR.
Quality gates
Every agent change must pass these gates before merging:
go build ./...— no compilation errors.go test ./...— all unit tests pass.golangci-lint run— clean (pinned to v2.12.2, matching CI).cd web && npm run build— for any frontend change.- Protocol / end-to-end changes ship a CI-executable acceptance test under
test/integration/and cite the relevant spec underdocs/rfcs/. - Output goes through a PR labeled
agent-roadmap, gated byreview-pr, and is merged only whenagent-approvedwith green CI.
Technical constraints
- No CGO — the project builds with
CGO_ENABLED=0for easy cross-platform deployment. Use pure Go drivers only (for examplegithub.com/glebarez/sqliteinstead ofgithub.com/mattn/go-sqlite3). - Database dual-compatibility — every schema change must work on both PostgreSQL (default) and SQLite.
- Upstream dependency — the core
layeh.com/radiuslibrary isreplaced ingo.modto the organization forkgithub.com/talkincode/radius; important upstream fixes are evaluated via thesync-upstream-radiusskill.
Common anti-patterns (prohibited)
- Exporting APIs without documentation.
- Committing implementation without tests.
- Giant PRs that mix many concerns.
- Implementing before writing tests.
- Pushing directly to
mainor skipping review. - Creating redundant standalone summary/report documents.
- Introducing CGO dependencies.
Where to go next
AGENT.md— the full, canonical agent development guide.- Documentation Map — find the README, security policy, feature checklist, roadmap, and RFC index.
- Protocol & RFC Reference — protocol standards mapped to the code and milestones.
Documentation Map
中文版本:文档地图
This handbook is being assembled incrementally. The first table lists the handbook’s own chapters; the second points to documents that still live elsewhere in the repository, so everything is reachable from a single place.
Handbook chapters
| Chapter | Contents |
|---|---|
| Overview | Project introduction, core capabilities, service model |
| Concepts & Terminology | AAA vocabulary, the authentication flow, password protocols |
| Quick Start | Install, initialize, first user, debugging |
| Vendor Integration Guide | Case studies: MikroTik, Huawei, Cisco, H3C, ZTE, iKuai, … |
| Portal / Hotspot Integration Boundary | Hard boundary: ToughRADIUS is the RADIUS AAA backend, not a hosted captive portal product |
| Scenario Cookbook | End-to-end ops scenarios (five-part): MikroTik PPPoE / Hotspot / CoA; Huawei BRAS speed tiers / line binding / CoA; H3C / ZTE / iKuai / Cisco per-vendor diffs |
| Admin UI Manual | The management console, page by page |
| Operations Guide | Configuration reference, certificates, monitoring, backup, CLI tools |
| LDAP / AD Authentication | Verify passwords against an LDAP/AD directory (PAP-family only): bind modes, TLS, security, metrics |
| FAQ | Frequently asked questions by theme |
| Protocol & RFC Reference | Protocol standards mapped to the code |
| Security Policy | Security advisories and update guidance |
| Agent Development Guide | Contributor digest of the AI-agent workflow, quality gates, and auto-delegation loop |
Repository documents
| Document | Description | Current location |
|---|---|---|
| README | Project introduction, features, and quick start | README.md |
| Agent guide | AI-agent development guide and working rules | Agent Development Guide (handbook digest) · AGENT.md (canonical) |
| Security policy | Security advisories and update guidance | Security Policy (canonical) · SECURITY.md (pointer) |
| Feature checklist | Feature scope baseline (TR-F IDs) | docs/feature-checklist.md · English |
| Roadmap | Long-term roadmap and milestones | docs/roadmap.md |
| RFC index | Protocol standards index used by the project | Protocol & RFC Reference (canonical) · docs/rfcs/README.md (raw catalog) |
Migration plan. The handbook now covers the README’s user-facing content (overview, quick start, vendor integration, admin manual, operations, FAQ); the README remains the GitHub landing page and links here. The agent guide now has a handbook digest chapter that points to the canonical
AGENT.md, which stays in the repository root because the agent tooling references it directly. The feature checklist and roadmap are living documents maintained indocs/by dedicated workflows and are linked rather than migrated.
mdbook & GitBook Coexistence
中文版本:mdbook 与 GitBook 并存
ToughRADIUS also publishes documentation through GitBook
(docs.toughradius.net). This section records the
decision (roadmap item M13.0) for how the new mdBook handbook relates to that
existing pipeline.
Decision: coexistence, not replacement
The mdBook handbook coexists with GitBook. It does not replace or disable the GitBook site. The two pipelines are kept separate and non-conflicting:
- mdBook handbook — lives in the repository under
docs-site/, is written in bilingual chapters, and is built and link-checked by CI on every pull request. It is the canonical, version-controlled, review-gated home for documentation that must evolve together with the code. - GitBook site — synchronizes from the repository through GitBook’s own GitHub
integration (an external service). It is configured by a committed
.gitbook.yamlat the repository root, which points GitBook at the samedocs-site/src/sources, usesintroduction.mdas the landing page, and reads the sharedSUMMARY.mdas its table of contents. GitBook therefore renders the curated bilingual handbook instead of inferring a tree from the whole repo.
Both pipelines build from the same docs-site/src/ sources, but each keeps its
own independent configuration (book.toml for mdBook, .gitbook.yaml for GitBook).
They do not share a build step and cannot break each other, yet they never drift
because they read the same chapters and the same SUMMARY.md.
Where each site is served
The two pipelines publish to separate domains, so they never shadow each other:
- mdBook handbook — deployed to GitHub Pages by
.github/workflows/pages.yml(roadmap item M13.5) and served at the custom domain https://www.toughradius.net/. The deploy workflow writes aCNAMEfile into the published artifact, so GitHub Pages keeps that custom domain on every release. For the domain to resolve,www.toughradius.netmust point at GitHub Pages — anArecord set to185.199.108.153–185.199.111.153, or a DNS-onlyCNAMEtotalkincode.github.io. The default project URL https://talkincode.github.io/toughradius/ stays available and redirects to the custom domain. - GitBook — serves
docs.toughradius.net, which resolves to GitBook’s own hosting (Cloudflare / Fastly), not GitHub Pages. Becausewww.toughradius.netmoves to GitHub Pages, GitBook should be configured to keep onlydocs.toughradius.netand releasewww, so the two services never claim the same host.
Single source of truth
To avoid content drift between the two pipelines, every document has exactly one
canonical home. Because mdBook and GitBook now read the same docs-site/src/
chapters and the same SUMMARY.md, the handbook sources are the single source for
both rendered sites. As scattered documents are migrated into the handbook
(roadmap items M13.2 / M13.3), the original file keeps a short pointer back to the
corresponding chapter instead of duplicating its content.
Editing the shared table of contents
docs-site/src/SUMMARY.md is read by both tools, so keep it to the subset of
Markdown that they parse the same way: a top # Summary title, a non-bulleted
[Introduction / 引言](./introduction.md) landing link, and nested bullet lists
for grouping. The two language sections are expressed as top-level entries
(- [English](./en/overview.md) and - [中文](./zh/overview.md)) with their pages
nested beneath them. Avoid # / ## part headers for grouping: mdBook only groups
on # while GitBook only groups on ##, so a nested list is the one form that both
render identically.
Language toggle in the menu bar
The mdBook output injects a small EN / 中文 switch into the top menu bar
(docs-site/assets/lang-toggle.{js,css}, wired via additional-js /
additional-css in book.toml). It rewrites the last /en/ or /zh/ path
segment, which works because the two language trees keep identical file names.
This toggle exists only on the mdBook/Pages pipeline; GitBook ignores
book.toml extras, so GitBook readers switch languages through the sidebar
sections and per-chapter cross-links instead.
Build and validation
- Local:
mdbook build docs-siteproduces the static site indocs-site/book/, andmdbook serve docs-sitepreviews it with live reload. - CI: a dedicated job builds the handbook and runs an offline link check over
the generated HTML, so a build failure or a broken internal link fails the
pipeline (roadmap item M13.4). The build output (
docs-site/book/) is a build artifact and is not committed.
概述
English version: Overview
ToughRADIUS 是一款使用 Go 语言编写、功能强大的开源 RADIUS 服务器,面向 ISP、 企业网络与运营商场景。它实现了标准 RADIUS 协议,并支持 RadSec(RADIUS over TLS), 同时附带基于 React Admin 的现代化 Web 管理界面。
核心能力
- 标准 RADIUS —— 完整支持 RFC 2865(认证)与 RFC 2866(计费)。
- RadSec —— 基于 TCP 的 TLS 加密 RADIUS(RFC 6614)。
- 动态授权 —— CoA 与 Disconnect 报文(RFC 5176)。
- EAP / 802.1X 套件 —— 挑战类方法(EAP-MD5、EAP-MSCHAPv2)与隧道类方法: EAP-TLS(RFC 5216,基于证书)、PEAPv0/EAP-MSCHAPv2(Windows / AD 兼容)、 EAP-TTLS(RFC 5281,内层 PAP / MS-CHAPv2)。MS-CHAPv2 类方法以兼容为先,存在类似 NTLMv1 的攻击面,可控证书环境优先选用 EAP-TLS。TLS 1.3(RFC 9190)、TEAP 与 EAP-PWD 在路线图中持续推进。
- 多厂商支持 —— 通过厂商私有属性(VSA)兼容 Cisco、MikroTik、华为等主流网络设备。
- 现代化管理界面 —— 基于 React Admin 的控制台,管理用户、套餐、在线会话、计费与审计日志。
- 多数据库 —— PostgreSQL(默认)与 SQLite(纯 Go,无需 CGO)。
服务模型
服务器以并发方式运行多个相互独立的服务;任意一个服务失败都会让进程退出,便于由 守护进程干净地重启。
| 服务 | 协议 / 端口 | 用途 |
|---|---|---|
| Web / Admin API | HTTP,TCP 1816 | 管理界面与 REST API |
| RADIUS 认证 | UDP 1812 | 认证 |
| RADIUS 计费 | UDP 1813 | 计费 |
| RadSec | TLS over TCP 2083 | 加密的 RADIUS 传输 |
下一步
- 核心术语与概念 —— AAA 术语、请求流转,以及各概念在产品中的落点。
- 快速开始 —— 约十分钟完成安装、初始化、建用户并用
radtest验证。 - 厂商对接指南 —— MikroTik、华为、Cisco、H3C、中兴、爱快及 标准设备的对接案例。
- 管理系统用户手册 —— 管理控制台每个页面的说明。
- 运维指南 —— 生产配置、证书、监控、备份与命令行工具。
- 常见问题解答 —— 大家都会问到的问题。
- 文档地图 —— 找到现有的 README、Agent 指南、安全策略、 功能清单、路线图与 RFC 索引。
- mdbook 与 GitBook 并存 —— 本手册与 GitBook 站点的关系, 以及单一事实来源策略。
核心术语与概念
English version: Concepts & Terminology
本章解释贯穿 ToughRADIUS 的 AAA 核心术语,并说明每个概念在产品中的落点。 协议规范的权威清单及其与代码的对应关系,请见协议与 RFC 索引。
一段话理解 AAA
RADIUS(Remote Authentication Dial In User Service)协议让网络设备向中心服务器
提出三个问题:这个用户是谁(认证 Authentication)、他能做什么
(授权 Authorization)、他用了多少(计费 Accounting)。ToughRADIUS
对三者全部作答:校验凭据、在 Access-Accept 中下发授权属性(IP 地址、带宽、
VLAN、会话限制),并从计费报文中记录用量。
核心术语
| 术语 | 在 ToughRADIUS 中的含义 |
|---|---|
| NAS(网络接入服务器) | 发出 RADIUS 请求的网络设备——路由器、交换机、BRAS 或无线控制器。每台 NAS 须在管理界面的 NAS 设备 中登记 IP 地址、共享密钥和厂商代码;未登记地址的请求会被丢弃。 |
| 共享密钥(shared secret) | 每台 NAS 一个的口令,用于认证 RADIUS 报文本身(RFC 2865 §3),NAS 侧与 ToughRADIUS 的 NAS 记录必须一致。 |
| 拨入用户 / RADIUS 用户 | RADIUS 用户 中的账号:用户名、密码、过期时间、可选的静态 IPv4/IPv6 地址、MAC/VLAN 绑定以及计费策略。 |
计费策略(RadiusProfile) | 计费策略 中的可复用模板:并发会话数(active_num)、上行/下行速率(单位 Kbps)、地址池、IPv6 前缀、域、MAC/VLAN 绑定开关。用户留空的字段自动继承策略值。 |
| VSA(厂商私有属性) | RADIUS 26 号属性(RFC 2865 §5.26),允许各厂商定义私有属性的容器。ToughRADIUS 内置 15+ 厂商的属性字典,并按 NAS 厂商代码下发对应厂商的授权属性。详见厂商对接指南。 |
| 厂商代码(vendor code) | 选择厂商行为的 IANA 私有企业号,例如 9 Cisco、2011 华为、14988 MikroTik、25506 H3C、3902 中兴、10055 爱快、0 标准。按 NAS 记录设置,决定使用哪个请求解析器及下发哪些 Access-Accept 属性。 |
| CoA / Disconnect(RFC 5176) | 动态授权:由服务器主动发起、修改在线会话(CoA-Request,如新的 Session-Timeout 或 Filter-Id)或终止会话(Disconnect-Request)的报文。在 在线会话 页面发起,发往 NAS 的 UDP 3799 端口(可按 NAS 覆盖)。 |
| RadSec(RFC 6614) | TCP 2083 端口上的 RADIUS over TLS。用 TLS 隧道包裹 RADIUS,使报文可以安全穿越不可信网络;纯 UDP RADIUS 仅靠共享密钥保护。 |
| EAP(RFC 3748) | 802.1X 网络使用的可扩展认证协议。ToughRADIUS 实现 EAP-MD5、EAP-MSCHAPv2、EAP-TLS、PEAPv0/EAP-MSCHAPv2 和 EAP-TTLS;当前方法与证书在 系统配置 → RADIUS 中选择。 |
| 计费会话 | NAS 通过 Accounting-Request 报文汇报的生命周期:Start → Interim-Update(多次)→ Stop(RFC 2866)。在线会话见 在线会话(radius_online 表),历史记录见 计费记录(radius_accounting 表)。 |
| Acct-Interim-Interval | NAS 发送中间计费更新的间隔(秒)。每个 Access-Accept 都会携带,取自 radius.AcctInterimInterval 配置。 |
| Session-Timeout | 会话最长剩余时间(秒)。ToughRADIUS 将其设为距用户过期时间的剩余秒数,确保会话不会超过账号有效期。 |
地址池(Framed-Pool) | 配置在 NAS 上的命名 IP 池。ToughRADIUS 只下发池名称,实际地址由 NAS 分配;静态地址则直接通过 Framed-IP-Address / Framed-IPv6-Address 下发。 |
| MAC 绑定 | 策略开启 bind_mac 后,首次出现的主叫 MAC 被记录到用户上,后续请求必须匹配。 |
| VLAN 绑定 | 策略开启 bind_vlan 后,从 NAS-Port-Id 解析出的内/外层 VLAN ID 会被记录并校验。需要支持 VLAN 提取的厂商解析器(华为、H3C、中兴)。 |
一次认证请求的流转
NAS ──Access-Request──▶ UDP 1812
│
▼
goroutine 池(ants,TOUGHRADIUS_RADIUS_POOL,默认 1024)
│
▼
1. NAS 查找 ─ 源 IP / identifier 必须匹配已登记的 NAS 记录
2. 厂商解析器 ─ 提取 MAC(Calling-Station-Id)与 VLAN(NAS-Port-Id)
· 华为 / H3C / 中兴解析器可提取 VLAN ID
· 其余厂商走默认解析器(仅 MAC)
3. 凭据校验 ─ PAP / CHAP / MS-CHAPv2,或 EAP 状态机
4. 检查器 ─ 账号状态、过期时间、MAC 绑定、VLAN 绑定、并发数(active_num)
5. Accept 增强器 ─ 标准属性(Session-Timeout、地址池、静态 IP、IPv6)
以及按 NAS 厂商代码选择的厂商属性
│
▼
NAS ◀─Access-Accept / Access-Reject──
认证失败会被归类到 Prometheus 风格计数器(radus_reject_passwd_error、
radus_reject_expire 等)并呈现在仪表盘,见运维指南。
拒绝延迟防护可减缓暴力破解:在 radius.RejectDelayWindowSeconds(默认 10 秒)
窗口内连续拒绝达到 radius.RejectDelayMaxRejects(默认 7 次)后,响应将被延迟。
密码协议速览
| 协议 | 密码如何传输 | 说明 |
|---|---|---|
| PAP | 随请求传输,由共享密钥 XOR 保护(RFC 2865 §5.2) | 兼容性最好;建议配合 RadSec 或可信网络使用。 |
| CHAP | 不传输——MD5 质询/应答(RFC 2865 §5.3) | 要求服务器保存明文口令。 |
| MS-CHAPv2 | 不传输——NT 哈希质询/应答(RFC 2548) | Windows 常用;存在众所周知的类 NTLMv1 攻击面。 |
| EAP(隧道类) | 在 TLS 隧道内传输(PEAP / EAP-TTLS)或以证书代替(EAP-TLS) | Wi-Fi / 802.1X 部署的推荐选择。 |
相关章节
- 协议与 RFC 索引 —— 上文引用的全部 RFC 与代码的对应关系。
- 厂商对接指南 —— 各厂商属性与计算公式。
- 快速开始 —— 用 10 分钟实际体验这些概念。
快速开始
English version: Quick Start
本章带你从零搭建一个可用的 RADIUS 服务器并创建一个测试用户,然后介绍如何调试
服务器行为。默认端口:管理界面 1816、RADIUS 认证 UDP 1812、计费 UDP
1813、RadSec TCP 2083。
1. 安装
三种方式任选其一。
方式 A —— 预编译二进制
从 GitHub Releases
页面下载对应平台的二进制(toughradius_linux_amd64、toughradius_linux_arm64、
toughradius_darwin_arm64、toughradius_windows_amd64.exe 等),然后:
chmod +x toughradius_linux_amd64
sudo mv toughradius_linux_amd64 /usr/local/bin/toughradius
方式 B —— Docker
docker pull talkincode/toughradius:latest
docker run -d --name toughradius \
-p 1816:1816 -p 1812:1812/udp -p 1813:1813/udp -p 2083:2083 \
-v toughradius-data:/var/toughradius \
talkincode/toughradius:latest -c /etc/toughradius.yml
镜像暴露 1816/tcp、1812/udp、1813/udp、2083/tcp。请将卷挂载到
/var/toughradius(默认工作目录),使 SQLite 数据库、日志与证书在容器重启后
得以保留。
方式 C —— 源码构建
需要 Go 1.25+ 与 Node.js 20+(React Admin 前端会被嵌入二进制):
git clone https://github.com/talkincode/toughradius.git
cd toughradius
make build # 先构建 web/ 再编译 Go 二进制 → release/toughradius
仅后端开发(SQLite 默认配置):
make runs # CGO_ENABLED=0 go run main.go -c toughradius.yml
make runf # 前端开发服务器 http://localhost:3000/admin
2. 配置
ToughRADIUS 按以下顺序查找配置:-c <文件> 参数、./toughradius.yml、
/etc/toughradius.yml、内置默认值。环境变量优先于配置文件
(见运维指南)。
一份精简的生产风格配置:
system:
appid: ToughRADIUS
location: Asia/Shanghai
workdir: /var/toughradius # 数据/日志/证书都在这里
debug: false
web:
host: 0.0.0.0
port: 1816
secret: change-me-to-a-long-random-string # JWT 签名密钥
database:
type: sqlite # 或 postgres(需配 host/port/user/passwd)
name: toughradius.db # 存放于 {workdir}/data/ 下
radiusd:
enabled: true
host: 0.0.0.0
auth_port: 1812
acct_port: 1813
radsec_port: 2083
debug: true # 输出完整报文转储;生产环境建议关闭
logger:
mode: production
file_enable: true
filename: /var/toughradius/toughradius.log
务必修改
web.secret,它用于签发管理端登录令牌。首次登录后请立即修改 默认管理员密码。
3. 初始化数据库并启动
# 仅第一次执行 —— 会删除并重建全部数据表
toughradius -initdb -c /etc/toughradius.yml
# 启动服务
toughradius -c /etc/toughradius.yml
-initdb 是破坏性操作;后续升级直接启动即可——结构迁移在启动时自动完成。
其他参数:-v 打印版本,-printcfg 以 JSON 打印合并后的配置。
4. 登录管理界面
打开 http://<服务器>:1816。默认管理员:
- 用户名:
admin - 密码:
toughradius
请立即在 账户设置 中修改密码;忘记密码可用 cmd/reset-password 重置
(见常见问题)。
5. 登记 NAS 并创建用户
- 网络节点 → 新建 —— 创建一个节点(逻辑分组),如
default。 - NAS 设备 → 新建 —— 登记你的网络设备:
- IP 地址:设备发出 RADIUS 报文的源地址。
- 密钥:共享密钥,如
testing123。 - 厂商代码:选择设备厂商(标准 / Cisco / 华为 / MikroTik / H3C / 中兴 / 爱快)——它决定下发哪些厂商私有属性,见厂商对接指南。
- CoA 端口:除非设备使用其他端口,保持
3799。
- 计费策略 → 新建 —— 例如
100M:并发数1、上行51200Kbps、 下行102400Kbps。 - RADIUS 用户 → 新建 —— 用户名
test1、密码111111,选择策略并设置 过期时间。
6. 用 radtest 验证
仓库自带一个小型 RADIUS 客户端(示例即默认值):
go run ./cmd/radtest auth \
-server 127.0.0.1 -secret testing123 \
-username test1 -password 111111
go run ./cmd/radtest flow ... # 一次跑完 认证 + 计费开始 + 计费结束
成功时会打印 Access-Accept 及返回的属性。flow 模式的会话会出现在
在线会话 中,仪表盘计数随之增长。
7. 调试
| 需求 | 方法 |
|---|---|
| 完整 RADIUS 报文转储 | YAML 中 radiusd.debug: true(或环境变量 TOUGHRADIUS_RADIUS_DEBUG=true),也可在运行时将 系统配置 → RADIUS → 日志级别 设为 debug |
| 日志文件位置 | logger.filename,默认 {workdir}/toughradius.log;logger.mode: development 输出适合人读的控制台格式 |
| 用户为何被拒绝 | 拒绝原因按类别计数(密码错误、已过期、MAC 绑定不符等)并显示在仪表盘;细节见日志 |
| 查看生效配置 | toughradius -printcfg -c <文件> |
| 压力测试 | go run ./cmd/benchmark —— 见运维指南 |
下一步
- 厂商对接指南 —— 配置 Cisco、华为、MikroTik、H3C、 中兴、爱快及标准设备。
- 管理系统用户手册 —— 管理界面每个页面的说明。
- 运维指南 —— 生产部署、TLS、EAP 证书、备份与监控。
厂商对接指南
English version: Vendor Integration Guide
ToughRADIUS 对所有设备都讲标准 RADIUS,并为其认识的厂商追加厂商私有属性 (VSA)。本章先介绍所有设备通用的对接步骤,再按厂商给出对接案例: ToughRADIUS 下发什么、解析什么,以及设备侧的参考配置。
NAS 记录上的厂商代码决定一切。 属性增强按管理界面中 NAS 设备记录的 厂商 字段选择,而不是靠嗅探报文。如果把一台 MikroTik 登记为
Standard, 认证不受影响,但不会下发Mikrotik-Rate-Limit(即不限速)。请先选对厂商。
📖 想要端到端的运维范例(PPPoE 分级套餐、Hotspot + MAC 认证、CoA / 强制下线)? 见场景实战手册。本章是属性参考卡,实战手册是照着做的剧本。
Portal 边界: ToughRADIUS 是 RADIUS AAA 后端,不托管 Captive Portal 登录页或访客开户注册流程。见 Portal / Hotspot 对接边界。
任意设备的通用对接步骤
- 登记 NAS:在 NAS 设备 → 新建 填写源 IP 地址(或 identifier)、 共享密钥,并选择正确的厂商。
- 设备指向服务器:认证 UDP
1812、计费 UDP1813、相同的共享密钥。 - 可选 CoA:ToughRADIUS 默认向 NAS 的 UDP
3799发送 CoA/Disconnect (RFC 5176);若设备监听其他端口,请在 NAS 记录上设置 CoA 端口。 每次交互最多等待 5 秒并重传 2 次。 - 创建计费策略与用户,用
go run ./cmd/radtest auth …验证 (见快速开始)。
所有厂商都会收到的标准属性
无论何种厂商,Access-Accept 中都可能携带:Session-Timeout(距账号过期的
秒数)、Acct-Interim-Interval、Framed-Pool、Framed-IP-Address(静态
IPv4)、Framed-IPv6-Prefix / Framed-IPv6-Address(RFC 6911)、
Framed-IPv6-Pool、Delegated-IPv6-Prefix(RFC 4818)与
Delegated-IPv6-Prefix-Pool —— 取决于用户/策略中设置了哪些字段。
限速单位
策略中的速率以 Kbps 存储,各厂商增强器按如下规则换算:
| 厂商 | 属性 | 下发值 |
|---|---|---|
| 华为(2011) | Huawei-Input/Output-Average-Rate、Huawei-Input/Output-Peak-Rate | 平均 = 速率_kbps × 1024(bit/s);峰值再 × 4;上限 Int32 |
| MikroTik(14988) | Mikrotik-Rate-Limit | 字符串 "{上行}k/{下行}k",如 51200k/102400k(按路由器视角 rx/tx) |
| H3C(25506) | H3C-Input/Output-Average-Rate 及峰值 | 与华为相同的 ×1024 / ×4 规则 |
| 中兴(3902) | ZTE-Rate-Ctrl-SCR-Up/Down | 速率_kbps × 1024 |
| 爱快(10055) | RP-Upstream/Downstream-Speed-Limit | 速率_kbps × 1024 × 8,上限 Int32 |
| Cisco(9)、标准(0) | —— | 仅标准属性;限速依赖设备侧策略,或基于内置 Cisco-AVPair 字典自行扩展 |
请求解析(MAC 与 VLAN)
默认解析器从 Calling-Station-Id 读取 MAC 地址。厂商解析器会在设备族存在
稳定编码时,额外处理请求侧 VSA 或 NAS-Port-Id:
slot/subslot/port:vlan[.vlan2]—— 如3/0/1:2814.727vlanid=<n>;vlanid2=<n>;—— 如slot=2;...;vlanid=503;vlanid2=100;
| 厂商 | MAC 来源 | VLAN 来源 | 说明 |
|---|---|---|---|
| 华为(2011) | Calling-Station-Id | NAS-Port-Id | 支持上面两种常见编码。 |
| H3C(25506) | Calling-Station-Id | NAS-Port-Id | 支持上面两种常见编码。 |
| 中兴(3902) | Calling-Station-Id | NAS-Port-Id | 支持上面两种常见编码。 |
| Radback(2352) | Mac-Addr VSA,缺省回退到 Calling-Station-Id | Bind-Dot1q-Vlan-Tag-Id VSA,缺省回退到 NAS-Port-Id | 仅请求侧 parser;当前未内置 Radback 响应增强器。 |
| Alcatel(3041) | AAT-User-MAC-Address VSA,缺省回退到 Calling-Station-Id | 有值时解析 NAS-Port-Id | 仅请求侧 parser;响应属性仍按部署需求定制。 |
| Aruba/HPE(14823) | Calling-Station-Id | Aruba-User-Vlan VSA,缺省回退到 NAS-Port-Id | 同时具备 Access-Accept 增强器,见下文。 |
| Juniper(2636) | Calling-Station-Id | Juniper-VoIP-Vlan VSA,缺省回退到 NAS-Port-Id | 仅请求侧 parser;响应属性仍按部署需求定制。 |
| MikroTik、爱快、Cisco、标准 | Calling-Station-Id | —— | 无厂商专用 VLAN parser;请使用设备侧策略或二次开发。 |
只要设备发送可识别的 MAC,MAC 绑定即可工作;VLAN 绑定则要求 NAS 命中上表中 支持 VLAN 的解析器,并且请求报文使用对应编码。
下文设备侧片段均为参考示例——命令语法随型号与系统版本而异,请以厂商 文档为准。
MikroTik(RouterOS)—— 厂商代码 14988
最经典的对接:PPPoE / Hotspot 配合 Mikrotik-Rate-Limit。
ToughRADIUS 下发 Mikrotik-Rate-Limit = "{up}k/{down}k",RouterOS 将其应用为
动态 simple queue(rx/tx 按路由器视角,即先用户上行)。
/radius add service=ppp,hotspot address=<TOUGHRADIUS_IP> secret=<SECRET> \
timeout=3s
/radius incoming set accept=yes port=3799
/ppp aaa set use-radius=yes accounting=yes interim-update=5m
radius incoming accept=yes开启 UDP 3799 上的 CoA/Disconnect。- Hotspot 场景:在 hotspot server profile 中启用 RADIUS
(
/ip hotspot profile set ... use-radius=yes)。
华为 —— 厂商代码 2011
典型的 BRAS(ME60/NE 系列)/ 汇聚场景。ToughRADIUS 下发速率四元组
(Huawei-Input/Output-Average-Rate,峰值 ×4)、Huawei-Domain-Name
(用户/策略配置了域时)以及静态 IPv6 的 Huawei-Framed-IPv6-Address。
华为解析器可从 NAS-Port-Id 提取 VLAN,MAC 与 VLAN 绑定均可用。
radius-server template tr_tpl
radius-server shared-key cipher <SECRET>
radius-server authentication <TOUGHRADIUS_IP> 1812
radius-server accounting <TOUGHRADIUS_IP> 1813
#
aaa
authentication-scheme auth_radius
authentication-mode radius
accounting-scheme acct_radius
accounting-mode radius
accounting interim interval 5
domain default
authentication-scheme auth_radius
accounting-scheme acct_radius
radius-server tr_tpl
如需 CoA/Disconnect,请在设备上启用 RADIUS 动态授权扩展
(radius-server authorization 指向服务器地址)。
Cisco —— 厂商代码 9
Cisco 设备使用标准属性认证(PAP / CHAP / MS-CHAPv2 / EAP 均可;会话、计费、
CoA 同样可用)。默认不下发 Cisco 私有属性——带宽策略请在设备侧实施,或基于
内置的 Cisco-AVPair 字典自行扩展。
aaa new-model
radius server TOUGHRADIUS
address ipv4 <TOUGHRADIUS_IP> auth-port 1812 acct-port 1813
key <SECRET>
aaa authentication ppp default group radius
aaa accounting network default start-stop group radius
aaa server radius dynamic-author
client <TOUGHRADIUS_IP> server-key <SECRET>
aaa server radius dynamic-author 启用 CoA/Disconnect(默认端口 3799)。
H3C —— 厂商代码 25506
速率语义与华为一致(H3C-Input/Output-Average-Rate ×1024,峰值 ×4)。
H3C 解析器可提取 VLAN,支持 VLAN 绑定。
radius scheme tr_scheme
primary authentication <TOUGHRADIUS_IP> 1812
primary accounting <TOUGHRADIUS_IP> 1813
key authentication simple <SECRET>
key accounting simple <SECRET>
user-name-format without-domain
#
domain default enable system
authentication ppp radius-scheme tr_scheme
accounting ppp radius-scheme tr_scheme
中兴 —— 厂商代码 3902
ToughRADIUS 下发 ZTE-Rate-Ctrl-SCR-Up/Down(速率 ×1024),并从
NAS-Port-Id 解析 VLAN。中兴 BRAS 的配置与华为类似,采用 radius 模板 + 域
的模式:将认证/计费模板绑定到服务器地址、密钥及 1812/1813 端口即可。
爱快(iKuai)—— 厂商代码 10055
国内常见的中小企业网关。ToughRADIUS 下发
RP-Upstream-Speed-Limit / RP-Downstream-Speed-Limit
(= 速率_kbps × 8192,上限 Int32)。在爱快 Web 控制台:认证计费 →
RADIUS 计费 —— 填写服务器地址、端口 1812/1813 与共享密钥,并在 PPPoE
服务端设置中启用 RADIUS。
Aruba/HPE —— 厂商代码 14823
Aruba/HPE 同时具备请求解析与 Access-Accept 响应增强能力。请求解析器会优先
读取报文中的 Aruba-User-Vlan,并保留与其他 VLAN-aware 解析器一致的
NAS-Port-Id 回退。
Access-Accept 中 ToughRADIUS 会下发:
| 属性 | 来源 | 边界 / no-op 规则 |
|---|---|---|
Aruba-User-Vlan | 用户或策略的 Vlanid1 | 仅在 VLAN ID 为 1..4094 时下发;缺省、0、4095 或负值都会省略。Vlanid2 没有 Aruba 属性映射,不会下发。 |
Aruba-User-Role | 用户或策略的 Domain | 继承后的 domain / role 字段非空且不为 N/A 时下发。 |
非 Aruba NAS 不会追加 Aruba VSA;字段未设置时也不会写入占位值。
标准 / 其他设备 —— 厂商代码 0
任何符合 RFC 的 NAS(pfSense、strongSwan、各类 Wi-Fi 控制器等)都能以
Standard 厂商代码对接 ToughRADIUS:凭据校验、会话控制、计费、IPv4/IPv6
属性一应俱全——只是没有私有限速属性。代码库还内置了更多厂商的属性字典
(Microsoft、F5、PfSense、Hillstone 等)供二次开发。对 Juniper、Alcatel、
Aruba、Radback 而言,当前能力不再只是“仅字典”:请以上文的请求解析器与
Aruba 增强器边界为准。单纯字典仍只定义属性;只有注册 parser 或 enhancer 后,
才会解析请求或增强响应。
对接排障
| 现象 | 可能原因 |
|---|---|
设备收到 Access-Accept 但没有限速 | NAS 记录厂商为 Standard,或设备忽略该 VSA——先检查厂商代码 |
| 所有请求被静默丢弃 | 源 IP 未登记为 NAS,或共享密钥不一致 |
| VLAN 绑定始终不匹配 | NAS 厂商未命中上表中的 VLAN-aware parser,或请求使用了非预期的 VSA / NAS-Port-Id 格式 |
| CoA/Disconnect 超时 | 设备未开启 CoA 监听,或端口非默认——在 NAS 记录上设置 CoA 端口 |
更多见常见问题。
Portal / Hotspot 对接边界
English version: Portal / Hotspot Integration Boundary
本章定义 Captive Portal / Hotspot 场景的产品边界。
铁律
ToughRADIUS 不提供、不托管、不运营 Portal 登录页。
Portal Server、访客开户注册、临时券码、短信/微信登录、支付流程、广告页、 准入放行控制等能力,属于独立的 Portal / 网关产品,不属于 ToughRADIUS 的产品 范围。
ToughRADIUS 只做 RADIUS AAA:
- UDP
1812上的认证; - UDP
1813上的计费; - 通过 RADIUS 属性、计费记录和可选 CoA / Disconnect 完成会话策略与审计;
- 在现有厂商 parser / enhancer 模型内,安全下发可表达为 RADIUS 属性的厂商能力。
支持的形态
支持的对接模型是:
客户端 -> NAS / WLAN 控制器 / 网关 Portal -> RADIUS -> ToughRADIUS
NAS、WLAN 控制器、Hotspot 网关或外部 Portal 产品负责:
- HTTP/HTTPS Portal 跳转;
- 登录页与用户交互;
- 认证前 / 认证后的网络放行控制;
- 厂商 Portal 回调或私有 Portal 协议;
- 设备侧会话准入与释放。
ToughRADIUS 负责:
- 用户、Profile、NAS 与策略数据;
- Access-Accept / Access-Reject 决策;
- 计费记录和在线会话状态;
- 标准与厂商 RADIUS 属性,例如超时、地址池、速率、VLAN、角色,或已支持厂商 enhancer 下发的 Portal URL;
- NAS 支持时的 CoA / Disconnect。
对 Hotspot 的含义
MikroTik Hotspot、华为 / H3C / 爱快 / Cisco WLAN 控制器、Aruba Captive Portal 等设备,仍然可以在设备自身负责 Portal 的前提下对接 ToughRADIUS。 设备是 Portal 实现方;本项目是 AAA 后端。
常见支持场景:
- Hotspot / PPPoE / WLAN 控制器向 ToughRADIUS 发送 Access-Request。
- 已登记设备通过 MAC 认证跳过 Portal 页面。
- Accounting update 维护在线会话与流量数据。
- NAS 支持时,通过 CoA / Disconnect 刷新或踢下会话。
- 厂商属性可以引导设备侧行为,但只能作为 RADIUS 属性下发;这不会把 ToughRADIUS 变成 Portal Server。
明确不做
不要把以下能力加入 ToughRADIUS:
- 面向访客或用户的托管登录页;
- 券码、二维码、短信、微信、OAuth 或支付开户注册流程;
- Captive Portal 前端应用或客户自助门户;
- 厂商私有 Portal Server 回调协议的一等子系统;
- 复制 NAS / 控制器职责的厂商 Portal 状态机;
- 通用营销、广告、CRM 或访客管理功能。
如果部署确实需要这些能力,应使用独立 Portal 产品,并通过 RADIUS 与 ToughRADIUS 对接。
允许的窄扩展
Portal 相关工作只有在留在 RADIUS 边界内时才允许:
- 为 Captive Portal URL、用户角色、Filter-Id、VLAN、Session-Timeout、 速率等 RADIUS 属性补充或修复厂商字典、parser、enhancer。
- 编写特定 NAS / 控制器如何把 ToughRADIUS 作为 RADIUS 后端的配置文档。
- 为请求解析、响应属性、计费或 CoA 行为补测试。
任何“让 ToughRADIUS 自带 Portal”的需求,在实现前都必须拒绝或转移到其他产品。
场景实战手册
English version: Scenario Cookbook
厂商对接指南是一张属性参考卡——它告诉你某个厂商 ToughRADIUS 会下发/解析哪些属性。本手册更进一步:以真实运维场景为单位, 端到端地把「业务诉求」翻译成「服务端配置 + 设备侧配置 + 验证 + 排障」。
每个场景的五段式
为了便于照着做、也便于排错,本手册的每个场景都用同一结构:
- 需求 / 场景 —— 用业务语言描述要解决的问题,不谈协议细节。
- ToughRADIUS 侧 —— 在管理后台具体配置什么;以及认证通过后实际下发 哪些属性、由哪段代码产生。
- 设备侧 —— NAS/路由器侧的参考配置。
- 验证 —— 如何确认它真的生效(radtest、设备命令、管理后台)。
- 排障 —— 以「症状 → 定位 → 解决」列出该场景最常见的坑。
阅读约定
- ToughRADIUS 侧的每条声明都锚定代码:下发的属性来自
internal/radiusd/plugins/auth/enhancers/的增强器,准入/拒绝判定来自internal/radiusd/plugins/auth/checkers/的检查器。它描述的是系统真实 行为,而非美好设想。 - 设备侧配置均为参考示例:命令语法随型号与系统版本而异,请以厂商文档与 实际固件为准。
- CoA / Disconnect 端口为 3799(RFC 5176)。网络上常见的
1700是某些 客户端的本地端口,不是本系统使用的目标端口。 - 速率在计费策略中以 Kbps 存储(界面标注单位)。换算规则见 厂商对接指南 · 限速单位。
现有实战手册
- MikroTik RouterOS —— PPPoE 宽带 ISP 分级套餐、 Hotspot + MAC 认证、CoA / 强制下线与 FUP。
- 华为 BRAS / NetEngine —— 带峰值速率与 AAA 域的宽带 分级套餐、线路防盗用(MAC + VLAN 绑定)与双栈 IPv6、CoA / 强制下线与 FUP。
- H3C / 中兴 / 爱快 / Cisco —— 其余主流厂商的差异速查 (下发的限速属性、单位倍率、MAC / VLAN 解析);场景机制照两本旗舰手册即可。
规划中(路线图 M13.8 后续批次):按需补充标准属性 / Wi-Fi 控制器等场景。 任意厂商的属性级细节,见厂商对接指南。
相关章节
实战手册:MikroTik RouterOS
English version: Cookbook: MikroTik RouterOS
MikroTik RouterOS(厂商代码 14988)是最常见的对接对象。ToughRADIUS 为其 注册了专门的厂商增强器,认证通过后下发:
Mikrotik-Rate-Limit = "{上行}k/{下行}k"—— 字符串限速(由mikrotik_enhancer.go产生;rx/tx按路由器视角,即第一段是用户上行)。- 以及所有设备通用的标准属性:
Session-Timeout、Acct-Interim-Interval、Framed-Pool、Framed-IP-Address等(由default_enhancer.go产生)。
前置条件:已在 NAS 设备 中把这台路由器登记为 厂商 = MikroTik、填写 正确的源 IP 与共享密钥;ToughRADIUS 可被设备访问(认证 1812、计费 1813)。 若登记成
Standard,认证仍可成功,但不会下发Mikrotik-Rate-Limit。
场景 A:PPPoE 宽带 ISP —— 分级套餐 + 地址池 + 到期断网 + 并发限制
需求 / 场景
一个小区 / 小型 ISP 用 PPPoE 拨号上网,需要:多档套餐(如家庭版下行 30M、 企业版下行 100M),账号到期自动断网且无法再拨,每账号限定并发会话数(防一号 多拨),IP 由统一地址池分配。
ToughRADIUS 侧
- 为每档套餐建一个计费策略(计费策略 → 新建):
- 上行 / 下行速率:单位为 Kbps。下行 30M 应填
30720,不是30; 上行 10M 填10240。 - 并发数
active_num:如1表示单账号最多 1 个在线会话(0= 不限)。 - 地址池:填地址池名(如
pppoe-pool),需与路由器上的/ip pool同名。
- 上行 / 下行速率:单位为 Kbps。下行 30M 应填
- 创建用户(用户 → 新建):用户名 / 密码、选择对应计费策略(速率、 并发、地址池由策略继承)、设置过期时间、状态 = 启用。如需固定 IP,可在用户 上设置静态 IPv4(将覆盖地址池)。
- 计费上报间隔:系统配置项
radius.AcctInterimInterval(默认120秒)决定 下发的Acct-Interim-Interval。
认证通过后,Access-Accept 实际携带(锚定代码):
| 属性 | 取值 | 来源 |
|---|---|---|
Mikrotik-Rate-Limit | "{上行kbps}k/{下行kbps}k",如 10240k/30720k | mikrotik_enhancer.go |
Session-Timeout | 距过期时间的剩余秒数(到点断开当前会话) | default_enhancer.go |
Acct-Interim-Interval | radius.AcctInterimInterval(默认 120) | default_enhancer.go |
Framed-Pool | 策略 / 用户设置的地址池名(设置了才下发) | default_enhancer.go |
Framed-IP-Address | 用户的静态 IPv4(设置了才下发) | default_enhancer.go |
并发限制不是靠下发属性实现的:它由
online_count_checker在认证阶段依据active_num与当前在线数判定——超限的新会话直接被拒(Access-Reject)。
设备侧(RouterOS,参考示例,以实际固件为准)
# 指向 ToughRADIUS(认证/计费同一共享密钥)
/radius add service=ppp address=<TOUGHRADIUS_IP> secret=<SECRET> timeout=3s
/radius incoming set accept=yes port=3799
# 开启 RADIUS 认证 + 计费 + 周期上报
/ppp aaa set use-radius=yes accounting=yes interim-update=5m
# 地址池名必须与下发的 Framed-Pool 一致
/ip pool add name=pppoe-pool ranges=10.10.0.2-10.10.255.254
# PPPoE 服务(remote-address 留给 RADIUS 的 Framed-Pool/Framed-IP 决定)
/ppp profile add name=radius-pppoe local-address=10.10.0.1
/interface pppoe-server server add service-name=isp interface=<bridge> \
default-profile=radius-pppoe disabled=no
限速无需手工建队列:路由器收到
Mikrotik-Rate-Limit后会自动生成动态 simple queue。
验证
- radtest(服务端):
成功时打印go run ./cmd/radtest auth -server <TOUGHRADIUS_IP> -nas-ip <NAS_IP> \ -username <用户名> -password <密码> -secret <SECRET>Access-Accept,应能看到Mikrotik-Rate-Limit、Session-Timeout, 以及(若设置了池)Framed-Pool。 - 路由器侧:
/ppp active print(在线连接)、/queue simple print(动态队列 及其速率)、/log print where topics~"radius"。 - 管理后台:在线会话 页应出现该会话(Framed IP、时长、上下行流量)。
排障(症状 → 定位 → 解决)
- 限速完全不生效 → ① 确认 NAS 登记为 MikroTik(登记成 Standard 不下发 VSA);
② 速率单位填错(填了
30而非30720);③ 方向记反(rx/tx是路由器视角, 第一段是用户上行)。 - 拨号成功但拿不到 IP / 地址不对 →
Framed-Pool名与/ip pool名不一致, 或策略未设地址池;改为同名,或在用户上设静态 IP。 - 账号到期后仍能上网 →
Session-Timeout只在到点时断开当前会话;已在线 的旧会话需等超时或在会话页手动强制下线。到期后再次拨号会被expire_checker拒(拒绝指标计入user expire)。 - 第二条连接被拒 →
active_num=1时online_count_checker拒绝并发,这是预期 行为;要允许多拨,调大计费策略的并发数。 - 连续认证失败后服务端变慢 / 不回包 →
reject_delay_guard在某用户名连续被拒 超过阈值(默认 7 次)后引入延迟以抑制暴力破解,稍后自动恢复。
场景 B:Hotspot + MAC 认证
需求 / 场景
公共 WiFi / Hotspot 环境下,希望部分已登记设备(打印机、IoT、长期访客设备) 免门户、直接按 MAC 地址放行并限速。
ToughRADIUS 侧
ToughRADIUS 判定一次请求是否为 MAC 认证的条件是(锚定代码
auth_stages.go / eap_helper.go):
当请求中解析出的 MAC 地址 等于用户名 时,即视为 MAC 认证;此时密码比对使用 该用户记录的 MAC 地址 字段,而非普通密码。
因此配置方式为:
- 创建用户,用户名 = 设备 MAC(字符串需与路由器实际发送的格式完全一致), 并在用户记录的 MAC 地址 字段填入同一 MAC。
- 给该用户分配计费策略(速率、并发照常生效)。
最大的坑是 MAC 格式:大小写与分隔符必须与 RouterOS hotspot 发送的
User-Name完全一致(ToughRADIUS 按字符串精确匹配)。RouterOS 的发送格式受mac-auth-mode等设置影响——存什么就必须发什么。
设备侧(RouterOS,参考示例,以实际固件为准)
/radius add service=hotspot address=<TOUGHRADIUS_IP> secret=<SECRET> timeout=3s
# 在 hotspot server profile 上启用 RADIUS 与 MAC 登录
/ip hotspot profile set <profile> use-radius=yes login-by=mac,http-chap
# mac-auth-mode / mac-auth-password 视固件而定
验证
- radtest:用
-user <MAC> -pwd <MAC>模拟一次 MAC 认证,观察是否Access-Accept。 - 路由器侧:
/ip hotspot active print应出现该设备;/log print查看认证日志。 - 管理后台:在线会话页出现对应会话。
排障(症状 → 定位 → 解决)
- MAC 认证总是失败 → 用户名 / MAC 字段与路由器实际发送的 MAC 字符串
格式不一致(大小写、分隔符)。抓一次请求看
User-Name原文,按原文重建用户。 - 被当成普通账号在认证 → 只有「请求 MAC == 用户名」才触发 MAC 认证路径;若 hotspot 未按 MAC 登录,请求会走门户用户名 / 密码逻辑。
场景 C:在线管控 —— CoA、强制下线与 FUP
需求 / 场景
对在线用户做实时管控:超出流量配额后限速(FUP)、临时强制重新认证,或直接把 某会话踢下线。
ToughRADIUS 侧
在 在线会话 页选中某会话,可执行两类动作(锚定 session_actions.go 与
管理系统用户手册 · 在线会话):
- 修改授权(CoA-Request):当前仅携带
Session-Timeout(#27) 和 / 或Filter-Id(#11)。- 用
Session-Timeout缩短会话寿命,促使客户端尽快重新认证。 - 用
Filter-Id让 RouterOS 套用一个预先定义好的 filter / address-list (例如一条限速或限站规则)。
- 用
- 强制下线(Disconnect-Request):直接终止该会话(操作需确认)。
实现 FUP「在线变速」的正确路径:ToughRADIUS 的 CoA 不直接改写
Mikrotik-Rate-Limit。要让某用户实时变速,标准做法是——先在计费策略 / 用户上 改速率,再对其发强制下线;客户端自动重拨后即按新速率重新授权。这条路径与 系统代码能力一致,适用于任意厂商。
操作员发起的 CoA / Disconnect 使用较短超时并自动重传一次,目标为 NAS 记录上的 CoA 端口(默认 3799)。
设备侧(RouterOS,参考示例,以实际固件为准)
# 必须开启,否则收不到 CoA/Disconnect
/radius incoming set accept=yes port=3799
- 防火墙需放行入向 UDP 3799(从 ToughRADIUS 到路由器)。
- 若使用
Filter-Id方案,需在 RouterOS 预先定义同名的 filter / queue / address-list(以实际固件为准)。
验证
- 在会话页点击 修改授权 / 强制下线,结果以通知形式反馈。
- 路由器侧
/log print可见 incoming 请求;强制下线后/ppp active print中该会话消失,随后客户端自动重拨。
排障(症状 → 定位 → 解决)
- CoA / Disconnect 无响应或超时 → ① RouterOS 未
radius incoming accept=yes; ② 防火墙挡了 UDP 3799;③ NAS 记录的 CoA 端口与设备incoming port不一致; ④ 共享密钥不一致。 - 改了速率但在线用户没变速 → 这是预期:CoA 不改限速,需强制下线让客户端 重拨后生效。
- 端口困惑 → 网上常说的 CoA
1700是某些客户端的本地端口;本系统与 RFC 5176 使用 3799。
相关章节
- 厂商对接指南 · MikroTik —— 属性参考卡。
- 管理系统用户手册 —— 用户 / 计费策略 / 在线会话 / CoA 表单。
- 常见问题解答 —— 更多跨场景排障问答。
实战手册:华为 BRAS / NetEngine
English version: Cookbook: Huawei BRAS / NetEngine
华为(厂商代码 2011)是国内运营商与企业网络中占主导地位的宽带 BRAS / 企业网关 (NetEngine / ME60 / 较老的 MA5200 系列)。ToughRADIUS 为其注册了专门的厂商增强器, 认证通过后下发:
- 一组四属性限速四元组(由
huawei_enhancer.go产生):Huawei-Input-Average-Rate、Huawei-Input-Peak-Rate、Huawei-Output-Average-Rate、Huawei-Output-Peak-Rate。 Huawei-Domain-Name—— 仅当用户 / 策略设置了**域名(domain)**时下发。Huawei-Framed-IPv6-Address—— 仅当用户设置了静态 IPv6 时下发。- 以及所有设备通用的标准属性(
Session-Timeout、Acct-Interim-Interval、Framed-Pool、Framed-IP-Address等),由default_enhancer.go产生。
请求侧,华为解析器(huawei_parser.go)从 Calling-Station-Id 提取 MAC
(把 - 规整为 :),从 NAS-Port-Id 提取内 / 外层 VLAN ID。正是这两项让华为设备
具备了 MAC 绑定与 VLAN 绑定能力。
前置条件:已在 NAS 设备 中把这台 BRAS 登记为 厂商 = Huawei、填写正确的 源 IP 与共享密钥;ToughRADIUS 可被访问(认证 1812、计费 1813)。若登记成
Standard,认证仍可成功,但上述华为 VSA 全都不会下发,VLAN 也不会被解析。
场景 A:PPPoE / IPoE 宽带 —— 分级套餐、峰值速率与 AAA 域
需求 / 场景
运营商或企业用华为 BRAS 跑宽带,需要多档套餐(如家庭版下行 30M / 上行 10M、 企业版下行 100M)。华为同时支持平均速率与峰值(突发)速率,并把用户归入 AAA 域(domain),以便 BRAS 套用对应的域策略。
ToughRADIUS 侧
- 为每档套餐建一个计费策略(计费策略 → 新建):
- 上行 / 下行速率:单位为 Kbps。下行 30M 应填
30720,不是30; 上行 10M 填10240。 - 域名 domain(可选):华为 AAA 域名(如
isp),将作为Huawei-Domain-Name下发,让 BRAS 把会话绑定到该域。
- 上行 / 下行速率:单位为 Kbps。下行 30M 应填
- 创建用户(用户 → 新建):用户名 / 密码、对应计费策略、过期时间、 状态 = 启用。用户级的域名字段会覆盖策略上的域名。
存储的 Kbps 速率如何变成四个华为 VSA(锚定 huawei_enhancer.go):
| 属性 | 取值 | 说明 |
|---|---|---|
Huawei-Input-Average-Rate | 上行kbps × 1024(bit/s) | 「Input」= 进入 BRAS 的流量 = 用户上行 |
Huawei-Input-Peak-Rate | 上行kbps × 1024 × 4 | 平均值 × 4 |
Huawei-Output-Average-Rate | 下行kbps × 1024(bit/s) | 「Output」= 流向用户的流量 = 下行 |
Huawei-Output-Peak-Rate | 下行kbps × 1024 × 4 | 平均值 × 4 |
Huawei-Domain-Name | 用户 / 策略的域名 | 设置了才下发 |
Session-Timeout、Acct-Interim-Interval、Framed-Pool、Framed-IP-Address | 标准 | 由 default_enhancer.go 产生 |
华为独有的两个坑。 ① 单位:限速 VSA 是 bit/s 而非 Kbps——ToughRADIUS 把存储的 Kbps 乘以 1024(二进制),且峰值是平均值的 × 4。所以「下行 30M」会被下发为
Output-Average-Rate = 30720 × 1024 = 31457280、Output-Peak-Rate = 125829120。② 方向命名:华为「Input」是用户上行, 「Output」是下行(BRAS 视角)——与 MikroTik 的rx/tx用词相反,但物理含义 相同。四个值都会被钳制到 Int32 上限。
设备侧(华为 VRP,参考示例,以实际固件为准)
# RADIUS 服务器模板
radius-server template tr-tmpl
radius-server shared-key cipher <SECRET>
radius-server authentication <TOUGHRADIUS_IP> 1812 weight 80
radius-server accounting <TOUGHRADIUS_IP> 1813 weight 80
#
# 绑定到模板的 AAA 域(与 Huawei-Domain-Name 对应)
aaa
authentication-scheme radius-auth
authentication-mode radius
accounting-scheme radius-acct
accounting-mode radius
domain isp
authentication-scheme radius-auth
accounting-scheme radius-acct
radius-server tr-tmpl
Huawei-Output-Average-Rate/ 峰值会映射到 BRAS 的每用户 CAR / QoS,无需手工 为每个用户建队列。
验证
- radtest(服务端):
成功时打印go run ./cmd/radtest auth -server <TOUGHRADIUS_IP> -nas-ip <NAS_IP> \ -username <用户名> -password <密码> -secret <SECRET>Access-Accept,应能看到四个华为限速属性以及(若设置了)Huawei-Domain-Name。 - BRAS 侧:
display access-user username <name>(在线会话、速率、域),display aaa online-fail-record查看失败记录。 - 管理后台:在线会话 页应出现该会话。
排障(症状 → 定位 → 解决)
- 限速不生效 → ① 确认 NAS 登记为 Huawei(登记成 Standard 不下发 VSA);
② 记住取值是 bit/s(
× 1024),若某档看起来「小了 1000 倍」,多半是设备侧把单位 当成了 Kbps。 - 速率方向反了(上行被限到下行的值) → Input/Output 方向搞混:华为 Input = 上行、Output = 下行;检查哪个策略字段喂给了哪个属性。
- 域策略未生效 → 用户 / 策略没设域名(则不下发
Huawei-Domain-Name), 或域名与 BRAS 上配置的domain不一致。 - 峰值 / 突发看起来过高 → 这是预期:增强器把峰值硬编码为平均值的 × 4; 要调就调平均档,而非峰值。
场景 B:线路防盗用 —— MAC + VLAN 绑定与双栈 IPv6
需求 / 场景
宽带运营商希望通过把账号绑定到接入线路来防止共享 / 盗用——绑定用户 MAC 和 / 或 接入 VLAN(内 / 外层,即 QinQ),并为双栈业务下发静态 IPv6。
ToughRADIUS 侧
华为把接入线路编码在解析器已读取的属性里:
- MAC 来自
Calling-Station-Id; - 内 / 外层 VLAN 来自
NAS-Port-Id。
随后由两个检查器执行绑定(锚定 mac_bind_checker.go / vlan_bind_checker.go):
- MAC 绑定 —— 在用户 / 策略上开启 绑定 MAC,并在用户上存好允许的 MAC。
认证时若请求 MAC 与所存 MAC 不同,会话被拒(
MacBindError)。 - VLAN 绑定 —— 在用户 / 策略上开启 绑定 VLAN,并存好允许的 VLAN ID 1 /
VLAN ID 2。若所存 VLAN 与解析出的不一致,会话被拒(
VlanBindError)。 - 静态 IPv6 —— 设置用户的 IPv6 地址;增强器随即下发
Huawei-Framed-IPv6-Address(若写成地址/前缀长度,下发前会去掉前缀长度)。
绑定只校验你存了的东西。 每个检查器在其开关关闭时会跳过,并且在任一侧 (所存值或请求值)为空 / 为 0 时也会跳过——它绝不会悄悄自动学习。所以要绑定一条 线路,必须(a)打开开关并且(b)在用户记录里填上 MAC / VLAN(通常取自首次 成功登录的抓取值)。
设备侧(华为 VRP,参考示例,以实际固件为准)
# IPoE / PPPoE 接入,向 RADIUS 上报接入线路。
# BRAS 接入默认把内/外层 VLAN 放进 NAS-Port-Id、把用户 MAC 放进 Calling-Station-Id。
radius-server template tr-tmpl
radius-server shared-key cipher <SECRET>
radius-server authentication <TOUGHRADIUS_IP> 1812 weight 80
radius-server accounting <TOUGHRADIUS_IP> 1813 weight 80
#
# 按设计需要在 BRAS 上启用 IPv6 地址/前缀下发
ipv6
验证
- 抓一次真实
Access-Request(或看认证日志),确认Calling-Station-Id与NAS-Port-Id的取值,把它们原样存到用户上,再开启绑定。 - 用匹配的 MAC 做 radtest 成功;换一个 MAC / VLAN 则被拒。
- BRAS 侧:
display access-user username <name>可见绑定的线路与下发的 IPv6。
排障(症状 → 定位 → 解决)
- 绑定把合法用户也拒了 → 所存 MAC / VLAN 与 BRAS 实际发送的不一致。读取真实的
Calling-Station-Id/NAS-Port-Id,按原值存好,再重新开启绑定。 - VLAN 绑定从不触发 → 解析出的 VLAN 为
0(你的组网里 BRAS 没把 VLAN 放进NAS-Port-Id),或 绑定 VLAN 没开,或用户存的 VLAN 为0;任一情况都会跳过。 - 没有下发 IPv6 → 用户没设静态 IPv6 地址,或该域在 BRAS 上没配 IPv6 / 双栈。
- 绑定被静默忽略 → 开关没开或所存值为空;检查器只有在开关开启且两侧都有值时 才生效。
场景 C:在线管控 —— CoA、强制下线与 FUP
需求 / 场景
对华为 BRAS 上的在线用户做实时管控:缩短会话、强制重新认证、超出配额后限速 (FUP),或把某会话踢下线。
ToughRADIUS 侧
在 在线会话 页选中某会话,可执行两类动作(锚定 session_actions.go 与
管理系统用户手册 · 在线会话):
- 修改授权(CoA-Request):当前仅携带
Session-Timeout(#27) 和 / 或Filter-Id(#11)——不会改写华为的限速 VSA。 - 强制下线(Disconnect-Request):直接终止该会话。
华为上实现 FUP「在线变速」的正确路径。 由于 CoA 不改写
Huawei-Output-Average-Rate,在线变速沿用与其他厂商一致的与厂商无关路径:先在 计费策略 / 用户上改速率,再对其发强制下线;客户端重拨后即按新的限速四元组重新 授权。(部分华为固件也会响应某个厂商私有的 CoA 限速属性,但 ToughRADIUS 不 下发这种属性——不要依赖它。)
操作员发起的 CoA / Disconnect 使用较短超时并自动重传一次,目标为 NAS 记录上的 CoA 端口(默认 3799)。
设备侧(华为 VRP,参考示例,以实际固件为准)
# RADIUS 模板必须接受动态授权(CoA/DM)。
radius-server template tr-tmpl
radius-server authorization <TOUGHRADIUS_IP> shared-key cipher <SECRET>
- 防火墙需放行从 ToughRADIUS 到 BRAS 的入向 UDP 3799。
- 若使用
Filter-Id方案,需在 BRAS 预先定义同名的 ACL / 用户组(以实际固件为准)。
验证
- 在会话页点击 修改授权 / 强制下线,结果以通知形式反馈。
- BRAS 侧:强制下线后
display access-user username <name>不再列出该会话, 客户端随后自动重拨。
排障(症状 → 定位 → 解决)
- CoA / Disconnect 无响应或超时 → ① RADIUS 模板未配
radius-server authorization;② 防火墙挡了 UDP 3799;③ NAS 记录的 CoA 端口与 BRAS 的 authorization 端口不一致;④ 共享密钥不一致。 - 改了速率但在线用户没变速 → 这是预期:CoA 不改限速,需强制下线让客户端 重拨后生效。
- 端口困惑 → 网上常说的 CoA
1700是某些客户端的本地端口;本系统与 RFC 5176 使用 3799。
相关章节
- 厂商对接指南 · Huawei —— 属性参考卡。
- 实战手册:MikroTik RouterOS —— RouterOS 上的同类场景。
- 管理系统用户手册 —— 用户 / 计费策略 / 在线会话 / CoA 表单。
- 常见问题解答 —— 更多跨场景排障问答。
实战手册:H3C / 中兴 / 爱快 / Cisco
English version: Cookbook: H3C, ZTE, iKuai & Cisco
这四家厂商跑的是与两本旗舰手册完全相同的运维场景——PPPoE / IPoE 分级套餐、
地址池、到期断网、单账号并发限制、MAC 绑定、CoA / 强制下线与 FUP。这些场景的机制
(一个套餐档、一次到期、一个并发上限、一次 CoA 如何生效)都一致,因为它们来自共享的
default_enhancer 与各 checker——而非来自厂商。
因此本章不重复四遍完整的五段式,而是各厂商的差异速查:对每台设备只说清 (1) ToughRADIUS 下发哪个限速属性、单位倍率,(2) 请求 MAC / VLAN 如何解析 (从而哪些绑定可用),以及 (3) 端到端步骤该照哪本旗舰手册做。
请配合一本旗舰手册阅读。 任何场景的完整分步,请照 MikroTik 或 Huawei 手册中对应小节 操作;只有下面列出的下发属性不同。
该照哪本手册
| 你想要… | 照做 | 各厂商差异(本章) |
|---|---|---|
| 分级套餐 + 地址池 + 到期 + 并发 | MikroTik 场景 A 或 Huawei 场景 A | 下发的限速属性与单位 |
| 线路防盗用(MAC / VLAN 绑定) | Huawei 场景 B | MAC 解析格式 + 是否支持 VLAN 绑定 |
| 在线管控(CoA / 下线 / FUP) | MikroTik 场景 C | 无——CoA 与厂商无关(Session-Timeout + Filter-Id) |
并发上限、到期断网、地址池与 CoA 对这里每家厂商都一样——它们由共享 checker /
default_enhancer执行,而非靠某个厂商 VSA。唯一真正的逐厂商变量就是下面的限速属性 和 MAC / VLAN 解析。
H3C —— 厂商代码 25506
限速属性(锚定 h3c_enhancer.go)——与华为同样的四元组形态:
| 属性 | 取值 |
|---|---|
H3C-Input-Average-Rate | 上行kbps × 1024(bit/s)—— 用户上行 |
H3C-Input-Peak-Rate | 上行kbps × 1024 × 4 |
H3C-Output-Average-Rate | 下行kbps × 1024(bit/s)—— 下行 |
H3C-Output-Peak-Rate | 下行kbps × 1024 × 4 |
即「下行 30M」档(30720 Kbps)会下发 H3C-Output-Average-Rate = 31457280、
峰值 125829120;所有值钳制到 Int32 上限。与华为不同,H3C 不下发域或 IPv6 VSA,
只有限速四元组。
请求解析(h3c_parser.go):MAC 来自 H3C-IP-Host-Addr(取末 17 位,即尾部的
aa:bb:cc:dd:ee:ff),该 VSA 缺失时回退到 Calling-Station-Id(-→:);内 / 外层
VLAN 来自 NAS-Port-Id。MAC 与 VLAN 绑定均支持。
设备侧(H3C Comware,参考,以实际固件为准):
radius scheme tr_scheme
primary authentication <TOUGHRADIUS_IP> 1812
primary accounting <TOUGHRADIUS_IP> 1813
key authentication simple <SECRET>
key accounting simple <SECRET>
user-name-format without-domain
#
domain default enable system
authentication ppp radius-scheme tr_scheme
accounting ppp radius-scheme tr_scheme
坑:把 NAS 登记为 H3C(不是 Huawei)。虽然限速算法一致,但 VSA 的厂商 ID 不同(25506 vs 2011);把 H3C 设备登记成 Huawei,会收到它忽略的华为 VSA,限速将 不生效。
ZTE 中兴 —— 厂商代码 3902
限速属性(锚定 zte_enhancer.go)——两个属性,无峰值:
| 属性 | 取值 |
|---|---|
ZTE-Rate-Ctrl-SCR-Up | 上行kbps × 1024(bit/s) |
ZTE-Rate-Ctrl-SCR-Down | 下行kbps × 1024(bit/s) |
「下行 30M」档下发 ZTE-Rate-Ctrl-SCR-Down = 31457280。没有峰值 / 突发属性——平均
速率即为上限。
请求解析(zte_parser.go):MAC 来自 Calling-Station-Id,但中兴发的是12 位
纯十六进制字符串;ToughRADIUS 会重排成 aa:bb:cc:dd:ee:ff。VLAN 从 NAS-Port-Id
解析。MAC 与 VLAN 绑定均支持——但做 MAC 绑定时,所存 MAC 要用 aa:bb:cc:dd:ee:ff
形式(这是解析器产出的格式)。
设备侧:中兴 BRAS 与华为同样使用 radius-template + domain 模式——把认证 / 计费 模板绑定到 ToughRADIUS 地址、共享密钥与端口 1812 / 1813(以实际固件为准)。
iKuai 爱快 —— 厂商代码 10055
国内常见的 SMB / SOHO 网关。
限速属性(锚定 ikuai_enhancer.go)——两个属性,倍率不同:
| 属性 | 取值 |
|---|---|
RP-Upstream-Speed-Limit | 上行kbps × 8192(= × 1024 × 8) |
RP-Downstream-Speed-Limit | 下行kbps × 8192 |
即「下行 30M」档(30720 Kbps)下发 RP-Downstream-Speed-Limit = 251658240。
坑 —— 高档位会被钳制。 因为倍率是
× 8192且取值钳制到 Int32 上限 (2147483647),任何高于约 256 Mbps(262144Kbps)的档位都会溢出并被钳制 ——300M 档会按钳制上限下发,而非 300M。超高档位请改在 iKuai 设备侧做策略。
请求解析:iKuai 使用默认解析器——MAC 来自 Calling-Station-Id(-→:),
且不解析 VLAN(恒为 0)。故 MAC 绑定可用,但 VLAN 绑定不可用(VLAN 检查总被
跳过)。
设备侧:在 iKuai web 控制台进入 认证计费 → RADIUS 计费,设置服务器地址、端口 1812 / 1813 与共享密钥,再在 PPPoE 服务器设置中启用 RADIUS。
Cisco —— 厂商代码 9
限速属性:无。ToughRADIUS 没有 Cisco 增强器,所以 Cisco NAS 只会收到
default_enhancer.go 下发的标准属性(Session-Timeout、Acct-Interim-Interval、
Framed-Pool、Framed-IP-Address 等)。认证(PAP / CHAP / MS-CHAPv2 / EAP)、会话
控制、计费与 CoA 都正常工作——限速请在设备侧做,或用随仓库附带的 Cisco-AVPair
字典做自定义集成。
请求解析:默认解析器——MAC 来自 Calling-Station-Id,不解析 VLAN。故 MAC 绑定
可用,VLAN 绑定不可用。
设备侧(Cisco IOS,参考,以实际平台为准):
aaa new-model
radius server TOUGHRADIUS
address ipv4 <TOUGHRADIUS_IP> auth-port 1812 acct-port 1813
key <SECRET>
aaa authentication ppp default group radius
aaa accounting network default start-stop group radius
aaa server radius dynamic-author
client <TOUGHRADIUS_IP> server-key <SECRET>
aaa server radius dynamic-author启用 CoA / Disconnect(默认端口 3799)。不要指望 Cisco 从 RADIUS 拿限速——用设备侧 service-policy / QoS 设置。
厂商能力矩阵
| 厂商 | 限速属性 | 单位倍率 | 峰值 | MAC 绑定 | VLAN 绑定 |
|---|---|---|---|---|---|
| H3C(25506) | H3C-Input/Output-Average-Rate + 峰值 | × 1024(bit/s) | ✅ 平均 × 4 | ✅ | ✅ |
| 中兴(3902) | ZTE-Rate-Ctrl-SCR-Up/Down | × 1024(bit/s) | ❌ | ✅ | ✅ |
| 爱快(10055) | RP-Upstream/Downstream-Speed-Limit | × 8192 | ❌ | ✅ | ❌ |
| Cisco(9) | 无(设备侧 QoS) | — | — | ✅ | ❌ |
(对比:MikroTik 下发 Kbps 单位的 Mikrotik-Rate-Limit 字符串;华为下发 × 1024、
峰值 × 4 的限速四元组外加域 / IPv6。)
排障(症状 → 定位 → 解决)
- 限速不生效 → ① NAS 没按该厂商精确登记(VSA 厂商 ID 必须匹配——即便算法相同,
H3C ≠ Huawei);② Cisco 本就没有 RADIUS 限速 VSA——请在设备侧做 QoS;③ 设备侧
单位搞错(这里除 iKuai 是
× 8192外,其余都是 bit/s)。 - iKuai 高档速率不对 / 被压低 →
× 8192的值溢出了 Int32 而被钳制;高档请在设备侧 做。 - iKuai / Cisco 的 VLAN 绑定从不触发 → 这是预期:它们的解析器不提取 VLAN(恒为
0),故 VLAN 检查总被跳过。改用 MAC 绑定,或换支持 VLAN 解析的厂商(华为 / H3C / 中兴)。 - 中兴 MAC 绑定从不匹配 → 把 MAC 按
aa:bb:cc:dd:ee:ff存储(中兴发 12 位纯十六 进制,ToughRADIUS 比对前会重排成冒号形式)。
相关章节
- 实战手册:MikroTik RouterOS —— 完整 PPPoE / Hotspot / CoA 剧本。
- 实战手册:华为 BRAS / NetEngine —— 完整分级套餐 / 线路绑定 / CoA 剧本。
- 厂商对接指南 —— 各厂商属性参考卡。
- 常见问题解答 —— 更多跨场景排障问答。
管理系统用户手册
English version: Admin UI Manual
管理控制台运行在 1816 端口(HTTP),基于 React Admin 构建,提供中英双语
(默认中文,可在顶栏语言菜单切换)及明暗两套主题。本章逐页介绍。
登录与账户
访问 http://<服务器>:1816,使用操作员账号登录。初始管理员为
admin / toughradius —— 请立即在 账户设置(右上角头像)中修改,该页
同时可编辑个人资料。密码至少 6 位。
操作员角色(在 操作员 页面设置):
| 级别 | 含义 |
|---|---|
super | 完整权限,包括系统配置与操作员管理 |
admin | 菜单可见性与 super 相同 |
operator | 仅日常页面——系统配置与操作员菜单不可见 |
仪表盘
首页汇总部署状态:
- 统计卡片 —— 用户总数(含停用/过期数)、在线用户数、今日认证与计费 请求数、今日上行/下行流量(GB)。
- IPv6 指标条 —— 携带 IPv6 的在线会话、IPv6 地址/前缀/委派前缀计数、 配置了静态 IPv6 的用户数。
- 图表 —— 认证趋势、策略分布饼图、24 小时上下行流量图。
计数来自内存指标,见运维指南。
网络节点
用于组织 NAS 设备的逻辑分组(名称、标签、备注)。请先建节点——每台 NAS 都归属一个节点。
NAS 设备
允许与本服务器通信的网络设备清单。未知源地址的请求会被丢弃。
| 字段 | 说明 |
|---|---|
| 名称 / Identifier | 自由命名;identifier 与 RADIUS NAS-Identifier 匹配 |
| IP 地址 / 主机名 | RADIUS 报文的源地址 |
| 厂商 | 决定厂商属性行为 —— 标准、Cisco、华为、MikroTik、H3C、中兴、爱快(见厂商对接指南) |
| 密钥 | RADIUS 共享密钥 |
| CoA 端口 | CoA/Disconnect 的目标端口,默认 3799 |
| 节点 / 状态 / 标签 / 备注 | 归属与生命周期管理 |
RADIUS 用户
拨入用户账号。列表支持按用户名、姓名、邮箱、手机、IP 过滤,支持 CSV 导出与 列排序。
主要表单字段:用户名/密码、状态(启用/停用)、计费策略(必选——速率、
并发、地址池由其继承)、过期时间(决定 Session-Timeout)、静态
ip_addr / ipv6_addr、IPv6 前缀池与委派前缀(编辑视图)、联系方式、备注。
批量导入:列表工具栏的导入按钮接受 .xlsx、.csv、.json 文件,并
报告成功/失败数量。可先导出现有列表作为模板参考。
详情视图分组展示全部信息,包括 IPv6 细节、MAC/VLAN 绑定值与时间戳。
计费策略
可复用的授权模板:
| 字段 | 含义 |
|---|---|
active_num | 单用户最大并发会话数(0 = 不限) |
up_rate / down_rate | 带宽,单位 Kbps;按厂商换算(列表对 ≥1024 的值以 Mbps 展示) |
addr_pool | NAS 用于分配地址的 Framed-Pool 池名 |
ipv6_prefix / 域 | IPv6 与华为域授权 |
bind_mac / bind_vlan | 将用户锁定到首次出现的 MAC / VLAN |
在线会话
实时会话(radius_online)。列包括会话 ID、Framed IP、NAS 地址/端口、开始
时间、时长、超时与流量计数。过滤器覆盖用户名、会话 ID、IPv4/IPv6 地址与
前缀、NAS 地址、MAC 及开始时间范围。
行级操作(RFC 5176 动态授权的入口):
- 修改授权(CoA) —— 发送
CoA-Request,携带新的会话超时和/或Filter-Id。 - 强制下线(Disconnect) —— 发送
Disconnect-Request终止会话(需确认)。
两者都发往 NAS 的 CoA 端口,结果以通知形式反馈。
计费记录
历史计费数据(radius_accounting),只读:会话 ID、地址、起止时间、上下行
总流量。过滤条件与在线会话一致并增加结束时间;支持 CSV 导出。
系统配置
仅 super/admin 可见。配置项由 schema 驱动、按组折叠展示(RADIUS 组默认
展开),存储在 sys_config 表——修改即时生效,无需重启。13 项 RADIUS 配置:
| 键 | 默认值 | 用途 |
|---|---|---|
EapMethod | eap-md5 | 当前 EAP 方法:eap-md5、eap-mschapv2、eap-tls、eap-peap、eap-ttls |
EapEnabledHandlers | * | 允许的 EAP 处理器逗号白名单 |
EapTlsCertFile / EapTlsKeyFile | 空 | EAP-TLS/PEAP/TTLS 的服务器证书/私钥;留空即禁用基于 TLS 的 EAP |
EapTlsCaFile | 空 | 校验客户端证书的 CA 链 |
EapTlsMinVersion | 1.2 | 最低 TLS 版本(1.2/1.3) |
IgnorePassword | false | 跳过密码校验(仅测试用) |
AccountingHistoryDays | 90 | 计费历史保留天数(@daily 清理;0 关闭) |
AcctInterimInterval | 300 | NAS 中间计费更新间隔(秒) |
SessionTimeout | 3600 | 默认会话超时(秒) |
LogLevel | info | RADIUS 日志级别(debug/info/warn/error) |
RejectDelayMaxRejects | 7 | 触发延迟响应的连续拒绝次数 |
RejectDelayWindowSeconds | 10 | 拒绝计数窗口(秒) |
工具栏:保存、刷新、重置(恢复默认值,需确认)以及 备份 / 恢复 —— 导出或回导一份包含节点、NAS、策略、用户、配置与操作员的 JSON 快照(不包含的内容见运维指南)。
操作员
控制台账号管理(仅 super/admin):用户名、密码、联系方式、级别、状态。
操作日志保存在数据库(sys_opr_log),一年后自动清理。
界面便利功能
- 语言切换(顶栏)—— 简体中文 / English,按浏览器记忆。
- 主题切换 —— 明/暗。
- 用户、会话、计费、NAS、节点、操作员列表均支持 CSV 导出。
- 所有列表页支持服务端分页与已激活过滤条件标签。
运维指南
English version: Operations Guide
在生产环境运行 ToughRADIUS 所需的一切:配置参考、环境变量、TLS/EAP 证书、 存储、监控、备份以及随附的命令行工具。
进程模型
一个静态二进制并发运行多个服务(Web/管理 API、RADIUS 认证、RADIUS 计费、 RadSec)。任一服务失败,整个进程退出,交由守护程序重启——请使用 systemd、Docker 或同类工具托管。
# /etc/systemd/system/toughradius.service(参考)
[Unit]
Description=ToughRADIUS server
After=network-online.target
[Service]
ExecStart=/usr/local/bin/toughradius -c /etc/toughradius.yml
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
端口
| 端口 | 协议 | 服务 | 配置键 |
|---|---|---|---|
| 1816 | TCP HTTP | 管理界面 + REST API | web.port |
| 1817 | TCP HTTPS | 管理界面 TLS(可选;启动失败不影响整体) | web.tls_port |
| 1812 | UDP | RADIUS 认证 | radiusd.auth_port |
| 1813 | UDP | RADIUS 计费 | radiusd.acct_port |
| 2083 | TCP TLS | RadSec(RFC 6614) | radiusd.radsec_port |
| 3799 | UDP(出向) | 发往 NAS 的 CoA/Disconnect | 按 NAS 的 CoA 端口 字段 |
配置
查找顺序:-c <文件> → ./toughradius.yml → /etc/toughradius.yml →
内置默认值。可用 toughradius -printcfg 查看合并结果。
system:
appid: ToughRADIUS
location: Asia/Shanghai # 定时任务/时间戳所用时区
workdir: /var/toughradius # 生产构建的默认值
debug: false
web:
host: 0.0.0.0
port: 1816
tls_port: 1817
secret: <随机字符串> # JWT 签名密钥——务必修改
database:
type: sqlite # sqlite | postgres
host: 127.0.0.1 # 仅 postgres
port: 5432
name: toughradius.db # sqlite 文件名(位于 {workdir}/data/)或 pg 库名
user: postgres
passwd: <密码>
max_conn: 100
idle_conn: 10
debug: false
radiusd:
enabled: true
host: 0.0.0.0
auth_port: 1812
acct_port: 1813
radsec_port: 2083
radsec_worker: 100
radsec_ca_cert: private/ca.crt # 相对路径基于 workdir 解析
radsec_cert: private/radsec.tls.crt
radsec_key: private/radsec.tls.key
debug: false # true = 完整报文转储
logger:
mode: production # development | production
file_enable: true
filename: /var/toughradius/toughradius.log
工作目录结构
启动时 ToughRADIUS 在 system.workdir 下创建:
/var/toughradius/
├── data/ # SQLite 数据库、指标数据
├── logs/
├── private/ # TLS 材料(权限 0700)
├── public/
└── backup/ # 配置备份的服务器侧副本
环境变量
环境变量优先于 YAML 文件:
| 变量 | 覆盖项 |
|---|---|
TOUGHRADIUS_SYSTEM_WORKER_DIR | system.workdir |
TOUGHRADIUS_SYSTEM_DEBUG | system.debug |
TOUGHRADIUS_WEB_HOST / _WEB_PORT / _WEB_TLS_PORT / _WEB_SECRET | web.* |
TOUGHRADIUS_DB_TYPE / _DB_HOST / _DB_PORT / _DB_NAME / _DB_USER / _DB_PWD / _DB_DEBUG | database.* |
TOUGHRADIUS_RADIUS_ENABLED / _RADIUS_HOST / _RADIUS_AUTHPORT / _RADIUS_ACCTPORT / _RADIUS_DEBUG | radiusd.* |
TOUGHRADIUS_RADIUS_RADSEC_PORT / _RADIUS_RADSEC_WORKER / _RADIUS_RADSEC_CA_CERT / _RADIUS_RADSEC_CERT / _RADIUS_RADSEC_KEY | RadSec 配置 |
TOUGHRADIUS_LOGGER_MODE / _LOGGER_FILE_ENABLE | logger.* |
TOUGHRADIUS_RADIUS_POOL | RADIUS 工作池大小(默认 1024) |
命令行参数
| 参数 | 作用 |
|---|---|
-c <文件> | 指定配置文件 |
-initdb | 删除并重建全部数据表后退出 |
-printcfg | 以 JSON 打印合并后的配置并退出 |
-v | 打印版本 / 构建时间 / 提交号并退出 |
-h | 帮助 |
RADIUS 运行时配置(EAP 方法、证书、间隔、拒绝延迟等)存储在数据库中,通过 系统配置 页面修改——无需重启。见 管理系统用户手册。
数据库
- SQLite(默认)—— 纯 Go 驱动,无 CGO,文件位于
{workdir}/data/<name>。适合中小规模部署;备份即拷贝文件。 - PostgreSQL —— 设置
database.type: postgres及 host/user/password。 推荐用于生产规模与高并发计费负载。
结构迁移(GORM AutoMigrate)在每次启动时自动执行,升级流程即:停止、
替换二进制、启动。-initdb 仅用于首次安装——它销毁全部数据。
需要关注的大表:radius_accounting(随会话持续增长)与 radius_online。
radius.AccountingHistoryDays 配置(默认 90,设为 0 关闭)定义计费历史的保留
窗口:@daily 定时任务会删除超过该天数的已结束 radius_accounting 记录(在线
会话不受影响),并清理连续多个计费中间更新周期未刷新的 radius_online 残留行。操作
日志(sys_opr_log)一年后自动清理。若数据量很大,仍建议把表增长监控与数据库级归档
纳入自己的运维流程。
TLS 与证书
三处相互独立的证书使用方:
| 使用方 | 文件 | 说明 |
|---|---|---|
| RadSec | radiusd.radsec_ca_cert / radsec_cert / radsec_key | TLS 1.2+;客户端证书提供则校验(VerifyClientCertIfGiven) |
| Web HTTPS | {workdir}/private/toughradius.tls.crt + .key(固定路径) | 监听 web.tls_port;加载失败仅记日志,HTTP 继续运行 |
| EAP(TLS/PEAP/TTLS) | 系统配置 → EapTlsCertFile、EapTlsKeyFile、EapTlsCaFile、EapTlsMinVersion | 证书/私钥留空即禁用基于 TLS 的 EAP 方法 |
用内置工具一次生成 CA/服务器/客户端全套证书:
go run ./cmd/certgen -type all -output /var/toughradius/private \
-server-cn radius.example.com -server-dns radius.example.com \
-days 3650
# 然后将 radsec_cert/radsec_key(或 EAP 配置)指向生成的文件
日志
zap 结构化日志。logger.mode: development 输出适合人读的控制台格式;
production 输出 JSON。文件输出由 logger.file_enable + logger.filename
控制。RADIUS 日志级别还可在运行时通过 系统配置 → LogLevel 调整;
radiusd.debug: true 会转储完整报文(生产环境请关闭)。
指标
计数器保存在内存中,经由管理仪表盘展示(没有 Prometheus /metrics HTTP
端点)。RADIUS 计数器包括:radus_accept、radus_online/radus_offline、
radus_accounting、radus_auth_drop / radus_acct_drop、
radus_radsec_saturated,以及按原因细分的拒绝计数——
radus_reject_passwd_error、radus_reject_not_exists、radus_reject_expire、
radus_reject_disabled、radus_reject_limit、radus_reject_bind_error、
radus_reject_unauthorized、radus_reject_other。计费请求在入口被丢弃时按
原因细分——radus_acct_drop_nas(未知/未授权 NAS)、radus_acct_drop_username
(缺少用户名)、radus_acct_drop_secret(Request Authenticator 校验失败)——
而 radus_acct_drop 作为背压与响应写入丢弃的兜底计数。系统仪表(CPU/内存、进程
CPU/内存)每 30 秒采样一次。
外部监控建议探测服务端口并采集日志文件;进程退出即故障信号(fail-fast 进程模型)。
备份与恢复
系统配置 → 备份 下载一份 JSON 快照(schema 版本 9.0),包含:节点、NAS
设备、计费策略、用户、系统配置、操作员。服务器侧同时在 {workdir}/backup/
留存一份。恢复 可回导该文件。
快照不包含计费历史与在线会话。完整的灾备方案还需备份数据库本身 (拷贝 SQLite 文件或使用
pg_dump)。
命令行工具
均位于 cmd/ 下,以 go run ./cmd/<工具> 运行:
| 工具 | 用途 |
|---|---|
radtest | 迷你 RADIUS 客户端:auth、acct、flow(认证 + 开始 + 结束)。参数:-server、-secret、-username、-password、-calling-station、-framed-ip、-session-id |
certgen | 生成 CA / 服务器 / 客户端证书(见上文) |
benchmark | 压力测试:总请求数 -n、并发 -c、认证/计费模式、CSV 统计输出 |
reset-password | 重置控制台操作员密码:go run ./cmd/reset-password -c <配置> -u admin -p <新密码> |
demo-seed | 填充演示用节点/NAS/策略/用户/会话数据 |
config-tool | 校验 / 汇总配置 schema JSON |
生产加固清单
- 修改
web.secret与默认admin密码。 -
radiusd.debug: false、logger.mode: production。 - 用防火墙将 UDP 1812/1813 与 TCP 1816 限制在可信网络内。
- 跨不可信网络传输 RADIUS 时使用 RadSec(2083)或可信二层/VPN 通道。
- 每台 NAS 使用独立且强度足够的共享密钥。
- EAP:优先
eap-tls/eap-peap/eap-ttls并使用正式证书;MS-CHAPv2 的注意事项见安全策略。 - 制定数据库备份计划(配置快照 + 数据库转储)。
- 守护进程配置自动重启,并对进程退出告警。
LDAP / AD 认证后端
English version: LDAP / AD Authentication Backend
ToughRADIUS 可以通过执行 LDAP bind(绑定) 操作,针对外部 LDAP 目录或 Microsoft Active Directory 校验用户口令,而不必(或不仅仅)使用自身数据库中 存储的口令。这样即可复用既有的企业目录,无需复制或迁移口令。
仅支持 PAP 族。 LDAP 后端通过「以明文口令执行 bind」来认证,因此只服务 裸 PAP 与 EAP-TTLS 内层 PAP(RFC 5281)。挑战/响应类方法——CHAP、 MS-CHAP、MS-CHAPv2、EAP-MD5、PEAP-MSCHAPv2——无法被服务,因为服务端 从不持有重算其响应所需的明文口令。启用 LDAP 后,这些方法会被有意拒绝并记录 可诊断原因;详见下文「认证流程」一节。
何时使用(以及何时不要用)
当你必须服务那些口令已存在于目录(OpenLDAP、FreeIPA 或 Active Directory)中 的用户,又不想给每个客户端签发证书时,就该用 LDAP 后端。它是混合环境与遗留 系统的务实桥梁:先用服务器证书保护隧道,再把用户名和口令塞进隧道里传输。
但要正视这笔权衡。EAP-TTLS/PAP 在 TLS 隧道内传输明文口令——它只受该隧道 保护。因此部署的安全性完全系于一张强壮且被正确校验的服务器证书与 TLS 1.2+。 如果每个客户端都能出示证书,请优先选 EAP-TLS;如果你需要为遗留客户端提供 目录后端的口令校验,那么 LDAP + EAP-TTLS/PAP 正是合适的工具——只是别对它的 安全性抱有不切实际的期待。
部署模型
LDAP 后端只替换口令校验这一步。它不会创建账号,也不携带授权数据。
每个用户仍需要一条本地 RadiusUser 记录,因为授权信息(套餐/资费、限速、
到期时间、并发会话上限、地址池、VLAN、MAC 绑定)是在认证之前由本地数据库
加载的。
换句话说:
- 认证(口令对不对?)→ 由 LDAP 目录通过 bind 完成。
- 授权(这个用户能做什么?)→ 仍由本地
RadiusUser记录决定,与不启用 LDAP 时完全一致。
全局开关 ldap.Enabled 对整台服务器启用或关闭该后端;没有「按用户指定认证源」
的字段。MAC 地址认证始终绕过 LDAP。
配置
在管理后台的系统配置页面、LDAP 分组下配置该后端;所有配置项也可通过 设置 API 修改。后端在每次认证尝试时都会重新读取配置,因此改动即时生效、 无需重启。
| 配置键 | 类型 | 默认值 | 适用模式 | 说明 |
|---|---|---|---|---|
ldap.Enabled | bool | false | 两者 | 启用 LDAP 后端。默认关闭。 |
ldap.ServerURL | string | (空) | 两者 | 目录 URL,如 ldap://dc.example.com:389 或 ldaps://dc.example.com:636。 |
ldap.BindMode | 枚举 | template | 两者 | template 或 search(见下文)。 |
ldap.BindDNTemplate | string | (空) | template | 含单个 %s(代入用户名)的 DN 模板,如 uid=%s,ou=people,dc=example,dc=com,或 AD 的 UPN 形式 %[email protected]。 |
ldap.BaseDN | string | (空) | search | 用户查找的子树基点,如 dc=example,dc=com。 |
ldap.UserFilter | string | (uid=%s) | search | 含单个 %s(代入用户名,代入前已转义)的过滤器,如 (uid=%s) 或 AD 的 (sAMAccountName=%s)。 |
ldap.SearchBindDN | string | (空) | search | 用于查找用户的只读服务账号 DN,如 cn=svc-radius,ou=svc,dc=example,dc=com。 |
ldap.SearchBindPassword | string | (空) | search | 服务账号 DN 的口令。 |
ldap.StartTLS | bool | false | 两者 | 在 bind 前用 StartTLS 将 ldap:// 连接升级为 TLS(RFC 4513 §3)。用 ldaps:// 时请关闭。 |
ldap.TLSSkipVerify | bool | false | 两者 | 跳过 TLS 证书校验。不安全——仅限实验/自签名环境。 |
ldap.Timeout | int(秒) | 5 | 两者 | 拨号与每次操作的超时秒数(1–60)。 |
两种 bind 模式
模板模式(ldap.BindMode = template)最简单:把用户名代入 BindDNTemplate
组成 DN,服务端直接以该 DN 加上所给口令执行 bind。适用于每个用户的 DN 都遵循
固定模式的场景。
BindMode = template
BindDNTemplate = uid=%s,ou=people,dc=example,dc=com
# Active Directory 写法:
# BindDNTemplate = %[email protected]
搜索模式(ldap.BindMode = search)适配 DN 不可预测的目录。服务端先以只读
服务账号(SearchBindDN / SearchBindPassword)bind,在 BaseDN 下用
UserFilter 搜索定位用户的 DN,然后以用户身份重新 bind(用所给口令)来
完成校验。
BindMode = search
ServerURL = ldaps://dc.example.com:636
BaseDN = dc=example,dc=com
UserFilter = (sAMAccountName=%s) # Active Directory
SearchBindDN = cn=svc-radius,ou=svc,dc=example,dc=com
SearchBindPassword = ********
传输安全
- 使用
ldaps://URL 走隐式 TLS(636 端口),或者使用ldap://URL 并设StartTLS = true将明文连接升级(RFC 4513 §3)。不要在ldaps://URL 上再 开 StartTLS。 - 生产环境请保持
TLSSkipVerify = false。仅在实验或自签名目录上启用它——它会 关闭证书校验,使 bind 暴露于被截获的风险。
认证流程
| 方法 | ldap.Enabled = true 时的行为 |
|---|---|
| 裸 PAP | 通过 LDAP bind 认证。 |
| EAP-TTLS / 内层 PAP | 通过 LDAP bind 认证;仍会为隧道派生 MS-MPPE 密钥。 |
| CHAP / MS-CHAP / MS-CHAPv2 | 拒绝并记录可诊断原因(无明文口令可用于 bind)。 |
| EAP-MD5 / PEAP-MSCHAPv2 | 同理拒绝。 |
| MAC 认证 | 完全绕过 LDAP。 |
对挑战/响应类方法的拒绝是有意为之的,且集中在「取口令」边界统一处理,而不是
在协议入口分叉。启用 LDAP 后,本地 RadiusUser.Password 通常为空,挑战/响应
方法若仍以空口令推算期望值,可能误判通过任意用户——因此这些方法被「失败
即关闭」。
安全模型
该后端在设计上保守:
- 空用户名或空口令在任何网络操作前即被拒绝。 带 DN 而口令为空的 bind 会被 许多服务器当作匿名绑定而通过(RFC 4513 §5.1.2),因此在最前端直接拒绝。
- 注入被中和。 搜索模式下用户名经 LDAP 过滤器转义(RFC 4515 §3);模板模式 下代入前经 DN 转义。
- 歧义搜索被拒绝。 若
UserFilter命中超过一条记录,则拒绝该次尝试而非 猜测。 - 目录故障绝不被报告为口令错误。 凭证无效(LDAP code 49)映射为口令拒绝; 其余任何失败——目录不可达、配置错误、服务账号 bind 失败——均映射为 后端不可用。这一区分同样驱动下文的指标。
可观测性
拒绝按原因计数(指标端点见运维指南):
radus_reject_passwd_error——口令错误(bind 返回凭证无效)。radus_reject_ldap_error——后端无法给出答案(目录不可达、StartTLS 失败、 服务账号 bind 失败、配置错误)。
将二者分开,意味着目录故障会体现为 radus_reject_ldap_error,而不是一片
「口令错误」的尖峰——于是对 radus_reject_ldap_error 的告警能清晰地指向目录
问题而非用户失误。
注意。
radus_accept在每次认证成功时自增一次——裸 PAP/CHAP 流程与 EAP 流程都汇聚到同一个 Access-Accept 出口——因此可与上述拒绝计数器配对,用于成功率 或 SLO 观测。目录故障只会体现为radus_reject_ldap_error,不会产生虚假的radus_accept,从而让成功与失败信号保持清晰分离。
故障排查
| 现象 | 可能原因 |
|---|---|
所有登录都失败,radus_reject_ldap_error 持续上升 | ServerURL 错误/不可达、TLS 握手失败,或(搜索模式)服务账号无法 bind。 |
| Windows/AD 客户端被拒,PAP 用户正常 | 这些客户端在用 PEAP-MSCHAPv2 或 MS-CHAPv2,LDAP 无法服务——改用 EAP-TTLS/PAP,或改用本地口令后端。 |
| 搜索模式找不到用户 | 检查 BaseDN 与 UserFilter;确认服务账号能读取用户条目。 |
| 实验环境 bind 正常、生产失败 | TLSSkipVerify 此前掩盖了无效证书——安装受信任证书并关闭该开关。 |
| 口令通过了但会话没有套餐/限速 | 缺少本地 RadiusUser 记录——LDAP 只校验口令,请创建本地账号以提供授权。 |
参见
- 运维指南——EAP/TLS 证书、指标、进程模型。
- 核心术语与概念——PAP、EAP、EAP-TTLS。
- 协议与 RFC 索引——RFC 5281(EAP-TTLS)、RFC 4511/4513(LDAP)。
常见问题解答
English version: FAQ
按主题分组的常见问题。如果这里没有覆盖你的问题,请检索 GitHub issues 或新开一个。
安装与访问
忘记管理员密码怎么办?
使用内置工具,指向服务器实际使用的配置文件:
go run ./cmd/reset-password -c /etc/toughradius.yml -u admin -p <新密码>
-initdb 之后的默认账号为 admin / toughradius。
SQLite 和 PostgreSQL 怎么选?
SQLite(默认)零依赖——纯 Go 驱动、单文件存于 {workdir}/data/——适合实验
与小规模部署。生产规模、高计费量,或需要外部备份工具(pg_dump、复制)时
选 PostgreSQL。
能不能不用 1812/1813/1816 这些端口?
可以——所有端口都可通过 YAML 或环境变量配置(radiusd.auth_port、
radiusd.acct_port、web.port 等)。见运维指南。
管理界面的 HTTPS 端口(1817)不工作
Web TLS 监听需要 {workdir}/private/toughradius.tls.crt 与 .key。文件缺失
或无效时只记录日志并仅停掉 HTTPS 监听——1816 上的 HTTP 继续服务。可用
cmd/certgen 生成证书或提供自己的证书。
-initdb 可以再跑一次吗?
不可以。 它会删除并重建所有表。仅在首次安装时执行。常规升级无需手动 处理表结构——迁移在启动时自动完成。
认证
所有请求都被忽略 / 超时
最常见的两个原因:
- 请求的源 IP 未登记为 NAS 设备——在 NAS 设备 中添加(或修正 NAT 使源地址符合预期)。
- 共享密钥不一致——RADIUS 对报文本身校验失败的包会静默丢弃。
打开 radiusd.debug: true 或把日志级别调到 debug 查看实际到达的报文。
用户认证成功但没有限速
只有 NAS 记录的厂商具备限速 VSA(华为、MikroTik、H3C、中兴、爱快)时才
会下发限速属性。登记为 Standard 或 Cisco 的 NAS 不会收到私有限速属性——
见厂商对接指南。同时确认用户的策略确实设置了
up_rate/down_rate(Kbps)。
拨号能认证成功,却拿不到 IP / 地址池没生效
只有在用户或其计费策略上设置了地址池时,Access-Accept 才会下发
Framed-Pool;且下发的池名必须与 NAS 上的地址池同名(如 RouterOS 的
/ip pool)。名字不一致、或根本没设池,客户端就拿不到(正确的)地址。需要固定
IP 时,可在用户上设置静态 IPv4(下发 Framed-IP-Address,覆盖地址池)。端到端
示例见场景实战手册 · MikroTik。
有多台 NAS,需要为每台单独配置吗?
需要。每台 NAS 都要在「NAS 设备」中单独登记,各自填源 IP(或 identifier) 与各自的共享密钥。ToughRADIUS 按报文源地址(或 NAS identifier)匹配对应的 NAS 记录及其密钥;来自未登记源地址的请求会被记录并以「未授权 NAS」拒绝。不同 NAS 可用不同密钥,无需统一。
某个用户为什么被拒绝?
拒绝按原因分类(密码错误、用户不存在、已过期、已停用、并发超限、MAC/VLAN
绑定不符、未授权 NAS 等)——仪表盘展示各类计数,日志记录细节。最容易让人
意外的是绑定不符:开启 bind_mac/bind_vlan 后,首次出现的 MAC/VLAN
会被记录,后续请求必须匹配;更换硬件后请清空用户上的已存绑定值。
连续输错密码后响应变慢,为什么?
这是拒绝延迟防爆破机制:在 RejectDelayWindowSeconds(默认 10 秒)窗口内
连续拒绝达到 RejectDelayMaxRejects(默认 7 次)后,响应会被延迟。两个参数
都可在 系统配置 中调整。
ToughRADIUS 支持 802.1X / 企业级 Wi-Fi 吗?
支持。可用的 EAP 方法:EAP-MD5、EAP-MSCHAPv2、EAP-TLS、PEAPv0/EAP-MSCHAPv2 与 EAP-TTLS(内层 PAP / MS-CHAP-V2)。在 系统配置 → EapMethod 中选择。
EAP-TLS / PEAP / EAP-TTLS 启动不了
基于 TLS 的 EAP 需要在系统配置中设置 EapTlsCertFile + EapTlsKeyFile——
留空时这些方法按设计处于禁用状态。用 cmd/certgen 生成服务器证书、填好
路径后重试。EapTlsCaFile 仅在需要校验客户端证书(EAP-TLS)时配置。
服务器与设备时钟不同步会有什么影响?
时钟漂移会带来隐蔽问题:基于 TLS 的 EAP(EAP-TLS / PEAP / TTLS)会校验证书的
有效期(NotBefore / NotAfter),双方时间偏差过大可能导致握手失败;计费的
起止时间与时长会失真;拒绝延迟防爆破窗口(默认 10 秒)也以服务器时钟为准。请在
服务器与 NAS 上启用 NTP 保持时间同步。
EAP 方法怎么选?
- EAP-TLS —— 最强(双向证书),需要给客户端发证。
- PEAPv0/EAP-MSCHAPv2 —— Windows/AD 兼容;注意 MS-CHAPv2 的类 NTLMv1 攻击面(见安全策略)。
- EAP-TTLS —— 通过内层 PAP 对接遗留/LDAP 后端,密码受 TLS 隧道保护。
会话、CoA 与计费
在线会话页的强制下线 / CoA 没有效果
依次检查:设备已开启动态授权(如 RouterOS 的 radius incoming、IOS 的
aaa server radius dynamic-author);NAS 记录上的 CoA 端口 与设备一致
(默认 3799);设备接受来自服务器地址的请求。ToughRADIUS 等待 5 秒并重试
2 次后才报告失败。
想在线给用户改速率(FUP 超额限速),怎么做?
ToughRADIUS 的「修改授权(CoA)」只携带 Session-Timeout 与 Filter-Id,
不会在线改写 Mikrotik-Rate-Limit 等限速属性。实时变速的标准做法是:先在
计费策略 / 用户上改速率,再对其执行「强制下线」,客户端自动重拨后即按新速率
重新授权;也可用 Filter-Id 让设备套用预先定义好的限速规则。详见
场景实战手册 · MikroTik · 在线管控。
CoA / 强制下线提示找不到会话
操作员发起的 CoA / Disconnect 需要先在「在线会话」中定位该会话,再据其 NAS
记录与会话标识(如 Acct-Session-Id)向设备发起。若在线记录已残留失效(NAS 未
上报计费停止)或会话已结束,就会匹配不到——刷新在线列表、确认设备计费正常后
重试。
在线会话里出现早已下线的用户
在线记录由 NAS 的计费报文创建/刷新。NAS 停发(重启、断链)时记录可能残留。
请确认设备开启了计费与中间更新(每个 Access-Accept 都会下发
Acct-Interim-Interval,默认 300 秒)。残留记录也可在界面上手动下线/删除。
计费表一直增长,哪些会自动清理?
两个 @daily 定时任务会自动清理数据:一个删除一年前的操作日志(SysOprLog);
另一个 SchedClearExpireData 会按 AccountingHistoryDays(默认 90 天)删除已结束
的 radius_accounting 计费历史(在线会话的记录不会被删),并清理连续多个计费中间更新
周期未刷新的 radius_online 残留在线行。将 AccountingHistoryDays 设为 0 可
关闭计费历史清理(永久保留)。若流量很大、需要更激进的瘦身,仍建议把数据库级
归档纳入运维流程。配置备份不包含计费历史——见备份与恢复。
并发会话数没有被限制
计费策略中的 active_num 是单用户并发上限(0 表示不限)。该检查统计
radius_online 表中的行数,依赖 NAS 正常上报计费——没有计费 Start 报文,
服务器无从得知谁在线。
运维
怎么接 Prometheus 监控?
目前没有 /metrics HTTP 端点;计数器在内存中并通过仪表盘展示。外部监控
建议探测端口、采集日志文件,并对进程退出告警(进程模型为 fail-fast)。
怎么安全升级?
停止服务、替换二进制(或拉取新的 Docker 标签)、启动。表结构迁移自动完成。 升级前先做配置备份(系统配置 → 备份)和数据库备份。
日志 / 数据 / 证书都存在哪里?
全部位于 system.workdir(默认 /var/toughradius)之下:data/(SQLite
数据库)、logs/、private/(TLS 材料)、backup/(配置快照)。见
运维指南。
协议与 RFC 索引
English version: Protocol & RFC Reference
ToughRADIUS 实现了标准 RADIUS、EAP、动态授权与安全传输等协议。本章是项目所依赖标准的 精选、面向实现的索引,将每个 RFC 映射到它在代码中的使用位置及 路线图里程碑。
完整 RFC 文本归档于
docs/rfcs/;
逐文件的原始目录见
docs/rfcs/README.md。
当两者出现差异时,以本章的引用为准。
已实现的标准
RADIUS 核心
- RFC 2865 —— RADIUS 认证;基础的请求/响应协议。
- RFC 2866 —— RADIUS 计费;会话开始 / 中间更新 / 结束记录。
RADIUS + EAP 集成
- RFC 3579 —— RADIUS 对 EAP 的支持(EAP-Message 与 Message-Authenticator)。
- RFC 3580 —— IEEE 802.1X 的 RADIUS 使用指南。
EAP 框架与方法
- RFC 3748 —— 可扩展认证协议(EAP)。框架本身,同时定义了 EAP-MD5,即 MD5-Challenge 方法(§5.4)。
- RFC 5216 —— EAP-TLS;基于证书的双向认证(里程碑 M1)。
- RFC 5281 —— EAP-TTLSv0;用 TLS 隧道承载内层 PAP / MS-CHAPv2(M9)。
- RFC 2759 —— MS-CHAP-V2,用作 EAP-MSCHAPv2 与 PEAPv0 的内层方法(M8)。 MS-MPPE 会话密钥按 RFC 2548 / RFC 5705 派生。
PEAPv0 没有独立 RFC,遵循 Microsoft/Cisco 的 PEAP 定义,内层承载 EAP-MSCHAPv2。 它以兼容为先——MS-CHAPv2 交换存在类似 NTLMv1 的攻击面——在可控证书环境下优先选用 EAP-TLS。
动态授权
- RFC 5176 —— CoA 与 Disconnect-Request,取代 RFC 3576(里程碑 M2)。
安全传输
- RFC 6614 —— 基于 TLS 的 RADIUS(RadSec)。
- RFC 6613 —— 基于 TCP 的 RADIUS。
厂商私有属性
- RFC 2548 —— Microsoft 厂商私有属性,包含承载 EAP 密钥材料的 MS-MPPE-Send/Recv-Key 属性。
IPv6
- RFC 3162、RFC 4818、RFC 6911 —— RADIUS 的 IPv6 地址与委派前缀属性 (里程碑 M3)。
路线图标准
| RFC | 标准 | 里程碑 |
|---|---|---|
| RFC 9190(+ RFC 9427) | EAP-TLS 1.3 与 TLS 1.3 密钥派生 | M10 |
| RFC 7170 / RFC 9930 | TEAP v1 —— 隧道 EAP,machine + user chaining | M11 |
| RFC 5931 | EAP-PWD —— 基于口令,无需为每客户端签发证书 | M12 |
目录准确性说明。 在
docs/rfcs/中,文件rfc7542-eap-pwd.txt标注有误: RFC 7542 是网络访问标识符(NAI),而 EAP-PWD 是 RFC 5931。同样,EAP-MD5 由 RFC 3748 §5.4 定义,而非 RFC 3851(一份 S/MIME 规范)。本章采用正确的引用。
另见
- 概述 —— 含 EAP 套件的能力概览。
- 文档地图 —— 各源文档的存放位置。
- RFC Editor —— 权威的在线 RFC 文本。
EAP 验收测试报告
每周 EAP 验收任务使用外部 eapol_test supplicant 验证 ToughRADIUS,并在这里展示最近保留的报告。
最近结论: 通过
最近场景摘要
| 场景 | 方法 | 预期 | 状态 | 耗时 | 说明 |
|---|---|---|---|---|---|
| EAP-TLS valid client certificate | EAP-TLS | Access-Accept | 通过 | 146 ms | external supplicant received the expected Access-Accept |
| EAP-TLS untrusted client certificate | EAP-TLS | Access-Reject | 通过 | 19 ms | external supplicant was rejected as expected |
| PEAP/MSCHAPv2 valid credentials | PEAP/MSCHAPv2 | Access-Accept | 跳过 | 0 ms | Skipped intentionally: eapol_test currently exposes a PEAP inner-framing interop gap (server rejects the decrypted phase-2 payload as an invalid inner EAP message). The in-process PEAP/MSCHAPv2 integration test remains the current acceptance coverage. |
| PEAP/MSCHAPv2 wrong password | PEAP/MSCHAPv2 | Access-Reject | 跳过 | 0 ms | Skipped intentionally: eapol_test currently exposes a PEAP inner-framing interop gap (server rejects the decrypted phase-2 payload as an invalid inner EAP message). The in-process PEAP/MSCHAPv2 integration test remains the current acceptance coverage. |
| EAP-TTLS/PAP valid credentials | EAP-TTLS/PAP | Access-Accept | 通过 | 122 ms | external supplicant received the expected Access-Accept |
| EAP-TTLS/MSCHAPv2 valid credentials | EAP-TTLS/MSCHAPv2 | Access-Accept | 通过 | 122 ms | external supplicant received the expected Access-Accept |
| Malformed external EAP client config | tooling | documented skip | 跳过 | 0 ms | Skipped intentionally: eapol_test parser failures do not exercise ToughRADIUS over RADIUS/EAP. Negative server behavior is covered by untrusted certificate and wrong password scenarios. |
保留报告
性能基准测试报告
每周 benchmark 任务基于现有 Go Benchmark* 函数记录 ToughRADIUS 性能信号。报告仅用于观察趋势,不会因为 GitHub 托管 runner 的耗时波动直接失败。
最近结论: 已记录
最近摘要
| 指标 | 值 |
|---|---|
| Benchmark 数量 | 15 |
| 包数量 | 6 |
| 最慢项 | github.com/talkincode/toughradius/v9/pkg/excel / BenchmarkWriteToFile |
| 最高 B/op | github.com/talkincode/toughradius/v9/pkg/excel / BenchmarkWriteToFile |
保留报告
安全策略
English version: Security Policy
本章是 ToughRADIUS 安全公告及其配套指引的权威归处。仓库根目录的
SECURITY.md
保留一个指向本章的简短指针,以保证单一事实来源。
安全公告
XSS 漏洞修复(v8.0.8)
v8.0.8 版本修复了一个严重的跨站脚本(XSS)漏洞。该问题位于登录接口对
errmsg 参数的处理。
| 项目 | 详情 |
|---|---|
| 漏洞类型 | 跨站脚本(XSS) |
| 严重程度 | 严重 |
| 影响版本 | v8.0.1 – v8.0.7 |
| 修复版本 | v8.0.8 |
| 受影响组件 | 登录接口(errmsg 参数) |
建议措施
强烈建议所有用户立即升级到最新版本。可参考文档地图 中的 README 与构建说明完成部署升级。
Agent 开发指南
English version: Agent Development Guide
本章是一份面向贡献者的摘要,介绍 ToughRADIUS 如何借助 AI 编码 agent 进行 开发,归纳工作规则、质量门禁与自动委托循环,使该工作流可从手册统一发现。
权威规则位于仓库根目录的
AGENT.md;该文件
保持权威地位,并被 agent 工具链直接引用。本章不替代它——如有歧义,以
AGENT.md 为准。
产品范围基线
开发始终锚定功能清单,绝不漂移到无关的产品方向。
- 权威范围基线为
docs/feature-checklist.md(英文版见docs/feature-checklist.en.md)。 - 每个任务、issue、PR、测试与评审记录都映射到形如
TR-F004的功能编号。 - 若某需求无法映射到现有编号,先更新功能清单(范围、状态、验收边界、理由), 再改动代码。
- 非目标
TR-N001–TR-N005(支付、CRM、通用监控栈、多租户、整体重写)除非先 显式修订清单,否则一律不在范围内。
路线图与技能库
agent 驱动开发围绕三份产物组织:
docs/roadmap.zh.md—— 长期路线图与里程碑,每项映射到TR-F编号,是 agent 工作的任务来源。.agents/skills/—— 可复用的技能 SOP,每个技能一个目录(.agents/skills/<name>/SKILL.md)。.agents/README.md—— 委托参考与共享护栏。
一个总调度层驱动循环,执行 SOP 负责领域内的具体工作:
| 角色 | 技能 | 职责 |
|---|---|---|
| 总调度 | orchestrate-roadmap | “自动委托开发”入口:选取下一个未勾选子任务、匹配 SOP、执行门禁、开 PR |
| 门禁 | review-pr | 以 CI 为锚的独立审查;通过标签/评论打回,仅在审过且 CI 绿时自动合并 |
| 自我迭代 | groom-roadmap | 每次合并后勾选已交付子任务并重新梳理路线图 |
执行 SOP 包括:新增厂商 VSA(add-radius-vendor)、新增 EAP 方法
(add-eap-method)、新增 Admin API(add-adminapi-endpoint)、新增 React Admin
资源(add-react-admin-resource)、新增配置项(add-config-schema)、新增验收
测试(add-acceptance-test)、同步上游 radius(sync-upstream-radius)、引用 RFC
(reference-rfc)、对齐清单(align-feature-checklist)、编写 Go 测试
(write-go-tests)、编写 Go API 文档(document-go-apis)。开始某类任务前先选用
匹配的技能。
agent 在你自己的主机上用你自己的 agent/CLI 运行,而非通过 CI 工作流执行, 因此密钥不会进入 CI,执行环境完全自控。
工作准则
动手前先理解现有代码
绝不盲目改代码。先用检索定位现有实现、相关测试与文档,再模仿项目的命名、错误 处理与数据流。修 bug 前先追踪完整执行路径,重构前先梳理依赖与副作用。
持续验证
不要等到最后才跑测试。每个逻辑改动后都跑一次测试,使回归立即暴露,而不是堆到 大批量改动的末尾。
代码是最好的文档
- 所有导出 API 均带完整 godoc 注释——用途、带约束的参数、返回值与错误条件
(复杂 API 附使用示例)。标准库风格的约定见
document-go-apis技能。 - 复杂逻辑带行内注释,解释为什么而非做什么。
- 厂商相关代码引用协议规范(RFC 编号、VSA 文档)。
- 不产出冗余的独立总结文档——信息应留在代码注释与 Git 历史中。
核心开发原则
测试驱动开发(TDD)
先写测试再写代码:用失败的测试定义预期行为(红),写最少代码使其通过(绿),
随后在测试保持通过的前提下重构。若改动 internal/radiusd/auth.go,测试位于
internal/radiusd/auth_test.go。统一约定见 write-go-tests 技能。
GitHub 工作流
- 仅走 Pull Request——禁止直接推
main;受保护分支会拒绝直接推送。 - 约定式提交——
<type>(<scope>): <subject>,类型如feat、fix、test、docs、refactor、perf、chore。 - 小而原子的改动优于巨型 PR。
仓库 issue/PR 自动化
仓库通过若干 GitHub Actions 做轻量分流,帮助维护者扫描队列;它们不替代阅读 原始 issue、PR diff 与 CI 输出。
| Workflow | 触发与结果 | 维护者注意事项 |
|---|---|---|
| AI issue summary | .github/workflows/ai-issue-summary.yml 在 issue opened 时运行,并用 GitHub Models 生成简短摘要作为 issue 评论。 | issue 标题和正文是不可信输入。自动摘要只作阅读便利,不能作为权威事实;判断仍以 issue 原文为准。 |
| Stale | .github/workflows/stale.yml 每天 04:24 UTC 自动运行,也可手动触发。60 天无活动后添加 stale,再过 14 天仍无活动则关闭。 | 评论、推送提交或移除 stale 可保活。带 pinned、security、help wanted、agent-roadmap、needs-human 的 issue 豁免;带 pinned、security、agent-roadmap、needs-human 的 PR 豁免;所有 milestone 豁免。 |
| Labeler | .github/workflows/labeler.yml 在 pull_request_target 上运行,并按 .github/labeler.yml 的路径规则打标签。 | 自动标签包括 go、javascript、github_actions、dependencies、doc。该 action 只读取变更文件列表和基础分支配置,不 checkout 或执行 PR 代码。 |
| Greetings | .github/workflows/greetings.yml 在贡献者首次打开 issue 或 PR 时运行,并发送入门提示评论。 | 评论仅用于引导,不改变评审要求或 issue 优先级。 |
最小可行产品(MVP)
每次改动以最小、可独立使用、可回滚且不破坏既有行为的单元交付。大型工作拆成 MVP 增量(例如:厂商属性解析 → 认证集成 → 计费 → 管理界面),而非一次性塞进 一个超大 PR。
质量门禁
每个 agent 改动在合并前必须通过以下门禁:
go build ./...—— 无编译错误。go test ./...—— 全部单元测试通过。golangci-lint run—— 干净(钉 v2.12.2,与 CI 一致)。cd web && npm run build—— 任何前端改动。- 协议 / 端到端改动在
test/integration/下附 CI 可执行的验收测试,并引用docs/rfcs/下对应的规范。 - 产出一律走打了
agent-roadmap标签的 PR,由review-pr门禁把关,仅在agent-approved且 CI 全绿时合并。
技术约束
- 禁用 CGO——项目以
CGO_ENABLED=0构建以便跨平台部署。仅使用纯 Go 驱动 (例如用github.com/glebarez/sqlite而非github.com/mattn/go-sqlite3)。 - 数据库双兼容——每次 schema 变更都必须同时兼容 PostgreSQL(默认)与 SQLite。
- 上游依赖——核心库
layeh.com/radius经go.modreplace指向组织 forkgithub.com/talkincode/radius;上游重要修复通过sync-upstream-radius技能评估。
常见反模式(禁止)
- 导出 API 不写文档。
- 不写测试就提交实现。
- 混杂多个关注点的巨型 PR。
- 先实现后补测试。
- 直接推
main或跳过评审。 - 产出冗余的独立总结/报告文档。
- 引入 CGO 依赖。
下一步
AGENT.md—— 完整、权威的 agent 开发指南。- 文档地图 —— 找到 README、安全策略、功能清单、路线图 与 RFC 索引。
- 协议与 RFC 索引 —— 协议标准与代码、里程碑的对应关系。
文档地图
English version: Documentation Map
本手册采用分批收编的方式构建。下表先列出手册自身的章节,再列出仍位于仓库中的 文档位置,使所有文档都能从同一入口访问。
手册章节
| 章节 | 内容 |
|---|---|
| 概述 | 项目简介、核心能力与服务模型 |
| 核心术语与概念 | AAA 术语、认证请求流转与密码协议 |
| 快速开始 | 安装、初始化、首个用户与调试 |
| 厂商对接指南 | MikroTik、华为、Cisco、H3C、中兴、爱快等对接案例 |
| Portal / Hotspot 对接边界 | 铁律:ToughRADIUS 是 RADIUS AAA 后端,不托管 Captive Portal 登录页 |
| 场景实战手册 | 端到端运维场景(五段式):MikroTik PPPoE / Hotspot / CoA;华为 BRAS 分级套餐 / 线路绑定 / CoA;H3C / 中兴 / 爱快 / Cisco 差异速查 |
| 管理系统用户手册 | 管理控制台逐页说明 |
| 运维指南 | 配置参考、证书、监控、备份与命令行工具 |
| LDAP / AD 认证后端 | 针对 LDAP/AD 目录校验口令(仅 PAP 族):bind 模式、TLS、安全、指标 |
| 常见问题解答 | 按主题分组的常见问题 |
| 协议与 RFC 索引 | 协议标准与代码的对应关系 |
| 安全策略 | 安全公告与升级指引 |
| Agent 开发指南 | 面向贡献者的 AI-agent 工作流、质量门禁与自动委托循环摘要 |
仓库内文档
| 文档 | 说明 | 当前位置 |
|---|---|---|
| README | 项目介绍、特性与快速上手 | README.md |
| Agent 指南 | AI Agent 开发指南与协作规则 | Agent 开发指南(手册摘要) · AGENT.md(权威) |
| 安全策略 | 安全公告与升级指引 | 安全策略(权威) · SECURITY.md(指针) |
| 功能清单 | 功能范围基线(TR-F 编号) | docs/feature-checklist.md · English |
| 路线图 | 长期路线图与里程碑 | docs/roadmap.zh.md |
| RFC 索引 | 项目使用的协议标准索引 | 协议与 RFC 索引(权威) · docs/rfcs/README.md(原始目录) |
迁移计划。 手册现已覆盖 README 中面向用户的内容(概述、快速开始、厂商 对接、管理手册、运维、FAQ);README 继续作为 GitHub 首页并链接到这里。 Agent 指南现已新增手册摘要章节,指向权威的
AGENT.md; 由于 agent 工具链直接引用,AGENT.md仍保留在仓库根目录。功能清单与路线图是 由专门流程维护的活文档,保持在docs/中并以链接方式纳入。
mdbook 与 GitBook 并存
English version: mdbook & GitBook Coexistence
ToughRADIUS 也通过 GitBook 发布文档(docs.toughradius.net)。本节记录路线图
条目 M13.0 的决策:新的 mdBook 手册与既有 GitBook 管线如何相处。
决策:并存而非替代
mdBook 手册与 GitBook 并存,不替代、也不停用 GitBook 站点。两套管线相互独立、 互不冲突:
- mdBook 手册 —— 位于仓库的
docs-site/目录,以双语章节编写,并在每个 Pull Request 上由 CI 构建与坏链校验。它是需要与代码同步演进的文档的权威来源:受版本 控制、走评审门禁。 - GitBook 站点 —— 通过 GitBook 自有的 GitHub 集成(外部服务)从仓库同步。它由仓库
根目录提交的
.gitbook.yaml配置:将 GitBook 指向同一份docs-site/src/源文件, 以introduction.md作为首页,并读取共享的SUMMARY.md作为目录。因此 GitBook 渲染的是经过整理的双语手册,而不是从整个仓库推断出的目录树。
两套管线都从同一份 docs-site/src/ 源文件构建,但各自保留独立配置(mdBook 用
book.toml,GitBook 用 .gitbook.yaml)。它们不共享构建步骤、不会相互破坏;同时由于
读取相同的章节与同一个 SUMMARY.md,也不会发生内容漂移。
各站点的对外地址
两套管线发布到不同域名,因此不会相互遮蔽:
- mdBook 手册 —— 由
.github/workflows/pages.yml(路线图条目 M13.5)部署到 GitHub Pages,使用自定义域名 https://www.toughradius.net/ 对外服务。部署 工作流会在产物中写入CNAME文件,因此每次发布都会保持该自定义域名。要让该域名 解析生效,www.toughradius.net必须指向 GitHub Pages —— 将A记录设为185.199.108.153–185.199.111.153,或将CNAME以「仅 DNS」方式指向talkincode.github.io。仓库默认项目地址 https://talkincode.github.io/toughradius/ 仍然可用,并会重定向到该自定义域名。 - GitBook —— 服务
docs.toughradius.net,解析到 GitBook 自有托管 (Cloudflare / Fastly),而非 GitHub Pages。由于www.toughradius.net迁移到 GitHub Pages,GitBook 应当配置为仅保留docs.toughradius.net并释放www,使两个 服务不会争用同一主机名。
单一事实来源
为避免两套管线之间出现内容漂移,每份文档只有唯一的权威位置。由于 mdBook 与 GitBook
现在读取同一份 docs-site/src/ 章节与同一个 SUMMARY.md,手册源文件即是两个渲染
站点的单一事实来源。随着散落文档逐步迁入手册(路线图条目 M13.2 / M13.3),原始文件会
保留一个指向对应章节的简短入口,而不是复制其内容。
编辑共享目录
docs-site/src/SUMMARY.md 会被两个工具同时读取,因此只能使用两者解析方式一致的
Markdown 子集:顶部的 # Summary 标题、一个非列表项的首页链接
[Introduction / 引言](./introduction.md),以及用于分组的嵌套列表。两个语言分区
表示为顶层条目(- [English](./en/overview.md) 与 - [中文](./zh/overview.md)),其余
页面嵌套其下。请避免使用 # / ## 标题来分组:mdBook 只按 # 分组,而 GitBook 只按
## 分组,因此嵌套列表是两者渲染方式完全一致的唯一形式。
导航栏语言切换
mdBook 输出会在顶部菜单栏注入一个 EN / 中文 切换链接
(docs-site/assets/lang-toggle.{js,css},通过 book.toml 的
additional-js / additional-css 接入)。它只改写路径中最后一段 /en/ 或 /zh/,之所以可行,是因为两个语言目录
保持完全相同的文件名。该切换仅存在于 mdBook/Pages 管线;GitBook 不读取
book.toml 的附加资源,GitBook 读者请使用侧边栏分区与每章的交叉链接切换语言。
构建与校验
- 本地:
mdbook build docs-site会在docs-site/book/生成静态站点;mdbook serve docs-site可开启热重载预览。 - CI:一个独立的任务负责构建手册,并对生成的 HTML 执行离线坏链检查,因此构建
失败或内部坏链都会让流水线变红(路线图条目 M13.4)。构建产物(
docs-site/book/) 属于构建工件,不纳入版本控制。