Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

ServiceProtocol / PortPurpose
Web / Admin APIHTTP, TCP 1816Management UI and REST API
RADIUS AuthUDP 1812Authentication
RADIUS AcctUDP 1813Accounting
RadSecTLS over TCP 2083Encrypted 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 radtest in 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

TermMeaning 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 secretA 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 userAn 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 codeThe 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 sessionThe 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-IntervalHow often (seconds) the NAS should send interim accounting updates. Returned in every Access-Accept from the radius.AcctInterimInterval setting.
Session-TimeoutMaximum 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 bindingWhen a profile enables bind_mac, the first calling MAC seen is stored on the user and later requests must match it.
VLAN bindingWhen 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

ProtocolWhere the password travelsNotes
PAPIn the request, XOR-protected by the shared secret (RFC 2865 §5.2)Universally supported; pair with RadSec or a trusted network.
CHAPNever — MD5 challenge/response (RFC 2865 §5.3)Requires the server to know the cleartext password.
MS-CHAPv2Never — 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.

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

  1. Network Nodes → Create — make a node (a logical group), e.g. default.
  2. 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 3799 unless your device uses another port.
  3. Billing Profiles → Create — e.g. 100M: concurrency 1, up rate 51200 Kbps, down rate 102400 Kbps.
  4. RADIUS Users → Create — username test1, password 111111, 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 needHow
Full RADIUS packet dumpsradiusd.debug: true in the YAML (or env TOUGHRADIUS_RADIUS_DEBUG=true), or set System Config → RADIUS → Log Level to debug at runtime
Log file locationlogger.filename, default {workdir}/toughradius.log; logger.mode: development for human-readable console output
Why a user is rejectedReject reasons are counted per cause (wrong password, expired, bound MAC mismatch, …) and shown on the dashboard; the log carries the detail
Inspect effective configtoughradius -printcfg -c <file>
Load testinggo run ./cmd/benchmark — see the Operations Guide

Next steps

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 no Mikrotik-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

  1. Register the NAS under NAS Devices → Create: source IP address (or identifier), shared secret, and the correct Vendor.
  2. Point the device at the server: authentication UDP 1812, accounting UDP 1813, the same shared secret.
  3. Optional CoA: ToughRADIUS sends CoA/Disconnect (RFC 5176) to the NAS on UDP 3799 by 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.
  4. 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:

VendorAttributesValue sent
Huawei (2011)Huawei-Input/Output-Average-Rate, Huawei-Input/Output-Peak-Rateaverage = rate_kbps × 1024 (bit/s); peak = × 4 further; clamped to Int32 max
MikroTik (14988)Mikrotik-Rate-Limitstring "{up}k/{down}k", e.g. 51200k/102400k (rx/tx from the router’s view)
H3C (25506)H3C-Input/Output-Average-Rate, peak variantssame ×1024 / ×4 scheme as Huawei
ZTE (3902)ZTE-Rate-Ctrl-SCR-Up/Downrate_kbps × 1024
iKuai (10055)RP-Upstream/Downstream-Speed-Limitrate_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.727
  • vlanid=<n>;vlanid2=<n>; — e.g. slot=2;...;vlanid=503;vlanid2=100;
VendorMAC sourceVLAN sourceNotes
Huawei (2011)Calling-Station-IdNAS-Port-IdSupports both encodings above.
H3C (25506)Calling-Station-IdNAS-Port-IdSupports both encodings above.
ZTE (3902)Calling-Station-IdNAS-Port-IdSupports both encodings above.
Radback (2352)Mac-Addr VSA, falling back to Calling-Station-IdBind-Dot1q-Vlan-Tag-Id VSA, falling back to NAS-Port-IdRequest parser only; no Radback response enhancer is shipped.
Alcatel (3041)AAT-User-MAC-Address VSA, falling back to Calling-Station-IdNAS-Port-Id when presentRequest parser only; response attributes remain deployment-specific.
Aruba/HPE (14823)Calling-Station-IdAruba-User-Vlan VSA, falling back to NAS-Port-IdAlso has an Access-Accept enhancer; see below.
Juniper (2636)Calling-Station-IdJuniper-VoIP-Vlan VSA, falling back to NAS-Port-IdRequest parser only; response attributes remain deployment-specific.
MikroTik, iKuai, Cisco, StandardCalling-Station-IdNo 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=yes enables 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:

AttributeSourceBoundary / no-op rule
Aruba-User-Vlanuser or profile Vlanid1Sent 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-Roleuser or profile DomainSent 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

SymptomLikely cause
Device gets Access-Accept but no bandwidth limitNAS record vendor is Standard, or the device ignores the VSA — check the vendor code first
All requests silently droppedSource IP not registered as a NAS, or shared secret mismatch
VLAN binding never matchesNAS vendor is not one of the VLAN-aware parsers above, or the request uses an unexpected VSA / NAS-Port-Id format
CoA/Disconnect times outDevice CoA listener disabled, or non-default port — set CoA port on the NAS record

More in the FAQ.

Portal / Hotspot Integration Boundary

中文版本:Portal / Hotspot 对接边界

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:

  1. 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.
  2. Document a specific NAS / controller configuration that uses ToughRADIUS as the RADIUS backend.
  3. 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:

  1. Need / scenario — the problem in business language, no protocol detail.
  2. 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.
  3. On the device side — reference configuration for the NAS/router.
  4. Verification — how to confirm it really works (radtest, device commands, admin UI).
  5. 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 in internal/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 1700 you 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.

Cookbook: MikroTik RouterOS

中文版本:实战手册: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 by mikrotik_enhancer.go; rx/tx is 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 by default_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 but Mikrotik-Rate-Limit is 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

  1. Create one rate profile per tier (Rate profiles → New):
    • Up / down rate: the unit is Kbps. 30M down means 30720, not 30; 10M up means 10240.
    • 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 pool name on the router.
  2. 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).
  3. Accounting interval: the config item radius.AcctInterimInterval (default 120 seconds) sets the emitted Acct-Interim-Interval.

After a successful auth, the Access-Accept actually carries (anchored to code):

AttributeValueSource
Mikrotik-Rate-Limit"{up_kbps}k/{down_kbps}k", e.g. 10240k/30720kmikrotik_enhancer.go
Session-Timeoutseconds remaining until expiry (drops the current session at the deadline)default_enhancer.go
Acct-Interim-Intervalradius.AcctInterimInterval (default 120)default_enhancer.go
Framed-Poolthe pool name from the profile / user (emitted only if set)default_enhancer.go
Framed-IP-Addressthe 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_checker at auth time, comparing active_num to 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-Limit the router creates a dynamic simple queue automatically.

Verification

  • radtest (server side):
    go run ./cmd/radtest auth -server <TOUGHRADIUS_IP> -nas-ip <NAS_IP> \
      -username <username> -password <password> -secret <SECRET>
    
    On success it prints Access-Accept; you should see Mikrotik-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 (30 instead of 30720); ③ direction reversed (rx/tx is the router’s view, the first field is the subscriber’s upload).
  • Dials up but gets no / wrong IP → the Framed-Pool name does not match the /ip pool name, or no pool is set on the profile; align the names or set a static IP on the user.
  • Account still online after expirySession-Timeout only 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 by expire_checker (counted under user expire).
  • The second connection is rejected → with active_num=1, online_count_checker rejects the concurrent session — this is expected; raise the profile’s concurrency to allow multiple dials.
  • Server slows down / stops replying after repeated auth failuresreject_delay_guard introduces 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:

  1. 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.
  2. 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-Name the RouterOS hotspot sends (ToughRADIUS compares strings exactly). RouterOS’s format is influenced by settings such as mac-auth-modewhat 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 for Access-Accept.
  • Router side: /ip hotspot active print should list the device; /log print for 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 / or Filter-Id (#11).
    • Use Session-Timeout to shorten the session’s life and force the client to re-authenticate sooner.
    • Use Filter-Id to have RouterOS apply a pre-defined filter / address-list (e.g. a rate or site-restriction rule).
  • 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-Id approach, 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 print shows the incoming request; after a forced disconnect, the session disappears from /ppp active print and 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’s incoming 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 1700 you often see online is a client-side local port; this system and RFC 5176 use 3799.

Cookbook: Huawei BRAS / NetEngine

中文版本:实战手册:华为 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 by default_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

  1. Create one rate profile per tier (Rate profiles → New):
    • Up / down rate: the unit is Kbps. 30M down means 30720, not 30; 10M up means 10240.
    • Domain (optional): the Huawei AAA domain name (e.g. isp), pushed as Huawei-Domain-Name so the BRAS binds the session to that domain.
  2. 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):

AttributeValueNote
Huawei-Input-Average-Rateup_kbps × 1024 (bit/s)“Input” = traffic into the BRAS = subscriber upload
Huawei-Input-Peak-Rateup_kbps × 1024 × 4average × 4
Huawei-Output-Average-Ratedown_kbps × 1024 (bit/s)“Output” = traffic out to the subscriber = download
Huawei-Output-Peak-Ratedown_kbps × 1024 × 4average × 4
Huawei-Domain-Namethe user / profile domainemitted only if a domain is set
Session-Timeout, Acct-Interim-Interval, Framed-Pool, Framed-IP-Addressstandardfrom 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 = 31457280 and Output-Peak-Rate = 125829120. ② Direction naming: Huawei “Input” is the subscriber’s upload and “Output” is the download (BRAS perspective) — the opposite words from MikroTik’s rx/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):
    go run ./cmd/radtest auth -server <TOUGHRADIUS_IP> -nas-ip <NAS_IP> \
      -username <username> -password <password> -secret <SECRET>
    
    On success it prints 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-record for 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-Name is not emitted), or the domain name does not match a domain configured 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):

  1. 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).
  2. 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).
  3. Static IPv6 — set the user’s IPv6 address; the enhancer then emits Huawei-Framed-IPv6-Address (the prefix length, if written as addr/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 the Calling-Station-Id and NAS-Port-Id values, 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 in NAS-Port-Id in your topology), or bind VLAN is off, or the user’s stored VLAN is 0; 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 / or Filter-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-Id approach, 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 1700 you often see online is a client-side local port; this system and RFC 5176 use 3799.

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…FollowWhat changes per vendor (this chapter)
Speed tiers + address pool + expiry + concurrencyMikroTik Scenario A or Huawei Scenario Athe rate attribute(s) emitted + unit
Line anti-fraud (MAC / VLAN binding)Huawei Scenario BMAC parse format + whether VLAN binding is available
Live control (CoA / disconnect / FUP)MikroTik Scenario Cnothing — 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:

AttributeValue
H3C-Input-Average-Rateup_kbps × 1024 (bit/s) — subscriber upload
H3C-Input-Peak-Rateup_kbps × 1024 × 4
H3C-Output-Average-Ratedown_kbps × 1024 (bit/s) — download
H3C-Output-Peak-Ratedown_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:

AttributeValue
ZTE-Rate-Ctrl-SCR-Upup_kbps × 1024 (bit/s)
ZTE-Rate-Ctrl-SCR-Downdown_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:

AttributeValue
RP-Upstream-Speed-Limitup_kbps × 8192 (= × 1024 × 8)
RP-Downstream-Speed-Limitdown_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 × 8192 and the value is clamped to the Int32 max (2147483647), any tier above roughly 256 Mbps (262144 Kbps) 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-author enables 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

