Thanks for taking a look. Issues, bug reports and pull requests are all welcome.
You need the .NET 10 SDK. Nothing else.
git clone https://github.com/XtremeOwnage/SimpleRadius.git
cd SimpleRadius
dotnet test # 100+ tests, about a second
dotnet run --project SimpleRadiusThe admin UI comes up on http://localhost:5285 and the RADIUS listeners on UDP 1812/1813.
In VS Code, press F5. Two launch profiles are defined in
SimpleRadius/Properties/launchSettings.json:
| Profile | Web UI | RADIUS | Database |
|---|---|---|---|
http |
5285 | 1812 / 1813 | data/simple-radius.db |
alternate-ports |
5286 | 11812 / 11813 (loopback) | data/simple-radius-debug.db |
They share no port, so both can run at once. Use alternate-ports when a real RADIUS server already holds
1812/1813 — binding a taken port fails outright and stops the host.
| Path | What lives there |
|---|---|
SimpleRadius/Radius/ |
Packet parsing, authenticators, attribute encoding, MAC normalisation |
SimpleRadius/Services/ |
Policy, accounting, the request handler, the UDP host, auth and logging setup |
SimpleRadius/Models/ |
Entities plus the startup and runtime settings classes |
SimpleRadius/Pages/ |
Razor Pages admin UI |
SimpleRadius.Tests/ |
Everything below |
deploy/ |
Kubernetes manifests and the systemd unit |
Two pieces are worth knowing before you change anything:
RadiusRequestHandlerholds the entire request path and touches no sockets, so the whole protocol flow can be driven from a test.RadiusServerProcessowns only the two UDP sockets.
Keep that split. It is why the tests can cover the interesting behaviour without any network setup.
dotnet testCovers packet encoding and authenticator digests against the RFC definitions, malformed input, the default-VLAN fallback, disabled clients and routers, wrong shared secrets, the accounting lifecycle, the attribute set each compatibility toggle produces, MAC formatting, sorting, session search and aggregation, the admin page queries, authentication wiring, a test that boots the whole app and requests every page, and two that drive the real listener over loopback UDP.
Please add a test with a behaviour change. A few things worth knowing:
- Tests run against real SQLite, not an in-memory provider, because several bugs found in this project were LINQ-to-SQLite translation failures that an in-memory provider happily accepts.
StartupTestsboots the application in the Development environment on purpose: DI scope validation only runs there, and it has already caught a container misconfiguration that worked fine in Production.TestDatabasegives you an isolated database with the real schema, including unique indexes.
Match the surrounding code. A few conventions that are consistent throughout:
- Comments explain why, not what. If a line looks odd, say what forced it — a protocol requirement, a provider limitation, a hardware quirk.
- Cite the RFC and section when implementing protocol behaviour.
- Prefer clear names over short ones.
- Nullable reference types are on. Do not silence warnings with
!unless you can justify it.
- Branch from
main. - Make sure
dotnet testpasses. - Describe what changed and why. Mention hardware you tested against — that is genuinely useful here.
- One logical change per PR where you can manage it.
CI runs build, tests, a vulnerable-package scan, a publish-size guard, and a container build with a smoke test. It has to be green.
CI fails if the publish output exceeds 40 MB. This is not arbitrary: the SQLite bundle ships native
binaries for every platform it supports — riscv64, mips64, s390x, iOS simulator — which is about 84 MB of
a 103 MB publish. A RID-specific publish keeps only the one that matters and lands around 10 MB. If that
check fires, something dropped the RuntimeIdentifier.
Semantic versioning, with the version in exactly one place:
Directory.Build.props.
| Change | Bump |
|---|---|
| Bug fix, no behaviour change | patch — 1.2.3 → 1.2.4 |
| New setting or feature, existing configs keep working | minor — 1.2.3 → 1.3.0 |
| A schema change, a renamed setting, a changed default | major — 1.2.3 → 2.0.0 |
Until 1.0.0, minor versions may still break things; the changelog will say so.
# Update CHANGELOG.md first, then:
git tag -a v1.2.3 -m "v1.2.3"
git push origin v1.2.3The tag drives everything. release.yml derives the version from it and
stamps it into the assemblies, the container labels and tags, the archive names and the deb/rpm metadata,
so a release cannot ship mismatched numbers.
Artifacts produced per release: multi-arch container image on GHCR, self-contained binaries for linux x64/arm64/arm, win x64/arm64 and osx-arm64, plus deb and rpm packages — each with a SHA-256 checksum.
One git tag (v1.2.3) produces this set of container tags on GHCR:
| Tag | Points at | Moves? |
|---|---|---|
1.2.3 |
exactly this release | no — immutable |
1.2 |
the newest patch of the 1.2 line | yes |
1 |
the newest release of the 1.x line | yes |
latest |
the newest stable release | yes |
docker pull ghcr.io/xtremeownage/simpleradius:1 follows the 1.x line and picks up patches;
pin :1.2.3 to freeze an exact build.
Pre-releases (v1.2.3-rc.1, any tag with a hyphen) are the exception: they publish only the exact
1.2.3-rc.1 container tag and are marked pre-release on GitHub. They never move 1.2, 1 or latest.
latest tracks the newest stable version, not the most recently pushed. Releasing a back-patch to an
older line (say v1.1.5 after v1.2.0 already exists) publishes 1.1.5 and moves 1.1, but leaves
latest, 1 and the GitHub "Latest" badge on the higher 1.2.0. The workflow decides this by comparing
the version against every existing tag, so releases do not have to be cut in strict order.
- Hardware reports. Which switches and access points work, and which need which reply-attribute settings. Compatibility is the hardest thing to test alone.
- EF Core migrations. The schema is currently created with
EnsureCreated, so a schema change means a fresh database. This is the biggest known gap. - CoA / Disconnect-Message support (RFC 5176), to move a device's VLAN without making it reconnect.
- Test coverage for anything you find under-covered.
Be decent to each other. Harassment or personal attacks are not welcome, and maintainers will remove comments, commits and issues that cross that line.