A feature-rich RADIUS library for Go. This is a significantly refactored version (v2) of the original library, optimized for clarity and ease of use.
This project forks from https://github.com/bronze1man/radius
- Simplified API: Clean and intuitive Go-native interfaces.
- Dictionary Support: Full support for FreeRADIUS-style dictionary files.
- Builtin Dictionary: Minimal standard attributes included out-of-the-box.
- Template System: Pre-resolve attributes and VSAs for packet construction.
- Lazy Decoding: Zero-allocation iterator-based decoding for high-performance use cases.
- Allocation Pooling: Use
sync.PoolforPacketstructs to reach absolute zero-allocation. - Enhanced Testing: Comprehensive test suite including "golden data" verification.
go get github.com/sergle/radius/v2This library supports FreeRADIUS-style dictionary files, including $INCLUDE, VENDOR, BEGIN-VENDOR, and vendor-specific attributes (VSAs).
Create a small top-level dictionary file that includes the base dictionary plus any vendor dictionaries you need.
Example dictionary file:
$INCLUDE /path/to/freeradius/dictionary
$INCLUDE /path/to/freeradius/dictionary.cisco
$INCLUDE /path/to/freeradius/dictionary.microsoft
Then load it in Go:
dict := radius.NewDictionary()
if err := dict.LoadFile("/path/to/your/dictionary"); err != nil {
log.Fatal(err)
}
// Optional: make this dictionary the default for package-level lookups.
radius.SetDefaultDictionary(dict)
// Example: pre-resolve a reply template with a Cisco VSA for reuse.
replyTemplate, _ := dict.CreateRequestTemplate(radius.AccessAccept, "Reply-Message")
replyTemplate.AddVSAAttribute(dict, "Cisco", "h323-remote-address")You can also call dict.LoadFile(...) multiple times (for example, once per vendor file). Using a root dictionary with $INCLUDE is usually easier to manage and matches how FreeRADIUS dictionaries are commonly organized.
package main
import (
"context"
"log"
"github.com/sergle/radius/v2"
)
func main() {
handler := radius.HandlerFunc(func(ctx context.Context, request *radius.Packet) *radius.Packet {
log.Printf("Received %s from %s", request.Code, request.ClientAddr)
if request.Code == radius.AccessRequest {
if request.GetUsername() == "admin" && request.GetPassword() == "secret" {
return request.ReplyAccept()
}
return request.ReplyReject()
}
return nil
})
// Option A: single shared secret for all clients
srv := radius.NewServer(":1812", "shared-secret", handler)
// Option B: per-client shared secrets (lookup by remote host IP)
// clients := radius.NewClientList([]radius.Client{
// radius.NewClient("192.0.2.10", "secret-a"),
// radius.NewClient("192.0.2.11", "secret-b"),
// })
// srv := radius.NewServerWithClientList(":1812", clients, handler)
log.Fatal(srv.ListenAndServe())
}package main
import (
"context"
"log"
"github.com/sergle/radius/v2"
)
func main() {
client := radius.NewRadClient("127.0.0.1:1812", "shared-secret")
req := client.NewRequest(radius.AccessRequest)
req.AddAVP(radius.AVP{Type: radius.AttrUserName, Value: []byte("admin")})
req.AddPassword("secret")
// Context-aware request with timeout
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
reply, err := client.SendContext(ctx, req)
if err != nil {
log.Fatal(err)
}
log.Printf("Reply: %s", reply.Code)
}CHAP sends a challenge and a response derived from the password: (response = MD5(chapID || password || challenge)).
package main
import (
"context"
"log"
"time"
"github.com/sergle/radius/v2"
)
func main() {
client := radius.NewRadClient("127.0.0.1:1812", "shared-secret")
req := client.NewRequest(radius.AccessRequest)
req.AddAVP(radius.AVP{Type: radius.AttrUserName, Value: []byte("admin")})
chapID := uint8(1)
challenge := []byte("1234567890abcdef") // 16 bytes (RFC2865: 1..16)
if err := req.SetCHAPPasswordFromSecret(chapID, "secret", challenge); err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
reply, err := client.SendContext(ctx, req)
if err != nil {
log.Fatal(err)
}
log.Printf("Reply: %s", reply.Code)
}For high-load proxies or filters where performance is critical, use lazy decoding to avoid unnecessary allocations.
// Decode without parsing attributes upfront
packet, _ := radius.DecodeRequestLazy(secret, buf)
// Attributes are parsed on-demand when using GetAVP or EachAVP
username := packet.GetUsername()
// Iterate over all attributes without heap allocations
packet.EachAVP(func(attr radius.AVP) bool {
log.Printf("Found AVP: %d", attr.Type)
return true
})RADIUS "Response Authenticator" integrity is based on an MD5 construction that is vulnerable to modern collision attacks in certain on-path (MITM) threat models (see BLAST RADIUS – Attack Details).
This library generates and verifies Message-Authenticator (HMAC-MD5, RFC 3579) when present, and automatically includes it on Access- packets it encodes*. For compatibility with legacy peers, decoding historically treated Message-Authenticator as optional.
If you want stricter behavior, you can opt-in to reject Access- packets that omit Message-Authenticator*:
packet, err := radius.DecodeRequestWithOptions(secret, buf, &radius.DecodeOptions{
RequireMessageAuthenticator: true,
})
if err != nil {
// err == radius.ErrMessageAuthenticatorMissing when absent on Access-*
log.Fatal(err)
}
_ = packetFor the absolute highest performance, use sync.Pool and direct buffer encoding.
// Acquire a packet from the internal pool (Zero B/op)
packet, err := radius.DecodeRequestPooled(secret, buf)
if err != nil {
log.Fatal(err)
}
defer packet.Release() // Crucial: Return packet to the pool
log.Printf("User: %s", packet.GetUsername())// Encode directly into a provided buffer, avoiding allocations
buf := make([]byte, 4096)
n, err := packet.EncodeTo(buf)- Import Path: Change
github.com/sergle/radiustogithub.com/sergle/radius/v2. - Attribute Names: Standard attributes are now prefixed with
Attr(e.g.,UserName->AttrUserName). - Server API: The
Serviceinterface now returns*Packetdirectly. UseHandlerFuncfor simple closures. - Packet Creation: Use
client.NewRequest(code)orradius.Request(code, secret)for more control.
- EAP MS-CHAPv2 packet format: http://tools.ietf.org/id/draft-kamath-pppext-eap-mschapv2-01.txt
- EAP MS-CHAPv2: https://tools.ietf.org/html/rfc2759
- RADIUS Access-Request: https://tools.ietf.org/html/rfc2865
- RADIUS Accounting-Request: https://tools.ietf.org/html/rfc2866
- RADIUS Support For EAP: https://tools.ietf.org/html/rfc3579
- RADIUS Implementation Issues: https://tools.ietf.org/html/rfc5080
MIT License. See LICENSE for details.