VendorRate attribute(s)Unit multiplierPeakMAC bindVLAN 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 × 8192 value 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).

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

LevelMeaning
superFull access, including System Config and Operators
adminSame menu visibility as super
operatorDay-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.

FieldNotes
Name / IdentifierFree-form name; identifier matches the RADIUS NAS-Identifier
IP address / HostnameThe source address of RADIUS packets
VendorDecides vendor attribute behavior — Standard, Cisco, Huawei, MikroTik, H3C, ZTE, iKuai (see the Vendor Integration Guide)
SecretRADIUS shared secret
CoA portWhere CoA/Disconnect are sent; default 3799
Node / Status / Tags / RemarkOrganization 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:

FieldMeaning
active_numMax concurrent sessions per user (0 = unlimited)
up_rate / down_rateBandwidth in Kbps; converted per vendor (list shows Mbps for values ≥ 1024)
addr_poolFramed-Pool name the NAS allocates from
ipv6_prefix / domainIPv6 and Huawei domain authorization
bind_mac / bind_vlanLock 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-Request with a new session timeout and/or Filter-Id.
  • 强制下线 / Disconnect — send a Disconnect-Request to 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:

KeyDefaultPurpose
EapMethodeap-md5Active EAP method: eap-md5, eap-mschapv2, eap-tls, eap-peap, eap-ttls
EapEnabledHandlers*Comma allow-list of permitted EAP handlers
EapTlsCertFile / EapTlsKeyFileemptyServer certificate/key for EAP-TLS/PEAP/TTLS; empty disables TLS-based EAP
EapTlsCaFileemptyCA bundle for client-certificate validation
EapTlsMinVersion1.2Minimum TLS version (1.2/1.3)
IgnorePasswordfalseSkip password verification (testing only)
AccountingHistoryDays90Accounting retention days (@daily cleanup; 0 disables)
AcctInterimInterval300Seconds between NAS interim updates
SessionTimeout3600Default session timeout seconds
LogLevelinfoRADIUS log verbosity (debug/info/warn/error)
RejectDelayMaxRejects7Consecutive rejects before delaying responses
RejectDelayWindowSeconds10Window 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

PortProtocolServiceConfig key
1816TCP HTTPAdmin UI + REST APIweb.port
1817TCP HTTPSAdmin UI over TLS (optional; start failure is non-fatal)web.tls_port
1812UDPRADIUS authenticationradiusd.auth_port
1813UDPRADIUS accountingradiusd.acct_port
2083TCP TLSRadSec (RFC 6614)radiusd.radsec_port
3799UDP (outbound)CoA/Disconnect to the NASper-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:

VariableOverrides
TOUGHRADIUS_SYSTEM_WORKER_DIRsystem.workdir
TOUGHRADIUS_SYSTEM_DEBUGsystem.debug
TOUGHRADIUS_WEB_HOST / _WEB_PORT / _WEB_TLS_PORT / _WEB_SECRETweb.*
TOUGHRADIUS_DB_TYPE / _DB_HOST / _DB_PORT / _DB_NAME / _DB_USER / _DB_PWD / _DB_DEBUGdatabase.*
TOUGHRADIUS_RADIUS_ENABLED / _RADIUS_HOST / _RADIUS_AUTHPORT / _RADIUS_ACCTPORT / _RADIUS_DEBUGradiusd.*
TOUGHRADIUS_RADIUS_RADSEC_PORT / _RADIUS_RADSEC_WORKER / _RADIUS_RADSEC_CA_CERT / _RADIUS_RADSEC_CERT / _RADIUS_RADSEC_KEYRadSec settings
TOUGHRADIUS_LOGGER_MODE / _LOGGER_FILE_ENABLElogger.*
TOUGHRADIUS_RADIUS_POOLRADIUS worker pool size (default 1024)

CLI flags

FlagEffect
-c <file>Configuration file path
-initdbDrop and recreate all tables, then exit
-printcfgPrint merged configuration as JSON, exit
-vPrint version / build time / commit, exit
-hUsage

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: postgres plus 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:

ConsumerFilesNotes
RadSecradiusd.radsec_ca_cert / radsec_cert / radsec_keyTLS 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, EapTlsMinVersionEmpty 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>:

ToolPurpose
radtestMini RADIUS client: auth, acct, flow (auth + start + stop). Flags: -server, -secret, -username, -password, -calling-station, -framed-ip, -session-id
certgenGenerate CA / server / client certificates (see above)
benchmarkLoad tester: total requests -n, concurrency -c, auth/acct modes, CSV stats output
reset-passwordReset a console operator password: go run ./cmd/reset-password -c <cfg> -u admin -p <new>
demo-seedPopulate demo nodes/NAS/profiles/users/sessions for evaluation
config-toolValidate / summarize the settings schema JSON

Production hardening checklist

  • Change web.secret and the default admin password.
  • 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-ttls with 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 RadiusUser row, 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.

