Skip to content
This repository has been archived by the owner on Mar 2, 2019. It is now read-only.

Commit

Permalink
Ported backend to txdbus (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
micolous committed Apr 24, 2014
1 parent 8bd917e commit cbda61c
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 67 deletions.
3 changes: 1 addition & 2 deletions docs/deploy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ Install some required depedencies that are needed to use the setup.py infrastruc

Now the rest of the installation can be resumed::

# apt-get install build-essential libxml2-dev libxslt-dev python-dev module-assistant
# m-a a-i xtables-addons
# apt-get install build-essential libxml2-dev libxslt-dev python-dev xtables-addons-dkms
# pip install -r requirements.txt

This will automatically upgrade any packages which are missing or out of date.
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ configparser>=3.2.0
pytz
djangorestframework>=2.3.10,<3.0.0
IPy
netifaces
txdbus
Twisted

# required for operation on linux
#dbus
Expand Down
113 changes: 70 additions & 43 deletions tollgate/backend/iptables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/python
"""iptables support module for tollgate
Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
Copyright 2008-2014 Michael Farrell <http://micolous.id.au/>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
Expand All @@ -20,8 +20,10 @@
from os.path import exists, join, isfile
from sys import exit
from re import compile as re_compile
import dbus, dbus.service, dbus.glib, glib
from dbus.mainloop.glib import DBusGMainLoop
from twisted.internet import reactor, defer
from txdbus import client, objects, error
from txdbus.interface import DBusInterface, Method


DEBUG = False

Expand Down Expand Up @@ -93,6 +95,10 @@ def check_symbols(*symbols):
symbols = list(symbols)
d = read_all_file('/proc/kallsyms')
for l in d.split('\n'):
# newline at EOF
if l == '':
continue

symbol_name = l.split(' ')[2].split("\t")[0]
if symbol_name in symbols:
symbols.remove(symbol_name)
Expand All @@ -105,7 +111,7 @@ def check_symbols(*symbols):

def load_modules(*modules):
for module in modules:
run('modprobe', '-v', module)
run(MODPROBE, '-v', module)

def create_nat():
# load kernel modules that may not have loaded.
Expand Down Expand Up @@ -141,7 +147,7 @@ def create_nat():
print "Though some of those modules may be in-kernel. These symbols are missing:"
print missing_symbols
print ""
print "This may mean that you have not built all dependancies."
print "This may mean that you have not built and installed all dependencies, or you're not running this as a superuser."
exit(1)
else:
# all symbols are there, continue onward.
Expand Down Expand Up @@ -346,12 +352,29 @@ def write_file(filename, value):
fh.close()

# backend api exposing
class PortalBackendAPI(dbus.service.Object):
def __init__(self, bus, object_path=DBUS_PATH):
dbus.service.Object.__init__(self, bus, object_path)
class PortalBackendAPI(objects.DBusObject):
iface = DBusInterface(
DBUS_INTERFACE,
Method('create_user', 's', ''),
Method('enable_user_unmetered', 's', ''),
Method('enable_user', 'sx', ''),
Method('add_host', 'sss', ''),
Method('flush_hosts', 's', ''),
Method('get_quota', 's', 'bt'),
Method('get_all_users_quota_remaining', '', 'a(sx)'),
Method('disable_user', 's', ''),
Method('ip4pf_flush', '', ''),
Method('ip4pf_add', 'sxxx', '')
)

dbusInterfaces = [iface]


@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='s', out_signature='')
def create_user(self, uid):
def __init__(self, object_path=DBUS_PATH):
super(PortalBackendAPI, self).__init__(object_path)


def dbus_create_user(self, uid):
"""Creates a user in the firewall."""
iptables('-N',user_rule(uid))

Expand All @@ -361,17 +384,15 @@ def create_user(self, uid):
ipset('create', ip_set_name(uid), 'bitmap:ip', 'range', INTERN_SUBNET)


@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='s', out_signature='')
def enable_user_unmetered(self, uid):
def dbus_enable_user_unmetered(self, uid):
"""Enableds a user and sets unmetered quota on a user."""
self.enable_user(uid, None)

@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='sx', out_signature='')
def enable_user(self, uid, quota):
def dbus_enable_user(self, uid, quota):
"""Enables a user and sets a quota on a user."""
# delete all rules on that user first
iptables('-F',user_rule(uid))

# ipset bitmap:ip,mac strangeness from http://comments.gmane.org/gmane.linux.network/217806
# "bitmap:ip,mac is a two dimensional set and therefore it requires two directional parameters" (src,src)
# (why you would want to match based on ip on one way and mac from another is beyond me...)
Expand All @@ -384,7 +405,6 @@ def enable_user(self, uid, quota):
iptables('-D','FORWARD','-i',INTERN_IFACE,'-m','set','--match-set',ipmac_set_name(uid),'src,src','-j',user_rule(uid))
iptables('-D','FORWARD','-o',INTERN_IFACE,'-m','set','--match-set',ip_set_name(uid),'dst','-j',user_rule(uid))


# then make them allowed
if quota != None:
# enforce quota limits for user.
Expand Down Expand Up @@ -416,23 +436,23 @@ def enable_user(self, uid, quota):
iptables('-I','FORWARD','4','-i',INTERN_IFACE,'-m','set','--match-set',ipmac_set_name(uid),'src,src','-j',user_rule(uid))
iptables('-I','FORWARD','4','-o',INTERN_IFACE,'-m','set','--match-set',ip_set_name(uid),'dst','-j',user_rule(uid))

@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='sss', out_signature='')
def add_host(self, uid, mac, ip):

def dbus_add_host(self, uid, mac, ip):
"""Registers a host as belonging to a certain user id."""
# add ip+mac ipset entry
ipset('add', ipmac_set_name(uid), ','.join([ip, mac]))

# add ip ipset entry
ipset('add', ip_set_name(uid), ip)

@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='s', out_signature='')
def flush_hosts(self, uid):

def dbus_flush_hosts(self, uid):
"""Removes all hosts for a user."""
ipset('flush', ipmac_set_name(uid))
ipset('flush', ip_set_name(uid))

@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='s', out_signature='bt')
def get_quota(self, uid):

def dbus_get_quota(self, uid):
"""Gets the user's quota.
Returns a tuple:
Expand All @@ -445,9 +465,9 @@ def get_quota(self, uid):
return (True, reset_quota2_amount(user_rule(uid)))
except:
return (False, 0)
@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='', out_signature='a(sx)')
def get_all_users_quota_remaining(self):


def dbus_get_all_users_quota_remaining(self):
"""
Gets all user's remaining quota.
Expand All @@ -474,9 +494,9 @@ def get_all_users_quota_remaining(self):
o.append((f[len(LIMIT_RULE_PREFIX):], quota))

return o
@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='s', out_signature='')
def disable_user(self, uid):


def dbus_disable_user(self, uid):
"""Disables a user's internet access by removing all their quota."""
iptables('-F',user_rule(uid))
try:
Expand All @@ -488,8 +508,7 @@ def disable_user(self, uid):
except:
pass

@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='', out_signature='')
def ip4pf_flush(self):
def dbus_ip4pf_flush(self):
"""Remove all IPv4 port forwarding rules."""
run(
IPTABLES,
Expand All @@ -503,8 +522,7 @@ def ip4pf_flush(self):
'-F', IP4PF_RULE
)

@dbus.service.method(dbus_interface=DBUS_INTERFACE, in_signature='sxxx', out_signature='')
def ip4pf_add(self, ip, protocol, port, external_port):
def dbus_ip4pf_add(self, ip, protocol, port, external_port):
"""Add a port forwarding entry"""
if port != 0:
# it's something with a port we need to handle.
Expand Down Expand Up @@ -555,19 +573,28 @@ def ip4pf_add(self, ip, protocol, port, external_port):
'-j', 'ACCEPT',
)


@defer.inlineCallbacks
def setup_dbus():
DBusGMainLoop(set_as_default=True)
system_bus = dbus.SystemBus()
name = dbus.service.BusName(DBUS_SERVICE, bus=system_bus)
return name

def boot_dbus(daemonise, name, pid_file=None):
PortalBackendAPI(name)
loop = glib.MainLoop()
"""
Sets up the PortalBackendAPI and exposes it to the System Bus.
Called by the reactor when it is running.
"""
conn = yield client.connect(reactor, 'system')
api = PortalBackendAPI()
conn.exportObject(api)
yield conn.requestBusName(DBUS_SERVICE)


def start(daemonise, pid_file=None):
reactor.callWhenRunning(setup_dbus)

# TODO: replace with twistd
if daemonise:
assert pid_file, 'Running in daemon mode means pid_file must be specified.'
assert pid_file is not None, 'Running in daemon mode means pid_file must be specified.'
from daemon import daemonize
daemonize(pid_file)
loop.run()

