Skip to content

sergle/radius

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

70 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

a golang radius library (v2)

PkgGoDev MIT License

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

Key Features

  • 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.Pool for Packet structs to reach absolute zero-allocation.
  • Enhanced Testing: Comprehensive test suite including "golden data" verification.

Installation

go get github.com/sergle/radius/v2

Dictionaries: Loading External Vendor Dictionaries (Cisco, Microsoft, ...)

This library supports FreeRADIUS-style dictionary files, including $INCLUDE, VENDOR, BEGIN-VENDOR, and vendor-specific attributes (VSAs).

Recommended: Use a “root” dictionary with $INCLUDE

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

Alternative: Load multiple files directly

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.

Quick Start (Server)

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())
}

Quick Start (Client)

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

Quick Start (Client with CHAP)

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

High Performance: Lazy Decoding

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

Security: Requiring Message-Authenticator (Optional)

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)
}
_ = packet

High Performance: Zero-Allocation Pooling

For the absolute highest performance, use sync.Pool and direct buffer encoding.

Pooled Decoding

// 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())

Direct Encoding

// Encode directly into a provided buffer, avoiding allocations
buf := make([]byte, 4096)
n, err := packet.EncodeTo(buf)

Migration Guide (v1 to v2)

  1. Import Path: Change github.com/sergle/radius to github.com/sergle/radius/v2.
  2. Attribute Names: Standard attributes are now prefixed with Attr (e.g., UserName -> AttrUserName).
  3. Server API: The Service interface now returns *Packet directly. Use HandlerFunc for simple closures.
  4. Packet Creation: Use client.NewRequest(code) or radius.Request(code, secret) for more control.

Documentation

References

License

MIT License. See LICENSE for details.

About

a golang radius client/server library (support VSA attributes, dictionary files)

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Go 99.7%
  • Makefile 0.3%