KeyTypeDefaultApplies toDescription
ldap.EnabledboolfalsebothTurn the LDAP backend on. Off by default.
ldap.ServerURLstring(empty)bothDirectory URL, e.g. ldap://dc.example.com:389 or ldaps://dc.example.com:636.
ldap.BindModeenumtemplatebothtemplate or search (see below).
ldap.BindDNTemplatestring(empty)templateDN 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.BaseDNstring(empty)searchSubtree base for the user lookup, e.g. dc=example,dc=com.
ldap.UserFilterstring(uid=%s)searchFilter with a single %s for the username (escaped before substitution), e.g. (uid=%s) or AD (sAMAccountName=%s).
ldap.SearchBindDNstring(empty)searchDN of the read-only service account used to find users, e.g. cn=svc-radius,ou=svc,dc=example,dc=com.
ldap.SearchBindPasswordstring(empty)searchPassword for the service-account DN.
ldap.StartTLSboolfalsebothUpgrade an ldap:// connection to TLS with StartTLS (RFC 4513 §3) before binding. Leave off for ldaps://.
ldap.TLSSkipVerifyboolfalsebothSkip TLS certificate verification. Insecure — lab / self-signed only.
ldap.Timeoutint (s)5bothDial 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 an ldap:// URL together with StartTLS = true to upgrade the plaintext connection (RFC 4513 §3). Do not enable StartTLS on an ldaps:// URL.
  • Leave TLSSkipVerify = false in production. Enable it only against a lab or self-signed directory — it disables certificate verification and exposes the bind to interception.

How authentication flows

MethodBehaviour when ldap.Enabled = true
Bare PAPAuthenticated by LDAP bind.
EAP-TTLS / inner PAPAuthenticated by LDAP bind; MS-MPPE keys are still derived for the tunnel.
CHAP / MS-CHAP / MS-CHAPv2Rejected with a diagnostic reason (no cleartext password to bind with).
EAP-MD5 / PEAP-MSCHAPv2Rejected for the same reason.
MAC authenticationBypasses 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 UserFilter matches 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_accept increments 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 as radus_reject_ldap_error, never as a false radus_accept, keeping the success and failure signals separate.

Troubleshooting

SymptomLikely cause
Every login fails, radus_reject_ldap_error climbingServerURL wrong/unreachable, TLS handshake failing, or (search mode) the service account cannot bind.
Windows/AD clients rejected, PAP users fineThe 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 userCheck BaseDN and UserFilter; confirm the service account can read user entries.
Bind works in the lab but fails in productionTLSSkipVerify was masking an invalid certificate — install a trusted certificate and turn it off.
Passwords accepted but the session has no plan/limitsThe local RadiusUser row is missing — LDAP only checks the password; create the local account for authorization.

See also

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:

  1. 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).
  2. 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

RFCStandardMilestone
RFC 9190 (+ RFC 9427)EAP-TLS 1.3 and TLS 1.3 key derivationM10
RFC 7170 / RFC 9930TEAP v1 — tunnel EAP, machine + user chainingM11
RFC 5931EAP-PWD — password-based, no per-client certificateM12

Catalog accuracy note. In docs/rfcs/, the file rfc7542-eap-pwd.txt is 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

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

ScenarioMethodExpectedStatusDurationDetail
EAP-TLS valid client certificateEAP-TLSAccess-Acceptpassed146 msexternal supplicant received the expected Access-Accept
EAP-TLS untrusted client certificateEAP-TLSAccess-Rejectpassed19 msexternal supplicant was rejected as expected
PEAP/MSCHAPv2 valid credentialsPEAP/MSCHAPv2Access-Acceptskipped0 msSkipped 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 passwordPEAP/MSCHAPv2Access-Rejectskipped0 msSkipped 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 credentialsEAP-TTLS/PAPAccess-Acceptpassed122 msexternal supplicant received the expected Access-Accept
EAP-TTLS/MSCHAPv2 valid credentialsEAP-TTLS/MSCHAPv2Access-Acceptpassed122 msexternal supplicant received the expected Access-Accept
Malformed external EAP client configtoolingdocumented skipskipped0 msSkipped 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

MetricValue
Benchmarks15
Packages6
Slowestgithub.com/talkincode/toughradius/v9/pkg/excel / BenchmarkWriteToFile
Highest B/opgithub.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.

ItemDetails
Vulnerability typeCross-Site Scripting (XSS)
SeverityCritical
Affected versionsv8.0.1 – v8.0.7
Fixed versionv8.0.8
Affected componentLogin endpoint (errmsg parameter)

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 at docs/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-N001TR-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 to TR-F IDs. 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:

RoleSkillPurpose
Coordinatororchestrate-roadmapEntry role for “auto-delegate development”: selects the next unchecked subtask, picks the matching SOP, enforces gates, opens a PR
Gatereview-prIndependent, CI-anchored review; requests changes via labels/comments and auto-merges only when approved and CI is green
Self-iterationgroom-roadmapAfter 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-apis skill 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 as feat, 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.

WorkflowTrigger and resultMaintainer 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 under docs/rfcs/.
  • Output goes through a PR labeled agent-roadmap, gated by review-pr, and is merged only when agent-approved with green CI.

Technical constraints

  • No CGO — the project builds with CGO_ENABLED=0 for easy cross-platform deployment. Use pure Go drivers only (for example github.com/glebarez/sqlite instead of github.com/mattn/go-sqlite3).
  • Database dual-compatibility — every schema change must work on both PostgreSQL (default) and SQLite.
  • Upstream dependency — the core layeh.com/radius library is replaced in go.mod to the organization fork github.com/talkincode/radius; important upstream fixes are evaluated via the sync-upstream-radius skill.

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 main or skipping review.
  • Creating redundant standalone summary/report documents.
  • Introducing CGO dependencies.

Where to go next

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

ChapterContents
OverviewProject introduction, core capabilities, service model
Concepts & TerminologyAAA vocabulary, the authentication flow, password protocols
Quick StartInstall, initialize, first user, debugging
Vendor Integration GuideCase studies: MikroTik, Huawei, Cisco, H3C, ZTE, iKuai, …
Portal / Hotspot Integration BoundaryHard boundary: ToughRADIUS is the RADIUS AAA backend, not a hosted captive portal product
Scenario CookbookEnd-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 ManualThe management console, page by page
Operations GuideConfiguration reference, certificates, monitoring, backup, CLI tools
LDAP / AD AuthenticationVerify passwords against an LDAP/AD directory (PAP-family only): bind modes, TLS, security, metrics
FAQFrequently asked questions by theme
Protocol & RFC ReferenceProtocol standards mapped to the code
Security PolicySecurity advisories and update guidance
Agent Development GuideContributor digest of the AI-agent workflow, quality gates, and auto-delegation loop