reactor.run()


42 changes: 20 additions & 22 deletions tollgate/backend/tollgate_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/python
"""tollgate backend service
Copyright 2008-2012 Michael Farrell <http://micolous.id.au/>
Copyright 2008-2014 Michael Farrell <http://micolous.id.au/>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
Expand All @@ -18,7 +18,7 @@
from configparser import ConfigParser
from sys import argv, exit
from optparse import OptionParser
import BaseHTTPServer, iptables
import BaseHTTPServer, iptables, netifaces

# constants
# default settings doesn't quite work yet.
Expand All @@ -28,6 +28,7 @@
'reject_tcp_rst': True,
'iptables': '/sbin/iptables',
'ipset': '/usr/sbin/ipset',
'modprobe': '/sbin/modprobe',
'internal_iface': 'eth1',
'external_iface': 'eth0',
'captive_rule': 'p2_captive',
Expand Down Expand Up @@ -83,13 +84,14 @@ def main(daemon_enable, pid_file, settings_file=SETTINGS_FILE):
print "Loading configuration: %s" % settings_file

if not config.read([settings_file,]):
print "Failure reading configuration file!"
print "Failure reading configuration file %r!" % settings_file
exit(1)

print "Setting configuration values..."
# FIXME: this should be done with proper classes instead of ugly global variables.
iptables.IPTABLES = config.get('tollgate', 'iptables')
iptables.IPSET = config.get('tollgate', 'ipset')
iptables.MODPROBE = config.get('tollgate', 'modprobe')
iptables.INTERN_IFACE = config.get('tollgate', 'internal_iface')
iptables.EXTERN_IFACE = config.get('tollgate', 'external_iface')
iptables.CAPTIVE_RULE = config.get('tollgate', 'captive_rule')
Expand Down Expand Up @@ -123,26 +125,22 @@ def main(daemon_enable, pid_file, settings_file=SETTINGS_FILE):
blacklist_hosts = None
if config.has_section('blacklist'):
blacklist_hosts = config.items('blacklist')

# get network interface configuration for LAN side
# TODO: replace this. This does some sanity checks
iface_info = iptables.run_capture_output('ip', '-4', 'addr', 'show', 'dev', iptables.INTERN_IFACE).split('\n')
if len(iface_info) != 3:
print "Error: Interface %s (internal side) does not have exactly 1 IPv4 address defined." % iptables.INTERN_IFACE
exit(1)

ip_parts = iface_info[1].split()
assert ip_parts[0] == 'inet', 'Interface does not have inet address!?'
assert '/' in ip_parts[1], 'Does not appear to be a CIDR address?'


# Get network interface configuration for LAN side in CIDR notation
#
# This gives slightly funny address, but ipset doesn't care that the IP in
# here is not the network address (but the host address).
iptables.INTERN_SUBNET = ip_parts[1]

#
# Note: This only gets the first IP address on the interface.
try:
intern_addresses = netifaces.ifaddresses(iptables.INTERN_IFACE)[netifaces.AF_INET]
except KeyError:
raise Exception, 'interface %s has no IPv4 addresses set' % (iptables.INTERN_IFACE,)

print "Creating DBUS API..."
b = iptables.setup_dbus()
assert len(intern_addresses) == 1, 'Interface %r must have exactly 1 IPv4 address set.' % (iptables.INTERN_IFACE,)
iptables.INTERN_SUBNET = '/'.join([intern_addresses[0][k] for k in ('addr', 'netmask')])

print "OK, no turning back now!"
print "Creating NAT..."
iptables.create_nat()

Expand All @@ -153,14 +151,14 @@ def main(daemon_enable, pid_file, settings_file=SETTINGS_FILE):
print "Setting blacklist hosts..."
parse_hostlist(blacklist_hosts, iptables.add_blacklist)


print "Starting DBUS Server (only debug messages will appear now)"
print "Creating DBUS API..."
try:
iptables.boot_dbus(daemon_enable, b, pid_file)
iptables.start(daemon_enable, pid_file)
except KeyboardInterrupt:
print "Got Control-C!"
exit(0)


def main_optparse():
"Version of main() that takes arguments as if it were a normal program."
parser = OptionParser(usage='%prog [--daemon] backend.ini')
Expand Down

0 comments on commit cbda61c

Please sign in to comment.