Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
smradius
smradius
Commits
bcaa1e81
Commit
bcaa1e81
authored
May 15, 2019
by
Nigel Kukard
Browse files
FEATURE: FUP
parent
2dc0563d
Changes
4
Hide whitespace changes
Inline
Side-by-side
lib/smradius/daemon.pm
View file @
bcaa1e81
...
...
@@ -903,6 +903,13 @@ sub process_request {
# We don't care if it fails
}
}
# TEST START
my
$coaReq
=
smradius::Radius::
Packet
->
new
(
$self
->
{'
radius
'}
->
{'
dictionary
'});
# Process the reply attributes
$self
->
_processReplyAttributes
(
$request
,
$user
,
$coaReq
);
# TEST END
# Check if we must POD the user
if
(
$PODUser
)
{
...
...
lib/smradius/modules/features/mod_feature_fup.pm
0 → 100644
View file @
bcaa1e81
# FUP support
# Copyright (C) 2007-2019, AllWorldIT
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
package
smradius::modules::features::
mod_feature_fup
;
use
strict
;
use
warnings
;
# Modules we need
use
smradius::
attributes
;
use
smradius::
constants
;
use
smradius::
logging
;
use
smradius::
util
;
use
AWITPT::
Util
;
use
List::
Util
qw( min )
;
use
MIME::
Lite
;
use
POSIX
qw( floor )
;
# Set our version
our
$VERSION
=
"
0.0.1
";
# Load exporter
use
base
qw(Exporter)
;
our
@EXPORT
=
qw(
)
;
our
@EXPORT_OK
=
qw(
)
;
# Plugin info
our
$pluginInfo
=
{
Name
=>
"
User FUP Feature
",
Init
=>
\
&init
,
# Authentication hook
'
Feature_Post-Authentication_hook
'
=>
\
&post_auth_hook
,
# Accounting hook
'
Feature_Post-Accounting_hook
'
=>
\
&post_acct_hook
,
};
# Some constants
my
$FUP_PERIOD_ATTRIBUTE
=
'
SMRadius-FUP-Period
';
my
$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE
=
'
SMRadius-FUP-Traffic-Threshold
';
my
$config
;
## @internal
# Initialize module
sub
init
{
my
$server
=
shift
;
my
$scfg
=
$server
->
{'
inifile
'};
# Defaults
$config
->
{'
enable_mikrotik
'}
=
0
;
# Setup SQL queries
if
(
defined
(
$scfg
->
{'
mod_feature_fup
'}))
{
# Check if option exists
if
(
defined
(
$scfg
->
{'
mod_feature_fup
'}{'
enable_mikrotik
'}))
{
# Pull in config
if
(
defined
(
my
$val
=
isBoolean
(
$scfg
->
{'
mod_feature_fup
'}{'
enable_mikrotik
'})))
{
if
(
$val
)
{
$server
->
log
(
LOG_NOTICE
,"
[MOD_FEATURE_FUP] Mikrotik-specific vendor return attributes ENABLED
");
$config
->
{'
enable_mikrotik
'}
=
$val
;
}
}
else
{
$server
->
log
(
LOG_NOTICE
,"
[MOD_FEATURE_FUP] Value for 'enable_mikrotik' is invalid
");
}
}
}
return
;
}
## @post_auth_hook($server,$user,$packet)
# Post authentication hook
#
# @param server Server object
# @param user User data
# @param packet Radius packet
#
# @return Result
sub
post_auth_hook
{
my
(
$server
,
$user
,
$packet
)
=
@_
;
# Skip MAC authentication
return
MOD_RES_SKIP
if
(
$user
->
{'
_UserDB
'}
->
{'
Name
'}
eq
"
SQL User Database (MAC authentication)
");
$server
->
log
(
LOG_DEBUG
,"
[MOD_FEATURE_FUP] POST AUTH HOOK
");
#
# Get threshold from attributes
#
my
$fupPeriod
=
_getAttributeKeyNumeric
(
$server
,
$user
,
$FUP_PERIOD_ATTRIBUTE
);
my
$trafficThreshold
=
_getAttributeKeyNumeric
(
$server
,
$user
,
$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE
);
# If we have no FUP period, skip
if
(
!
defined
(
$fupPeriod
))
{
return
MOD_RES_SKIP
;
};
# If we have no traffic threshold, display an info message and skip
if
(
!
defined
(
$trafficThreshold
))
{
$server
->
log
(
LOG_INFO
,"
[MOD_FEATURE_FUP] User has a '
$FUP_PERIOD_ATTRIBUTE
' defined, but NOT a
"
.
"
'
$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE
' attribute, aborting FUP checks.
");
return
MOD_RES_SKIP
;
};
#
# Get current traffic and uptime usage
#
my
$accountingUsage
=
_getAccountingUsage
(
$server
,
$user
,
$packet
,
$fupPeriod
);
if
(
!
defined
(
$accountingUsage
))
{
return
MOD_RES_SKIP
;
}
#
# Display our FUP info
#
_logUsage
(
$server
,
$fupPeriod
,
$accountingUsage
->
{'
TotalDataUsage
'},
$trafficThreshold
);
#
# Check if the user has exceeded the FUP
#
my
$fupExceeded
=
(
$accountingUsage
->
{'
TotalDataUsage
'}
>
$trafficThreshold
)
?
1
:
0
;
#
# Add conditional variables
#
addAttributeConditionalVariable
(
$user
,"
SMRadius_FUP
",
$fupExceeded
);
return
MOD_RES_ACK
;
}
## @post_acct_hook($server,$user,$packet)
# Post authentication hook
#
# @param server Server object
# @param user User data
# @param packet Radius packet
#
# @return Result
sub
post_acct_hook
{
my
(
$server
,
$user
,
$packet
)
=
@_
;
# We cannot cap a user if we don't have a UserDB module can we? no userdb, no cap?
return
MOD_RES_SKIP
if
(
!
defined
(
$user
->
{'
_UserDB
'}
->
{'
Name
'}));
# Skip MAC authentication
return
MOD_RES_SKIP
if
(
$user
->
{'
_UserDB
'}
->
{'
Name
'}
eq
"
SQL User Database (MAC authentication)
");
# User is either connecting 'START' or disconnecting 'STOP'
return
MOD_RES_SKIP
if
(
$packet
->
rawattr
('
Acct-Status-Type
')
ne
"
1
"
&&
$packet
->
rawattr
('
Acct-Status-Type
')
ne
"
3
");
$server
->
log
(
LOG_DEBUG
,"
[MOD_FEATURE_FUP] POST ACCT HOOK
");
#
# Get threshold from attributes
#
my
$fupPeriod
=
_getAttributeKeyNumeric
(
$server
,
$user
,
$FUP_PERIOD_ATTRIBUTE
);
my
$trafficThreshold
=
_getAttributeKeyNumeric
(
$server
,
$user
,
$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE
);
# If we have no FUP period, skip
if
(
!
defined
(
$fupPeriod
))
{
return
MOD_RES_SKIP
;
};
# If we have no traffic threshold, display an info message and skip
if
(
!
defined
(
$trafficThreshold
))
{
$server
->
log
(
LOG_INFO
,"
[MOD_FEATURE_FUP] User has a '
$FUP_PERIOD_ATTRIBUTE
' defined, but NOT a
"
.
"
'
$FUP_TRAFFIC_THRESHOLD_ATTRIBUTE
' attribute, aborting FUP checks.
");
return
MOD_RES_SKIP
;
};
#
# Get current traffic and uptime usage
#
my
$accountingUsage
=
_getAccountingUsage
(
$server
,
$user
,
$packet
,
$fupPeriod
);
if
(
!
defined
(
$accountingUsage
))
{
return
MOD_RES_SKIP
;
}
#
# Display our FUP info
#
_logUsage
(
$server
,
$fupPeriod
,
$accountingUsage
->
{'
TotalDataUsage
'},
$trafficThreshold
);
#
# Check if the user has exceeded the FUP
#
my
$fupExceeded
=
(
$accountingUsage
->
{'
TotalDataUsage
'}
>
$trafficThreshold
)
?
1
:
0
;
#
# Add conditional variables
#
addAttributeConditionalVariable
(
$user
,"
SMRadius_FUP
",
$fupExceeded
);
return
MOD_RES_ACK
;
}
## @internal
# Code snippet to grab the current uptime limit by processing the user attributes
sub
_getAttributeKeyNumeric
{
my
(
$server
,
$user
,
$attributeKey
)
=
@_
;
# Short circuit return if we don't have the uptime key set
return
if
(
!
defined
(
$user
->
{'
Attributes
'}
->
{
$attributeKey
}));
# Short circuit if we do not have a valid attribute operator: ':='
if
(
!
defined
(
$user
->
{'
Attributes
'}
->
{
$attributeKey
}
->
{'
:=
'}))
{
$server
->
log
(
LOG_NOTICE
,"
[MOD_FEATURE_FUP] No valid operators for attribute '
"
.
$user
->
{'
Attributes
'}
->
{
$attributeKey
}
.
"
'
");
return
;
}
$server
->
log
(
LOG_DEBUG
,"
[MOD_FEATURE_FUP] Attribute '
"
.
$attributeKey
.
"
' is defined
");
# Check for valid attribute value
if
(
!
defined
(
$user
->
{'
Attributes
'}
->
{
$attributeKey
}
->
{'
:=
'}
->
{'
Value
'})
||
$user
->
{'
Attributes
'}
->
{
$attributeKey
}
->
{'
:=
'}
->
{'
Value
'}
!~
/^\d+$/
)
{
$server
->
log
(
LOG_NOTICE
,"
[MOD_FEATURE_FUP] Attribute '
"
.
$user
->
{'
Attributes
'}
->
{
$attributeKey
}
->
{'
:=
'}
->
{'
Value
'}
.
"
' is NOT a numeric value
");
return
;
}
return
$user
->
{'
Attributes
'}
->
{
$attributeKey
}
->
{'
:=
'}
->
{'
Value
'};
}
## @internal
# Code snippet to grab the accounting usage of a user for a specific period
sub
_getAccountingUsage
{
my
(
$server
,
$user
,
$packet
,
$period
)
=
@_
;
foreach
my
$module
(
@
{
$server
->
{'
module_list
'}})
{
# Do we have the correct plugin?
if
(
defined
(
$module
->
{'
Accounting_getUsage
'}))
{
$server
->
log
(
LOG_INFO
,"
[MOD_FEATURE_FUP] Found plugin: '
"
.
$module
->
{'
Name
'}
.
"
'
");
# Fetch users session uptime & bandwidth used for a specific period
if
(
my
$res
=
$module
->
{'
Accounting_getUsage
'}(
$server
,
$user
,
$packet
,
$period
))
{
return
$res
;
}
$server
->
log
(
LOG_ERR
,"
[MOD_FEATURE_FUP] No usage data found for user '
"
.
$user
->
{'
Username
'}
.
"
'
");
}
}
return
;
}
## @internal
# Code snippet to log our FUP information
sub
_logUsage
{
my
(
$server
,
$period
,
$total
,
$threshold
)
=
@_
;
$server
->
log
(
LOG_INFO
,"
[MOD_FEATURE_FUP] FUP information [period: %s days, total: %s, threshold: %s]
",
$period
,
$total
,
$threshold
);
return
;
}
## @internal
# Function snippet to return a attribute
sub
_getAttribute
{
my
(
$server
,
$user
,
$attributeName
)
=
@_
;
# Check the attribute exists
return
if
(
!
defined
(
$user
->
{'
Attributes
'}
->
{
$attributeName
}));
$server
->
log
(
LOG_DEBUG
,"
[MOD_FEATURE_CAPPING] User attribute '
"
.
$attributeName
.
"
' is defined
");
# Check the required operator is present in this case :=
if
(
!
defined
(
$user
->
{'
Attributes
'}
->
{
$attributeName
}
->
{'
:=
'}))
{
$server
->
log
(
LOG_NOTICE
,"
[MOD_FEATURE_CAPPING] User attribute '
"
.
$attributeName
.
"
' has no ':=' operator
");
return
;
}
# Check the operator value is defined...
if
(
!
defined
(
$user
->
{'
Attributes
'}
->
{
$attributeName
}
->
{'
:=
'}
->
{'
Value
'}))
{
$server
->
log
(
LOG_NOTICE
,"
[MOD_FEATURE_CAPPING] User attribute '
"
.
$attributeName
.
"
' has no value
");
return
;
}
return
$user
->
{'
Attributes
'}
->
{
$attributeName
}
->
{'
:=
'}
->
{'
Value
'};
}
1
;
# vim: ts=4
smradiusd.conf
View file @
bcaa1e81
...
...
@@ -163,6 +163,7 @@ mod_feature_capping
mod_feature_user_stats
mod_feature_update_user_stats_sql
mod_feature_validity
mod_feature_fup
EOT
...
...
t/200-dbtests.t
View file @
bcaa1e81
...
...
@@ -752,8 +752,6 @@ if ($child = fork()) {
my
$session3_ID
=
9858240
;
my
$session3_Timestamp
=
time
();
my
$session3_Timestamp_str
=
DateTime
->
from_epoch
(
epoch
=>
$session3_Timestamp
,
time_zone
=>
'
UTC
')
->
strftime
('
%Y-%m-%d %H:%M:%S
');
$res
=
smradius::
client
->
run
(
"
--raddb
","
dicts
",
...
...
@@ -811,6 +809,100 @@ if ($child = fork()) {
);
#
# Check that if we send an accounting ALIVE we trigger the FUP
#
my
$user5_ID
=
testDBInsert
("
Create user 'testuser5'
",
"
INSERT INTO users (UserName,Disabled) VALUES ('testuser5',0)
"
);
my
$user5attr1_ID
=
testDBInsert
("
Create user 'testuser5' attribute 'User-Password'
",
"
INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)
",
$user5_ID
,'
User-Password
','
==
','
test456
'
);
my
$user5attr2_ID
=
testDBInsert
("
Create user 'testuser5' attribute 'SMRadius-FUP-Period'
",
"
INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)
",
$user5_ID
,'
SMRadius-FUP-Period
','
:=
','
1
'
);
my
$user5attr3_ID
=
testDBInsert
("
Create user 'testuser5' attribute 'SMRadius-FUP-Traffic-Threshold'
",
"
INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)
",
$user5_ID
,'
SMRadius-FUP-Traffic-Threshold
','
:=
',
800
);
# Add an attribute so we can check the FUP match results
my
$user5attr4_ID
=
testDBInsert
("
Create user 'testuser5' attribute 'SMRadius-Evaluate'
",
"
INSERT INTO user_attributes (UserID,Name,Operator,Value,Disabled) VALUES (?,?,?,?,0)
",
$user5_ID
,'
SMRadius-Evaluate
','
||+=
',"
SMRadius_FUP > 0 ? [14988:Mikrotik-Rate-Limit] = 1638k/8m
"
);
my
$session4_ID
=
9858240
;
my
$session4_Timestamp
=
time
()
-
3600
;
$res
=
smradius::
client
->
run
(
"
--raddb
","
dicts
",
"
127.0.0.1
",
"
acct
",
"
secret123
",
'
User-Name=testuser5
',
'
NAS-IP-Address=10.0.0.1
',
'
Acct-Delay-Time=12
',
'
NAS-Identifier=Test-NAS2
',
'
Acct-Status-Type=Interim-Update
',
'
Acct-Output-Packets=786933
',
'
Acct-Output-Gigawords=0
',
'
Acct-Output-Octets=708163705
',
'
Acct-Input-Packets=670235
',
'
Acct-Input-Gigawords=0
',
'
Acct-Input-Octets=102600046
',
'
Acct-Session-Time=800
',
'
Event-Timestamp=
'
.
$session4_Timestamp
,
'
Framed-IP-Address=10.0.1.1
',
'
Acct-Session-Id=
'
.
$session4_ID
,
'
NAS-Port-Id=wlan1
',
'
Called-Station-Id=testservice2
',
'
Calling-Station-Id=00:00:0C:EE:47:BF
',
'
NAS-Port-Type=Ethernet
',
'
NAS-Port=15729175
',
'
Framed-Protocol=PPP
',
'
Service-Type=Framed-User
',
);
is
(
ref
(
$res
),"
HASH
","
smradclient should return a HASH
");
my
$session4_Timestamp2
=
time
();
$res
=
smradius::
client
->
run
(
"
--raddb
","
dicts
",
"
127.0.0.1
",
"
acct
",
"
secret123
",
'
User-Name=testuser5
',
'
NAS-IP-Address=10.0.0.1
',
'
Acct-Delay-Time=8
',
'
NAS-Identifier=Test-NAS2
',
'
Acct-Status-Type=Interim-Update
',
'
Acct-Output-Packets=700000
',
'
Acct-Output-Gigawords=0
',
'
Acct-Output-Octets=850000000
',
'
Acct-Input-Packets=100000
',
'
Acct-Input-Gigawords=0
',
'
Acct-Input-Octets=100000000
',
'
Acct-Session-Time=1000
',
'
Event-Timestamp=
'
.
$session4_Timestamp2
,
'
Framed-IP-Address=10.0.1.1
',
'
Acct-Session-Id=
'
.
$session4_ID
,
'
NAS-Port-Id=wlan1
',
'
Called-Station-Id=testservice2
',
'
Calling-Station-Id=00:00:0C:EE:47:BF
',
'
NAS-Port-Type=Ethernet
',
'
NAS-Port=15729175
',
'
Framed-Protocol=PPP
',
'
Service-Type=Framed-User
',
);
is
(
ref
(
$res
),"
HASH
","
smradclient should return a HASH
");
sleep
(
5
);
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment