Menu

Kicking a user when Rd-Data-Total is exceeded

Help
2014-09-02
2017-01-12
  • Robert Gratwick

    Robert Gratwick - 2014-09-02

    Firstly, thank you Dirk for such a useful piece of software.

    I just started exploring it's features and I have two questions as to whether some functionality exists or I have misconfigured RadiusDesk.

    Q1
    Say I have a data cap plan of 10Mb (Rd-Data-Total:=10000000) with a monthly reset, I have set the reply attribute 'Acct-Interim-Interval:=120', and I get those accounting updates in the radius log.

    When a logged in user exceeds the 10Mb quota I would expect the user to be kicked off.
    If the user logs off and logins they get a Reject Response because the quota is exceeded, which is expected behaviour.
    My NAS understands COA/POD packets and I have tested it with the 'kick user off' function in the Activity Monitor dialog.
    Do I need to configure something else to ensure a logged in user is kicked off when Rd-Data-Total is exceeded?

    Q2
    Following on from the last question, how do I set it up so that when a user is kicked off for exceeding the quota, instead of totally rejecting them I would like to instead reply with reduced WISPr-Bandwidth-Up/Down values to throttle the account until the monthly reset comes around.

    Edit:
    It seems one way to throttle the speed for a user that has exceed a quota is to modify check_usage_data.pl in the following way

    sub authorise {
    ...
    if($RAD_CHECK{'Rd-Avail-Data'} <= 0){
            if($RAD_CHECK{'Rd-Reset-Type-Data'} ne 'never'){
               #$RAD_REPLY{'Reply-Message'} = "Maximum $RAD_CHECK{'Rd-Reset-Type-Data'} usage exceeded";
            $RAD_REPLY{'WISPr-Bandwidth-Max-Down'} = 400000;
            $RAD_REPLY{'WISPr-Bandwidth-Max-Up'} = 200000;
            $RAD_REPLY{'Reply-Message'} = "Maximum usage exceeded - Service is now slowed and will reset on day $RAD_CHECK{'Rd-Reset-Day'} of month ";
            }else{
                $RAD_REPLY{'Reply-Message'} = "Maximum usage exceeded";
            }
       #return RLM_MODULE_REJECT;
        return RLM_MODULE_UPDATED;
        }
    ...
    

    This snip is rough and ready just for testing. To make it more configurable I could add two new attributes; Rd-Throttled-Up and Rd-Throttled-Down and test those attributes exist before splitting down the default code path or the new feature path. Would this be a useful patch to contribute?

    Thanks

     

    Last edit: Robert Gratwick 2014-09-03
  • Robert Gratwick

    Robert Gratwick - 2014-09-03

    Here's my attempt at solving my first question.
    To check if quota is exceeded on accounting updates and kick the user.

    /etc/raddb/sites-avaiable/default

    accounting {
    ...
    RADIUSdesk_acct_kick
    ...
    }
    

    /etc/raddb/policy.conf

    RADIUSdesk_acct_kick {
       sql  #If the sql data sourced fine; we can do tests for the presence of the following special attributes
      if(ok){
            if(("%{control:Rd-Total-Data}")&&("%{control:Rd-Reset-Type-Data}")&&("%{control:Rd-Cap-Type-Data == 'hard'}")){
        #       pl_reset_time_for_data
      #     if(updated){ # Reset Time was updated,
      #        update control {
      #             Rd-Used-Data := "%{sql:SELECT IFNULL(SUM(acctinputoctets - GREATEST((%{control:Rd-Start-Time} - UNIX_TIMESTAMP(acctstarttime)), 0))+ SUM(acctoutputoctets -GREATEST((%{control:Rd-Start-Time} - UNIX_TIMESTAMP(acctstarttime)), 0)),0) FROM radacct WHERE callingstationid='%{request:User-Name}' AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%{control:Rd-Start-Time}'}"
      #     }
      #     if(exists($RAD_CHECK{'Rd-Total-Data'}) && exists($RAD_CHECK{'Rd-Used-Data'})){
      #         $RAD_CHECK{'Rd-Avail-Data'} = $RAD_CHECK{'Rd-Total-Data'} - $RAD_CHECK{'Rd-Used-Data'};
        #       }   
        #       if($RAD_CHECK{'Rd-Avail-Data'} <= 0){   #   user is over quota - send disconnect    
        #           update disconnect {
        #               User-Name = "%{User-Name}"
        #               Acct-Session-Id = "%{Acct-Session-Id}"
        #               NAS-IP-Address = "%{NAS-IP-Address}"
        #           }
        #       }
            }
        #}
        }   
    }
    

    The output log shows I'm not getting past first base, the control attributes are not evaluating to anything

    +++[sql] = ok
    +++? if (ok)
    ? Evaluating (ok) -> TRUE
    +++? if (ok) -> TRUE
    +++if (ok) {
    ++++? if (("%{control:Rd-Total-Data}")&&("%{control:Rd-Reset-Type-Data}")&&("%{control:Rd-Cap-Type-Data == 'hard'}"))
        expand: %{control:Rd-Total-Data} -> 
    ?? Evaluating ("%{control:Rd-Total-Data}") -> FALSE
    ?? Skipping ("%{control:Rd-Reset-Type-Data}")
    ?? Skipping ("%{control:Rd-Cap-Type-Data == 'hard'}")
    ++++? if (("%{control:Rd-Total-Data}")&&("%{control:Rd-Reset-Type-Data}")&&("%{control:Rd-Cap-Type-Data == 'hard'}")) -> FALSE
    +++} # if (ok) = ok
    ++} # policy RADIUSdesk_acct_kick = ok
    

    Any hints to solve this problem?

     
  • Dirk van der Walt

    Hi Robert,

    I have to answer before my PM nap......

    Answer to the first Q. You can do a 'Test RADIUS' when selecting the user / voucher and see the exact reply attributes that the FreeRADIUS gives back to the NAS after successful authentication.

    Confirm that:

    1.) The required attributes are in fact returned in the dummy test.
    2.) Your NAS do actually listen and support those attributes with it's RADIUS implementation.
    3.) If you use a NAS like PF-Sense they (last time I checked) do not support some reply attributes. This is why you have to configure it to 're-auth' in the background to cut a user off once the profile's cap was reached.

    The second Q was discussed or queried previously. Your rough take in it is definately in the right direction.

    The only thing will be to force a disconnect or kick off a COA.... that can be a bit tricky....

    Let us know what you find.

    Cheers

     
    • Robert Gratwick

      Robert Gratwick - 2014-09-06

      Hi Dirk,

      Thanks for the heads up on the Radius Client test feature.

      To clarify my equipment. I'm using a Ubiquiti EdgeMax as my NAS and I have tested that it responds as expected to an Acct-Interim-Interval reply attribute and disconnect COA packets.

      Taking a step back to simplify the problem.

      How do I get check attributes like Rd-Total-Data and Rd-Reset-Type-Data to be sourced for use in the accounting section.

      in /etc/raddb/sites-available/default I add a call to my test function in the accounting section

      accounting {
      ...
          RADIUSdesk_acct_kick
      ...
      }
      

      in /etc/raddb/policy.conf I add my test function

      RADIUSdesk_acct_kick {
          pl_reset_time_for_data
          if(update){
              if("%{control:Rd-Avail-Data}" <= 0){
              # do nothing yet just check the log to see if Rd-Avail-Data is resolved 
              }
          }
      }
      

      in reset_time_fordata.pl I add an accounting subroutine

      sub accounting {
          if(exists($RAD_CHECK{'Rd-Total-Data'}) && exists($RAD_CHECK{'Rd-Used-Data'})){
              $RAD_CHECK{'Rd-Avail-Data'} = $RAD_CHECK{'Rd-Total-Data'} - $RAD_CHECK{'Rd-Used-Data'};
              return RLM_MODULE_UPDATED;
          }else{
              return RLM_MODULE_NOOP;
          }
      
          #$RAD_CHECK{'Rd-Avail-Data'} = 1234;
          #return RLM_MODULE_UPDATED;   
      }
      

      This is the result in the log

      ++++[pl_reset_time_for_data] = noop
      ++++? if (update)
      ? Evaluating (update) -> TRUE
      ++++? if (update) -> TRUE
      ++++if (update) {
      +++++? if ("%{control:Rd-Avail-Data}" <= 0)
          expand: %{control:Rd-Avail-Data} -> 
      ? Evaluating ("%{control:Rd-Avail-Data}" <= 0) -> TRUE
      +++++? if ("%{control:Rd-Avail-Data}" <= 0) -> TRUE
      +++++if ("%{control:Rd-Avail-Data}" <= 0) { ... } # empty sub-section is ignored
      ++++} # if (update) = noop
      +++} # if (ok) = noop
      ++} # policy RADIUSdesk_acct_kick = ok
      

      you see the noop because $RAD_CHECK{'Rd-Total-Data'} etc don't resolve

      just to check the idea works, I hardcode a Rd-Avail-Data value in accounting

      sub accounting {
      
      #   if(exists($RAD_CHECK{'Rd-Total-Data'}) && exists($RAD_CHECK{'Rd-Used-Data'})){
       #       $RAD_CHECK{'Rd-Avail-Data'} = $RAD_CHECK{'Rd-Total-Data'} - $RAD_CHECK{'Rd-Used-Data'};
        #      return RLM_MODULE_UPDATED;
         # }else{
          #    return RLM_MODULE_NOOP;
          #}
      
        $RAD_CHECK{'Rd-Avail-Data'} = 1234;
        return RLM_MODULE_UPDATED;    
      }
      

      logfile

      ++++[pl_reset_time_for_data] = updated
      ++++? if (update)
      ? Evaluating (update) -> TRUE
      ++++? if (update) -> TRUE
      ++++if (update) {
      +++++? if ("%{control:Rd-Avail-Data}" <= 0)
          expand: %{control:Rd-Avail-Data} -> 1234
      ? Evaluating ("%{control:Rd-Avail-Data}" <= 0) -> FALSE
      +++++? if ("%{control:Rd-Avail-Data}" <= 0) -> FALSE
      ++++} # if (update) = updated
      +++} # if (ok) = updated
      ++} # policy RADIUSdesk_acct_kick = updated
      ++[exec] = noop
      

      Here you see a real value is returned.

      In regard to the kick feature, I have tested something like this with success but it's messy, so I was trying to encapsulate the dogs breakfast in the 'if' statement into a module.

      accounting {
      ...
          if("%{sql:select value from radgroupcheck where attribute='Max-Octets' and groupname= (SELECT groupname FROM radusergroup WHERE username = '%{User-Name}' ORDER BY priority limit 1)}" <= "%{sql:SELECT (SUM(AcctInputOctets)+(AcctOutputOctets)) FROM radacct WHERE UserName='%{User-Name}'}"){
          #user is over quota
          #send disconnect    
                 update disconnect {
                      User-Name = "%{User-Name}"
                      Acct-Session-Id = "%{Acct-Session-Id}"
                      NAS-IP-Address = "%{NAS-IP-Address}"
                  }
          }
      ..
      }
      

      So what's the trick to sourcing the user check attributes within the accounting section?
      Cheers
      Robert

       
  • Dirk van der Walt

    Hi Robert,

    I've did a bit of googleing on the Edgemax and it seems this page list the supported AVPs

    https://community.ubnt.com/t5/EdgeMAX-Updates-Blog/EdgeMax-software-release-v1-3-0/ba-p/591711

    You thus have to ensure that your counter reply with a value for:

    Session-Octets-Limit

    In order to enforce the cap on the session.

    Here's a bit more on the counters:

    http://www.radiusdesk.com/technical_discussions/principle_profile

    On the COA in the accounting I'll have to come back to you, but as far as i recall it is not that simple to fetch the auth detail in the acct section since FreeRADIUS sort of does it behind the scenes (differently for each one).

    Cheers

     
    • Fabrizio Lazzaretti

      Hi Dirk,

      the page regarding priciple profile is missing from technical discussion page.
      Fabrizio

       
  • Robert Gratwick

    Robert Gratwick - 2014-09-06

    Hi Dirk,

    Yes the Session-Octets-Limit is a good idea but ... http://community.ubnt.com/t5/EdgeMAX/EdgeMax-not-sending-Accounting-stop-packet-when-using-Session/m-p/972527#M39828

    In summary the Egdemax has two issues; it has a 2Gb (32bit) limit on the size of the Session-Octets-Limit attribute, and it doesn't send an Accounting Stop packet when the limit is exceeded. So I can't use that attribute.

    Maybe I'm going about this the wrong way. Others must have come across the problem of a users quota being exceeded in a session, and as long as they don't log off they can continue forever at the same bandwidth.

    It would be interesting to know how Freeradius does get the auth details behind the scenes.

    I keep searching, maybe post to the Freeradius list. Gulp ;-)
    Robert

     
  • Robert Gratwick

    Robert Gratwick - 2014-09-11

    The setup below will kick a user off if quota is exceeded after receiving an Accounting update packet and throttle the users bandwidth on subsequent logins until the roll-over reset period.

    I'm using a Ubiquiti Edgemax as my NAS. See the various threads above as to how I got to this point for more background.

    To start
    1. Data usage profile component needs two new check attributes configured; Rd-Throttled-Up and Rd-Throttled-Down.
    2. User needs a new private check attribute; Rd-Throttled.
    3. Data usage or bandwidth profile component needs a Acct-Interim-Interval reply attribute set.
    4. When creating a plan(User-Profile) component ensure the bandwidth profile component has a high priority than the Data cap profile component. (eg 100, 80)

    I initially went down the track of trying to source all the users attributes for use in the accounting section.
    However I couldn't workout how to do that, I later determined that it would perhaps not be very efficient to source all of the users attributes every Acct-Interim-Internal period for each user.
    In the code below I just query for the attributes I need.

    Improvements
    SQL querys are not my strength, I'm sure this can be improved.
    In check_usage_data.pl I do something a bit odd, I use $RAD_CHECK{'Rd-Throttled'} as a return variable.
    Instead of sending a COA disconnect I could send an update.
    It's my first play with Freeradius/RaduisDesk and I may have missed some key concepts and best practices along the way. As a first-cut this achieves what I need for now. Any pointers will be gladly accepted.

    Dirk, you may incorporate any of this into RadiusDesk, if it's useful.

    Add three new attributes to dictionary
    /etc/raddb/dictionary

    ATTRIBUTE Rd-Throttled-Down     3147 integer    #user private attribute, when throttled this is the down bandwidth
    ATTRIBUTE Rd-Throttled-Up           3148 integer    #user private attribute, when throttled this is the up bandwith
    ATTRIBUTE Rd-Throttled      3149 integer    #user private attribute -set to 1 if user is throttled, 0 not throttled
    

    /etc/raddb/policy.conf

    RADIUSdesk_acct_kick {
    
            # if it's already slowed don't both to check anymore
            if("%{sql:Select value from radcheck where attribute='Rd-Throttled' and username= '%{User-Name}'}" == 0){
    
                # source attributes for use in reset_time_for_data.pl
                update control {
                    Rd-Reset-Type-Data := "%{sql:Select value from radgroupcheck where attribute='Rd-Reset-Type-Data' and groupname=(select groupname from radusergroup where username=(SELECT value FROM radcheck WHERE attribute='User-Profile' and username = '%{User-Name}' limit 1) order by  priority limit 1)}"
                    Rd-Reset-Day := "%{sql:Select value from radcheck where attribute='Rd-Reset-Day' and username = '%{User-Name}'}"
                }
    
                # works out Rd-Start-Time for Rd-Used-Data query below
                pl_reset_time_for_data
    
                update control {
                    Rd-Total-Data := "%{sql:Select value from radgroupcheck where attribute='Rd-Total-Data' and groupname=(select groupname from radusergroup where username=(SELECT value FROM radcheck WHERE attribute='User-Profile' and username = '%{User-Name}' limit 1) order by  priority limit 1)}"
                    Rd-Used-Data := "%{sql:SELECT IFNULL(SUM(acctinputoctets - GREATEST((%{control:Rd-Start-Time} - UNIX_TIMESTAMP(acctstarttime)), 0))+ SUM(acctoutputoctets -GREATEST((%{control:Rd-Start-Time} - UNIX_TIMESTAMP(acctstarttime)), 0)),0) FROM radacct WHERE username='%{request:User-Name}' AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%{control:Rd-Start-Time}'}"
                }
    
                # work out amount used, amount in plan, find difference
                pl_check_usage_data
    
                if(update){
                    if(control:Rd-Avail-Data <= 0){
                        update disconnect {
                            User-Name = "%{User-Name}"
                            Acct-Session-Id = "%{Acct-Session-Id}"
                            NAS-IP-Address = "%{NAS-IP-Address}"
                        }
                    }
                }
            }
    
            ...
    
             RADIUSdesk_data_counter {
                ...
    
                #Now we know how much they are allowed to use and the usage.
                pl_check_usage_data
                if("%{control:Rd-Throttled}" == 1){
                    "%{sql:update `radcheck` set value= 1 where username='%{User-Name}' and attribute='Rd-Throttled'}"
                }
                else{
                    "%{sql:update `radcheck` set value= 0 where username='%{User-Name}' and attribute='Rd-Throttled'}"
                }
    
             }
    

    /etc/raddb/reset_time_for_data.pl

    sub accounting {
    
        if($RAD_CHECK{'Rd-Reset-Type-Data'} =~ /never/i){ #Return immediately 
            return RLM_MODULE_NOOP;
        }
    
        if($RAD_CHECK{'Rd-Reset-Type-Data'} =~ /monthly/i){
            $RAD_CHECK{'Rd-Start-Time'} = start_of_month()
        }
        if($RAD_CHECK{'Rd-Reset-Type-Data'} =~ /weekly/i){
            $RAD_CHECK{'Rd-Start-Time'} = start_of_week()
        }
        if($RAD_CHECK{'Rd-Reset-Type-Data'} =~ /daily/i){
            $RAD_CHECK{'Rd-Start-Time'} = start_of_day()
        }
    
        #Add a dynamic reset interval where the start time will be now minus the interval
        if($RAD_CHECK{'Rd-Reset-Type-Data'} =~ /dynamic/i){
            my $dyn_start = dynamic_start();
            if($dyn_start != 0){
                $RAD_CHECK{'Rd-Start-Time'} = $dyn_start;
            }
        }
    
        if(exists($RAD_CHECK{'Rd-Start-Time'})){
            return RLM_MODULE_UPDATED;    
        }else{
            return RLM_MODULE_NOOP;
        }
    
    }
    

    /etc/raddb/check_usage_data.pl

    sub authorize {
        ...
        if($RAD_CHECK{'Rd-Avail-Data'} <= 0){
                if(exists($RAD_CHECK{'Rd-Throttled-Up'}) && exists($RAD_CHECK{'Rd-Throttled-Down'})){
                    $RAD_REPLY{'WISPr-Bandwidth-Max-Down'} = $RAD_CHECK{'Rd-Throttled-Down'};
                    $RAD_REPLY{'WISPr-Bandwidth-Max-Up'} = $RAD_CHECK{'Rd-Throttled-Up'};
                    $RAD_REPLY{'Reply-Message'} = "Maximum usage exceeded - Service is now slowed and will reset on day $RAD_CHECK{'Rd-Reset-Day'} of month ";              
                    $RAD_CHECK{'Rd-Throttled'} = 1; # odd bit, I just need to return some flag, this is not setting the Rd-Throttled attribute here, that's done in policy.conf         
                    return RLM_MODULE_UPDATED;
              }else{
                if($RAD_CHECK{'Rd-Reset-Type-Data'} ne 'never'){
                      $RAD_REPLY{'Reply-Message'} = "Maximum $RAD_CHECK{'Rd-Reset-Type-Data'} usage exceeded";
                    }else{
                    $RAD_REPLY{'Reply-Message'} = "Maximum usage exceeded";
                    }
                return RLM_MODULE_REJECT;
              }
            }
    
            #If inside quota unset slowed check flag
            $RAD_CHECK{'Rd-Throttled'} = 0;
    
            #Set the Rd-Tmp-Avail-Data if it is not already set
        ...
    }
    
    sub accounting {
    
        if(exists($RAD_CHECK{'Rd-Total-Data'}) && exists($RAD_CHECK{'Rd-Used-Data'})){
            $RAD_CHECK{'Rd-Avail-Data'} = $RAD_CHECK{'Rd-Total-Data'} - $RAD_CHECK{'Rd-Used-Data'};
    
            return RLM_MODULE_UPDATED;
        }else{
            return RLM_MODULE_NOOP;
        }
    
     }
    

    /etc/raddb/default

    accounting {
        ...
        RADIUSdesk_acct_kick
        ... 
    }
    

    Setup COA disconnect

    /etc/raddb/sites-available/originate-coa

    home_server edgemax_coa {
        type = coa
        ipaddr = 10.8.0.18
        port = 3799
        secret = testing123
        coa {
            irt = 2
            mrt = 16
            mrc = 5
            mrd = 30
        }
    }
    
    ...
    home_server_pool coa {
        type = fail-over
    
        # Point to the CoA server above.
        #home_server = localhost-coa
        home_server = edgemax_coa
        ...
    

    Sym. link

    cd /etc/raddb/sites-enabled
    sudo ln -s /etc/raddb/sites-available/originate-coa originate-coa
    

    Debugging tips for beginners ...me

    Test your NAS does something with disconnect packets
    echo "User-Name=user.name" | radclient -r 1 10.8.0.18:3799 disconnect "testing123"
    On NAS 'tail -f /var/log/pppoe-radius-disconnect.log'

    'radtest user.name password localhost 0 testing123' is useful to see response packets
    (eg is it slowed when quota is exceeded and if it switches back to normal)

    'radclient localhost auto testing123 -f acct_update.txt' is useful to send accounting packets.

    Read Dirks book "Freeradius Beginners guide"

     
  • Dirk van der Walt

    Hi Robert,

    Sometimes it is good when one is slow to answer on the forum. It forces people to invent creative solutions!

    Someone was just asking today about if this is possible.

    Many many thanks for sharing this with the community. I'm sure more will have a use for this.

    As time permits I'll definitely use this as the content of a Technical discussion page on the RADIUSdesk website.

    ....I suppose the fear of getting a tongue-lashing can also be a way to become creative ;-)!!!

    Cheers

     
  • ryan low

    ryan low - 2015-05-27

    Hi Robert, Thank you very much for your sharing and you solved the question that i always look for. But i did a small modification:

    Rd-Total-Data := "%{sql:Select value from radgroupcheck where attribute='Rd-Total-Data' and groupname=(select groupname from radusergroup where username=(SELECT value FROM radcheck WHERE attribute='User-Profile' and username = '%{User-Name}' limit 1) order by priority limit 1)}"

    to

    Rd-Total-Data := "%{sql:Select value from radcheck where attribute='Rd-Total-Data' and username='%{request:User-Name}'}"

    reason is Rd-Total-Data is user based which is in radcheck. radgroupcheck return null to me.

    Next,
    I integrate this concept to topup concept. Which when your quota is finish, coa will a kick you out. next login you will get throttled speed. Then, if you top your quota, you will be kick out again. To refresh your speed.

    Due to quota counter reason, when you are in throttled speed. the account in sql is not recorded.

     
  • Clive Cliff

    Clive Cliff - 2017-01-12

    Hi,

    Am using a Mikrotik Routerboard RB 3011 as my NAS. When the data is capped, the user connects and when the data allocation gets used up, the user is not being disconnected.

    Please assist

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.