Commit d9d68b45 authored by Ad Schellevis's avatar Ad Schellevis

(captiveportal, new) cleanup and add features to background process

parent caf43295
...@@ -39,83 +39,172 @@ from lib.arp import ARP ...@@ -39,83 +39,172 @@ from lib.arp import ARP
from lib.ipfw import IPFW from lib.ipfw import IPFW
from lib.daemonize import Daemonize from lib.daemonize import Daemonize
class CPBackgroundProcess(object):
""" background process helper class
"""
def __init__(self):
# open syslog and notice startup
syslog.openlog('captiveportal', logoption=syslog.LOG_DAEMON)
syslog.syslog(syslog.LOG_NOTICE, 'starting captiveportal background process')
# handles to ipfw, arp the config and the internal administration
self.ipfw = IPFW()
self.arp = ARP()
self.cnf = Config()
self.db = DB()
self._conf_zone_info = self.cnf.get_zones()
def list_zone_ids(self):
""" return zone numbers
"""
return self._conf_zone_info.keys()
def initialize_fixed(self):
""" initialize fixed ip / hosts per zone
"""
cpzones = self._conf_zone_info
for zoneid in cpzones:
for conf_section in ['allowedaddresses', 'allowedmacaddresses']:
for address in cpzones[zoneid][conf_section]:
if conf_section.find('mac') == -1:
sessions = self.db.sessions_per_address(zoneid, ip_address=address)
ip_address = address
mac_address = None
else:
sessions = self.db.sessions_per_address(zoneid, mac_address=address)
ip_address = None
mac_address = address
sessions_deleted = 0
for session in sessions:
if session['authenticated_via'] not in ('---ip---', '---mac---'):
sessions_deleted += 1
self.db.del_client(zoneid, session['sessionId'])
if sessions_deleted == len(sessions) or len(sessions) == 0:
# when there's no session active, add a new one
# (only administrative, the sync process will add it if neccesary)
if ip_address is not None:
self.db.add_client(zoneid, "---ip---", "", ip_address, "")
else:
self.db.add_client(zoneid, "---mac---", "", "", mac_address)
# cleanup removed static sessions
for dbclient in self.db.list_clients(zoneid):
if dbclient['authenticated_via'] == '---ip---' \
and dbclient['ipAddress'] not in cpzones[zoneid]['allowedaddresses']:
self.ipfw.delete(zoneid, dbclient['ipAddress'])
self.db.del_client(zoneid, dbclient['sessionId'])
elif dbclient['authenticated_via'] == '---mac---' \
and dbclient['macAddress'] not in cpzones[zoneid]['allowedmacaddresses']:
if dbclient['ipAddress'] != '':
self.ipfw.delete(zoneid, dbclient['ipAddress'])
self.db.del_client(zoneid, dbclient['sessionId'])
def sync_zone(self, zoneid):
""" Synchronize captiveportal zone.
Handles timeouts and administrative changes to this zones sessions
"""
if zoneid in self._conf_zone_info:
# fetch data for this zone
cpzone_info = self._conf_zone_info[zoneid]
registered_addresses = self.ipfw.list_table(zoneid)
registered_addr_accounting = self.ipfw.list_accounting_info()
expected_clients = self.db.list_clients(zoneid)
concurrent_users = self.db.find_concurrent_user_sessions(zoneid)
# handle connected clients, timeouts, address changes, etc.
for db_client in expected_clients:
# fetch ip address (or network) from database
cpnet = db_client['ipAddress'].strip()
# there are different reasons why a session should be removed, check for all reasons and
# use the same method for the actual removal
drop_session_reason = None
# session cleanups, only for users not for static hosts/ranges.
if db_client['authenticated_via'] not in ('---ip---', '---mac---'):
# check if hardtimeout is set and overrun for this session
if 'hardtimeout' in cpzone_info and str(cpzone_info['hardtimeout']).isdigit():
# hardtimeout should be set and we should have collected some session data from the client
if int(cpzone_info['hardtimeout']) > 0 and float(db_client['startTime']) > 0:
if (time.time() - float(db_client['startTime'])) / 60 > int(cpzone_info['hardtimeout']):
drop_session_reason = "session %s hit hardtimeout" % db_client['sessionId']
# check if idletimeout is set and overrun for this session
if 'idletimeout' in cpzone_info and str(cpzone_info['idletimeout']).isdigit():
# idletimeout should be set and we should have collected some session data from the client
if int(cpzone_info['idletimeout']) > 0 and float(db_client['last_accessed']) > 0:
if (time.time() - float(db_client['last_accessed'])) / 60 > int(cpzone_info['idletimeout']):
drop_session_reason = "session %s hit idletimeout" % db_client['sessionId']
# cleanup concurrent users
if 'concurrentlogins' in cpzone_info and int(cpzone_info['concurrentlogins']) == 0:
if db_client['sessionId'] in concurrent_users:
drop_session_reason = "remove concurrent session %s" % db_client['sessionId']
# if mac address changes, drop session. it's not the same client
current_arp = self.arp.get_by_ipaddress(cpnet)
if current_arp is not None and current_arp['mac'] != db_client['macAddress']:
drop_session_reason = "mac address changed for session %s" % db_client['sessionId']
elif db_client['authenticated_via'] == '---mac---':
# detect mac changes
current_ip = self.arp.get_address_by_mac(db_client['macAddress'])
if current_ip != None:
if db_client['ipAddress'] != '':
# remove old ip
self.ipfw.delete(zoneid, db_client['ipAddress'])
self.db.update_client_ip(zoneid, db_client['sessionId'], current_ip)
self.ipfw.add_to_table(zoneid, current_ip)
self.ipfw.add_accounting(current_ip)
# check session, if it should be active, validate its properties
if drop_session_reason is None:
# registered client, but not active according to ipfw (after reboot)
if cpnet not in registered_addresses:
self.ipfw.add_to_table(zoneid, cpnet)
# is accounting rule still available? need to reapply after reload / reboot
if cpnet not in registered_addr_accounting:
self.ipfw.add_accounting(cpnet)
else:
# remove session
syslog.syslog(syslog.LOG_NOTICE, drop_session_reason)
self.ipfw.delete(zoneid, cpnet)
self.db.del_client(zoneid, db_client['sessionId'])
# if there are addresses/networks in the underlying ipfw table which are not in our administration,
# remove them from ipfw.
for registered_address in registered_addresses:
address_active = False
for db_client in expected_clients:
if registered_address == db_client['ipAddress']:
address_active = True
break
if not address_active:
self.ipfw.delete(zoneid, registered_address)
def main(): def main():
syslog.openlog('captiveportal', logoption=syslog.LOG_DAEMON) """ Background process loop, runs as backend daemon for all zones. only one should be active at all times.
syslog.syslog(syslog.LOG_NOTICE, 'starting captiveportal background process') The main job of this procedure is to sync the administration with the actual situation in the ipfw firewall.
# handle to ipfw, arp and the config """
ipfw = IPFW() bgprocess = CPBackgroundProcess()
arp = ARP() bgprocess.initialize_fixed()
cnf = Config()
while True: while True:
try: try:
# construct objects # open database
db = DB() bgprocess.db.open()
# update accounting info # reload cached arp table contents
db.update_accounting_info(ipfw.list_accounting_info()) bgprocess.arp.reload()
# update accounting info, for all zones
bgprocess.db.update_accounting_info(bgprocess.ipfw.list_accounting_info())
# process sessions per zone # process sessions per zone
arp.reload() for zoneid in bgprocess.list_zone_ids():
cpzones = cnf.get_zones() bgprocess.sync_zone(zoneid)
for zoneid in cpzones:
registered_addresses = ipfw.list_table(zoneid) # close the database handle while waiting for the next poll
registered_add_accounting = ipfw.list_accounting_info() bgprocess.db.close()
expected_clients = db.list_clients(zoneid)
concurrent_users = db.find_concurrent_user_sessions(zoneid)
# handle connected clients, timeouts, address changes, etc.
for db_client in expected_clients:
# fetch ip address (or network) from database
cpnet = db_client['ipAddress'].strip()
# there are different reasons why a session should be removed, check for all reasons and
# use the same method for the actual removal
drop_session_reason = None
# session cleanups, only for users not for static hosts/ranges.
if db_client['userName'] != '':
# check if hardtimeout is set and overrun for this session
if 'hardtimeout' in cpzones[zoneid] and str(cpzones[zoneid]['hardtimeout']).isdigit():
# hardtimeout should be set and we should have collected some session data from the client
if int(cpzones[zoneid]['hardtimeout']) > 0 and float(db_client['startTime']) > 0:
if (time.time() - float(db_client['startTime'])) / 60 > int(cpzones[zoneid]['hardtimeout']):
drop_session_reason = "session %s hit hardtimeout" % db_client['sessionId']
# check if idletimeout is set and overrun for this session
if 'idletimeout' in cpzones[zoneid] and str(cpzones[zoneid]['idletimeout']).isdigit():
# idletimeout should be set and we should have collected some session data from the client
if int(cpzones[zoneid]['idletimeout']) > 0 and float(db_client['last_accessed']) > 0:
if (time.time() - float(db_client['last_accessed'])) / 60 > int(cpzones[zoneid]['idletimeout']):
drop_session_reason = "session %s hit idletimeout" % db_client['sessionId']
# cleanup concurrent users
if 'concurrentlogins' in cpzones[zoneid] and int(cpzones[zoneid]['concurrentlogins']) == 0:
if db_client['sessionId'] in concurrent_users:
drop_session_reason = "remove concurrent session %s" % db_client['sessionId']
# if mac address changes, drop session. it's not the same client
current_arp = arp.get_by_ipaddress(db_client['ipAddress'])
if current_arp is not None and current_arp['mac'] != db_client['macAddress']:
drop_session_reason = "mac address changed for session %s" % db_client['sessionId']
# check session, if it should be active, validate its properties
if drop_session_reason is None:
# registered client, but not active according to ipfw (after reboot)
if cpnet not in registered_addresses:
ipfw.add_to_table(zoneid, cpnet)
# is accounting rule still available? need to reapply after reload / reboot
if cpnet not in registered_add_accounting and db_client['ipAddress'] not in registered_add_accounting:
ipfw.add_accounting(cpnet)
else:
# remove session
syslog.syslog(syslog.LOG_NOTICE, drop_session_reason)
db.del_client(zoneid, db_client['sessionId'])
ipfw.delete_from_table(zoneid, cpnet)
ipfw.del_accounting(cpnet)
# cleanup, destruct
del db
# sleep # sleep
time.sleep(5) time.sleep(5)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment