diff --git a/broker/l2tp_broker.cfg.example b/broker/l2tp_broker.cfg.example index cb0d6e4c..8e593d9c 100644 --- a/broker/l2tp_broker.cfg.example +++ b/broker/l2tp_broker.cfg.example @@ -7,16 +7,8 @@ port=53,123,8942 interface=lo ; Maximum number of tunnels that will be allowed by the broker max_tunnels=1024 -; Tunnel port base. This port is not visible to clients, but must be free on the server. -; This port is used by the actual l2tp tunnel, but tunneldigger sets up NAT rules so that clients -; can keep using the control port. -port_base=20000 ; Tunnel id base tunnel_id_base=100 -; Namespace (for running multiple brokers); note that you must also -; configure disjunct ports, and tunnel identifiers in order for -; namespacing to work -namespace=default ; Reject connections if there are less than N seconds since the last connection. ; Can be less than a second (e.g., 0.1). connection_rate_limit=10 diff --git a/broker/setup.py b/broker/setup.py index 9b9c2020..4e55eb6a 100644 --- a/broker/setup.py +++ b/broker/setup.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from setuptools import find_packages, setup -VERSION = '0.3.0' +VERSION = '0.4.0' setup( name='tunneldigger-broker', @@ -14,7 +14,7 @@ url='https://github.com/wlanslovenija/tunneldigger', license='AGPLv3', package_dir={'': 'src'}, - packages=find_packages(where='src', exclude=['_ffi_src', '_ffi_src.*']), + packages=find_packages(where='src'), package_data={}, classifiers=[ 'Development Status :: 4 - Beta', @@ -24,16 +24,5 @@ ], include_package_data=True, zip_safe=False, - setup_requires=[ - 'cffi>=1.4.1', - ], - install_requires=[ - 'netfilter>=0.6.2', - 'cffi>=1.4.1', - ], extras_require={}, - cffi_modules=[ - 'src/_ffi_src/build_conntrack.py:ffibuilder', - ], - ext_package='tunneldigger_broker._ffi', ) diff --git a/broker/src/_ffi_src/__init__.py b/broker/src/_ffi_src/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/broker/src/_ffi_src/build_conntrack.py b/broker/src/_ffi_src/build_conntrack.py deleted file mode 100644 index a8a8d40d..00000000 --- a/broker/src/_ffi_src/build_conntrack.py +++ /dev/null @@ -1,122 +0,0 @@ - - -from cffi import FFI - -ffibuilder = FFI() -ffibuilder.set_source( - '_conntrack', - ''' - #include - - #include - #include - ''', - libraries=[ - 'nfnetlink', - 'netfilter_conntrack', - ], -) - -ffibuilder.cdef(''' -typedef unsigned char u_int8_t; -typedef unsigned short int u_int16_t; -typedef unsigned int u_int32_t; - -enum { - CONNTRACK, - ... -}; - -enum nf_conntrack_msg_type { - NFCT_T_UNKNOWN, - NFCT_T_NEW, - NFCT_T_UPDATE, - NFCT_T_DESTROY, - NFCT_T_ALL, - NFCT_T_ERROR, - ... -}; - -enum { - NFCT_CB_FAILURE, - NFCT_CB_STOP, - NFCT_CB_CONTINUE, - NFCT_CB_STOLEN, - ... -}; - -enum nf_conntrack_query { - NFCT_Q_CREATE, - NFCT_Q_UPDATE, - NFCT_Q_DESTROY, - NFCT_Q_GET, - NFCT_Q_FLUSH, - NFCT_Q_DUMP, - NFCT_Q_DUMP_RESET, - NFCT_Q_CREATE_UPDATE, - ... -}; - -enum nf_conntrack_attr { - ATTR_L3PROTO, - ATTR_L4PROTO, - ATTR_IPV4_SRC, - ATTR_IPV4_DST, - ATTR_IPV6_SRC, - ATTR_IPV6_DST, - ATTR_PORT_SRC, - ATTR_PORT_DST, - ... -}; - -enum { - NFCT_CMP_ALL, - NFCT_CMP_MASK, - ... -}; - -typedef int nfct_callback(enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data); - -struct nf_conntrack* nfct_new(); -void nfct_destroy(struct nf_conntrack *ct); -struct nfct_handle* nfct_open(u_int8_t subsys_id, unsigned int subscriptions); -int nfct_close(struct nfct_handle * cth); -int nfct_fd(struct nfct_handle *cth); -int nfct_catch(struct nfct_handle *h); -int nfct_callback_register(struct nfct_handle *h, - enum nf_conntrack_msg_type type, - nfct_callback *cb, - void *data); -int nfct_query(struct nfct_handle *h, - const enum nf_conntrack_query qt, - const void *data); - -int nfct_cmp(const struct nf_conntrack *ct1, - const struct nf_conntrack *ct2, - unsigned int flags); - -void nfct_set_attr_u8(struct nf_conntrack *ct, - const enum nf_conntrack_attr type, - uint8_t value); - -void nfct_set_attr_u16(struct nf_conntrack *ct, - const enum nf_conntrack_attr type, - uint16_t value); - -void nfct_set_attr_u32(struct nf_conntrack *ct, - const enum nf_conntrack_attr type, - uint32_t value); - -void nfct_set_attr_u64(struct nf_conntrack *ct, - const enum nf_conntrack_attr type, - uint64_t value); - -extern "Python" int query_callback(enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data); -''') - -if __name__ == '__main__': - ffibuilder.compile(verbose=True) diff --git a/broker/src/tunneldigger_broker/_ffi/__init__.py b/broker/src/tunneldigger_broker/_ffi/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/broker/src/tunneldigger_broker/broker.py b/broker/src/tunneldigger_broker/broker.py index 997d6c87..9ef0205e 100644 --- a/broker/src/tunneldigger_broker/broker.py +++ b/broker/src/tunneldigger_broker/broker.py @@ -3,10 +3,6 @@ import time import traceback -from . import conntrack -import netfilter.table -import netfilter.rule - from . import l2tp, protocol, network, tunnel as td_tunnel # Logger. @@ -23,8 +19,6 @@ def __init__( hook_manager, max_tunnels, tunnel_id_base, - tunnel_port_base, - namespace, connection_rate_limit, pmtu_fixed, log_ip_addresses, @@ -35,16 +29,12 @@ def __init__( :param hook_manager: Hook manager :param max_tunnels: Maximum number of tunnels to allow :param tunnel_id_base: Base local tunnel identifier - :param tunnel_port_base: Base local tunnel port - :param namespace: Netfilter namespace to use """ self.hook_manager = hook_manager self.max_tunnels = max_tunnels self.tunnel_id_base = tunnel_id_base self.tunnel_ids = set(range(tunnel_id_base, tunnel_id_base + max_tunnels)) - self.tunnel_port_base = tunnel_port_base - self.namespace = namespace self.tunnels = {} self.last_tunnel_created = None self.connection_rate_limit = connection_rate_limit @@ -103,7 +93,7 @@ def create_tunnel(self, broker, address, uuid, remote_tunnel_id, client_features try: tunnel = td_tunnel.Tunnel( broker=broker, - address=(broker.address[0], self.tunnel_port_base + tunnel_id), + address=broker.address, endpoint=address, uuid=uuid, tunnel_id=tunnel_id, @@ -149,45 +139,6 @@ def destroy_tunnel(self, tunnel): del self.tunnels[tunnel.tunnel_id] def initialize(self): - """ - Sets up netfilter rules so new packets to the same port are redirected - into the per-tunnel socket. - """ - - prerouting_chain = "L2TP_PREROUTING_%s" % self.namespace - postrouting_chain = "L2TP_POSTROUTING_%s" % self.namespace - nat = netfilter.table.Table('nat') - self.rule_prerouting_jmp = netfilter.rule.Rule(jump=prerouting_chain) - self.rule_postrouting_jmp = netfilter.rule.Rule(jump=postrouting_chain) - - try: - nat.flush_chain(prerouting_chain) - nat.delete_chain(prerouting_chain) - except netfilter.table.IptablesError: - pass - - try: - nat.flush_chain(postrouting_chain) - nat.delete_chain(postrouting_chain) - except netfilter.table.IptablesError: - pass - - nat.create_chain(prerouting_chain) - nat.create_chain(postrouting_chain) - try: - nat.delete_rule('PREROUTING', self.rule_prerouting_jmp) - except netfilter.table.IptablesError: - pass - nat.prepend_rule('PREROUTING', self.rule_prerouting_jmp) - - try: - nat.delete_rule('POSTROUTING', self.rule_postrouting_jmp) - except netfilter.table.IptablesError: - pass - nat.prepend_rule('POSTROUTING', self.rule_postrouting_jmp) - - # Initialize connection tracking manager. - self.conntrack = conntrack.ConnectionManager() # Initialize netlink. self.netlink = l2tp.NetlinkInterface() @@ -204,8 +155,8 @@ def initialize(self): def close(self): """ - Shuts down all managed tunnels and restores netfilter state. The tunnel - manager instance should not be used after calling this method. + Shuts down all managed tunnels. The tunnel manager instance + should not be used after calling this method. """ for tunnel in list(self.tunnels.values()): @@ -214,16 +165,6 @@ def close(self): except: traceback.print_exc() - # Restore netfilter rules. - nat = netfilter.table.Table('nat') - nat.delete_rule('PREROUTING', self.rule_prerouting_jmp) - nat.delete_rule('POSTROUTING', self.rule_postrouting_jmp) - nat.flush_chain('L2TP_PREROUTING_%s' % self.namespace) - nat.flush_chain('L2TP_POSTROUTING_%s' % self.namespace) - nat.delete_chain('L2TP_PREROUTING_%s' % self.namespace) - nat.delete_chain('L2TP_POSTROUTING_%s' % self.namespace) - - del self.conntrack del self.netlink @@ -245,13 +186,8 @@ def __init__(self, address, interface, tunnel_manager): self.tunnel_manager = tunnel_manager self.hook_manager = tunnel_manager.hook_manager - self.conntrack = tunnel_manager.conntrack self.netlink = tunnel_manager.netlink - # Clear out the connection tracking tables. - self.conntrack.killall(proto=socket.IPPROTO_UDP, src=self.address[0]) - self.conntrack.killall(proto=socket.IPPROTO_UDP, dst=self.address[0]) - def get_tunnel_manager(self): """ Returns the tunnel manager for this broker. diff --git a/broker/src/tunneldigger_broker/conntrack.py b/broker/src/tunneldigger_broker/conntrack.py deleted file mode 100644 index a8902f3a..00000000 --- a/broker/src/tunneldigger_broker/conntrack.py +++ /dev/null @@ -1,150 +0,0 @@ -import logging -import socket -import struct - -from ._ffi._conntrack import ffi, lib - -__all__ = [ - 'ConntrackError', - 'ConnectionManager', -] - -# Logger. -logger = logging.getLogger("tunneldigger.conntrack") - - -class ConntrackError(Exception): - """ - Base class for all exceptions produced by the connection manager. - """ - - pass - - -class ConnectionManager(object): - """ - Interface to connection tracking API. - """ - - def __init__(self, family=socket.AF_INET): - """ - Construct connection manager. - """ - - self.family = family - - def _build_query(self, proto=None, src=None, dst=None, sport=None, dport=None): - """ - Helper for preparing conntrack queries. The caller is responsible - for freeing the allocated query object. - """ - - ct = lib.nfct_new() - if not ct: - raise ConntrackError("nfct_new failed") - - try: - lib.nfct_set_attr_u8(ct, lib.ATTR_L3PROTO, self.family) - - if self.family == socket.AF_INET: - # IPv4. - if src: - lib.nfct_set_attr_u32(ct, lib.ATTR_IPV4_SRC, inet_pton(self.family, src)) - if dst: - lib.nfct_set_attr_u32(ct, lib.ATTR_IPV4_DST, inet_pton(self.family, dst)) - else: - raise ConntrackError("Unsupported address family: {}".format(self.family)) - - if proto: - # Layer 4 protocol. - lib.nfct_set_attr_u8(ct, lib.ATTR_L4PROTO, proto) - if sport: - # Source port. - lib.nfct_set_attr_u16(ct, lib.ATTR_PORT_SRC, socket.htons(sport)) - if dport: - # Destination port. - lib.nfct_set_attr_u16(ct, lib.ATTR_PORT_DST, socket.htons(dport)) - except: - lib.nfct_destroy(ct) - raise - - return ct - - def kill(self, proto, src, dst, sport, dport): - """ - Remove a specific connection tracking entry. - """ - - ct = self._build_query(proto, src, dst, sport, dport) - - try: - handle = lib.nfct_open(lib.CONNTRACK, 0) - if not handle: - raise ConntrackError("nfct_open failed") - - lib.nfct_query(handle, lib.NFCT_Q_DESTROY, ct) - finally: - lib.nfct_close(handle) - lib.nfct_destroy(ct) - - def killall(self, proto=None, src=None, dst=None, sport=None, dport=None): - """ - Remove all connection tracking entries matching the filter. - """ - - ct = self._build_query(proto, src, dst, sport, dport) - - try: - handle_query = lib.nfct_open(lib.CONNTRACK, 0) - if not handle_query: - raise ConntrackError("nfct_open failed") - - try: - handle_update = lib.nfct_open(lib.CONNTRACK, 0) - if not handle_update: - raise ConntrackError("nfct_open failed") - - def callback(entry_type, entry_ct): - if not lib.nfct_cmp(ct, entry_ct, lib.NFCT_CMP_ALL | lib.NFCT_CMP_MASK): - return lib.NFCT_CB_CONTINUE - - # Remove any matching conntrack entries using the update handle. - if lib.nfct_query(handle_update, lib.NFCT_Q_DESTROY, entry_ct) == -1: - logger.warning("Failed to remove entry from conntrack table.") - return lib.NFCT_CB_CONTINUE - - try: - cb_handle = ffi.new_handle(callback) - lib.nfct_callback_register(handle_query, lib.NFCT_T_ALL, lib.query_callback, cb_handle) - family_ref = ffi.new('int*') - family_ref[0] = self.family - result = lib.nfct_query(handle_query, lib.NFCT_Q_DUMP, family_ref) - if result == -1: - raise ConntrackError("nfct_query failed") - finally: - lib.nfct_close(handle_update) - finally: - lib.nfct_close(handle_query) - finally: - lib.nfct_destroy(ct) - - -def inet_pton(family, address): - """ - Wrapper for inet_pton. - """ - - if family == socket.AF_INET: - return struct.unpack('=I', socket.inet_pton(family, address))[0] - else: - raise NotImplementedError - - -@ffi.def_extern() -def query_callback(entry_type, entry_ct, data): - """ - Callback dispatcher interface. - """ - - callback = ffi.from_handle(data) - return callback(entry_type, entry_ct) diff --git a/broker/src/tunneldigger_broker/main.py b/broker/src/tunneldigger_broker/main.py index 87652429..3d937766 100644 --- a/broker/src/tunneldigger_broker/main.py +++ b/broker/src/tunneldigger_broker/main.py @@ -75,8 +75,6 @@ hook_manager=hook_manager, max_tunnels=config.getint('broker', 'max_tunnels'), tunnel_id_base=config.getint('broker', 'tunnel_id_base'), - tunnel_port_base=config.getint('broker', 'port_base'), - namespace=config.get('broker', 'namespace'), connection_rate_limit=config.getfloat('broker', 'connection_rate_limit'), pmtu_fixed=config.getint('broker', 'pmtu'), log_ip_addresses=config.getboolean('log', 'log_ip_addresses'), @@ -85,8 +83,6 @@ logger.info("Maximum number of tunnels is %d." % tunnel_manager.max_tunnels) logger.info("Tunnel identifier base is %d." % tunnel_manager.tunnel_id_base) -logger.info("Tunnel port base is %d." % tunnel_manager.tunnel_port_base) -logger.info("Namespace is %s." % tunnel_manager.namespace) # Initialize one broker for each port. brokers = [] diff --git a/broker/src/tunneldigger_broker/tunnel.py b/broker/src/tunneldigger_broker/tunnel.py index 9772459c..608765fb 100644 --- a/broker/src/tunneldigger_broker/tunnel.py +++ b/broker/src/tunneldigger_broker/tunnel.py @@ -1,8 +1,5 @@ -from . import conntrack import fcntl import logging -import netfilter.table -import netfilter.rule import random import socket import struct @@ -125,57 +122,6 @@ def setup_tunnel(self): self.socket.close() raise TunnelSetupFailed - # Setup netfilter rules. - self.prerouting_rule = netfilter.rule.Rule( - in_interface=self.interface, - protocol='udp', - source=self.endpoint[0], - destination=self.address[0], - matches=[ - netfilter.rule.Match('udp', '--sport %d --dport %d' % (self.endpoint[1], self.broker.address[1])), - ], - jump=netfilter.rule.Target('DNAT', '--to %s:%d' % self.address) - ) - - self.postrouting_rule = netfilter.rule.Rule( - out_interface=self.interface, - protocol='udp', - source=self.address[0], - destination=self.endpoint[0], - matches=[ - netfilter.rule.Match('udp', '--sport %d --dport %d' % (self.address[1], self.endpoint[1])), - ], - jump=netfilter.rule.Target('SNAT', '--to %s:%d' % self.broker.address) - ) - - try: - nat = netfilter.table.Table('nat') - nat.append_rule('L2TP_PREROUTING_%s' % self.broker.tunnel_manager.namespace, self.prerouting_rule) - nat.append_rule('L2TP_POSTROUTING_%s' % self.broker.tunnel_manager.namespace, self.postrouting_rule) - except netfilter.table.IptablesError: - raise TunnelSetupFailed - - # Clear connection tracking table to force the kernel to evaluate the newly added netfilter rules. Note - # that the below filter must match the above netfilter rules. - try: - self.broker.conntrack.kill( - proto=socket.IPPROTO_UDP, - src=self.endpoint[0], - dst=self.address[0], - sport=self.endpoint[1], - dport=self.broker.address[1], - ) - - self.broker.conntrack.kill( - proto=socket.IPPROTO_UDP, - src=self.address[0], - dst=self.endpoint[0], - sport=self.address[1], - dport=self.endpoint[1], - ) - except conntrack.ConntrackError: - logger.warning("Failed to clear connection tracking table entries.") - self.created_time = time.time() # Respond with tunnel establishment message. @@ -332,14 +278,6 @@ def close(self, reason=protocol.ERROR_REASON_UNDEFINED): super(Tunnel, self).close() - # Clear netfilter rules. - try: - nat = netfilter.table.Table('nat') - nat.delete_rule('L2TP_PREROUTING_%s' % self.broker.tunnel_manager.namespace, self.prerouting_rule) - nat.delete_rule('L2TP_POSTROUTING_%s' % self.broker.tunnel_manager.namespace, self.postrouting_rule) - except netfilter.table.IptablesError: - pass - self.broker.tunnel_manager.destroy_tunnel(self) def create_tunnel(self, address, uuid, remote_tunnel_id, client_features):