Netgate Discussion Forum
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Search
    • Register
    • Login

    Bug in captive portal accounting with multiple radius servers

    Scheduled Pinned Locked Moved Captive Portal
    3 Posts 2 Posters 3.1k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • L
      lsoltero
      last edited by

      Hello All,

      both pfSense 1.2.3 and 2.0 captive portal support authentication against multiple radius servers.  We are currently running pfSense 1.2.3-RELEASE but the following argument also applies to 2.0.

      under the captive portal authentication section in the webadmin admins have the option of entering 1 or 2 radius servers against which to authenticate.  Admins are also given the option to authenticate every minute and also to send accounting updates.  When configuring for interim accounting updates we notice that only the primary radius ever received accounting updates while authentication can happen at either radius sever.  In otherwords… if the primary radius server goes down authentication continues to happen on the secondary radius sever ** BUT ** accounting updates are never sent to this second server.  Once the primary server comes back on line then accounting updates resume.

      Looking at the captive portal code the reason is obvious.  Here is the prototype for radius authentication
      function RADIUS_AUTHENTICATION($username,$password,$radiusservers,$clientip,$clientmac,$ruleno) {

      the function takes an array of radiusservers as an argument.  Up to 10 radius servers can be configured.  The radius code uses round-robin to access the servers starting with the first one. So... if the primary fails the code goes on to the next server.  The code tries 3 times to access the server with a timeout of 3 seconds. So if in 9 seconds there is no response from the radius server the client moves on to the next.

      Here is the prototype for the authentication function
      function RADIUS_ACCOUNTING_START($ruleno,$username,$sessionid,$radiusip,$radiusport,$radiuskey,$clientip,$clientmac) {

      Notice that this function only takes one radius server as an argument!  Unlike radius authentication the radius accounting function only supports one host.

      Looking at the code in /usr/local/captiveportal/index.php and in /etc/inc/captiveportal.inc we find

      index.php...

      if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
                  $acct_val = RADIUS_ACCOUNTING_START($ruleno,
                                                                  $username,
                                                                  $sessionid,
                                                                  $radiusservers[0]['ipaddr'],
                                                                  $radiusservers[0]['acctport'],
                                                                  $radiusservers[0]['key'],
                                                                  $clientip,
                                                                  $clientmac);

      captiveportal.inc …

      /* do periodic RADIUS reauthentication? */
              if (!$timedout && isset($config['captiveportal']['reauthenticate']) &&
                  ($radiusservers !== false)) {

      if (isset($config['captiveportal']['radacct_enable'])) {
                      if ($config['captiveportal']['reauthenticateacct'] == "stopstart") {
                          /* stop and restart accounting */
                          RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
                                                $cpdb[$i][4], // username
                                                $cpdb[$i][5], // sessionid
                                                $cpdb[$i][0], // start time
                                                $radiusservers[0]['ipaddr'],
                                                $radiusservers[0]['acctport'],
                                                $radiusservers[0]['key'],
                                                $cpdb[$i][2], // clientip
                                                $cpdb[$i][3], // clientmac
                                                10); // NAS Request
                          exec("/sbin/ipfw zero {$cpdb[$i][1]}");
                          RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
                                                  $cpdb[$i][4], // username
                                                  $cpdb[$i][5], // sessionid
                                                  $radiusservers[0]['ipaddr'],
                                                  $radiusservers[0]['acctport'],
                                                  $radiusservers[0]['key'],
                                                  $cpdb[$i][2], // clientip
                                                  $cpdb[$i][3]); // clientmac
                      } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
                          RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
                                                $cpdb[$i][4], // username
                                                $cpdb[$i][5], // sessionid
                                                $cpdb[$i][0], // start time
                                                $radiusservers[0]['ipaddr'],
                                                $radiusservers[0]['acctport'],
                                                $radiusservers[0]['key'],
                                                $cpdb[$i][2], // clientip
                                                $cpdb[$i][3], // clientmac
                                                10, // NAS Request
                                                true); // Interim Updates
                      }
                  }

      /* check this user against RADIUS again */
                  $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
                                                base64_decode($cpdb[$i][6]), // password
                                                  $radiusservers,
                                                $cpdb[$i][2], // clientip
                                                $cpdb[$i][3], // clientmac
                                                $cpdb[$i][1]); // ruleno

      note that accounting data is only sent to $radiusservers[0]['ipaddr'] i.e. the primary RADIUS sever.

      The obvious solution is to modify RADIUS_ACCOUNTING_START/STOP to accept an array of radius servers in the same way that RADIUS_AUTHENTICATION does.

      Here is the code in radius_authentication.inc for RADIUS_AUTHENTICATION()  supporting multiple servers

      /*
          Add support for more then one radiusserver.
          At most 10 servers may be specified.
          When multiple servers are given, they are tried in round-robin fashion until a valid response is received
          */

      foreach ($radiusservers as $radsrv) {

      // Add a new server to our instance
              $rauth->addServer($radsrv['ipaddr'], $radsrv['port'], $radsrv['key']);

      }

      here is the code in radius_accounting.inc for RADIUS_ACCOUNTING_START()

      // Create our instance
          $racct = new Auth_RADIUS_Acct_Start;

      // Construct data package
          $racct->username = $username;
          $racct->addServer($radiusip, $radiusport, $radiuskey);

      it seems that since  Anth_RADIUS_Acct_Start is derived from Auth_RADIUS that the same

      addServer scheme can be used for accounting as for authentication.  Here is the addServer method in Auth_RADIUS which supports multiple RADIUS servers.

      /**
          * Adds a RADIUS server to the list of servers for requests.
          *
          * At most 10 servers may be specified.    When multiple servers
          * are given, they are tried in round-robin fashion until a
          * valid response is received
          *
          * @access public
          * @param  string  $servername  Servername or IP-Address
          * @param  integer $port        Portnumber
          * @param  string  $sharedSecret Shared secret
          * @param  integer $timeout      Timeout for each request
          * @param  integer $maxtries    Max. retries for each request
          * @return void
          */
          function addServer($servername = 'localhost', $port = 0, $sharedSecret = 'testing123', $timeout = 5, $maxtries = 3)
          {
              $this->_servers[] = array($servername, $port, $sharedSecret, $timeout, $maxtries);
          }

      I will hack up the code in the next few days to see if I can get this to work…

      Not sending accounting updates to the secondary/backup RADIUS server is a pretty serious omission if the the accounting info is being used as part of the authentication process.

      --luis

      1 Reply Last reply Reply Quote 0
      • L
        lsoltero
        last edited by

        The following patches fix the captive portal so that it uses multiple radius servers for accounting.

        –- captiveportal.inc 2010-12-25 00:22:13.000000000 +0000
        +++ captiveportal.inc.new 2011-01-02 08:28:20.000000000 +0000
        @@ -540,9 +562,7 @@
                                                    $cpdb[$i][4], // username
                                                    $cpdb[$i][5], // sessionid
                                                    $cpdb[$i][0], // start time
        -                                          $radiusservers[0]['ipaddr'],
        -                                          $radiusservers[0]['acctport'],
        -                                          $radiusservers[0]['key'],
        +                                          $radiusservers,
                                                    $cpdb[$i][2], // clientip
                                                    $cpdb[$i][3], // clientmac
                                                    10); // NAS Request
        @@ -550,9 +570,7 @@
                            RADIUS_ACCOUNTING_START($cpdb[$i][1], // ruleno
                                                    $cpdb[$i][4], // username
                                                    $cpdb[$i][5], // sessionid
        -                                            $radiusservers[0]['ipaddr'],
        -                                            $radiusservers[0]['acctport'],
        -                                            $radiusservers[0]['key'],
        +                                            $radiusservers,
                                                    $cpdb[$i][2], // clientip
                                                    $cpdb[$i][3]); // clientmac
                        } else if ($config['captiveportal']['reauthenticateacct'] == "interimupdate") {
        @@ -560,9 +578,7 @@
                                                    $cpdb[$i][4], // username
                                                    $cpdb[$i][5], // sessionid
                                                    $cpdb[$i][0], // start time
        -                                          $radiusservers[0]['ipaddr'],
        -                                          $radiusservers[0]['acctport'],
        -                                          $radiusservers[0]['key'],
        +                                          $radiusservers,
                                                    $cpdb[$i][2], // clientip
                                                    $cpdb[$i][3], // clientmac
                                                    10, // NAS Request
        @@ -573,7 +589,7 @@
                    /* check this user against RADIUS again */
                    $auth_list = RADIUS_AUTHENTICATION($cpdb[$i][4], // username
                                                  base64_decode($cpdb[$i][6]), // password
        -                                            $radiusservers,
        +                                          $radiusservers,
                                                  $cpdb[$i][2], // clientip
                                                  $cpdb[$i][3], // clientmac
                                                  $cpdb[$i][1]); // ruleno
        @@ -605,9 +621,7 @@
          $dbent[4], // username
          $dbent[5], // sessionid
          $dbent[0], // start time

        • $radiusservers[0]['ipaddr'],
        • $radiusservers[0]['acctport'],
        • $radiusservers[0]['key'],
        • $radiusservers,
            $dbent[2], // clientip
            $dbent[3], // clientmac
            $term_cause, // Acct-Terminate-Cause
          @@ -678,9 +692,7 @@
            $cpdb[$i][4], // username
            $cpdb[$i][5], // sessionid
            $cpdb[$i][0], // start time
        • $radiusservers[0]['ipaddr'],
        • $radiusservers[0]['acctport'],
        • $radiusservers[0]['key'],
        • $radiusservers,
            $cpdb[$i][2], // clientip
            $cpdb[$i][3], // clientmac
            7); // Admin Reboot
          @@ -844,7 +856,11 @@
                                          if ($line) {
                                                  $radsrv = array();
                                                  list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
          -                                        $radiusservers[] = $radsrv;
        • // make sure server is available before adding to the list
        • $stat = mwexec("ping -t 7 -oqc 3 -i 0.5 {$radsrv['ipaddr']} 2>1 ");
        • if ( $stat == 0 ) {
          +                                        $radiusservers[] = $radsrv;
        • }
                                          }
                                  }
                                  fclose($fd);

        Note that the ping is not required.  However, it makes things go a little faster since the captive portal would otherwise have to timeout numerous times to get the reports to the backup radius server.

        –- radius_accounting.inc 2011-01-02 07:38:16.000000000 +0000
        +++ radius_accounting.inc.new 2011-01-02 07:52:15.000000000 +0000
        @@ -43,7 +43,7 @@

        */

        -function RADIUS_ACCOUNTING_START($ruleno,$username,$sessionid,$radiusip,$radiusport,$radiuskey,$clientip,$clientmac) {
        +function RADIUS_ACCOUNTING_START($ruleno,$username,$sessionid,$radiusservers,$clientip,$clientmac) {

        global $config;

        @@ -81,7 +81,19 @@

        // Construct data package
            $racct->username = $username;
        -    $racct->addServer($radiusip, $radiusport, $radiuskey);
        +
        +    /*
        +    Add support for more then one radiusserver.
        +    At most 10 servers may be specified.
        +    When multiple servers are given, they are tried in round-robin fashion until a valid response is received
        +    */
        +   
        +    foreach ($radiusservers as $radsrv) {
        +   
        +        // Add a new server to our instance
        +        $racct->addServer($radsrv['ipaddr'], $radsrv['port'], $radsrv['key']);
        +   
        +    }

        if (PEAR::isError($racct->start())) {
                $retvalue['acct_val'] = 1;
        @@ -146,7 +158,7 @@
        –---------------------------
        */

        -function RADIUS_ACCOUNTING_STOP($ruleno,$username,$sessionid,$start_time,$radiusip,$radiusport,$radiuskey,$clientip,$clientmac, $term_cause = 1, $interimupdate=false,$stop_time = null) {
        +function RADIUS_ACCOUNTING_STOP($ruleno,$username,$sessionid,$start_time,$radiusservers,$clientip,$clientmac, $term_cause = 1, $interimupdate=false,$stop_time = null) {

        global $config;

        @@ -181,26 +193,25 @@
            else
                $racct = new Auth_RADIUS_Acct_Stop;

        +    // See RADIUS_ACCOUNTING_START for info
        +    $racct->authentic = RADIUS_AUTH_RADIUS;
        +
        +    // Construct data package
        +    $racct->username = $username;
        +
            /*
        -    * Currently disabled
        -    Add support for more then one radiusserver.
        -    At most 10 servers may be specified.
        -    When multiple servers are given, they are tried in round-robin fashion until a valid response is received
        +    Add support for more then one radiusserver.
        +    At most 10 servers may be specified.
        +    When multiple servers are given, they are tried in round-robin fashion until a valid response is received
        +    */

        foreach ($radiusservers as $radsrv) {

        + 
                // Add a new server to our instance
                $racct->addServer($radsrv['ipaddr'], $radsrv['port'], $radsrv['key']);

        + 
            }
        -    */

        -    // See RADIUS_ACCOUNTING_START for info
        -    $racct->authentic = RADIUS_AUTH_RADIUS;

        -    // Construct data package
        -    $racct->username = $username;
        -    $racct->addServer($radiusip, $radiusport, $radiuskey);
            // Set session_time
            $racct->session_time = $session_time;

        @@ -306,4 +317,4 @@

        }

        -?>
        \ No newline at end of file
        +?>

        –- index.php 2011-01-02 07:25:55.000000000 +0000
        +++ index.php.new 2011-01-02 07:28:52.000000000 +0000
        @@ -321,9 +321,7 @@
                    $acct_val = RADIUS_ACCOUNTING_START($ruleno,
                                                                    $username,
                                                                    $sessionid,
        -                                                            $radiusservers[0]['ipaddr'],
        -                                                            $radiusservers[0]['acctport'],
        -                                                            $radiusservers[0]['key'],
        +                                                            $radiusservers,
                                                                    $clientip,
                                                                    $clientmac);

        1 Reply Last reply Reply Quote 0
        • E
          eri--
          last edited by

          I belive you are looking only at 1.2.3 code!

          1 Reply Last reply Reply Quote 0
          • First post
            Last post
          Copyright 2025 Rubicon Communications LLC (Netgate). All rights reserved.