uspot: accounting: implement Accounting-On/Off

The RFC[1] says about Acct-Status-Type:

    It MAY be used by the client to mark the start of accounting (for
    example, upon booting) by specifying Accounting-On and to mark the
    end of accounting (for example, just before a scheduled reboot) by
    specifying Accounting-Off.

The RFC errata[2] further specifies that Accounting-On and
Accounting-Off messages apply to the whole NAS.

The RFC also mandates that[3]:

    Either NAS-IP-Address or NAS-Identifier MUST be present in a
    RADIUS Accounting-Request.  It SHOULD contain a NAS-Port or NAS-
    Port-Type attribute or both unless the service does not involve a
    port or the NAS does not distinguish among its ports.

And[4]:

    An Accounting-Request packet MUST have an Acct-Session-Id.
    The Acct-Session-Id SHOULD contain UTF-8 encoded 10646 characters.

Finally the freeRADIUS recommendations here[5] suggest that:

 1. Acct-Status-Type = Accounting-On should not be used to indicate
    sub-system reboot.
 2. IANA should allocate two new values for Acct-Status-Type:
    Subsystem-On, and Subsystem-Off. These values have meaning similar
    to Accounting-On and Accounting-Off, except that they apply to a
    subystem of the NAS.
 3. NASes should use these new values to indicate subsystem on/off.
 4. The Called-Station-Id attribute should contain values unique to each
    subsystem.
 5. The NAS should signal that the entire system has rebooted by using
    the existing Accounting-On and Accounting-Off values, with a value
    for Called-Station-Id that is global to the NAS, or to omit it
    entirely.

In order to reconcile all this, this commit implements Accounting-On and
Accounting-Off requests as follows:

- When accounting.uc is started, it loops through each uspot interface
  and keeps track of the acct_server seen for each interface. Then for
  each interface that do not use a previously seen server, it generates
  a unique session ID, and sends an Accounting-On request to the
  RADIUS server, using this session ID and the configured NAS-ID.
- When accounting.uc stops, it sends an Accounting-Off request for each
  uspot interface for which an Accounting-On message was previously sent,
  using the same global session ID.

If/when the Subsystem-On/Subsystem-Off values are implemented, this
commit can be revisited to simply lift the restriction on unique servers
and change the acct_type value accordingly.

Finally, it appears that while NAS-ID is provided in the request thus
making NAS-IP unnecessary, libradcli still includes this field in the
request. Likewise, it also insists on sending a NAS-Port attribute.

[1]: https://datatracker.ietf.org/doc/html/rfc2866#section-5.1
[2]: https://www.rfc-editor.org/errata_search.php?rfc=2866
[3]: https://datatracker.ietf.org/doc/html/rfc2866#section-4.1
[4]: https://datatracker.ietf.org/doc/html/rfc2866#section-5.5
[5]: https://freeradius.org/rfc/acct_status_type_subsystem.html

Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
This commit is contained in:
Thibaut VARÈNE
2023-05-25 11:53:55 +02:00
committed by John Crispin
parent a647368f15
commit 95a7b6d54d

View File

@@ -231,6 +231,40 @@ function client_reset(interface, mac, reason) {
client_kick(interface, mac, false);
}
function radius_accton(interface)
{
// assign a global interface session ID for Accounting-On/Off messages
let math = require('math');
let sessionid = '';
for (let i = 0; i < 16; i++)
sessionid += sprintf('%x', math.rand() % 16);
interfaces[interface].sessionid = sessionid;
const acct_type_accton = 7; // Accounting-On
let payload = {
acct_type: acct_type_accton,
acct_session: sessionid,
};
payload = radius_init(interface, null, payload);
payload.acct = true;
radius_call(interface, null, payload);
debug(interface, null, 'acct-on call');
}
function radius_acctoff(interface)
{
const acct_type_acctoff = 8; // Accounting-Off
let payload = {
acct_type: acct_type_acctoff,
acct_session: interfaces[interface].sessionid,
};
payload = radius_init(interface, null, payload);
payload.acct = true;
radius_call(interface, null, payload);
debug(interface, null, 'acct-off call');
}
function accounting(interface) {
let list = ubus.call('spotfilter', 'client_list', { interface });
@@ -277,8 +311,30 @@ function accounting(interface) {
}
}
function start()
{
let seen = {};
for (let interface, data in interfaces) {
if (!data.settings.acct_server || (data.settings.acct_server in seen))
continue; // avoid sending duplicate requests to the same server
seen[data.settings.acct_server] = 1;
radius_accton(interface);
}
}
function stop()
{
for (let interface, data in interfaces) {
if (data.sessionid) // we have previously sent Accounting-On
radius_acctoff(interface);
}
}
uloop.init();
start();
uloop.timer(10000, function() {
for (let interface in interfaces)
accounting(interface);
@@ -286,3 +342,5 @@ uloop.timer(10000, function() {
});
uloop.run();
stop();