summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/freeradius/api/client.go
blob: 01f784c17d026fa4622b35b34d40845534b64773 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// 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)),
	}
}