src/go/plugin/go.d/modules/freeradius/api/client.go
// SPDX-License-Identifier: GPL-3.0-or-later
package api
import (
"context"
"crypto/hmac"
"crypto/md5"
"fmt"
"net"
"strconv"
"time"
"layeh.com/radius"
"layeh.com/radius/rfc2869"
)
type Status struct {
AccessRequests int64 `stm:"access-requests"`
AccessAccepts int64 `stm:"access-accepts"`
AccessRejects int64 `stm:"access-rejects"`
AccessChallenges int64 `stm:"access-challenges"`
AuthResponses int64 `stm:"auth-responses"`
AuthDuplicateRequests int64 `stm:"auth-duplicate-requests"`
AuthMalformedRequests int64 `stm:"auth-malformed-requests"`
AuthInvalidRequests int64 `stm:"auth-invalid-requests"`
AuthDroppedRequests int64 `stm:"auth-dropped-requests"`
AuthUnknownTypes int64 `stm:"auth-unknown-types"`
AccountingRequests int64 `stm:"accounting-requests"`
AccountingResponses int64 `stm:"accounting-responses"`
AcctDuplicateRequests int64 `stm:"acct-duplicate-requests"`
AcctMalformedRequests int64 `stm:"acct-malformed-requests"`
AcctInvalidRequests int64 `stm:"acct-invalid-requests"`
AcctDroppedRequests int64 `stm:"acct-dropped-requests"`
AcctUnknownTypes int64 `stm:"acct-unknown-types"`
ProxyAccessRequests int64 `stm:"proxy-access-requests"`
ProxyAccessAccepts int64 `stm:"proxy-access-accepts"`
ProxyAccessRejects int64 `stm:"proxy-access-rejects"`
ProxyAccessChallenges int64 `stm:"proxy-access-challenges"`
ProxyAuthResponses int64 `stm:"proxy-auth-responses"`
ProxyAuthDuplicateRequests int64 `stm:"proxy-auth-duplicate-requests"`
ProxyAuthMalformedRequests int64 `stm:"proxy-auth-malformed-requests"`
ProxyAuthInvalidRequests int64 `stm:"proxy-auth-invalid-requests"`
ProxyAuthDroppedRequests int64 `stm:"proxy-auth-dropped-requests"`
ProxyAuthUnknownTypes int64 `stm:"proxy-auth-unknown-types"`
ProxyAccountingRequests int64 `stm:"proxy-accounting-requests"`
ProxyAccountingResponses int64 `stm:"proxy-accounting-responses"`
ProxyAcctDuplicateRequests int64 `stm:"proxy-acct-duplicate-requests"`
ProxyAcctMalformedRequests int64 `stm:"proxy-acct-malformed-requests"`
ProxyAcctInvalidRequests int64 `stm:"proxy-acct-invalid-requests"`
ProxyAcctDroppedRequests int64 `stm:"proxy-acct-dropped-requests"`
ProxyAcctUnknownTypes int64 `stm:"proxy-acct-unknown-types"`
}
type (
radiusClient interface {
Exchange(ctx context.Context, packet *radius.Packet, address string) (*radius.Packet, error)
}
Config struct {
Address string
Port int
Secret string
Timeout time.Duration
}
Client struct {
address string
secret string
timeout time.Duration
radiusClient
}
)
func New(conf Config) *Client {
return &Client{
address: net.JoinHostPort(conf.Address, strconv.Itoa(conf.Port)),
secret: conf.Secret,
timeout: conf.Timeout,
radiusClient: &radius.Client{Retry: time.Second, MaxPacketErrors: 10},
}
}
func (c Client) Status() (*Status, error) {
packet, err := newStatusServerPacket(c.secret)
if err != nil {
return nil, fmt.Errorf("error on creating StatusServer packet: %v", err)
}
resp, err := c.queryServer(packet)
if err != nil {
return nil, fmt.Errorf("error on request to '%s': %v", c.address, err)
}
return decodeResponse(resp), nil
}
func (c Client) queryServer(packet *radius.Packet) (*radius.Packet, error) {
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
defer cancel()
resp, err := c.Exchange(ctx, packet, c.address)
if err != nil {
return nil, err
}
if resp.Code != radius.CodeAccessAccept {
return nil, fmt.Errorf("'%s' returned response code %d", c.address, resp.Code)
}
return resp, nil
}
func newStatusServerPacket(secret string) (*radius.Packet, error) {
// https://wiki.freeradius.org/config/Status#status-of-freeradius-server
packet := radius.New(radius.CodeStatusServer, []byte(secret))
if err := FreeRADIUSStatisticsType_Set(packet, FreeRADIUSStatisticsType_Value_All); err != nil {
return nil, err
}
if err := rfc2869.MessageAuthenticator_Set(packet, make([]byte, 16)); err != nil {
return nil, err
}
hash := hmac.New(md5.New, packet.Secret)
encode, err := packet.Encode()
if err != nil {
return nil, err
}
if _, err := hash.Write(encode); err != nil {
return nil, err
}
if err := rfc2869.MessageAuthenticator_Set(packet, hash.Sum(nil)); err != nil {
return nil, err
}
return packet, nil
}
func decodeResponse(resp *radius.Packet) *Status {
return &Status{
AccessRequests: int64(FreeRADIUSTotalAccessRequests_Get(resp)),
AccessAccepts: int64(FreeRADIUSTotalAccessAccepts_Get(resp)),
AccessRejects: int64(FreeRADIUSTotalAccessRejects_Get(resp)),
AccessChallenges: int64(FreeRADIUSTotalAccessChallenges_Get(resp)),
AuthResponses: int64(FreeRADIUSTotalAuthResponses_Get(resp)),
AuthDuplicateRequests: int64(FreeRADIUSTotalAuthDuplicateRequests_Get(resp)),
AuthMalformedRequests: int64(FreeRADIUSTotalAuthMalformedRequests_Get(resp)),
AuthInvalidRequests: int64(FreeRADIUSTotalAuthInvalidRequests_Get(resp)),
AuthDroppedRequests: int64(FreeRADIUSTotalAuthDroppedRequests_Get(resp)),
AuthUnknownTypes: int64(FreeRADIUSTotalAuthUnknownTypes_Get(resp)),
AccountingRequests: int64(FreeRADIUSTotalAccountingRequests_Get(resp)),
AccountingResponses: int64(FreeRADIUSTotalAccountingResponses_Get(resp)),
AcctDuplicateRequests: int64(FreeRADIUSTotalAcctDuplicateRequests_Get(resp)),
AcctMalformedRequests: int64(FreeRADIUSTotalAcctMalformedRequests_Get(resp)),
AcctInvalidRequests: int64(FreeRADIUSTotalAcctInvalidRequests_Get(resp)),
AcctDroppedRequests: int64(FreeRADIUSTotalAcctDroppedRequests_Get(resp)),
AcctUnknownTypes: int64(FreeRADIUSTotalAcctUnknownTypes_Get(resp)),
ProxyAccessRequests: int64(FreeRADIUSTotalProxyAccessRequests_Get(resp)),
ProxyAccessAccepts: int64(FreeRADIUSTotalProxyAccessAccepts_Get(resp)),
ProxyAccessRejects: int64(FreeRADIUSTotalProxyAccessRejects_Get(resp)),
ProxyAccessChallenges: int64(FreeRADIUSTotalProxyAccessChallenges_Get(resp)),
ProxyAuthResponses: int64(FreeRADIUSTotalProxyAuthResponses_Get(resp)),
ProxyAuthDuplicateRequests: int64(FreeRADIUSTotalProxyAuthDuplicateRequests_Get(resp)),
ProxyAuthMalformedRequests: int64(FreeRADIUSTotalProxyAuthMalformedRequests_Get(resp)),
ProxyAuthInvalidRequests: int64(FreeRADIUSTotalProxyAuthInvalidRequests_Get(resp)),
ProxyAuthDroppedRequests: int64(FreeRADIUSTotalProxyAuthDroppedRequests_Get(resp)),
ProxyAuthUnknownTypes: int64(FreeRADIUSTotalProxyAuthUnknownTypes_Get(resp)),
ProxyAccountingRequests: int64(FreeRADIUSTotalProxyAccountingRequests_Get(resp)),
ProxyAccountingResponses: int64(FreeRADIUSTotalProxyAccountingResponses_Get(resp)),
ProxyAcctDuplicateRequests: int64(FreeRADIUSTotalProxyAcctDuplicateRequests_Get(resp)),
ProxyAcctMalformedRequests: int64(FreeRADIUSTotalProxyAcctMalformedRequests_Get(resp)),
ProxyAcctInvalidRequests: int64(FreeRADIUSTotalProxyAcctInvalidRequests_Get(resp)),
ProxyAcctDroppedRequests: int64(FreeRADIUSTotalProxyAcctDroppedRequests_Get(resp)),
ProxyAcctUnknownTypes: int64(FreeRADIUSTotalProxyAcctUnknownTypes_Get(resp)),
}
}