Repository documents

DocumentDescriptionCurrent location
READMEProject introduction, features, and quick startREADME.md
Agent guideAI-agent development guide and working rulesAgent Development Guide (handbook digest) · AGENT.md (canonical)
Security policySecurity advisories and update guidanceSecurity Policy (canonical) · SECURITY.md (pointer)
Feature checklistFeature scope baseline (TR-F IDs)docs/feature-checklist.md · English
RoadmapLong-term roadmap and milestonesdocs/roadmap.md
RFC indexProtocol standards index used by the projectProtocol & 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 in docs/ 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.yaml at the repository root, which points GitBook at the same docs-site/src/ sources, uses introduction.md as the landing page, and reads the shared SUMMARY.md as 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 a CNAME file into the published artifact, so GitHub Pages keeps that custom domain on every release. For the domain to resolve, www.toughradius.net must point at GitHub Pages — an A record set to 185.199.108.153185.199.111.153, or a DNS-only CNAME to talkincode.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. Because www.toughradius.net moves to GitHub Pages, GitBook should be configured to keep only docs.toughradius.net and release www, 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-site produces the static site in docs-site/book/, and mdbook serve docs-site previews 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 APIHTTP,TCP 1816管理界面与 REST API
RADIUS 认证UDP 1812认证
RADIUS 计费UDP 1813计费
RadSecTLS 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-TimeoutFilter-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-IntervalNAS 发送中间计费更新的间隔(秒)。每个 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_errorradus_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 部署的推荐选择。

相关章节

快速开始

English version: Quick Start

本章带你从零搭建一个可用的 RADIUS 服务器并创建一个测试用户,然后介绍如何调试 服务器行为。默认端口:管理界面 1816、RADIUS 认证 UDP 1812、计费 UDP 1813、RadSec TCP 2083

1. 安装

三种方式任选其一。

方式 A —— 预编译二进制

GitHub Releases 页面下载对应平台的二进制(toughradius_linux_amd64toughradius_linux_arm64toughradius_darwin_arm64toughradius_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/tcp1812/udp1813/udp2083/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 并创建用户

  1. 网络节点 → 新建 —— 创建一个节点(逻辑分组),如 default
  2. NAS 设备 → 新建 —— 登记你的网络设备:
    • IP 地址:设备发出 RADIUS 报文的源地址。
    • 密钥:共享密钥,如 testing123
    • 厂商代码:选择设备厂商(标准 / Cisco / 华为 / MikroTik / H3C / 中兴 / 爱快)——它决定下发哪些厂商私有属性,见厂商对接指南
    • CoA 端口:除非设备使用其他端口,保持 3799
  3. 计费策略 → 新建 —— 例如 100M:并发数 1、上行 51200 Kbps、 下行 102400 Kbps。
  4. 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.loglogger.mode: development 输出适合人读的控制台格式
用户为何被拒绝拒绝原因按类别计数(密码错误、已过期、MAC 绑定不符等)并显示在仪表盘;细节见日志
查看生效配置toughradius -printcfg -c <文件>
压力测试go run ./cmd/benchmark —— 见运维指南

下一步

厂商对接指南

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 对接边界

任意设备的通用对接步骤

  1. 登记 NAS:在 NAS 设备 → 新建 填写源 IP 地址(或 identifier)、 共享密钥,并选择正确的厂商
  2. 设备指向服务器:认证 UDP 1812、计费 UDP 1813、相同的共享密钥。
  3. 可选 CoA:ToughRADIUS 默认向 NAS 的 UDP 3799 发送 CoA/Disconnect (RFC 5176);若设备监听其他端口,请在 NAS 记录上设置 CoA 端口。 每次交互最多等待 5 秒并重传 2 次。
  4. 创建计费策略与用户,用 go run ./cmd/radtest auth … 验证 (见快速开始)。

所有厂商都会收到的标准属性

无论何种厂商,Access-Accept 中都可能携带:Session-Timeout(距账号过期的 秒数)、Acct-Interim-IntervalFramed-PoolFramed-IP-Address(静态 IPv4)、Framed-IPv6-Prefix / Framed-IPv6-Address(RFC 6911)、 Framed-IPv6-PoolDelegated-IPv6-Prefix(RFC 4818)与 Delegated-IPv6-Prefix-Pool —— 取决于用户/策略中设置了哪些字段。

限速单位

策略中的速率以 Kbps 存储,各厂商增强器按如下规则换算:

厂商属性下发值
华为(2011)Huawei-Input/Output-Average-RateHuawei-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.727
  • vlanid=<n>;vlanid2=<n>; —— 如 slot=2;...;vlanid=503;vlanid2=100;
厂商MAC 来源VLAN 来源说明
华为(2011)Calling-Station-IdNAS-Port-Id支持上面两种常见编码。
H3C(25506)Calling-Station-IdNAS-Port-Id支持上面两种常见编码。
中兴(3902)Calling-Station-IdNAS-Port-Id支持上面两种常见编码。
Radback(2352)Mac-Addr VSA,缺省回退到 Calling-Station-IdBind-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-IdAruba-User-Vlan VSA,缺省回退到 NAS-Port-Id同时具备 Access-Accept 增强器,见下文。
Juniper(2636)Calling-Station-IdJuniper-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 时下发;缺省、04095 或负值都会省略。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 边界内时才允许:

  1. 为 Captive Portal URL、用户角色、Filter-Id、VLAN、Session-Timeout、 速率等 RADIUS 属性补充或修复厂商字典、parser、enhancer。
  2. 编写特定 NAS / 控制器如何把 ToughRADIUS 作为 RADIUS 后端的配置文档。
  3. 为请求解析、响应属性、计费或 CoA 行为补测试。

任何“让 ToughRADIUS 自带 Portal”的需求,在实现前都必须拒绝或转移到其他产品。

场景实战手册

English version: Scenario Cookbook

厂商对接指南是一张属性参考卡——它告诉你某个厂商 ToughRADIUS 会下发/解析哪些属性。本手册更进一步:以真实运维场景为单位, 端到端地把「业务诉求」翻译成「服务端配置 + 设备侧配置 + 验证 + 排障」。

每个场景的五段式

为了便于照着做、也便于排错,本手册的每个场景都用同一结构:

  1. 需求 / 场景 —— 用业务语言描述要解决的问题,不谈协议细节。
  2. ToughRADIUS 侧 —— 在管理后台具体配置什么;以及认证通过后实际下发 哪些属性、由哪段代码产生。
  3. 设备侧 —— NAS/路由器侧的参考配置。
  4. 验证 —— 如何确认它真的生效(radtest、设备命令、管理后台)。
  5. 排障 —— 以「症状 → 定位 → 解决」列出该场景最常见的坑。

阅读约定

  • 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-TimeoutAcct-Interim-IntervalFramed-PoolFramed-IP-Address 等(由 default_enhancer.go 产生)。

前置条件:已在 NAS 设备 中把这台路由器登记为 厂商 = MikroTik、填写 正确的源 IP 与共享密钥;ToughRADIUS 可被设备访问(认证 1812、计费 1813)。 若登记成 Standard,认证仍可成功,但不会下发 Mikrotik-Rate-Limit


场景 A:PPPoE 宽带 ISP —— 分级套餐 + 地址池 + 到期断网 + 并发限制

需求 / 场景

一个小区 / 小型 ISP 用 PPPoE 拨号上网,需要:多档套餐(如家庭版下行 30M、 企业版下行 100M),账号到期自动断网且无法再拨,每账号限定并发会话数(防一号 多拨),IP 由统一地址池分配。

ToughRADIUS 侧

  1. 为每档套餐建一个计费策略计费策略 → 新建):
    • 上行 / 下行速率:单位为 Kbps。下行 30M 应填 30720不是 30; 上行 10M 填 10240
    • 并发数 active_num:如 1 表示单账号最多 1 个在线会话(0 = 不限)。
    • 地址池:填地址池名(如 pppoe-pool),需与路由器上的 /ip pool 同名。
  2. 创建用户用户 → 新建):用户名 / 密码、选择对应计费策略(速率、 并发、地址池由策略继承)、设置过期时间、状态 = 启用。如需固定 IP,可在用户 上设置静态 IPv4(将覆盖地址池)。
  3. 计费上报间隔:系统配置项 radius.AcctInterimInterval(默认 120 秒)决定 下发的 Acct-Interim-Interval

