Project

General

Profile

Actions

Feature #13844

open

Make RADIUS Start/Stop accounting immediately log off a user that exceeds quota when reauthentication is disabled

Added by Reid Linnemann about 1 year ago. Updated 14 days ago.

Status:
New
Priority:
Normal
Category:
Captive Portal
Target version:
Start date:
Due date:
% Done:

0%

Estimated time:
Plus Target Version:
24.07
Release Notes:
Default

Description

In captiveportal_prune_old, when accounting start/stop packets are sent, the response attributes are not examined and we are instead reliant on a reauthentication to actually log the user off, as rule counters are cleared at each accounting start request.

It may be possible to configure FreeRADIUS to return the user's total bytes in/out in an Accounting-Response so that upon updating the user's accounting statistics the total usage may be immediately compared to the known quota and the user promptly logged off, as is done for interim accounting updates.


Related issues

Related to Bug #13838: Captive Portal RADIUS start/stop accounting does not reset counters at each accounting startClosedReid Linnemann

Actions
Actions #1

Updated by Reid Linnemann about 1 year ago

  • Related to Bug #13838: Captive Portal RADIUS start/stop accounting does not reset counters at each accounting start added
Actions #2

Updated by Reid Linnemann about 1 year ago

From my research the RADIUS standards facilitate this by way of RFC-3576 Disconnect-Request requests, which are supported by freeradius. The catch, however, is that currently the NAS (captive portal) is not a long-lived service but an ephemeral script run either via the user logging in to the captive portal web form or by the /etc/rc.prunecaptiveportal periodic task. To properly support async user disconnect with this standard mechanism, we need an actual captive portal service that obviates the periodic task and instead does its own periodic accounting and housekeeping, as well as listening for RFC-3576 requests from the RADIUS.

We could alternatively define another vendor specific attribute indicating that the user's session should be terminated, and send that attribute in response to Accounting-Stop and Interim-Update accounting requests, thus completely removing the user disconnect decision from the captive portal code and properly relying on the RADIUS service to tell it to take action and disconnect the user. The captive portal should only be making this decision when we are doing our own local accounting based on the portal-wide settings without RADIUS accounting.

Actions #3

Updated by Dale Harron about 1 year ago

I considered a separate redmine but this issue must be resolved simultaneous with this redmine as simply removing the need to turn on user reauthentication to logoff under stop/start is not sufficient to accurately log the user off. The incorporation of stop/start auto-log-off assumes the accounting quota value is accurate which is not true because of the situation below. Both can be corrected simultaneously and with reauthenticate then turned off and the slower stop/start/reauthentication frequency, the syslog will not overflow every few seconds.

Stop/Start and Reauthenticate every minute with a sleep(1) in the stop/start logic seriously restricts the number of simultaneous users that can be authenticated through freeRadius. Once every 10 minutes, 600 sec and greater is more reliable. Consider increasing that time by exposing the value in the interim setting from the GUI to also set the frequency of the stop/start & reauthenticate to improve freeRadius stability. This solution not only addresses the immediate issue of having to use reauthenticate to log the user off, it will enable scaling once you incorporate the user selected stop/start frequency (i.e. the value of interim in the GUI). freeRadius is currently dropping both data volume and time accounting information as it needs a greater delay to "keep up". This is especially true for multiple simultaneous logins to a single user account. The code sample below from approx line 684 in captiveportal.inc Feb 10th compile of 23.01 has enabled this capability for us. Note: this is a simple cut/paste from the interim section of code, thus not pretty.

approx line 684
/* do periodic reauthentication? For Radius servers, send accounting updates? /
if (!$timedout) {
//Radius servers : send accounting
if (isset($cpcfg['radacct_enable']) && $cpentry['authmethod'] === 'radius') {
if (substr($cpcfg['reauthenticateacct'], 0, 9) === "stopstart") {
/
stop and restart accounting /
if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
/
---- Original Code --------------------------------------------------------------------------
$rastart_time = 0;
$rastop_time = 60;
----------------------------------------------------------------------------------------------- /
/
Override the fixed 60 second stop/start frequency and use the interim value set in the GUI instead */

$rastart_time = 0;
$rastop_time = $cpentry[10];
/* -------------------------------------------------------------------------------------------------- /
/
It should be noted that the code below for Stop/Start will result in time cumulating too fast, especially for multiple simultaneous logins /
/
It (Stop/Start) should count from the last accounting interval, not from the beginning of the session. Using the above values would correct that */
} else {
$rastart_time = $cpentry[0];
$rastop_time = time();
}

/*-----------------------------------------------------------------------------------------------------------*/
/* Copy the interim delay code here to use interim update setting from freeRadius GUI as stop/start frequency instead of 1 minute */

