Skip to content

Commit

Permalink
Initial zeroconf app and example config for its use
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianSipos committed Dec 1, 2024
1 parent 25fe435 commit b650ce9
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 41 deletions.
34 changes: 34 additions & 0 deletions container/example-zeroconf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
nets:
dtnA:
subnet4: "192.168.100.0/24"
dtnB:
subnet4: "192.168.110.0/24"
# subnet6: "fda1:1cec:f450:c055::/64"

nodes:
node000:
nets: [dtnA]
keys:
sign:
keytype: SECP256R1
config:
apps:
zeroconf:
enumerate: true
tls_enable: false

node001:
nets: [dtnA, dtnB]
config:
apps:
zeroconf:
offer: true
tls_enable: false

node002:
nets: [dtnB]
config:
apps:
zeroconf:
enumerate: true
tls_enable: false
32 changes: 18 additions & 14 deletions container/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,30 @@ nodes:
node000:
nets: [dtnA]
keys:
sign:
keytype: SECP256R1
sign:
keytype: SECP256R1
config:
apps:
nmp:
enable: true
tls_enable: true
apps:
nmp:
enable: true
zeroconf:
enumerate: true
tls_enable: true

node001:
nets: [dtnA, dtnB]
config:
apps:
nmp:
enable: true
tls_enable: true
apps:
nmp:
enable: true
zeroconf:
offer: true
tls_enable: true

node002:
nets: [dtnB]
config:
apps:
nmp:
enable: true
tls_enable: true
apps:
nmp:
enable: true
tls_enable: true
4 changes: 2 additions & 2 deletions container/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,11 @@ def action(self, act):
bp_rx_routes = extconfig.get('bp_rx_routes', [])
bp_rx_routes += [
{
'eid_pattern': r'dtn://{{node_name}}/.*',
'eid_pattern': f'dtn://{node_name}/.*',
'action': 'deliver',
},
{
'eid_pattern': '".*"',
'eid_pattern': '.*',
'action': 'forward',
},
]
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version = "0.0.0"
authors = [
{ name="Brian Sipos", email="[email protected]" },
]
description = "A demonstration agent for the DTN BPv7/TCPCLv4/UDPCL"
description = "A demonstration agent for the DTN BPv7/TCPCLv4/UDPCLv2"
readme = "README.md"
license = { text="LGPL-3" }
requires-python = ">=3.7"
Expand All @@ -29,14 +29,14 @@ dependencies = [
"cryptography >=0.9",
"certvalidator",
"dbus-python",
"lakers-python",
"portion >=2.1",
"psutil",
"PyGObject >=3.34", # glib integration
"PyYAML",
"python3-dtls",
"scapy >=2.4,<2.4.5",
"six",
"zeroconf",
]

[project.optional-dependencies]
Expand Down Expand Up @@ -84,4 +84,4 @@ bp-agent = "bp.cmd:main"

[project.urls]
"Homepage" = "https://github.com/BrianSipos/dtn-demo-agent"
"Bug Tracker" = "https://github.com/BrianSipos/dtn-demo-agent/issues"
"Bug Tracker" = "https://github.com/BrianSipos/dtn-demo-agent/issues"
9 changes: 8 additions & 1 deletion src/bp/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ def exec_loop(self):
# wait for graceful shutdown
eloop.run()

def add_tx_route(self, item:TxRouteItem):
self._config.tx_route_table.append(item)

def get_cla(self, name:str) -> bp.cla.AbstractAdaptor:
return self._cl_agent[name]

def _bus_name_changed(self, servname, old_owner, new_owner):
for cl_agent in self._cl_agent.values():
if cl_agent.serv_name == servname:
Expand Down Expand Up @@ -350,6 +356,7 @@ def _do_fwd(self):
ctr.record_action('forward')
except Exception as err:
self._logger.error('Failed to forward bundle %s: %s', ctr.log_name(), err)
self._logger.debug('%s', traceback.format_exc())
ctr.record_action('delete', StatusReport.ReasonCode.NO_ROUTE)

self._finish_bundle(ctr)
Expand Down Expand Up @@ -435,7 +442,7 @@ def cl_attach(self, cltype, servname):
except KeyError:
raise ValueError('Invalid cltype: {}'.format(cltype))