认证通过后,Access-Accept 实际携带(锚定代码):

属性取值来源
Mikrotik-Rate-Limit"{上行kbps}k/{下行kbps}k",如 10240k/30720kmikrotik_enhancer.go
Session-Timeout距过期时间的剩余秒数(到点断开当前会话)default_enhancer.go
Acct-Interim-Intervalradius.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-LimitSession-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=1online_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 地址 字段,而非普通密码。

因此配置方式为:

  1. 创建用户用户名 = 设备 MAC(字符串需与路由器实际发送的格式完全一致), 并在用户记录的 MAC 地址 字段填入同一 MAC。
  2. 给该用户分配计费策略(速率、并发照常生效)。

最大的坑是 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

相关章节

实战手册:华为 BRAS / NetEngine

English version: Cookbook: Huawei BRAS / NetEngine

本章是场景实战手册的一部分,沿用其五段式与阅读约定

华为(厂商代码 2011)是国内运营商与企业网络中占主导地位的宽带 BRAS / 企业网关 (NetEngine / ME60 / 较老的 MA5200 系列)。ToughRADIUS 为其注册了专门的厂商增强器, 认证通过后下发:

  • 一组四属性限速四元组(由 huawei_enhancer.go 产生): Huawei-Input-Average-RateHuawei-Input-Peak-RateHuawei-Output-Average-RateHuawei-Output-Peak-Rate
  • Huawei-Domain-Name —— 仅当用户 / 策略设置了**域名(domain)**时下发。
  • Huawei-Framed-IPv6-Address —— 仅当用户设置了静态 IPv6 时下发。
  • 以及所有设备通用的标准属性(Session-TimeoutAcct-Interim-IntervalFramed-PoolFramed-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 侧

  1. 为每档套餐建一个计费策略计费策略 → 新建):
    • 上行 / 下行速率:单位为 Kbps。下行 30M 应填 30720不是 30; 上行 10M 填 10240
    • 域名 domain(可选):华为 AAA 域名(如 isp),将作为 Huawei-Domain-Name 下发,让 BRAS 把会话绑定到该域。
  2. 创建用户用户 → 新建):用户名 / 密码、对应计费策略过期时间、 状态 = 启用。用户级的域名字段会覆盖策略上的域名。

存储的 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-TimeoutAcct-Interim-IntervalFramed-PoolFramed-IP-Address标准default_enhancer.go 产生