$session_time = $pruning_time - $cpentry[0];
if (!empty($cpentry[10]) && $cpentry[10] > 60) {
$interval = $cpentry[10];
} else {
$interval = 0;
}
$past_interval_min = ($session_time > $interval);
if ($interval != 0) {
$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
}
if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {

/*-----------------------------------------------------------------------------------------------------------*/
captiveportal_send_server_accounting('stop',
$cpentry1, // ruleno
$cpentry4, // username
$cpentry2, // clientip
$cpentry3, // clientmac
$cpentry5, // sessionid
$rastart_time, // start time
$rastop_time, // Stop Time
10); // NAS Request
/* XXX rewrite to C wrapper pfSense_pf_anchor_zerocnt() /
captiveportal_anchor_zerocnt($cpentry2, 'auth');
if ($cpcfg['reauthenticateacct'] "stopstartfreeradius") {
/
Need to pause here or the FreeRADIUS server gets confused about packet ordering. /
/
Original Code modified to support more users ----------------------------------------------------------------------------------- /
/
-------------------------------------------- sleep(1); ------------------------------------------------------------------------- /
usleep(250000);
}
captiveportal_send_server_accounting('start',
$cpentry[1], // ruleno
$cpentry[4], // username
$cpentry[2], // clientip
$cpentry[3], // clientmac
$cpentry[5]); // sessionid
/
-----------------------------------------------------------------------------------------------------------*/
/* Copy the interim delay code here to use interim update setting from freeRadius GUI as stop/start frequency instead of 1 minute */

}

/*-----------------------------------------------------------------------------------------------------------*/
} else if ($cpcfg['reauthenticateacct'] "interimupdate") {
$session_time = $pruning_time - $cpentry0;
if (!empty($cpentry10) && $cpentry10 > 60) {
$interval = $cpentry10;
} else {
$interval = 0;
}
$past_interval_min = ($session_time > $interval);
if ($interval != 0) {
$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
}
if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
captiveportal_send_server_accounting('update',
$cpentry1, // ruleno
$cpentry4, // username
$cpentry2, // clientip
$cpentry3, // clientmac
$cpentry5, // sessionid
$cpentry0); // start time
}
}
}

/* check this user again */
if (isset($cpcfg['reauthenticate']) && $cpentry['context'] !== 'voucher') {

/*-----------------------------------------------------------------------------------------------------------*/
/* Copy the interim delay code here to use interim update setting from freeRadius GUI as reauthenticate frequency instead of 1 minute */

$session_time = $pruning_time - $cpentry[0];
if (!empty($cpentry[10]) && $cpentry[10] > 60) {
$interval = $cpentry[10];
} else {
$interval = 0;
}
$past_interval_min = ($session_time > $interval);
if ($interval != 0) {
$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
}
if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {

/*-----------------------------------------------------------------------------------------------------------*/

$auth_result = captiveportal_authenticate_user(
$cpentry[4], // username
base64_decode($cpentry[6]), // password
$cpentry[3], // clientmac
$cpentry[2], // clientip
$cpentry[1], // ruleno
$cpentry['context']); // context
if ($auth_result['result'] === false) {
captiveportal_disconnect($cpentry, 17);
captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT - REAUTHENTICATION FAILED", $auth_list['reply_message']);
$unsetindexes[] = $cpentry[5];
} else if ($auth_result['result'] === true) {
if ($cpentry['authmethod'] !== $auth_result['auth_method']) {
// if the user got authenticated against another server type: we update the database
if (!empty($cpentry[5])) {
captiveportal_update_entry($cpentry['sessionid'], $auth_result['auth_method'], 'authmethod');
captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CHANGED AUTHENTICATION SERVER", $auth_list['reply_message']);
}
// User was logged on a RADIUS server, but is now logged in by another server type : we send an accounting Stop
if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $cpentry['authmethod'] 'radius') {
if ($cpcfg['reauthenticateacct'] = "stopstartfreeradius") {
$rastart_time = 0;
$rastop_time = 60;
} else {
$rastart_time = $cpentry[0];
$rastop_time = time();
}
captiveportal_send_server_accounting('stop',
$cpentry[1], // ruleno
$cpentry[4], // username
$cpentry[2], // clientip
$cpentry[3], // clientmac
$cpentry[5], // sessionid
$rastart_time, // start time
$rastop_time, // Stop Time
3); // Lost Service
// User was logged on a non-RADIUS Server but is now logged in by a RADIUS server : we send an accounting Start
} else if(isset($config['captiveportal'][$cpzone]['radacct_enable']) && $auth_result['auth_method'] === 'radius') {
captiveportal_send_server_accounting('start',
$cpentry[1], // ruleno
$cpentry[4], // username
$cpentry[2], // clientip
$cpentry[3], // clientmac
$cpentry[5], // sessionid
$cpentry[0]); // start_time
}
}
captiveportal_reapply_attributes($cpentry, $auth_result['attributes']);
}
/*-----------------------------------------------------------------------------------------------------------*/
/* Copy the interim delay code here to use interim update setting from freeRadius GUI as reauthenticate frequency instead of 1 minute */
}

/*-----------------------------------------------------------------------------------------------------------*/
}
}
}
unset($cpdb);

captiveportal_prune_old_automac();
Actions #4

Updated by Jim Pingle 11 months ago

  • Plus Target Version changed from 23.05 to 23.09
Actions #5

Updated by Jim Pingle 7 months ago

  • Plus Target Version changed from 23.09 to 24.01
Actions #6

Updated by Jim Pingle 6 months ago

  • Plus Target Version changed from 24.01 to 24.03
Actions #7

Updated by Dale Harron about 2 months ago

As per comment in #13843, please include multiuser, parallel, simultaneous logins, cumulative when calculating totals against the quota. This includes multiuser sessions that have previously been terminated.

Actions #8

Updated by Jim Pingle 14 days ago

  • Plus Target Version changed from 24.03 to 24.07
Actions

Also available in: Atom PDF