RADIUS AcctSessionId issue (duplicated accounting)

RouterOS generates a RADIUS AcctSessionId which is not ‘very’ unique. The same session-id is generated from time to time within a time period of a few days or hours. If the session-id was previously used for a user and the same user is now getting the same session-id, the Radius accounting (radacct) will update the new AND the old accounting entry.
This problem increases with multiple RouterOS units installed and users can roam between units.
Example:
Old radacct entry:
AcctSessionId: 80f000e0, UserName: testuser, Date: 2006-05-20 21:44:13

New radacct entry:
AcctSessionId: 80f000e0, UserName: testuser, Date: 2006-05-30 16:14:23

(Note: same AcctSessionId)

The FreeRadius radacct update is using AcctSessionId, UserName and NASIPAddress to update a session entry:
accounting_update_query = "UPDAT

E ${acct_table1}
SET FramedIPAddress = ‘%{Framed-IP-Address}’,
AcctSessionTime = ‘%{Acct-Session-Time}’,
AcctInputOctets = ‘%{Acct-Input-Octets}’,
AcctOutputOctets = ‘%{Acct-Output-Octets}’
WHERE AcctSessionId = ‘%{Acct-Session-Id}’
AND UserName = ‘%{SQL-User-Name}’
AND NASIPAddress= ‘%{NAS-IP-Address}’"

With this scenario set, Radius will update both entries, which means that the session time and down/upload traffic will be accounted twice.

Current RouterOS AcctSessionId’s: 80f000e0, 80f000e1, 80f000e2, 80f000e3, 80f000e5
Other vendors are using a more unique Id:
447ce53300000013, 447c862f0000000f, 447c57cd00000006

If I create a unique sql key for AcctSessionId and UserName the INSERT will be ignored and the ‘old’ entry will be updated. This works but will cause incorrect accounting entries as the old entry will be overwritten.

A ‘more’ unique AcctSessionId is desireable! Or is there a way to preset the lenght of the sessionId?

Dont update your databases by using RadSessionId, use RadUniqueID.

There is a setting in radius’s config file that you can specify which attributes to use to create this unique hash to ensure that it stays unique. I use

acct_unique {
key = “User-Name, Acct-Session-Id, NAS-IP-Address, Client-IP-Address, NAS-Port”
}


Never had problems so far.

Thanks!
Will check it out!

Just more to this as I was in a bit of a hurry this morning… Accounting data - needs to be managed. It is very unefficient to sit with a accounting table with millions and millions of records, as radius needs to run select queries against that data, that can potentially take to long to query.

Create two more tables for yourself…

mysql> DESCRIBE RadiusAcctTotals;
+-----------------+-------------+------+-----+------------+----------------+
| Field           | Type        | Null | Key | Default    | Extra          |
+-----------------+-------------+------+-----+------------+----------------+
| TotAcctId       | bigint(21)  | NO   | PRI | NULL       | auto_increment |
| UserName        | varchar(64) | NO   | MUL | NULL       |                |
| AcctDate        | date        | NO   | MUL | 0000-00-00 |                |
| ConnNum         | bigint(12)  | YES  |     | NULL       |                |
| ConnTotDuration | bigint(12)  | YES  |     | NULL       |                |
| ConnMaxDuration | bigint(12)  | YES  |     | NULL       |                |
| ConnMinDuration | bigint(12)  | YES  |     | NULL       |                |
| InputOctets     | bigint(12)  | YES  |     | NULL       |                |
| OutputOctets    | bigint(12)  | YES  |     | NULL       |                |
+-----------------+-------------+------+-----+------------+----------------+
9 rows in set (0.02 sec)

mysql> DESCRIBE RadiusAcctMonthlyTotals;
+-----------------+-------------+------+-----+------------+----------------+
| Field           | Type        | Null | Key | Default    | Extra          |
+-----------------+-------------+------+-----+------------+----------------+
| MTotAcctId      | bigint(21)  | NO   | PRI | NULL       | auto_increment |
| UserName        | varchar(64) | NO   | MUL | NULL       |                |
| AcctDate        | date        | NO   | MUL | 0000-00-00 |                |
| ConnNum         | bigint(12)  | YES  |     | NULL       |                |
| ConnTotDuration | bigint(12)  | YES  |     | NULL       |                |
| ConnMaxDuration | bigint(12)  | YES  |     | NULL       |                |
| ConnMinDuration | bigint(12)  | YES  |     | NULL       |                |
| InputOctets     | bigint(12)  | YES  |     | NULL       |                |
| OutputOctets    | bigint(12)  | YES  |     | NULL       |                |
+-----------------+-------------+------+-----+------------+----------------+
9 rows in set (0.01 sec)

As part of your daily maintenance run, move all accounting data from the Radius Accounting table that is not active (has a acctstoptime) and older than a couple of days, to the RadiusAcctTotals tables. Then, delete the old records from the radius accounting table. Thus, your main accounting table used my radius is small, compact, and won’t give you any issues like this.