华为独有的两个坑。单位:限速 VSA 是 bit/s 而非 Kbps——ToughRADIUS 把存储的 Kbps 乘以 1024(二进制),且峰值是平均值的 × 4。所以「下行 30M」会被下发为 Output-Average-Rate = 30720 × 1024 = 31457280Output-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):

  1. MAC 绑定 —— 在用户 / 策略上开启 绑定 MAC,并在用户上存好允许的 MAC。 认证时若请求 MAC 与所存 MAC 不同,会话被拒(MacBindError)。
  2. VLAN 绑定 —— 在用户 / 策略上开启 绑定 VLAN,并存好允许的 VLAN ID 1 / VLAN ID 2。若所存 VLAN 与解析出的不一致,会话被拒(VlanBindError)。
  3. 静态 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-IdNAS-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

相关章节

实战手册:H3C / 中兴 / 爱快 / Cisco

English version: Cookbook: H3C, ZTE, iKuai & Cisco

本章是场景实战手册的一部分,沿用其阅读约定

这四家厂商跑的是与两本旗舰手册完全相同的运维场景——PPPoE / IPoE 分级套餐、 地址池、到期断网、单账号并发限制、MAC 绑定、CoA / 强制下线与 FUP。这些场景的机制 (一个套餐档、一次到期、一个并发上限、一次 CoA 如何生效)都一致,因为它们来自共享的 default_enhancer 与各 checker——而非来自厂商。

因此本章不重复四遍完整的五段式,而是各厂商的差异速查:对每台设备只说清 (1) ToughRADIUS 下发哪个限速属性、单位倍率,(2) 请求 MAC / VLAN 如何解析 (从而哪些绑定可用),以及 (3) 端到端步骤该照哪本旗舰手册做。

请配合一本旗舰手册阅读。 任何场景的完整分步,请照 MikroTikHuawei 手册中对应小节 操作;只有下面列出的下发属性不同。

该照哪本手册

你想要…照做各厂商差异(本章)
分级套餐 + 地址池 + 到期 + 并发MikroTik 场景 AHuawei 场景 A下发的限速属性与单位
线路防盗用(MAC / VLAN 绑定)Huawei 场景 BMAC 解析格式 + 是否支持 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-IdMAC 与 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 Mbps262144 Kbps)的档位都会溢出并被钳制 ——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-TimeoutAcct-Interim-IntervalFramed-PoolFramed-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 比对前会重排成冒号形式)。

相关章节

管理系统用户手册

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_poolNAS 用于分配地址的 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 配置:

