Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] [RFT] Move options to config file #60

Merged
merged 17 commits into from
Sep 7, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 112 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,38 +60,48 @@ 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`):

```
usage:
respondd.py -h
respondd.py [-p <port>] [-g <group>] [-i [<group>%]<if0>] [-i [<group>%]<if1> ..] [-d <dir>] [-b <batman_iface>[:<mesh_ipv4>] [-n <domain code>] ..]
respondd.py [-f <configfile>] [-d <dir>]

optional arguments:
-h, --help show this help message and exit
-p <port> port number to listen on (default 1001)
-g <link local group>
link-local multicast group (default ff02::2:1001), set
to emtpy string to disable
-s <site local group>
site-local multicast group (default ff05::2:1001), set
to empty string to disable
-i <iface> listening interface (default bat0), may be specified
multiple times
-d <dir> data provider directory (default: $PWD/providers)
-b <iface> batman-adv interface to answer for (default: bat0).
Specify once per domain
-m <mesh_ipv4> mesh ipv4 address
-n <domain code> (default) domain code for system/domain_code
-c <domain code_file>
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 <your-clientbridge-if> -i <your-mesh-vpn-if> -b <your-batman-if> -m <mesh ipv4 address> -n <domain code>`
-h, --help show this help message and exit
-f <configfile> config file to use (default: $PWD/respondd.conf)
-d <dir> 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:
```
# 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: <domain code>
# Default domain type
DomainType: batadv

# A domain
[<domain code>]
# Batman interface, mandatory
BatmanInterface: <your-batman-if>
# Other listen interfaces
Interfaces: <your-clientbridge-if>, <your-mesh-vpn-if>
# IPv4 gateway option for ddhcpd
IPv4Gateway: <mesh ipv4 address>
```

* `<your-clientbridge-if>`: interfacename of mesh-bridge (for example br-ffXX)
* `<your-mesh-vpn-if>`: interfacename of fastd or tuneldigger (for example ffXX-mvpn)
Expand All @@ -97,25 +110,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'`
* `<domain code>`: The internal domain_code, identical with the gluon domain_name

The ipv4 address can be requested for example by
The <mesh ipv4 address> 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
DomainType: 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
DomainType: 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

Expand Down
97 changes: 97 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from configparser import ConfigParser

class GlobalOptions():
''' Container class for global options
'''
def __init__(self, port, default_domain):
self.port = port
self.default_domain = default_domain

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

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)

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='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):
''' 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

class Config():
''' Represents a parsed config file
'''
@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():
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):
''' Set all global options
'''
self.globals = GlobalOptions(
parser.getint('Defaults', 'Port', fallback=1001),
parser.get('Defaults', 'DefaultDomain', fallback=None)
)

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):
return self.globals.port

def get_default_domain(self):
return self.globals.default_domain

def get_domain_config(self, domain):
return self.domains[domain]
102 changes: 102 additions & 0 deletions domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from config import BatmanDomainOptions, DomainOptions

class Domain():
''' Abstract container object for a freifunk domain
'''
def __init__(self, config):
self.config = config

def get_ipv4_gateway(self):
return self.config.ipv4_gateway

def get_multicast_address_link(self):
return self.config.mcast_link

def get_multicast_address_site(self):
return self.config.mcast_site

def get_interfaces(self):
''' Returns list off all interfaces respondd queries are
expected to arrive on
'''
return self.config.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, config):
super().__init__(config)

def get_interfaces(self):
return super().get_interfaces() + [self.get_batman_interface()]

def get_batman_interface(self):
return self.config.batman_iface

def get_provider_args(self):
args = super().get_provider_args()
args.update({ 'batadv_dev': self.get_batman_interface() })
return args

class DomainRegistry():
''' Simple singleton based registry for freifunk domains
'''
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):
''' Get all domain interfaces known to this registry
'''
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():
''' Domain type, links domain type to its options
'''
@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

# 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 = {
'simple': DomainType('simple', DomainOptions, Domain),
'batadv': DomainType('batadv', BatmanDomainOptions, BatadvDomain),
}

Loading