Tuesday 2 September 2014

Small Radius Accounting server on python with LDAP connectivity


I was looking for specific testing scenario and I need to create a simple Radius accounting server which is connected to LDAP. This server should do :

 Upon receiving client Radius accounting start packet , server parses Framed-IP-Address, Calling-Station-Id and many other 3GPP- vendor and custom attributes and update this information in LDAP DB and respond with ACK.

At the beginning I was experimented with pyrad, but spent some time to create and attach customized dictionary properly and also for 3GPP attributes and later had difficulties to parse these attributes , but  chose pyprotosim software (http://sourceforge.net/projects/pyprotosim/) and documentation for python sockets to build small udp server and clients. The dictionary is stored in xml format and easy can be exported and added your own attributes when needed.
So, here is the an example of simple Radius accounting server which is using python-ldap module for LDAP connectivity.
In Ubuntu :
sudo apt-get install python-ldap will install it on the system.
and unpack pyprotosim and create some directory under it.

$demo_radius_acct_server.py
#!/usr/bin/python  
# Simple example of small Radius Accounting Server on basis of pyprotosim #software  
# which parses incoming message and searches Calling-Station-Id and Framed-IP-Address AVPs  
# (which are IDENTITY AND IPADDRESS of user in LDAP).  
# If the accounting packet is of type = Start, then it connects to LDAP DB, where  
# this user is stored and calls function to update received from radius packet new IP address in  
# LDAP DB for that user (identity).  
# Please refer to BSD license and copyright information for pyprotosim #software to (http://sourceforge.net/projects/pyprotosim/)  
##############################################  
import sys  
import ldap  
#Next line is to include parent directory in PATH where libraries are  
sys.path.append("../")  
# Remove it normally  
from libRadius import *  
######################################################################   
# Function to update new IP address for this subscriber in LDAP DB  
######################################################################  
def update_user(identity,ipaddress):  
  try:  
   print "UPDATING IP ADDRESS in LDAP DB"  
   IDENTITY=identity  
   IPADDRESS=ipaddress  
   l = ldap.initialize(LDAP_HOST_URL)  
   ldap_username = LDAP_USERNAME  
   ldap_password = LDAP_PASSWORD  
   l.simple_bind(ldap_username, ldap_password)  
   baseDN = "cn=users,o=mycompany,o=org"  
   searchScope = ldap.SCOPE_SUBTREE  
   retrieveAttributes = None  
  except:  
   # NO LDAP CONNECTIVITY - RAISE an ERROR  
   print "THERE IS NO LDAP CONNECTIVITY!!!! PLEASE CHECK LDAP CONNECTION"  
  try:    
   from ldap import modlist  
   # Here is your ldap filter string  
   ldap_filter='identity=' + str(IDENTITY)  
   dn=ldap_filter + "," + baseDN  
   mod_attrs = [( ldap.MOD_REPLACE, 'ipaddress', str(IPADDRESS) )]      
   l.modify_s(dn,mod_attrs)  
   print "successfully modified ipaddress for user:", IDENTITY, "in LDAB DB"  
  except ldap.LDAPError, error_message:  
    print error_message     
  # Its nice to the server to disconnect and free resources when done    
  l.unbind_s()  
##############################################################################   
# Function to search IP address of client for this subscriber in LDAP DB  
##############################################################################  
# That's the main function that updates IP address value in LDAP DB accordingly  
# for the given user identity and creates Accounting-Response  
def create_Acct_Response():  
  # Create message header (empty)  
  RES=HDRItem()  
  stripHdr(RES,data.encode("hex"))  
  RID=RES.Identifier  
  RES.Code=dictCOMMANDname2code("Accounting-Response")  
  RES.Identifier=RID  
  REQ_avps=splitMsgAVPs(RES.msg)  
  STATE=findAVP("Acct-Status-Type",REQ_avps)  
  try:  
   IDENTITY=findAVP("Calling-Station-Id",REQ_avps)  
   IPADDR=findAVP("Framed-IP-Address",REQ_avps)  
  except:  
   pass  
  print "Found IDENTITY/IP:", str(IDENTITY) + ", " + str(IPADDR)  
  STATE=STATE.encode("hex")  
  if STATE == '00000001':  
   print "THIS IS ACCOUNTING START"  
   # updating new IP address in LDAB DB for given user:  
   update_user(IDENTITY,IPADDR)  
  elif STATE == '00000002':  
   print "THIS IS ACCOUNTING STOP"  
   # do some code here  
  elif STATE == '00000003':  
   print "THIS IS ACCOUNTING INTERIM"  
   # do some code here  
  elif STATE == '00000007':  
   print "THIS IS ACCOUNTING ON"  
   # add code here if needed  
  elif STATE == '00000008':  
   print "THIS IS ACCOUNTING OFF"  
   # add code here if needed  
  else:  
   print "UNKNOWN PACKET RECEIVED"  
  # The RES_avps are left empty here because function createWithAuthenticator must be constructed with RES_avps according libRadius.xml  
  # But according RFC for Radius protocol, accounting response could have no any AVPs in it, therefore leaving RES_avps as empty array  
  RES_avps=[]  
  auth=createZeroAuthenticator()  
  # Accounting-Response has no need to have attributes in it  
  msg=createWithAuthenticator(RES,auth,RES_avps,SECRET)  
  # msg now contains Accounting-Response as hex string  
  return msg  
if __name__ == "__main__":  
  #logging.basicConfig(level=logging.DEBUG)  
  #logging.basicConfig(level=logging.INFO)  
  LoadDictionary("../dictRadius.xml")  
  #Change your LDAP IP and Port here:  
  LDAP_HOST_URL='ldap://127.0.0.1:389'  
  #Change to your ldap user  
  LDAP_USERNAME="cn=admin,o=mycompany,o=org"  
  LDAP_PASSWORD="secret"  
   # Set up here IP and Port for RADIUS ACCOUNTING server  
  RADIUS_IP = "127.0.0.1"  
  RADIUS_PORT = 1813  
  BUFFER_SIZE=4096  
  # Set up shared secret here  
  SECRET="SOMEPASSWORD"  
  # Now creating simple udp socket  
  RADIUS_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  
  RADIUS_server.bind((RADIUS_IP, RADIUS_PORT))  
  # Looping server until user sends CTRL+C or kill to stop it.  
  while True:  
   data, addr = RADIUS_server.recvfrom(BUFFER_SIZE)  
   if (data != ""):  
    msg=create_Acct_Response()  
    dbg="Sending response"  
    logging.info(dbg)  
    RADIUS_server.sendto(msg.decode("hex"),addr)  
   #End of code  
So, upon receving radius accounting packet , the value of new IP address in LDAP DB is updated successfully

./demo_radius_acct_server.py

Found IDENTITY/IP: 123456789012346, 192.168.0.220
THIS IS ACCOUNTING START
UPDATING IP ADDRESS in LDAP DB
successfully modified ipaddress for user: 123456789012346 in LDAB DB


No comments:

Post a Comment