Hello Harrison -
Further to my previous mail, here is a patched version of
AddressAllocatorSQL.pm for you to test.
Please let me know if it works correctly and I will roll the fix into the
next release.
regards
Hugh
On Tuesday 21 August 2001 12:59, Harrison Ng wrote:
> > Hello,
>
> Does anyone know is it valid to include %{attribute-name} in AllocateQuery
> of <AddressAllocator SQL>.
> We've append a new column called MSISDN in RADPOOL to record
> Calling-Station-Id. Then add custom AllocateQuery as below.
> Under trace 4, it seems that radiator return empty value for
> %{Calling-Station-Id}.
> However we tried similar things in AuthSelect of <AuthBy SQL>,
> %{Calling-Station-Id} works properly.
> Can anyone give me a hint.
>
> Harrison
> SmarTone Mobile Communications Ltd.
>
>
>
>
>
>
> This is an extract of radius.cfg
> --------------------------------------------
>
> <AuthBy SQL>
>
> Identifier getpoolhint
> DBSource dbi:mysql:radius:10.25.157.33
> DBUsername dbuser1
> DBAuth netra323
>
> AuthSelect select POOLHINT from APN where ROAMDIGIT =
> left("%{Calling-Station-Id}",3)
> AuthColumnDef 0, PoolHint, reply
>
> AccountingTable ACCOUNTING
>
> AcctColumnDef USERNAME,User-Name
> AcctColumnDef CLASS,Class
> AcctColumnDef ACCTSESSIONID,Acct-Session-Id
> AcctColumnDef NASIPADDRESS,NAS-IP-Address
> AcctColumnDef ACCTSTATUSTYPE,Acct-Status-Type
> AcctColumnDef NASPORT,NAS-Port,integer
> AcctColumnDef ACCTAUTHENTIC,Acct-Authentic
> AcctColumnDef NASIDENTIFIER,NAS-Identifier
> AcctColumnDef FRAMEDPROTOCOL,Framed-Protocol
> AcctColumnDef CALLINGSTATIONID,Calling-Station-Id
> AcctColumnDef FRAMEDIPADDRESS,Framed-IP-Address
> AcctColumnDef TIMESTAMP,Timestamp,integer
>
> AcctFailedLogFileName %L/grad1.%c.missing.%Y%m%d
>
> </AuthBy SQL>
>
>
>
> <AddressAllocator SQL>
>
> Identifier myallocator
>
> DBSource dbi:mysql:radius:10.25.157.33
> DBUsername dbuser1
> DBAuth netra323
>
> AllocateQuery update RADPOOL set
> STATE=1,TIME_STAMP=%0,EXPIRY=%1,USERNAME='%2',MSISDN='%{Calling-Station-Id}
>' \
> where YIADDR='%3' and TIME_STAMP%4
>
> <AddressPool local>
> Subnetmask 255.255.255.0
> Range 202.140.74.11 202.140.74.20
> </AddressPool>
>
> <AddressPool roam>
> Subnetmask 255.255.255.0
> Range 10.25.155.11 10.25.155.20
> </AddressPool>
>
> </AddressAllocator SQL>
>
>
>
> This is radiator trace 4
> --------------------------------
>
> Tue Aug 21 10:48:09 2001: DEBUG: Packet dump:
> *** Received from 202.140.74.1 port 2859 ....
> Code: Access-Request
> Identifier: 199
> Authentic: <0><0>'_<0><0><7><249><0><0>bm<0><0><7>T
> Attributes:
> User-Name = "aaaa"
> User-Password =
> "A<228><31><204><205>(<27><166>-v<150>Z<220><180><207>U"
> NAS-IP-Address = 202.140.74.1
> Service-Type = Framed-User
> Framed-Protocol = PPP
> NAS-Port = 1
> Calling-Station-Id = "85298699517"
> NAS-Identifier = "radius"
>
> Tue Aug 21 10:48:09 2001: DEBUG: Check if Handler Client-Id =
> 202.140.74.1,NAS-Identifier = "radius" should be used to handle this
> request Tue Aug 21 10:48:09 2001: DEBUG: Handling request with Handler
> 'Client-Id = 202.140.74.1,NAS-Identifier = "radius"'
> Tue Aug 21 10:48:09 2001: DEBUG: simultaneous Deleting session for aaaa,
> 202.140.74.1, 1
> Tue Aug 21 10:48:09 2001: DEBUG: do query is: delete from RADONLINE where
> NASIDENTIFIER='202.140.74.1' and NASPORT=1
>
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthFILE
> Tue Aug 21 10:48:09 2001: DEBUG: Radius::AuthFILE looks for match with aaaa
> Tue Aug 21 10:48:09 2001: DEBUG: Radius::AuthFILE looks for match with
> DEFAULT
> Tue Aug 21 10:48:09 2001: DEBUG: Radius::AuthFILE ACCEPT: Accept explicitly
> by Auth-Type=Accept
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthSQL
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthSQL
> Tue Aug 21 10:48:09 2001: DEBUG: Query is: select POOLHINT from APN where
> ROAMDIGIT = left("85298699517",3)
>
> Tue Aug 21 10:48:09 2001: DEBUG: Radius::AuthSQL looks for match with aaaa
> Tue Aug 21 10:48:09 2001: DEBUG: Radius::AuthSQL ACCEPT:
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthDYNADDRESS
> Tue Aug 21 10:48:09 2001: DEBUG: Query is: select TIME_STAMP, YIADDR,
> SUBNETMASK, DNSSERVER from RADPOOL
> where POOL='local' and STATE=0 order by TIME_STAMP
>
> Tue Aug 21 10:48:09 2001: DEBUG: do query is: update RADPOOL set
> STATE=1,TIME_STAMP=998362089,EXPIRY=998448489,USERNAME='aaaa',MSISDN=''
> where YIADDR='202.140.74.11' and TIME_STAMP=998362069
>
> Tue Aug 21 10:48:09 2001: DEBUG: Access accepted for aaaa
> Tue Aug 21 10:48:09 2001: DEBUG: Packet dump:
> *** Sending to 202.140.74.1 port 2859 ....
> Code: Access-Accept
> Identifier: 199
> Authentic: <0><0>'_<0><0><7><249><0><0>bm<0><0><7>T
> Attributes:
> Framed-IP-Netmask = 255.255.255.0
> Framed-IP-Address = 202.140.74.11
> Class = "SI=Testing"
>
> Tue Aug 21 10:48:09 2001: DEBUG: Packet dump:
> *** Received from 202.140.74.1 port 2860 ....
> Code: Accounting-Request
> Identifier: 200
> Authentic: <181><135>T<224><207>t<172>mc<239>$<206>c*W<182>
> Attributes:
> User-Name = "aaaa"
> Class = "SI=Testing"
> Acct-Session-Id = ""000000Testing""
> NAS-IP-Address = 202.140.74.1
> Acct-Status-Type = Start
> NAS-Port = 1
> Acct-Authentic = RADIUS
> NAS-Identifier = "radius"
> Framed-Protocol = PPP
> Calling-Station-Id = "85298699517"
> Framed-IP-Address = 202.140.74.11
>
> Tue Aug 21 10:48:09 2001: DEBUG: Check if Handler Client-Id =
> 202.140.74.1,NAS-Identifier = "radius" should be used to handle this
> request Tue Aug 21 10:48:09 2001: DEBUG: Handling request with Handler
> 'Client-Id = 202.140.74.1,NAS-Identifier = "radius"'
> Tue Aug 21 10:48:09 2001: DEBUG: simultaneous Adding session for aaaa,
> 202.140.74.1, 1
> Tue Aug 21 10:48:09 2001: DEBUG: do query is: delete from RADONLINE where
> NASIDENTIFIER='202.140.74.1' and NASPORT=1
>
> Tue Aug 21 10:48:09 2001: DEBUG: do query is: insert into RADONLINE
> (USERNAME,NASIDENTIFIER,NASPORT,ACCTSESSIONID,TIME_STAMP,FRAMEDIPADDRESS,NA
>S PORTTYPE,SERVICETYPE) values
> ('aaaa','202.140.74.1',1,'"000000Testing"',998362089,'202.140.74.11','','')
>
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthFILE
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthSQL
> Tue Aug 21 10:48:09 2001: DEBUG: Handling accounting with Radius::AuthSQL
> Tue Aug 21 10:48:09 2001: DEBUG: do query is: insert into ACCOUNTING
> (USERNAME, CLASS, ACCTSESSIONID, NASIPADDRESS,
> ACCTSTATUSTYPE, NASPORT, ACCTAUTHENTIC, NASIDENTIFIER, FRAMEDPROTOCOL,
> CALLINGSTATIONID, FRAMEDIPADDRESS, TIMESTAMP)
> values
> ('aaaa', 'SI=Testing', '"000000Testing"', '202.140.74.1',
> 'Start', 1, 'RADIUS', 'radius', 'PPP', '85298699517', '202.140.74.11',
> 998362089)
>
> Tue Aug 21 10:48:09 2001: DEBUG: Handling with Radius::AuthDYNADDRESS
> Tue Aug 21 10:48:09 2001: DEBUG: Accounting accepted
> Tue Aug 21 10:48:09 2001: DEBUG: Packet dump:
> *** Sending to 202.140.74.1 port 2860 ....
> Code: Accounting-Response
> Identifier: 200
> Authentic: <181><135>T<224><207>t<172>mc<239>$<206>c*W<182>
> Attributes:
>
> Tue Aug 21 10:49:54 2001: DEBUG: Packet dump:
> *** Received from 202.140.74.1 port 2863 ....
> Code: Accounting-Request
> Identifier: 201
> Authentic: <157><156>W<220>8k<9><26>H<181><203>a<133><229><130><24>
> Attributes:
> User-Name = "aaaa"
> Class = "SI=Testing"
> Acct-Session-Id = ""000000Testing""
> NAS-IP-Address = 202.140.74.1
> Acct-Status-Type = Stop
> NAS-Port = 1
> Acct-Authentic = RADIUS
> NAS-Identifier = "radius"
> Framed-Protocol = PPP
> Calling-Station-Id = "85298699517"
> Framed-IP-Address = 202.140.74.11
>
> Tue Aug 21 10:49:54 2001: DEBUG: Check if Handler Client-Id =
> 202.140.74.1,NAS-Identifier = "radius" should be used to handle this
> request Tue Aug 21 10:49:54 2001: DEBUG: Handling request with Handler
> 'Client-Id = 202.140.74.1,NAS-Identifier = "radius"'
> Tue Aug 21 10:49:54 2001: DEBUG: simultaneous Deleting session for aaaa,
> 202.140.74.1, 1
> Tue Aug 21 10:49:54 2001: DEBUG: do query is: delete from RADONLINE where
> NASIDENTIFIER='202.140.74.1' and NASPORT=1
>
> Tue Aug 21 10:49:54 2001: DEBUG: Handling with Radius::AuthFILE
> Tue Aug 21 10:49:54 2001: DEBUG: Handling with Radius::AuthSQL
> Tue Aug 21 10:49:54 2001: DEBUG: Handling accounting with Radius::AuthSQL
> Tue Aug 21 10:49:54 2001: DEBUG: do query is: insert into ACCOUNTING
> (USERNAME, CLASS, ACCTSESSIONID, NASIPADDRESS,
> ACCTSTATUSTYPE, NASPORT, ACCTAUTHENTIC, NASIDENTIFIER, FRAMEDPROTOCOL,
> CALLINGSTATIONID, FRAMEDIPADDRESS, TIMESTAMP)
> values
> ('aaaa', 'SI=Testing', '"000000Testing"', '202.140.74.1',
> 'Stop', 1, 'RADIUS', 'radius', 'PPP', '85298699517', '202.140.74.11',
> 998362194)
>
> Tue Aug 21 10:49:54 2001: DEBUG: Handling with Radius::AuthDYNADDRESS
> Tue Aug 21 10:49:54 2001: DEBUG: do query is: update RADPOOL set STATE=0,
> TIME_STAMP=998362194 where YIADDR='202.140.74.11'
>
> Tue Aug 21 10:49:54 2001: DEBUG: Accounting accepted
> Tue Aug 21 10:49:54 2001: DEBUG: Packet dump:
> *** Sending to 202.140.74.1 port 2863 ....
> Code: Accounting-Response
> Identifier: 201
> Authentic: <157><156>W<220>8k<9><26>H<181><203>a<133><229><130><24>
> Attributes:
----------------------------------------
Content-Type: text/html; charset="iso-8859-1"; name="Attachment: 1"
Content-Transfer-Encoding: quoted-printable
Content-Description:
----------------------------------------
--
Radiator: the most portable, flexible and configurable RADIUS server
anywhere. Available on *NIX, *BSD, Windows 95/98/2000, NT, MacOS X.
-
Nets: internetwork inventory and management - graphical, extensible,
flexible with hardware, software, platform and database independence.
# AddressAllocatorSQL
#
# Implements IP address allocation from an SQL database.
# Called by AuthDYNADDRESS.pm
#
# Author: Mike McCauley ([EMAIL PROTECTED])
# Copyright (C) 2000 Open System Consultants
# $Id: AddressAllocatorSQL.pm,v 1.8 2001/06/07 03:52:14 mikem Exp $
package Radius::AddressAllocatorSQL;
use Radius::AddressAllocatorGeneric;
use Radius::SqlDb;
use strict;
use vars qw($VERSION @ISA);
BEGIN
{
@ISA = qw(Radius::AddressAllocatorGeneric Radius::SqlDb);
}
#####################################################################
# Contruct a new Dynamic address allocator
sub new
{
my ($class, $file) = @_;
my $self = $class->SUPER::new($file);
# Make sure all the addresses in all the
# pools are present in the database.
my $pool;
foreach $pool (@{$self->{AddressPools}})
{
$self->createPool($pool);
}
$self->reclaimExpired();
return $self;
}
#####################################################################
# Do per-instance default initialization
# This is called by Configurabel during Configurable::new before
# the config file is parsed. Its a good place initalze
# instance variables
# that might get overridden when the config file is parsed.
sub initialize
{
my ($self) = @_;
$self->Radius::AddressAllocatorGeneric::initialize;
$self->Radius::SqlDb::initialize;
$self->{FindQuery} = "select TIME_STAMP, YIADDR, SUBNETMASK, DNSSERVER from RADPOOL
where POOL='%0' and STATE=0 order by TIME_STAMP";
$self->{AllocateQuery} = "update RADPOOL set STATE=1,
TIME_STAMP=%0,
EXPIRY=%1, USERNAME='%2' where YIADDR='%3' and TIME_STAMP %4";
$self->{CheckPoolQuery} = "select STATE from RADPOOL where YIADDR='%0'";
$self->{AddAddressQuery} = "insert into RADPOOL (STATE, TIME_STAMP,
POOL, YIADDR, SUBNETMASK, DNSSERVER) values (0, %t, '%0', '%1',
'%2', '%3')";
$self->{DeallocateQuery} = "update RADPOOL set STATE=0,
TIME_STAMP=%t where YIADDR='%0'";
$self->{ReclaimQuery} = "update RADPOOL set STATE=0
where state!=0 and EXPIRY < %0";
$self->{DefaultLeasePeriod} = 86400; # 1 day
$self->{LeaseReclaimInterval} = 86400; # 1 day
}
#####################################################################
# Override the object function in Configurable
sub object
{
my ($self, $file, $keyword, $args) = @_;
if ($keyword eq 'AddressPool')
{
my $pool = Radius::AddressPool->new($file, $args);
push(@{$self->{AddressPools}}, $pool);
}
else
{
return 0;
}
}
#####################################################################
# Override the keyword function in Configurable
sub keyword
{
my ($self, $file, $keyword, $value) = @_;
$self->match_keyword
($keyword, $value,
'DefaultLeasePeriod' => 'integer',
'LeaseReclaimInterval' => 'integer',
'FindQuery' => 'string',
'AllocateQuery' => 'string',
'DeallocateQuery' => 'string',
'CheckPoolQuery' => 'string',
'AddAddressQuery' => 'string',
'ReclaimQuery' => 'string') && return 1;
# Multiple inheritance
return $self->Radius::AddressAllocatorGeneric::keyword
($file, $keyword, $value)
|| $self->Radius::SqlDb::keyword
($file, $keyword, $value);
}
#####################################################################
# Allocate an address for username with the given pool hint
# return a hash of interesting values for AuthBy DYNADDRESS
# to do stuff with
sub allocate
{
my ($self, $caller, $username, $hint, $p, $rp) = @_;
my %details;
# Find the oldest free address in this pool
my $q = &Radius::Util::format_special
($self->{FindQuery},
$p, $rp, $hint, $username);
my $iterations = 20; # Permit up to 20 collissions
my ($last_time_stamp, $sth, $update, $now, $expiry,
$lease_period, $timestamp_compare);
while ($iterations-- > 0)
{
$sth = $self->prepareAndExecute($q);
return ($main::REJECT,
"Address pool database not available")
unless $sth;
if (($last_time_stamp,
$details{yiaddr}, $details{subnetmask},
$details{dnsserver})
= $self->getOneRow($sth))
{
# Got a new address, update the state and timestamp
# and expiry time
$now = time;
# Use the Session-Timeout if its available, else
# the DefaultLeasePeriod
$lease_period = $rp->getAttrByNum
($Radius::Radius::SESSION_TIMEOUT);
$lease_period = $self->{DefaultLeasePeriod}
unless defined $lease_period;
$expiry = $now + $lease_period;
$timestamp_compare = $last_time_stamp eq '' ?
'is NULL' : "=$last_time_stamp";
$update = &Radius::Util::format_special
($self->{AllocateQuery},
$p, $rp,
$now, $expiry, $username, $details{yiaddr},
$timestamp_compare);
# If this fails, then its prob because someone else
# got the same address before us. Try again
next unless $self->do($update) == 1;
# Call the callers allocateDone() function to process
# the results
$caller->allocateDone($p, $rp, \%details);
# And tell the caller to accept immediately
return ($main::ACCEPT);
}
else
{
# Hmmm, no spare addresses
return ($main::REJECT, "No available addresses");
}
}
# Hmmmm, should not happen: too many other people trying
# to get addresses at the same time
return ($main::REJECT, "Too many simultaneous address requests");
}
#####################################################################
# Confirm a previously allocated address is in use
sub confirm
{
my ($self, $caller, $address, $p, $rp) = @_;
return ($main::ACCEPT);
}
#####################################################################
# Free a previously allocated address
sub deallocate
{
my ($self, $caller, $address, $p, $rp) = @_;
my $q = &Radius::Util::format_special
($self->{DeallocateQuery},
$p, $rp, $address);
$self->do($q);
return ($main::ACCEPT);
}
#####################################################################
# Glue between the timeout callback and the reclaimExpired function
sub reclaimExpiredTimeout
{
my ($handle, $self) = @_;
$self->reclaimExpired();
}
#####################################################################
# Arrange for reclamation of expired leases every
# LeaseReclaimInterval seconds
sub reclaimExpired
{
my ($self) = @_;
&main::log($main::LOG_DEBUG, "Reclaiming expired leases");
my $now = time;
my $q = &Radius::Util::format_special
($self->{ReclaimQuery}, undef, undef, $now);
$self->do($q);
# Arrange to be called again in LeaseReclaimInterval seconds
&Radius::Select::add_timeout($now + $self->{LeaseReclaimInterval},
\&reclaimExpiredTimeout, $self);
}
#####################################################################
# Ensure all the addresses in an address pool are present
# in the database
sub createPool
{
my ($self, $pool) = @_;
# For each range, and for each address in the range
# make sure it exists in the database
my ($range, $i4, $address, $state);
foreach $range (@{$pool->{Range}})
{
if ($range =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s+(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/)
{
# 2 addresses a.b.c.d e.f.g.h
my ($l1, $l2, $l3, $l4) = ($1, $2, $3, $4);
my ($u1, $u2, $u3, $u4) = ($5, $6, $7, $8);
# Some error checking. We only permit the
# last octet to vary
if ( $l1 != $u1 || $l1 > 255
|| $l2 != $u2 || $l2 > 255
|| $l3 != $u3 || $l3 > 255
|| $l4 > $u4)
{
&main::log($main::LOG_ERR, "Invalid Range $range. Ignored");
next;
}
my $lower = ($l1 << 24) + ($l2 << 16) + ($l3 << 8) + $l4;
my $upper = ($u1 << 24) + ($u2 << 16) + ($u3 << 8) + $u4;
$self->checkAddressRange($pool, $lower, $upper);
}
elsif ($range =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})/)
{
# CIDR block a.b.c.d/x
my ($l1, $l2, $l3, $l4, $bs) = ($1, $2, $3, $4, $5);
# Some error checking. We only permit the
# last octet to vary
if ($l1 > 255 || $l2 > 255 || $l3 > 255 || $l4 > 255
|| $bs > 32)
{
&main::log($main::LOG_ERR, "Invalid CIDR block $range. Ignored");
next;
}
my $lower = ($l1 << 24) + ($l2 << 16) + ($l3 << 8) + $l4;
my $mask = (1 << (32 - $bs)) - 1;
$lower &= ~$mask;
my $upper = $lower | $mask;
$self->checkAddressRange($pool, $lower, $upper);
}
else
{
&main::log($main::LOG_WARNING,
"Range $range, unknown Range format. Ignored");
}
}
}
#####################################################################
# Ensure that a single address exists, else create it
# Return 0 if it was created, else 0
# Address is in the form of an integer
sub checkAddress
{
my ($self, $pool, $address) = @_;
# Convert integer address to a dotted quad
my $dquad = sprintf("%d.%d.%d.%d",
$address >> 24 & 0xff,
$address >> 16 & 0xff,
$address >> 8 & 0xff,
$address & 0xff);
# Check whether it already exists
&main::log($main::LOG_DEBUG, "Checking address $dquad");
my $q = &Radius::Util::format_special
($self->{CheckPoolQuery},
undef, undef, $dquad);
my $sth = $self->prepareAndExecute($q);
last unless $sth;
if (!$self->getOneRow($sth))
{
# Not there, add it
$q = &Radius::Util::format_special
($self->{AddAddressQuery},
undef, undef,
$pool->{Name}, $dquad, $pool->{Subnetmask},
$pool->{DNSServer});
return $self->do($q);
}
return;
}
#####################################################################
# Check that all the addresses in the range $lower to $upper
# inclusive exist in the pool, else create them
sub checkAddressRange
{
my ($self, $pool, $lower, $upper) = @_;
my $i;
for ($i = $lower; $i <= $upper; $i++)
{
$self->checkAddress($pool, $i);
}
}
#####################################################################
#####################################################################
#####################################################################
# This is where we define the companion class AddressPool
# which allows us to tell the database what our pools are
# each AddressPool will be checked out at startup time, to ensure
# it is in the database
package Radius::AddressPool;
use vars qw($VERSION @ISA);
BEGIN
{
@ISA = qw(Radius::Configurable);
}
#####################################################################
# Contruct a new AddressPool
sub new
{
my ($class, $file, $name) = @_;
my $self = $class->SUPER::new($file);
$self->{Name} = $name;
&main::log($main::LOG_ERR, "No Ranges defined for AddressPool at '$main::config_file' line $.")
if !defined $self->{Range} && $file;
return $self;
}
#####################################################################
# Do per-instance default initialization
# This is called by Configurabel during Configurable::new before
# the config file is parsed. Its a good place initalze
# instance variables
# that might get overridden when the config file is parsed.
sub initialize
{
my ($self) = @_;
$self->SUPER::initialize;
$self->{Subnetmask} = '255.255.255.255';
}
#####################################################################
# Override the keyword function in Configurable
sub keyword
{
my ($self, $file, $keyword, $value) = @_;
$self->match_keyword
($keyword, $value,
'Range' => 'stringarray',
'Subnetmask' => 'string',
'DNSServer' => 'string') && return 1;
return $self->SUPER::keyword($file, $keyword, $value);
}
1;