agent = cls()
agent = cls(agent=self)
agent.serv_name = servname
agent.peer_node_seen = self._cl_peer_node_seen(cltype)
agent.recv_bundle_finish = self._cl_recv_bundle_finish(cltype)
Expand Down
1 change: 1 addition & 0 deletions src/bp/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from . import fragment
from . import bpsec
from . import nmp
from . import zeroconf
2 changes: 1 addition & 1 deletion src/bp/app/nmp.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
''' Application layer adaptors.
''' Prototype of Secure Advertisement and Neighborhood Discovery (SAND).
'''
import cbor2
from dataclasses import dataclass, field, fields
Expand Down
168 changes: 168 additions & 0 deletions src/bp/app/zeroconf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
''' Prototype of Zero-Configuration BP router discovery.
'''
from gi.repository import GLib as glib
import ipaddress
import logging
import random
import re
import socket
from typing import List
import ifaddr
from zeroconf import (
Zeroconf,
ServiceInfo,
ServiceBrowser,
ServiceStateChange,
)

from bp.config import Config, TxRouteItem
from bp.app.base import app, AbstractApplication

LOGGER = logging.getLogger(__name__)

SVCLOCAL = '_dtn-bundle._tcp.local.'
''' Global service name to register under '''


@app('zeroconf')
class App(AbstractApplication):

DBUS_IFACE = 'org.ietf.dtn.bp.zeroconf'
''' Interface name '''

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self._config = None
self._zco = None
self._browser = None

def load_config(self, config:Config):
super().load_config(config)
self._config = config.apps.get(self._app_name, {})

self._zco = Zeroconf()

if self._config.get('offer', False):
glib.timeout_add(1e3 * random.randint(5, 8), self._offer)
if self._config.get('enumerate', False):
glib.timeout_add(1e3 * random.randint(5, 8), self._enumerate)

def _iface_addrs(self) -> List[ipaddress._IPAddressBase]:
all_addrs = []
for adapter in ifaddr.get_adapters():
for ipobj in adapter.ips:
if isinstance(ipobj.ip, tuple):
# ipv6
addr = ipobj.ip[0]
else:
# ipv4
addr = ipobj.ip

addrobj = ipaddress.ip_address(addr)
if addrobj.is_loopback or addrobj.is_reserved:
continue

all_addrs.append(addrobj)

return all_addrs

def _iface_nets(self) -> List[ipaddress._BaseNetwork]:
all_nets = []
for adapter in ifaddr.get_adapters():
for ipobj in adapter.ips:
if isinstance(ipobj.ip, tuple):
# ipv6
addr = ipobj.ip[0]
else:
# ipv4
addr = ipobj.ip

ifaceobj = ipaddress.ip_interface(f'{addr}/{ipobj.network_prefix}')
if ifaceobj.ip.is_loopback or ifaceobj.ip.is_reserved:
continue

all_nets.append(ifaceobj.network)

return all_nets

def _offer(self):
''' Deferred async offer on mDNS. '''

hostname = (
self._config.get('hostname')
or socket.gethostname().split('.', 1)[0]
)
fqdn = hostname + '.local.'

# Offer all reachable unicast addresses
all_addrs = self._iface_addrs()
# ignore when no usable addresses
if not all_addrs:
return False

instname = self._config.get('instance', hostname)
instlocal = f'{instname}.{SVCLOCAL}'

servinfo = ServiceInfo(
SVCLOCAL,
instlocal,
weight=1,
server=fqdn,
addresses=list(map(str, all_addrs)),
port=4556,
properties=dict(
txtvers=1,
protovers=4,
),
)
self._zco.register_service(servinfo)
LOGGER.info('mDNS registered as %s on %s',
instlocal, all_addrs)

return False

def _enumerate(self):

self._browser = ServiceBrowser(
self._zco,
[SVCLOCAL],
handlers=[self._serv_state_change]
)

return False

def _serv_state_change(self, zeroconf, service_type, name, state_change):
all_nets = self._iface_nets()
LOGGER.info('Iface nets: %s', all_nets)

if state_change == ServiceStateChange.Added:
info = zeroconf.get_service_info(service_type, name)
LOGGER.info('Service added: %s', info)

addr_objs = list(map(ipaddress.ip_address, info.addresses))
LOGGER.info('Possible addresses: %s', addr_objs)
usable_addrs = [
addr
for addr in addr_objs
if [net for net in all_nets if addr in net]
]
LOGGER.info('Usable addresses: %s', usable_addrs)
if not usable_addrs:
return
best_addr = str(usable_addrs[0])
best_port = int(info.port)

route = TxRouteItem(
eid_pattern=re.compile(r'.*'),
next_nodeid=None, # FIXME needed?
cl_type='tcpcl',
raw_config=dict(
address=best_addr,
port=best_port,
),
)
LOGGER.info('Route item %s', route)
self._agent.add_tx_route(route)

self._agent.get_cla('tcpcl').connect(best_addr, best_port)
Loading

0 comments on commit b650ce9

Please sign in to comment.