A couple of steps are done here:

  1. Delete entries older than 90 days, as these would be in AcctMonthlyTotals already
DELETE FROM RadiusAcctTotals WHERE AcctDate = '" . $Date_Start . "'
  1. Copy the days closed Accounting Data from the default Radius Accounting tables, into a summarised version of our own Accounting Data (We do this on a per user basis, rather than a per session bases which means less records)
INSERT INTO RadiusAcctTotals (UserName, AcctDate, ConnNum, ConnTotDuration, ConnMaxDuration, ConnMinDuration, InputOctets, OutputOctets) SELECT UserName, '" . $Date_Small_Start . "',COUNT(RadAcctId), SUM(AcctSessionTime), MAX(AcctSessionTime), MIN(AcctSessionTime), SUM(AcctInputOctets), SUM(AcctOutputOctets) FROM RadiusAccounting WHERE AcctStopTime >= '" . $Date_Start . "' AND AcctStopTime < '" . $Date_End . "' AND UserName LIKE '%@%' GROUP BY UserName
  1. Delete the Accounting Data from the Radius Accounting tables that we just copied to our own summarised Accounting Tables
DELETE FROM RadiusAccounting WHERE AcctStopTime >= '" . $Date_Start . "' AND AcctStopTime < '" . $Date_End . "' AND UserName LIKE '%@%'

At the end of the month, I normally run a query against my daily populated accounting table, and create monthly totals for my users in the monthly accounting table. At the end of the day, I can keep 100 years (if I want) worth of accounting data based on a per month usage with zero impact on my radius server…

At the end of the month, we run amongst others:

  1. Move a summarised (we now move from daily to monthly) accounting data from our own accounting tables to the monthly tables
INSERT INTO RadiusAcctMonthlyTotals (UserName, AcctDate, ConnNum, ConnTotDuration, ConnMaxDuration, ConnMinDuration, InputOctets, OutputOctets) SELECT UserName, '" . $Date_Start . "', SUM(ConnNum), SUM(ConnTotDuration), MAX(ConnMaxDuration), MIN(ConnMinDuration), SUM(InputOctets), SUM(OutputOctets) FROM RadiusAcctTotals WHERE AcctDate >= '" . $Date_Start . "' AND AcctDate <= '" . $Date_End . "' GROUP BY UserName
  1. Delete our daily stats the stats that we have in our monthly table
DELETE FROM RadiusAcctTotals WHERE AcctDate >= '" . $Date_Start . "' AND AcctDate <= '" . $Date_End . "'

You can make your own system as you desire obviously, and you won’t be able to simply copy & paste mine :smiley: but you get the idea. Data needs to be managed, you can’t just dump things in a database and expect them to work forever. The basic idea is that you have as little as possible records in the accounting tables used by radius, but how you achieve that can obviously be done in many, many ways - some which might suit you, some which may not.


C

Thanks for the very comprehensive solution! Guess I can stop working on that issue as I already started working on it. Will take your advise and integrate into our system asap (pointHotspot.com).
Cheers!

Glad to be able to help. One last thing just to clear up your original question (complaint?) about SessionID…

SessionID is generated by the NAS, but, the NAS is only required to have a unique Session ID for each connected ACTIVE Session. Once a user disconnects and a different user connects, the NAS is well within it’s rights to assign the same SessionID to the different user.

When two users are connected at the same time and have the same SessionID - THEN the NAS is definately in the wrong and the manufacturers needs to be shot, and hung out to dry :smiley: (This obviously excludes MPPP - which is not supported by MT in any case, but which will obviously have the same SessionID).

To overcome this, FreeRadius has come up with the UniqueID Module to generate a unique hash, based on the value of various attributes. It’s also not guaranteed to be unique, but the chances of getting a duplicate is WAY less. If you’re really clever and combine this with the rlm_exec module, you can generate a hash based on /dev/random, or /bin/date +%s which is guaranteed to always be unique (I’ve even seen people use SELECT UUID() in MySQL). Some bigger installations I’ve seen actually required things like this :slight_smile:

Now I have another topic that must be tied into the WIKI eventually when I get the time one day…


C

Thanks again!
I already changed the radacct update using the unique session id, just didn’t know that is was actually available at that time (‘%{Acct-Unique-Session-Id}’).
Yes, I understand the issue with the session id generated by the NAS. And with more units on the radius server the problems would increase.

FreeRadius is using the standard key = “User-Name, Acct-Session-Id, NAS-IP-Address, Client-IP-Address, NAS-Port” for creating the unique session id. Don’t have to change anything here.

Found a note in the FR ./doc directory (tuning_guide)
“Add AcctUniqueId in the accounting_stop query. Especially if you have a lot of access servers or your NAS does not send very random Session-Ids. That way you will always have one candidate row to search for, instead of all the rows that have the same AcctSessionId”

Anyhow, I added the unique id into ‘accounting_update_query’ and ‘accounting_start_query_alt’

That should do the trick!