默认值用途
EapMethodeap-md5当前 EAP 方法:eap-md5eap-mschapv2eap-tlseap-peapeap-ttls
EapEnabledHandlers*允许的 EAP 处理器逗号白名单
EapTlsCertFile / EapTlsKeyFileEAP-TLS/PEAP/TTLS 的服务器证书/私钥;留空即禁用基于 TLS 的 EAP
EapTlsCaFile校验客户端证书的 CA 链
EapTlsMinVersion1.2最低 TLS 版本(1.2/1.3
IgnorePasswordfalse跳过密码校验(仅测试用)
AccountingHistoryDays90计费历史保留天数(@daily 清理;0 关闭)
AcctInterimInterval300NAS 中间计费更新间隔(秒)
SessionTimeout3600默认会话超时(秒)
LogLevelinfoRADIUS 日志级别(debug/info/warn/error
RejectDelayMaxRejects7触发延迟响应的连续拒绝次数
RejectDelayWindowSeconds10拒绝计数窗口(秒)

工具栏:保存刷新重置(恢复默认值,需确认)以及 备份 / 恢复 —— 导出或回导一份包含节点、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

端口

端口协议服务配置键
1816TCP HTTP管理界面 + REST APIweb.port
1817TCP HTTPS管理界面 TLS(可选;启动失败不影响整体)web.tls_port
1812UDPRADIUS 认证radiusd.auth_port
1813UDPRADIUS 计费radiusd.acct_port
2083TCP TLSRadSec(RFC 6614)radiusd.radsec_port
3799UDP(出向)发往 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_DIRsystem.workdir
TOUGHRADIUS_SYSTEM_DEBUGsystem.debug
TOUGHRADIUS_WEB_HOST / _WEB_PORT / _WEB_TLS_PORT / _WEB_SECRETweb.*
TOUGHRADIUS_DB_TYPE / _DB_HOST / _DB_PORT / _DB_NAME / _DB_USER / _DB_PWD / _DB_DEBUGdatabase.*
TOUGHRADIUS_RADIUS_ENABLED / _RADIUS_HOST / _RADIUS_AUTHPORT / _RADIUS_ACCTPORT / _RADIUS_DEBUGradiusd.*
TOUGHRADIUS_RADIUS_RADSEC_PORT / _RADIUS_RADSEC_WORKER / _RADIUS_RADSEC_CA_CERT / _RADIUS_RADSEC_CERT / _RADIUS_RADSEC_KEYRadSec 配置
TOUGHRADIUS_LOGGER_MODE / _LOGGER_FILE_ENABLElogger.*
TOUGHRADIUS_RADIUS_POOLRADIUS 工作池大小(默认 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_onlineradius.AccountingHistoryDays 配置(默认 90,设为 0 关闭)定义计费历史的保留 窗口:@daily 定时任务会删除超过该天数的已结束 radius_accounting 记录(在线 会话不受影响),并清理连续多个计费中间更新周期未刷新的 radius_online 残留行。操作 日志(sys_opr_log)一年后自动清理。若数据量很大,仍建议把表增长监控与数据库级归档 纳入自己的运维流程。

TLS 与证书

三处相互独立的证书使用方:

使用方文件说明
RadSecradiusd.radsec_ca_cert / radsec_cert / radsec_keyTLS 1.2+;客户端证书提供则校验VerifyClientCertIfGiven
Web HTTPS{workdir}/private/toughradius.tls.crt + .key(固定路径)监听 web.tls_port;加载失败仅记日志,HTTP 继续运行
EAP(TLS/PEAP/TTLS)系统配置 → EapTlsCertFileEapTlsKeyFileEapTlsCaFileEapTlsMinVersion证书/私钥留空即禁用基于 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_acceptradus_online/radus_offlineradus_accountingradus_auth_drop / radus_acct_dropradus_radsec_saturated,以及按原因细分的拒绝计数—— radus_reject_passwd_errorradus_reject_not_existsradus_reject_expireradus_reject_disabledradus_reject_limitradus_reject_bind_errorradus_reject_unauthorizedradus_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 客户端:authacctflow(认证 + 开始 + 结束)。参数:-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: falselogger.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」来认证,因此只服务 裸 PAPEAP-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.Enabledboolfalse两者启用 LDAP 后端。默认关闭。
ldap.ServerURLstring(空)两者目录 URL,如 ldap://dc.example.com:389ldaps://dc.example.com:636
ldap.BindMode枚举template两者templatesearch(见下文)。
ldap.BindDNTemplatestring(空)template含单个 %s(代入用户名)的 DN 模板,如 uid=%s,ou=people,dc=example,dc=com,或 AD 的 UPN 形式 %[email protected]
ldap.BaseDNstring(空)search用户查找的子树基点,如 dc=example,dc=com
ldap.UserFilterstring(uid=%s)search含单个 %s(代入用户名,代入前已转义)的过滤器,如 (uid=%s) 或 AD 的 (sAMAccountName=%s)
ldap.SearchBindDNstring(空)search用于查找用户的只读服务账号 DN,如 cn=svc-radius,ou=svc,dc=example,dc=com
ldap.SearchBindPasswordstring(空)search服务账号 DN 的口令。
ldap.StartTLSboolfalse两者在 bind 前用 StartTLS 将 ldap:// 连接升级为 TLS(RFC 4513 §3)。用 ldaps:// 时请关闭。
ldap.TLSSkipVerifyboolfalse两者跳过 TLS 证书校验。不安全——仅限实验/自签名环境。
ldap.Timeoutint(秒)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,或改用本地口令后端。
搜索模式找不到用户检查 BaseDNUserFilter;确认服务账号能读取用户条目。
实验环境 bind 正常、生产失败TLSSkipVerify 此前掩盖了无效证书——安装受信任证书并关闭该开关。
口令通过了但会话没有套餐/限速缺少本地 RadiusUser 记录——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_portradiusd.acct_portweb.port 等)。见运维指南

管理界面的 HTTPS 端口(1817)不工作

Web TLS 监听需要 {workdir}/private/toughradius.tls.crt.key。文件缺失 或无效时只记录日志并停掉 HTTPS 监听——1816 上的 HTTP 继续服务。可用 cmd/certgen 生成证书或提供自己的证书。

-initdb 可以再跑一次吗?

不可以。 它会删除并重建所有表。仅在首次安装时执行。常规升级无需手动 处理表结构——迁移在启动时自动完成。

认证

所有请求都被忽略 / 超时

最常见的两个原因:

  1. 请求的源 IP 未登记为 NAS 设备——在 NAS 设备 中添加(或修正 NAT 使源地址符合预期)。
  2. 共享密钥不一致——RADIUS 对报文本身校验失败的包会静默丢弃。

打开 radiusd.debug: true 或把日志级别调到 debug 查看实际到达的报文。

用户认证成功但没有限速

只有 NAS 记录的厂商具备限速 VSA(华为、MikroTik、H3C、中兴、爱快)时才 会下发限速属性。登记为 StandardCisco 的 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-TimeoutFilter-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 3162RFC 4818RFC 6911 —— RADIUS 的 IPv6 地址与委派前缀属性 (里程碑 M3)。

路线图标准

RFC标准里程碑
RFC 9190(+ RFC 9427)EAP-TLS 1.3 与 TLS 1.3 密钥派生M10
RFC 7170 / RFC 9930TEAP v1 —— 隧道 EAP,machine + user chainingM11
RFC 5931EAP-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 certificateEAP-TLSAccess-Accept通过146 msexternal supplicant received the expected Access-Accept
EAP-TLS untrusted client certificateEAP-TLSAccess-Reject通过19 msexternal supplicant was rejected as expected
PEAP/MSCHAPv2 valid credentialsPEAP/MSCHAPv2Access-Accept跳过0 msSkipped 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 passwordPEAP/MSCHAPv2Access-Reject跳过0 msSkipped 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 credentialsEAP-TTLS/PAPAccess-Accept通过122 msexternal supplicant received the expected Access-Accept
EAP-TTLS/MSCHAPv2 valid credentialsEAP-TTLS/MSCHAPv2Access-Accept通过122 msexternal supplicant received the expected Access-Accept
Malformed external EAP client configtoolingdocumented skip跳过0 msSkipped 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/opgithub.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-N001TR-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>,类型如 featfixtestdocsrefactorperfchore
  • 小而原子的改动优于巨型 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 可保活。带 pinnedsecurityhelp wantedagent-roadmapneeds-human 的 issue 豁免;带 pinnedsecurityagent-roadmapneeds-human 的 PR 豁免;所有 milestone 豁免。
Labeler.github/workflows/labeler.ymlpull_request_target 上运行,并按 .github/labeler.yml 的路径规则打标签。自动标签包括 gojavascriptgithub_actionsdependenciesdoc。该 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/radiusgo.mod replace 指向组织 fork github.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.153185.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.tomladditional-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/) 属于构建工件,不纳入版本控制。