From 065fa9cdf0d19b4cc9c28d5ff152dcab7e6006a8 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Fri, 10 Apr 2020 16:59:41 +0200 Subject: [PATCH 01/17] Replace argument based config system by config file --- config.py | 76 +++++++++++++++++++++++++++++++++ domain.py | 86 ++++++++++++++++++++++++++++++++++++++ respondd.conf.example | 22 ++++++++++ respondd.py | 97 +++++++++++-------------------------------- 4 files changed, 209 insertions(+), 72 deletions(-) create mode 100644 config.py create mode 100644 domain.py create mode 100644 respondd.conf.example diff --git a/config.py b/config.py new file mode 100644 index 0000000..611e20f --- /dev/null +++ b/config.py @@ -0,0 +1,76 @@ +from configparser import ConfigParser + +class GlobalOptions(): + def __init__(self, port, mcast_link, mcast_site, default_domain, default_domain_type, ipv4_gateway): + self.port = port + self.mcast_link = mcast_link + self.mcast_site = mcast_site + self.default_domain = default_domain + self.default_domain_type = default_domain_type + self.ipv4_gateway = ipv4_gateway + +class DomainOptions(): + @classmethod + def from_parser(cls, section, parser, globals): + from domain import DomainType + + type = parser.get(section, 'DomainType', fallback=globals.default_domain_type) + return DomainType.get(type).options(section, parser, globals) + + def __init__(self, name, parser, globals): + self.name = name + self.vpn_iface = parser.get(name, 'VPNInterface', fallback='mvpn-' + name) + self.mcast_link = parser.get(name, 'MulticastLinkAddress', fallback=globals.mcast_link) + self.mcast_site = parser.get(name, 'MulticastSiteAddress', fallback=globals.mcast_site) + self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=globals.ipv4_gateway), + +class BatmanDomainOptions(DomainOptions): + def __init__(self, name, parser, globals): + from domain import BatadvDomain + + super().__init__(name, parser, globals) + self.batman_iface = parser.get(name, 'BatmanInterface', fallback='bat-' + name) + self.domain_type = BatadvDomain + +class Config(): + ''' Represents a parsed config file + ''' + @classmethod + def from_file(cls, fname): + parser = ConfigParser(empty_lines_in_values=False, default_section='Defaults') + with open(fname) as file: + parser.read_file(file) + return cls(parser) + + def __init__(self, parser): + self._initialize_global_options(parser) + self.domains = { } + for domain in parser.sections(): + self._initialize_domain_options(parser, domain) + if not self.globals.default_domain: + self.globals.default_domain = self.domains[domain] + + def _initialize_global_options(self, parser): + self.globals = GlobalOptions( + parser.getint(None, 'Port', fallback=1001), + parser.get(None, 'MulticastLinkAddress', fallback='ff02::2:1001'), + parser.get(None, 'MulticastSiteAddress', fallback='ff05::2:1001'), + parser.get(None, 'DefaultDomain', fallback=None), + parser.get(None, 'DefaultDomainType', fallback=None), + parser.get(None, 'IPv4Gateway', fallback=None), + ) + + def _initialize_domain_options(self, parser, domain): + self.domains[domain] = DomainOptions.from_parser(domain, parser, self.globals) + + def get_domain_names(self): + return self.domains.keys() + + def get_port(self): + return self.globals.port + + def get_default_domain(self): + return self.globals.default_domain + + def get_domain_config(self, domain): + return self.domains[domain] diff --git a/domain.py b/domain.py new file mode 100644 index 0000000..4926f2c --- /dev/null +++ b/domain.py @@ -0,0 +1,86 @@ +from config import BatmanDomainOptions + +class Domain(): + def __init__(self, mcast_link, mcast_site, ipv4_gateway): + self.mcast_link = mcast_link + self.mcast_site = mcast_site + self.ipv4_gateway = ipv4_gateway + + def get_ipv4_gateway(self): + return self.ipv4_gateway + + def get_multicast_address_link(self): + return self.mcast_link + + def get_multicast_address_site(self): + return self.mcast_site + + def get_interfaces(self): + raise NotImplementedException() + + def get_provider_args(self): + return { 'mesh_ipv4': self.get_ipv4_gateway() } + +class BatadvDomain(Domain): + def __init__(self, domconfig): + self.batman_iface = domconfig.batman_iface + self.vpn_iface = domconfig.vpn_iface + super().__init__(domconfig.mcast_link, domconfig.mcast_site, domconfig.ipv4_gateway) + + def get_interfaces(self): + return [self.batman_iface, self.vpn_iface] + + def get_batman_interface(self): + return self.batman_iface + + def get_provider_args(self): + args = super().get_provider_args() + args.update({ 'batadv_dev': self.get_batman_interface() }) + return args + +class DomainRegistry(): + instance = None + @classmethod + def get_instance(cls): + if not cls.instance: + cls.instance = cls() + return cls.instance + + def __init__(self): + self.domain_by_iface = { } + self.default_domain = None + + def add_domain(self, dom): + for iface in dom.get_interfaces(): + self.domain_by_iface[iface] = dom + + def get_domain_by_interface(self, iface): + if iface in self.domain_by_iface: + return self.domain_by_iface[iface] + return None + + def get_interfaces(self): + return self.domain_by_iface.keys() + + def get_default_domain(self): + return self.default_domain + + def set_default_domain(self, dom): + self.default_domain = dom + +class DomainType(): + @staticmethod + def get(name): + if not name in domain_types: + raise Exception("Unknown domain type") + return domain_types[name] + + def __init__(self, name, options, domain_type): + self.name = name + self.options = options + self.domain_type = domain_type + +domain_types = { + 'batadv': DomainType('batadv', BatmanDomainOptions, BatadvDomain), +} + diff --git a/respondd.conf.example b/respondd.conf.example new file mode 100644 index 0000000..cb5fc8f --- /dev/null +++ b/respondd.conf.example @@ -0,0 +1,22 @@ +# Default settings +[Defaults] +# Listen port +Port: 1001 +# Default multicast listen addresses +MulticastLinkAddress: ff02::2:1001 +MulticastSiteAddress: ff05::2:1001 +# Default domain to use +DefaultDomain: ffki +# Default domain type +DefaultDomainType: batadv + +# A domain +[ffki] +# This is a batman domain +DomainType: batadv +# Batman interface +BatmanInterface: bat-ffki +# VPN interface +VPNInterface: mvpn-ffki +# IPv4 gateway option for ddhcpd +IPv4Gateway: 10.116.128.8 diff --git a/respondd.py b/respondd.py index e27ef05..d37cbea 100755 --- a/respondd.py +++ b/respondd.py @@ -9,10 +9,12 @@ import os from zlib import compress +from config import Config +from domain import BatadvDomain, DomainRegistry from providers import get_providers import util -def get_handler(providers, batadv_ifaces, batadv_mesh_ipv4_overrides, env): +def get_handler(providers): class ResponddUDPHandler(socketserver.BaseRequestHandler): def multi_request(self, providernames, local_env): ret = {} @@ -30,21 +32,18 @@ def handle(self): ifindex = self.request[2] response = None - # Find batman interface the query belongs to - batadv_dev = util.ifindex_to_batiface(ifindex, batadv_ifaces) - if batadv_dev == None: + iface = util.ifindex_to_iface(ifindex) + + domain = DomainRegistry.get_instance().get_domain_by_interface(iface) + if not domain: return - # Clone global environment and populate with interface-specific data - local_env = dict(env) - local_env['batadv_dev'] = batadv_dev - if batadv_dev in batadv_mesh_ipv4_overrides: - local_env['mesh_ipv4'] = batadv_mesh_ipv4_overrides[batadv_dev] + provider_env = domain.get_provider_args() if data.startswith("GET "): - response = self.multi_request(data.split(" ")[1:], local_env) + response = self.multi_request(data.split(" ")[1:], provider_env) else: - answer = providers[data].call(local_env) + answer = providers[data].call(provider_env) if answer: response = str.encode(json.dumps(answer)) @@ -56,63 +55,27 @@ def handle(self): if __name__ == "__main__": parser = argparse.ArgumentParser(usage=""" %(prog)s -h - %(prog)s [-p ] [-g ] [-i [%%]] [-i [%%] ..] [-d ] [-b [:][:] [-n ] [-c ] ..]""") - parser.add_argument('-p', dest='port', - default=1001, type=int, metavar='', - help='port number to listen on (default 1001)') - parser.add_argument('-g', dest='link_group', - default='ff02::2:1001', metavar='', - help='link-local multicast group (default ff02::2:1001), set to emtpy string to disable') - parser.add_argument('-s', dest='site_group', - default='ff05::2:1001', metavar='', - help='site-local multicast group (default ff05::2:1001), set to empty string to disable') - parser.add_argument('-i', dest='mcast_ifaces', - action='append', default=[ 'bat0' ], metavar='', - help='listening interface (default bat0), may be specified multiple times') + %(prog)s [-f ] [-d ]""") + parser.add_argument('-f', dest='config', + default='./respondd.conf', metavar='', + help='config file to use') parser.add_argument('-d', dest='directory', default='./providers', metavar='', help='data provider directory (default: $PWD/providers)') - parser.add_argument('-b', dest='batadv_ifaces', - action='append', default=[ 'bat0' ], metavar='', - help='batman-adv interface to answer for (default: bat0). Specify once per domain') - parser.add_argument('-m', dest='mesh_ipv4', - metavar='', - help='mesh ipv4 address') - parser.add_argument('-n', dest='domain_code', metavar='', - help='(default) domain code for system/domain_code') - parser.add_argument('-c', dest='domain_code_file', metavar='', - help='domain_code.json path (if info is not in file, fallback to -n\'s value)') args = parser.parse_args() - # Read domain-codes from file - known_codes = util.read_domainfile(args.domain_code_file) - - # Extract batman interfaces from commandline parameters - # and overwrite domain-codes from file with commandline arguments - batadv_mesh_ipv4_overrides = { } - batadv_ifaces = [ ] - for ifspec in args.batadv_ifaces: - iface, *left_over = ifspec.split(':') - batadv_ifaces.append(iface) - try: - # if left_over list is not empty, there is at least an override address - possible_override = left_over.pop(0) - # this clause is necessary in case one does not specify an ipv4 override, but a domain-code - if '' != possible_override: - batadv_mesh_ipv4_overrides[iface] = possible_override - # if left_over list is not empty, there is a domain_code - known_codes[iface] = left_over.pop(0) - except IndexError: - continue - - global_handler_env = { 'domain_code': args.domain_code, 'known_codes': known_codes, 'mesh_ipv4': args.mesh_ipv4 } + config = Config.from_file(args.config) + for domname in config.get_domain_names(): + domcfg = config.get_domain_config(domname) + DomainRegistry.get_instance().add_domain(domcfg.domain_type(domcfg)) + DomainRegistry.get_instance().set_default_domain metasocketserver.MetadataUDPServer.address_family = socket.AF_INET6 metasocketserver.MetadataUDPServer.allow_reuse_address = True server = metasocketserver.MetadataUDPServer( - ("", args.port), - get_handler(get_providers(args.directory), batadv_ifaces, batadv_mesh_ipv4_overrides, global_handler_env) + ("", config.get_port()), + get_handler(get_providers(args.directory)) ) server.daemon_threads = True @@ -125,22 +88,12 @@ def join_group(mcast_group, if_index=0): mreq ) - # Extract multicast interfaces from commandline parameters - mcast_iface_groups = { } - for ifspec in args.mcast_ifaces: - iface, *groups = reversed(ifspec.split('%')) - # Populate with default link and site mcast groups if entry not yet created - if not iface in mcast_iface_groups: - mcast_iface_groups[iface] = [ group for group in [ args.link_group, args.site_group ] if len(group) > 0 ] - # Append group specified on commndline - mcast_iface_groups[iface] += groups - for (if_index, if_name) in socket.if_nameindex(): # Check if daemon should listen on interface - if if_name in mcast_iface_groups: - groups = mcast_iface_groups[if_name] + if if_name in DomainRegistry.get_instance().get_interfaces(): + dom = DomainRegistry.get_instance().get_domain_by_interface(if_name) # Join all multicast groups specified for this interface - for group in groups: - join_group(group, if_index) + join_group(dom.get_multicast_address_link(), if_index) + join_group(dom.get_multicast_address_site(), if_index) server.serve_forever() From dbdbca13196fbc310a2fab862620518687eb178c Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Fri, 10 Apr 2020 17:48:34 +0200 Subject: [PATCH 02/17] Replace VPNInterface by Interfaces option --- config.py | 4 ++-- domain.py | 4 ++-- respondd.conf.example | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index 611e20f..5ccac7c 100644 --- a/config.py +++ b/config.py @@ -19,10 +19,10 @@ def from_parser(cls, section, parser, globals): def __init__(self, name, parser, globals): self.name = name - self.vpn_iface = parser.get(name, 'VPNInterface', fallback='mvpn-' + name) + self.interfaces = list(map(str.strip, parser.get(name, 'Interfaces', fallback='').split(','))) self.mcast_link = parser.get(name, 'MulticastLinkAddress', fallback=globals.mcast_link) self.mcast_site = parser.get(name, 'MulticastSiteAddress', fallback=globals.mcast_site) - self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=globals.ipv4_gateway), + self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=globals.ipv4_gateway) class BatmanDomainOptions(DomainOptions): def __init__(self, name, parser, globals): diff --git a/domain.py b/domain.py index 4926f2c..58dbe31 100644 --- a/domain.py +++ b/domain.py @@ -24,11 +24,11 @@ def get_provider_args(self): class BatadvDomain(Domain): def __init__(self, domconfig): self.batman_iface = domconfig.batman_iface - self.vpn_iface = domconfig.vpn_iface + self.interfaces = domconfig.interfaces super().__init__(domconfig.mcast_link, domconfig.mcast_site, domconfig.ipv4_gateway) def get_interfaces(self): - return [self.batman_iface, self.vpn_iface] + return [self.batman_iface] + self.interfaces def get_batman_interface(self): return self.batman_iface diff --git a/respondd.conf.example b/respondd.conf.example index cb5fc8f..045c2ae 100644 --- a/respondd.conf.example +++ b/respondd.conf.example @@ -16,7 +16,7 @@ DefaultDomainType: batadv DomainType: batadv # Batman interface BatmanInterface: bat-ffki -# VPN interface -VPNInterface: mvpn-ffki +# Other listen interfaces +Interfaces: mvpn-ffki # IPv4 gateway option for ddhcpd IPv4Gateway: 10.116.128.8 From e3c7785b08ff5b29014910ef17ee68480b77e4e9 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Fri, 10 Apr 2020 18:09:36 +0200 Subject: [PATCH 03/17] Remove unused utility functions --- util.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/util.py b/util.py index a8e386c..e35e721 100644 --- a/util.py +++ b/util.py @@ -35,7 +35,6 @@ def ifindex_to_iface(if_index): return iface return None - def iface_match_recursive(iface, candidates): """Check if iface has any connection with an interface from candidates through master/slave relationship and return the name of the first @@ -73,17 +72,3 @@ def ifindex_to_batiface(if_index, batman_ifaces): if iface in batman_ifaces or iface == None: return iface return iface_match_recursive(iface, batman_ifaces) - -def read_domainfile(dcf_path): - """Read a json file which holds all currently known assignments of - bat interfaces to domains as a dictionary within a dict and below its key 'domaincodes' - and return it as python dict. - Return an empty dict, if the given path was None. - """ - if dcf_path is None: - return {} - with open(dcf_path, "r") as dc_file: - try: - return json.load(dc_file)["domaincodes"] - except KeyError: - return {} From d0a6f9896b411ce9ce8d4a97bec1e551fc44d19c Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 15:12:45 +0200 Subject: [PATCH 04/17] Add some more function and class level documentation --- config.py | 28 +++++++++++++++++++++++++++- domain.py | 23 +++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 5ccac7c..e12544f 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,8 @@ from configparser import ConfigParser class GlobalOptions(): + ''' Container class for global options + ''' def __init__(self, port, mcast_link, mcast_site, default_domain, default_domain_type, ipv4_gateway): self.port = port self.mcast_link = mcast_link @@ -10,14 +12,22 @@ def __init__(self, port, mcast_link, mcast_site, default_domain, default_domain_ self.ipv4_gateway = ipv4_gateway class DomainOptions(): + ''' Base container class for per domain options + ''' @classmethod def from_parser(cls, section, parser, globals): + ''' Builds a DomainOptions object from a config section + Handles domain type specific options automatically + ''' from domain import DomainType type = parser.get(section, 'DomainType', fallback=globals.default_domain_type) - return DomainType.get(type).options(section, parser, globals) + # Get DomainOptions subclass for type and instantiate + return DomainType.get(type.lower()).options(section, parser, globals) def __init__(self, name, parser, globals): + ''' Initialize common options + ''' self.name = name self.interfaces = list(map(str.strip, parser.get(name, 'Interfaces', fallback='').split(','))) self.mcast_link = parser.get(name, 'MulticastLinkAddress', fallback=globals.mcast_link) @@ -25,10 +35,16 @@ def __init__(self, name, parser, globals): self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=globals.ipv4_gateway) class BatmanDomainOptions(DomainOptions): + ''' Container for batman specific options + ''' def __init__(self, name, parser, globals): + ''' Initialize common and batman-specific options + ''' from domain import BatadvDomain + # Parse common options super().__init__(name, parser, globals) + # Parse batman specific options self.batman_iface = parser.get(name, 'BatmanInterface', fallback='bat-' + name) self.domain_type = BatadvDomain @@ -37,12 +53,16 @@ class Config(): ''' @classmethod def from_file(cls, fname): + ''' Load config from file + ''' parser = ConfigParser(empty_lines_in_values=False, default_section='Defaults') with open(fname) as file: parser.read_file(file) return cls(parser) def __init__(self, parser): + ''' load config from a config parser + ''' self._initialize_global_options(parser) self.domains = { } for domain in parser.sections(): @@ -51,6 +71,8 @@ def __init__(self, parser): self.globals.default_domain = self.domains[domain] def _initialize_global_options(self, parser): + ''' Set all global options + ''' self.globals = GlobalOptions( parser.getint(None, 'Port', fallback=1001), parser.get(None, 'MulticastLinkAddress', fallback='ff02::2:1001'), @@ -61,9 +83,13 @@ def _initialize_global_options(self, parser): ) def _initialize_domain_options(self, parser, domain): + ''' Populate options for domain from config parser + ''' self.domains[domain] = DomainOptions.from_parser(domain, parser, self.globals) def get_domain_names(self): + ''' Get list of all domain names listed in the config + ''' return self.domains.keys() def get_port(self): diff --git a/domain.py b/domain.py index 58dbe31..123ffa5 100644 --- a/domain.py +++ b/domain.py @@ -1,6 +1,8 @@ from config import BatmanDomainOptions class Domain(): + ''' Abstract container object for a freifunk domain + ''' def __init__(self, mcast_link, mcast_site, ipv4_gateway): self.mcast_link = mcast_link self.mcast_site = mcast_site @@ -16,19 +18,27 @@ def get_multicast_address_site(self): return self.mcast_site def get_interfaces(self): - raise NotImplementedException() + ''' Returns list off all interfaces respondd queries are + expected to arrive on + ''' + return self.interfaces def get_provider_args(self): + ''' Returns dict of parameters respondd queries are + expected to arrive on + ''' return { 'mesh_ipv4': self.get_ipv4_gateway() } class BatadvDomain(Domain): + ''' Container object for a batman freifunk domain + ''' def __init__(self, domconfig): self.batman_iface = domconfig.batman_iface self.interfaces = domconfig.interfaces super().__init__(domconfig.mcast_link, domconfig.mcast_site, domconfig.ipv4_gateway) def get_interfaces(self): - return [self.batman_iface] + self.interfaces + return super().get_interfaces() + [self.batman_iface] def get_batman_interface(self): return self.batman_iface @@ -39,6 +49,8 @@ def get_provider_args(self): return args class DomainRegistry(): + ''' Simple singleton based registry for freifunk domains + ''' instance = None @classmethod def get_instance(cls): @@ -60,6 +72,8 @@ def get_domain_by_interface(self, iface): return None def get_interfaces(self): + ''' Get all domain interfaces known to this registry + ''' return self.domain_by_iface.keys() def get_default_domain(self): @@ -69,6 +83,8 @@ def set_default_domain(self, dom): self.default_domain = dom class DomainType(): + ''' Domain type, links domain type to its options + ''' @staticmethod def get(name): if not name in domain_types: @@ -80,6 +96,9 @@ def __init__(self, name, options, domain_type): self.options = options self.domain_type = domain_type +# List of domain types, key is used as domain type in config +# Use only lower case keys, domain type from config is converted to lower +# case during parsing domain_types = { 'batadv': DomainType('batadv', BatmanDomainOptions, BatadvDomain), } From 2e24b20f86aa15675ed49a355f42c47927169640 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 15:32:56 +0200 Subject: [PATCH 05/17] show default config file location --- respondd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/respondd.py b/respondd.py index d37cbea..35bc958 100755 --- a/respondd.py +++ b/respondd.py @@ -58,7 +58,7 @@ def handle(self): %(prog)s [-f ] [-d ]""") parser.add_argument('-f', dest='config', default='./respondd.conf', metavar='', - help='config file to use') + help='config file to use (default: $PWD/respondd.conf)') parser.add_argument('-d', dest='directory', default='./providers', metavar='', help='data provider directory (default: $PWD/providers)') From 5b91f24113b74ba81a78152e1facd029aad7dcec Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 15:49:17 +0200 Subject: [PATCH 06/17] Update README for new config system --- README.md | 137 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 83a39dc..bc719fb 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ information needed for example to display the name of the server on node-maps. systemctl restart respondd systemctl status respondd +Note that you might need to transition from the old, commandline argument based config method to +the new config file based method when upgrading from an older version + ### Alfred Open the Alfred port UDP 16962 in your firewall. Add _announce.sh_ to your @@ -62,33 +65,40 @@ ratelimit it on the allowed interfaces for the same reason. Those are all available options (`respondd --help`): ``` +usage: respondd.py -h - respondd.py [-p ] [-g ] [-i [%]] [-i [%] ..] [-d ] [-b [:] [-n ] ..] + respondd.py [-f ] [-d ] optional arguments: - -h, --help show this help message and exit - -p port number to listen on (default 1001) - -g - link-local multicast group (default ff02::2:1001), set - to emtpy string to disable - -s - site-local multicast group (default ff05::2:1001), set - to empty string to disable - -i listening interface (default bat0), may be specified - multiple times - -d data provider directory (default: $PWD/providers) - -b batman-adv interface to answer for (default: bat0). - Specify once per domain - -m mesh ipv4 address - -n (default) domain code for system/domain_code - -c - domain_code.json path (if info is not in file, - fallback to -n's value) - - -This is a possible configuration for a site with a single domain: - - `respondd.py -d /opt/mesh-announce/providers -i -i -b -m -n ` + -h, --help show this help message and exit + -f config file to use (default: $PWD/respondd.conf) + -d data provider directory (default: $PWD/providers) + +``` +Configuration is done via a ini-style config file. A possible config for a setup with a single batman domain in outlined in `respondd.conf.example`. +The following is a more complete breakdown of the settings required: +``` +# Default settings +[Defaults] +# Listen port, defaults to 1001 +Port: 1001 +# Default multicast listen addresses +MulticastLinkAddress: ff02::2:1001 +MulticastSiteAddress: ff05::2:1001 +# Default domain to use +DefaultDomain: +# Default domain type +DefaultDomainType: batadv + +# A domain +[] +# Batman interface, mandatory +BatmanInterface: +# Other listen interfaces +Interfaces: , +# IPv4 gateway option for ddhcpd +IPv4Gateway: +``` * ``: interfacename of mesh-bridge (for example br-ffXX) * ``: interfacename of fastd or tuneldigger (for example ffXX-mvpn) @@ -97,25 +107,94 @@ This is a possible configuration for a site with a single domain: you can get the ip with `ip a s dev br-ffXX|grep inet|head -n1|cut -d" " -f 6|sed 's|/.*||g'` * ``: The internal domain_code, identical with the gluon domain_name -The ipv4 address can be requested for example by +The can be requested for example by [ddhcpd](https://github.com/TobleMiner/gluon-sargon/blob/feature-respondd-gateway-update/ddhcpd/files/usr/sbin/ddhcpd-gateway-update#L3) via `gluon-neighbour-info -p 1001 -d ff02::1 -i bat0 -r gateway` This will request all json objects for all gateways. The json object for the -gateway can then be selected by the known macadress. The ip4 is stored in +gateway can then be selected by the known macadress. The IPv4 address is stored in `node_id.address.ipv4`. Configuration for a multi-domain site (domains 'one', 'two' and 'three') might look like this: - `respondd.py -d /opt/mesh-announce/providers -i meshvpn-one -i br-one -i bat-one -b bat-one -i meshvpn-two -i br-two -i bat-two -b bat-two -i meshvpn-three -i br-three -i bat-three -b bat-three` +``` +# Default settings +[Defaults] +# Listen port, defaults to 1001 +Port: 1001 +# Default multicast listen addresses +MulticastLinkAddress: ff02::2:1001 +MulticastSiteAddress: ff05::2:1001 +# Default domain type +DefaultDomainType: batadv +# IPv4 gateway option for ddhcpd +IPv4Gateway: 10.42.0.1 + +# First domain +[one] +# Batman interface, mandatory for batman domains +BatmanInterface: bat-one +# Other listen interfaces +Interfaces: br-one, mvpn-one + +# Second domain +[two] +# Batman interface, mandatory for batman domains +BatmanInterface: bat-two +# Other listen interfaces +Interfaces: br-two, mvpn-two + +# Third domain +[three] +# Batman interface, mandatory for datman domains +BatmanInterface: bat-three +# Other listen interfaces +Interfaces: br-three, mvpn-three +``` In a more complex configuration involving the distributed DHCP deamon ddhcpd you might want to advertise different ipv4 gateways depending on the domain the query came from. This can be realized by adding gateway address overrides to the corresponding batman interfaces: - `respondd.py -d /opt/mesh-announce/providers -i meshvpn-one -i br-one -i bat-one -b bat-one:10.42.1.1 -i meshvpn-two -i br-two -i bat-two -b bat-two:10.42.2.1 -i meshvpn-three -i br-three -i bat-three -b bat-three:10.42.3.1` - +``` +# Default settings +[Defaults] +# Listen port, defaults to 1001 +Port: 1001 +# Default multicast listen addresses +MulticastLinkAddress: ff02::2:1001 +MulticastSiteAddress: ff05::2:1001 +# Default domain type +DefaultDomainType: batadv + +# First domain +[one] +# Batman interface, mandatory for batman domains +BatmanInterface: bat-one +# Other listen interfaces +Interfaces: br-one, mvpn-one +# IPv4 gateway option for ddhcpd +IPv4Gateway: 10.42.0.1 + +# Second domain +[two] +# Batman interface, mandatory for batman domains +BatmanInterface: bat-two +# Other listen interfaces +Interfaces: br-two, mvpn-two +# IPv4 gateway option for ddhcpd +IPv4Gateway: 10.42.8.1 + +# Third domain +[three] +# Batman interface, mandatory for datman domains +BatmanInterface: bat-three +# Other listen interfaces +Interfaces: br-three, mvpn-three +# IPv4 gateway option for ddhcpd +IPv4Gateway: 10.42.16.1 +``` ### Debugging From cbbbb9c524bc09e2145817609d7c0559eb927b21 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 16:14:35 +0200 Subject: [PATCH 07/17] Support default domain --- domain.py | 24 ++++++++++-------------- respondd.py | 11 ++++++++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/domain.py b/domain.py index 123ffa5..af73666 100644 --- a/domain.py +++ b/domain.py @@ -3,25 +3,23 @@ class Domain(): ''' Abstract container object for a freifunk domain ''' - def __init__(self, mcast_link, mcast_site, ipv4_gateway): - self.mcast_link = mcast_link - self.mcast_site = mcast_site - self.ipv4_gateway = ipv4_gateway + def __init__(self, config): + self.config = config def get_ipv4_gateway(self): - return self.ipv4_gateway + return self.config.ipv4_gateway def get_multicast_address_link(self): - return self.mcast_link + return self.config.mcast_link def get_multicast_address_site(self): - return self.mcast_site + return self.config.mcast_site def get_interfaces(self): ''' Returns list off all interfaces respondd queries are expected to arrive on ''' - return self.interfaces + return self.config.interfaces def get_provider_args(self): ''' Returns dict of parameters respondd queries are @@ -32,16 +30,14 @@ def get_provider_args(self): class BatadvDomain(Domain): ''' Container object for a batman freifunk domain ''' - def __init__(self, domconfig): - self.batman_iface = domconfig.batman_iface - self.interfaces = domconfig.interfaces - super().__init__(domconfig.mcast_link, domconfig.mcast_site, domconfig.ipv4_gateway) + def __init__(self, config): + super().__init__(config) def get_interfaces(self): - return super().get_interfaces() + [self.batman_iface] + return super().get_interfaces() + [self.get_batman_interface()] def get_batman_interface(self): - return self.batman_iface + return self.config.batman_iface def get_provider_args(self): args = super().get_provider_args() diff --git a/respondd.py b/respondd.py index 35bc958..265bfef 100755 --- a/respondd.py +++ b/respondd.py @@ -36,7 +36,10 @@ def handle(self): domain = DomainRegistry.get_instance().get_domain_by_interface(iface) if not domain: - return + # Try default domain, ignore request if not configured + domain = DomainRegistry.get_instance().get_default_domain() + if not domain: + return provider_env = domain.get_provider_args() @@ -68,8 +71,10 @@ def handle(self): config = Config.from_file(args.config) for domname in config.get_domain_names(): domcfg = config.get_domain_config(domname) - DomainRegistry.get_instance().add_domain(domcfg.domain_type(domcfg)) - DomainRegistry.get_instance().set_default_domain + domain = domcfg.domain_type(domcfg) + DomainRegistry.get_instance().add_domain(domain) + if domname == config.get_default_domain(): + DomainRegistry.get_instance().set_default_domain(domain) metasocketserver.MetadataUDPServer.address_family = socket.AF_INET6 metasocketserver.MetadataUDPServer.allow_reuse_address = True From 08d7336b98d3bf94d4b9e5d91868b78289ca612b Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 16:17:00 +0200 Subject: [PATCH 08/17] Add simple domain type --- domain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/domain.py b/domain.py index af73666..2bc57f6 100644 --- a/domain.py +++ b/domain.py @@ -96,6 +96,7 @@ def __init__(self, name, options, domain_type): # Use only lower case keys, domain type from config is converted to lower # case during parsing domain_types = { + 'simple': DomainType('simple', DomainOptions, Domain), 'batadv': DomainType('batadv', BatmanDomainOptions, BatadvDomain), } From 579c1722fc961f5aa68d284cab27831bbba26eb5 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 16:24:34 +0200 Subject: [PATCH 09/17] Split commandline section --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc719fb..b93e728 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ and B.A.T.M.A.N. advanced interfaces. Please *don't* open the port globally, as it can be used for traffic amplification attacks. You also might want to ratelimit it on the allowed interfaces for the same reason. -#### commandline options +#### Commandline options Those are all available options (`respondd --help`): @@ -75,6 +75,9 @@ optional arguments: -d data provider directory (default: $PWD/providers) ``` + +#### Configuration + Configuration is done via a ini-style config file. A possible config for a setup with a single batman domain in outlined in `respondd.conf.example`. The following is a more complete breakdown of the settings required: ``` From f7f3e18ab90c34625198390309751c039cdd7a71 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 16:33:12 +0200 Subject: [PATCH 10/17] Enhance example config documentation --- respondd.conf.example | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/respondd.conf.example b/respondd.conf.example index 045c2ae..e0be388 100644 --- a/respondd.conf.example +++ b/respondd.conf.example @@ -1,22 +1,40 @@ # Default settings [Defaults] # Listen port +# optional, default: 1001 Port: 1001 -# Default multicast listen addresses +# Default link local listen addresses +# optional, default: ff02::2:1001 MulticastLinkAddress: ff02::2:1001 +# Default site local listen addresses +# optional, default: ff05::2:1001 MulticastSiteAddress: ff05::2:1001 # Default domain to use +# optional, if specified incoming requests that can not be mapped to a domain +# are mapped to this domain DefaultDomain: ffki # Default domain type +# optional DefaultDomainType: batadv +# Default ddhcpd IPv4 gateway address +# optional +IPv4Gateway: 10.116.128.8 # A domain +# User your own domain name here [ffki] # This is a batman domain +# optional, mandatory if DefaultDomainType not set, default: @DefaultDomainType +# supported domain types are: simple, batadv DomainType: batadv # Batman interface +# only for batadv domains, mandatory for batadv domains BatmanInterface: bat-ffki # Other listen interfaces +# optional, specify listen/multicast interfaces for this domain here Interfaces: mvpn-ffki # IPv4 gateway option for ddhcpd +# optional, default: @IPv4Gateway IPv4Gateway: 10.116.128.8 + +# An arbitrary number of further domains may follow here From 0bbdd2dad242304fea355cb79cb38a1422be03eb Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Sat, 11 Apr 2020 16:40:32 +0200 Subject: [PATCH 11/17] Import domain options --- domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain.py b/domain.py index 2bc57f6..4675f77 100644 --- a/domain.py +++ b/domain.py @@ -1,4 +1,4 @@ -from config import BatmanDomainOptions +from config import BatmanDomainOptions, DomainOptions class Domain(): ''' Abstract container object for a freifunk domain From 3d462c3a862c1835116c18deeadffd42f8115f7d Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Wed, 15 Apr 2020 11:27:01 +0200 Subject: [PATCH 12/17] Don't shadow type() function --- config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index e12544f..5b56049 100644 --- a/config.py +++ b/config.py @@ -21,9 +21,9 @@ def from_parser(cls, section, parser, globals): ''' from domain import DomainType - type = parser.get(section, 'DomainType', fallback=globals.default_domain_type) + domain_type = parser.get(section, 'DomainType', fallback=globals.default_domain_type) # Get DomainOptions subclass for type and instantiate - return DomainType.get(type.lower()).options(section, parser, globals) + return DomainType.get(domain_type.lower()).options(section, parser, globals) def __init__(self, name, parser, globals): ''' Initialize common options From 6781afa5190f624e83c0e8b79441e7d57e5b6b33 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Wed, 15 Apr 2020 11:27:38 +0200 Subject: [PATCH 13/17] Fix domain type fallback --- config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config.py b/config.py index 5b56049..8ebf80a 100644 --- a/config.py +++ b/config.py @@ -28,11 +28,14 @@ def from_parser(cls, section, parser, globals): def __init__(self, name, parser, globals): ''' Initialize common options ''' + from domain import Domain + self.name = name self.interfaces = list(map(str.strip, parser.get(name, 'Interfaces', fallback='').split(','))) self.mcast_link = parser.get(name, 'MulticastLinkAddress', fallback=globals.mcast_link) self.mcast_site = parser.get(name, 'MulticastSiteAddress', fallback=globals.mcast_site) self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=globals.ipv4_gateway) + self.domain_type = Domain class BatmanDomainOptions(DomainOptions): ''' Container for batman specific options @@ -78,7 +81,7 @@ def _initialize_global_options(self, parser): parser.get(None, 'MulticastLinkAddress', fallback='ff02::2:1001'), parser.get(None, 'MulticastSiteAddress', fallback='ff05::2:1001'), parser.get(None, 'DefaultDomain', fallback=None), - parser.get(None, 'DefaultDomainType', fallback=None), + parser.get(None, 'DefaultDomainType', fallback='simple'), parser.get(None, 'IPv4Gateway', fallback=None), ) From ce359cbff08bae00022cec3a90f99e61aaca904c Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Wed, 15 Apr 2020 12:02:26 +0200 Subject: [PATCH 14/17] Use configparser correctly Misread the documentation, inheritance from the default section should now work correctly --- README.md | 6 +++--- config.py | 22 +++++++--------------- respondd.conf.example | 4 ++-- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b93e728..5a9f83d 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ MulticastSiteAddress: ff05::2:1001 # Default domain to use DefaultDomain: # Default domain type -DefaultDomainType: batadv +DomainType: batadv # A domain [] @@ -131,7 +131,7 @@ Port: 1001 MulticastLinkAddress: ff02::2:1001 MulticastSiteAddress: ff05::2:1001 # Default domain type -DefaultDomainType: batadv +DomainType: batadv # IPv4 gateway option for ddhcpd IPv4Gateway: 10.42.0.1 @@ -169,7 +169,7 @@ Port: 1001 MulticastLinkAddress: ff02::2:1001 MulticastSiteAddress: ff05::2:1001 # Default domain type -DefaultDomainType: batadv +DomainType: batadv # First domain [one] diff --git a/config.py b/config.py index 8ebf80a..fc8b59c 100644 --- a/config.py +++ b/config.py @@ -3,13 +3,9 @@ class GlobalOptions(): ''' Container class for global options ''' - def __init__(self, port, mcast_link, mcast_site, default_domain, default_domain_type, ipv4_gateway): + def __init__(self, port, default_domain): self.port = port - self.mcast_link = mcast_link - self.mcast_site = mcast_site self.default_domain = default_domain - self.default_domain_type = default_domain_type - self.ipv4_gateway = ipv4_gateway class DomainOptions(): ''' Base container class for per domain options @@ -21,7 +17,7 @@ def from_parser(cls, section, parser, globals): ''' from domain import DomainType - domain_type = parser.get(section, 'DomainType', fallback=globals.default_domain_type) + domain_type = parser.get(section, 'DomainType', fallback='simple') # Get DomainOptions subclass for type and instantiate return DomainType.get(domain_type.lower()).options(section, parser, globals) @@ -32,9 +28,9 @@ def __init__(self, name, parser, globals): self.name = name self.interfaces = list(map(str.strip, parser.get(name, 'Interfaces', fallback='').split(','))) - self.mcast_link = parser.get(name, 'MulticastLinkAddress', fallback=globals.mcast_link) - self.mcast_site = parser.get(name, 'MulticastSiteAddress', fallback=globals.mcast_site) - self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=globals.ipv4_gateway) + self.mcast_link = parser.get(name, 'MulticastLinkAddress', fallback='ff02::2:1001') + self.mcast_site = parser.get(name, 'MulticastSiteAddress', fallback='ff05::2:1001') + self.ipv4_gateway = parser.get(name, 'IPv4Gateway', fallback=None) self.domain_type = Domain class BatmanDomainOptions(DomainOptions): @@ -77,12 +73,8 @@ def _initialize_global_options(self, parser): ''' Set all global options ''' self.globals = GlobalOptions( - parser.getint(None, 'Port', fallback=1001), - parser.get(None, 'MulticastLinkAddress', fallback='ff02::2:1001'), - parser.get(None, 'MulticastSiteAddress', fallback='ff05::2:1001'), - parser.get(None, 'DefaultDomain', fallback=None), - parser.get(None, 'DefaultDomainType', fallback='simple'), - parser.get(None, 'IPv4Gateway', fallback=None), + parser.getint('Defaults', 'Port', fallback=1001), + parser.get('Defaults', 'DefaultDomain', fallback=None) ) def _initialize_domain_options(self, parser, domain): diff --git a/respondd.conf.example b/respondd.conf.example index e0be388..e85bed2 100644 --- a/respondd.conf.example +++ b/respondd.conf.example @@ -15,7 +15,7 @@ MulticastSiteAddress: ff05::2:1001 DefaultDomain: ffki # Default domain type # optional -DefaultDomainType: batadv +DomainType: batadv # Default ddhcpd IPv4 gateway address # optional IPv4Gateway: 10.116.128.8 @@ -24,7 +24,7 @@ IPv4Gateway: 10.116.128.8 # User your own domain name here [ffki] # This is a batman domain -# optional, mandatory if DefaultDomainType not set, default: @DefaultDomainType +# optional, mandatory if DomainType not set, default: @DomainType # supported domain types are: simple, batadv DomainType: batadv # Batman interface From 4e0d1bd76666a87ca9de3a780505e6b97b3a9124 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Wed, 15 Apr 2020 12:49:32 +0200 Subject: [PATCH 15/17] Update config example --- respondd.conf.example | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/respondd.conf.example b/respondd.conf.example index e85bed2..4726df4 100644 --- a/respondd.conf.example +++ b/respondd.conf.example @@ -14,7 +14,8 @@ MulticastSiteAddress: ff05::2:1001 # are mapped to this domain DefaultDomain: ffki # Default domain type -# optional +# optional, default: simple +# supported domain types are: simple, batadv DomainType: batadv # Default ddhcpd IPv4 gateway address # optional @@ -24,11 +25,17 @@ IPv4Gateway: 10.116.128.8 # User your own domain name here [ffki] # This is a batman domain -# optional, mandatory if DomainType not set, default: @DomainType +# optional, default: @Defaults.DomainType # supported domain types are: simple, batadv DomainType: batadv +# Link local listen addresses +# optional, default: @Defaults.MulticastLinkAddress +MulticastLinkAddress: ff02::2:1001 +# Site local listen addresses +# optional, default: @Defautls.MulticastSiteAddress +MulticastSiteAddress: ff05::2:1001 # Batman interface -# only for batadv domains, mandatory for batadv domains +# only for batadv domains, defaults to bat- BatmanInterface: bat-ffki # Other listen interfaces # optional, specify listen/multicast interfaces for this domain here From 0c80ffd9e95cd2a71f001538a2843dc00a234b72 Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Wed, 15 Apr 2020 18:24:57 +0200 Subject: [PATCH 16/17] State format of interface specification --- respondd.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/respondd.conf.example b/respondd.conf.example index 4726df4..b30f146 100644 --- a/respondd.conf.example +++ b/respondd.conf.example @@ -38,7 +38,7 @@ MulticastSiteAddress: ff05::2:1001 # only for batadv domains, defaults to bat- BatmanInterface: bat-ffki # Other listen interfaces -# optional, specify listen/multicast interfaces for this domain here +# optional, specify comma separated list of listen/multicast interfaces for this domain here Interfaces: mvpn-ffki # IPv4 gateway option for ddhcpd # optional, default: @IPv4Gateway From d3d587b10f58c310b13c7a5f8f5dc0b86ff35f3a Mon Sep 17 00:00:00 2001 From: Tobias Schramm Date: Mon, 20 Apr 2020 16:45:51 +0200 Subject: [PATCH 17/17] Add domain_code to provider env --- domain.py | 8 +++++++- providers/nodeinfo/system/domain_code.py | 9 +++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/domain.py b/domain.py index 4675f77..bc9158c 100644 --- a/domain.py +++ b/domain.py @@ -6,6 +6,9 @@ class Domain(): def __init__(self, config): self.config = config + def get_name(self): + return self.config.name + def get_ipv4_gateway(self): return self.config.ipv4_gateway @@ -25,7 +28,10 @@ def get_provider_args(self): ''' Returns dict of parameters respondd queries are expected to arrive on ''' - return { 'mesh_ipv4': self.get_ipv4_gateway() } + return { + 'domain_code': self.get_name(), + 'mesh_ipv4': self.get_ipv4_gateway() + } class BatadvDomain(Domain): ''' Container object for a batman freifunk domain diff --git a/providers/nodeinfo/system/domain_code.py b/providers/nodeinfo/system/domain_code.py index 28b2181..31074ae 100644 --- a/providers/nodeinfo/system/domain_code.py +++ b/providers/nodeinfo/system/domain_code.py @@ -2,10 +2,7 @@ class Source(providers.DataSource): def required_args(self): - return ['batadv_dev', 'domain_code', 'known_codes'] + return ['domain_code'] - def call(self, batadv_dev, domain_code, known_codes): - try: - return known_codes[batadv_dev] - except KeyError: - return domain_code + def call(self, domain_code): + return domain_code