The External Routing allows implementing routing algorithms externally (e.g. outside the dtn7-rs codebase). It works by exposing a realtime JSON API via WebSocket. With the help of the erouting it is possible to easily implement new routing algorithms in different languages. All languages that can encode / decode JSON and communicate via WebSocket should in theory work.
To enable the erouting use -r external
as a routing algorithm.
Configuration can also happen via a config file by setting the routing to external
.
routing = "external"
The WebSocket is accessible under the same port as defined by -w
, --web-port
and the route /ws/erouting
. An example for a web port 3000 would be 127.0.0.1:3000/ws/erouting
.
Connecting is only possible if no other external router is already connected. If a connection is successful the ERouting will start by sending an initial PeerState
and ServiceState
packet. After that RequestSenderForBundle
and other Packets can be received or send.
All packets are JSON encoded and contain a field called type
which specifies (as the name implies) the type of the packet.
dtnd → external
The RequestSenderForBundle
is the most important packet. It specifies that a bundle wants to be sent by dtnd
and senders that it can be forwarded to are wanted from your router.
clas
: The registered clas at the time of the requestbp
: The bundle pack containing information about the bundle
{
"type": "RequestSenderForBundle",
"clas": ["mtcp", "lora"],
"bp": { ... }
}
dtnd → external
The SendingFailed
is emitted when the sending of a bundle failed.
{
"type": "SendingFailed",
"bid": "bundle_id",
"cla_sender": "cla_name"
}
dtnd → external
The Error
is emitted when an error occurs. At the Moment the only emitted error is when an external router is already connected. In that case the error will be sent and the connection closed.
{
"type": "Error",
"reason": "Error reason"
}
dtnd → external
The Timeout
is emitted when no SenderForBundleReponse
was recieved or the timeout (250ms) ran out.
{
"type": "Timeout",
"bp": { ... }
}
dtnd → external
The IncomingBundle
is emitted when a bundle is incoming.
{
"type": "IncomingBundle",
"bundle": { ... }
}
dtnd → external
The IncomingBundleWithoutPreviousNode
is emitted when a bundle is incoming with a previous node.
{
"type": "IncomingBundleWithoutPreviousNode",
"bid": "bundle_id",
"node_name": "nodex"
}
dtnd → external
The EncounteredPeer
is a signal that a new peer is encountered.
{
"type": "EncounteredPeer",
"name": "nodex",
"eid": [ 1, "//nodex/..." ],
"peer": { ... }
}
dtnd → external
The DroppedPeer
is a signal that a peer was dropped.
{
"type": "DroppedPeer",
"name": "nodex",
"eid": [ 1, "//nodex/..." ]
}
dtnd → external
The PeerState
is the initial state of the peers.
{
"type": "PeerState",
"peers": {
"peer_addr_x": { ... },
"peer_addr_y": { ... },
"peer_addr_z": { ... },
...
}
}
dtnd → external
The ServiceState
is the initial state of the peers.
{
"type": "ServiceState",
"services": {
"tag": "name",
...
}
}
external → dtnd
The AddService
packet can be sent to register a service.
{
"type": "AddService",
"tag": 10,
"service": "name"
}
external → dtnd
The ResponseSenderForBundle
as the name suggests is the response to a RequestSenderForBundle
packet by your router.
bp
: The original bundle pack of the requestclas
: List of applicable senders each containing:remote
: The remote addressport
(optional): Port for the CLAagent
: Name of the CLAnext_hop
: EndpointID of the next hop (e.g. the node where the bundle should be sent to)
{
"type": "ResponseSenderForBundle",
"bp": { ... },
"clas": [
{
"remote": { ... },
"port": 1837,
"agent": "mtcp",
"next_hop": [ 1, "//nodex/..." ]
},
...
]
}
In order to implement a basic routing strategy you most likely want to do the following
- Connect to the WebSocket
- Keep track of the peers by:
- Saving the initial
PeerState
- Adding peer if
PeerEncountered
packet is received - Removing peer
PeerDropped
packet is received
- Saving the initial
- Responding to
SenderForBundle
packet with aResponseSenderForBundle
packet based on some kind of strategy and most likely involving the available peers.
The example shows how you can implement a direct routing strategy in python. Direct routing means that a bundle is only passed if the target peer is directly connected to the current node.
import websocket
import json
# Minimal example for external routing with a direct routing strategy.
#
# Requirements:
# pip3 install websocket-client
# Dict of available peers
peers = {}
# Dict of delivered bundles
delivered = {}
# Extract node name from [1, "//nodex/..."]
def get_node_from_endpoint(endpoint):
url = endpoint[1][2:]
return url[:url.index("/")]
#
# Base WebSocket Handler
#
def on_open(ws):
print("Connected")
def on_message(ws, raw):
print(raw)
msg = json.loads(raw)
switcher = {
"RequestSenderForBundle": on_sender_for_bundle,
"PeerState": on_peer_state,
"PeerEncountered": on_peer_encountered,
"PeerDropped": on_peer_dropped,
"SendingFailed": on_sending_failed,
"Timeout": on_sending_timeout,
}
switcher.get(msg["type"])(msg)
#
# Packet Handlers
#
def on_sender_for_bundle(msg):
dest = msg["bp"]["destination"]
bundle_id = msg["bp"]["id"]
print("===> SenderForBundle: To", dest[1], bundle_id)
# Check if already delivered
global delivered
if bundle_id in delivered:
print("Bundle was already delivered!")
return
# Check if peer is known
target = peers.get(get_node_from_endpoint(dest))
if target is not None:
req_cla_list = msg['clas']
peer_cla_list = target["cla_list"]
target_clas = []
print(target.eid)
# Search for possible clas
for c in peer_cla_list:
if req_cla_list.index(c[0]) >= 0:
target_clas.append({
"remote": target["addr"],
"agent": c[0],
"port": c[1],
"next_hop": target["eid"]
})
# Build and send response
resp = {
"type": "ResponseSenderForBundle",
"bp": msg["bp"],
"clas": target_clas,
"delete_afterwards": True
}
# If some cla was found set as delivered
if len(target_clas) > 0:
delivered[bundle_id] = True
print("Peer is known and sending is requested via", target_clas)
else:
print("Peer is known but no cla could be selected")
# Send response
wsapp.send(json.dumps(resp))
else:
print("Peer not directly known")
def on_peer_state(msg):
print("===> PeerState")
global peers
peers = msg["peers"]
def on_peer_encountered(msg):
print("===> PeerEncountered: Encountered", msg["name"])
global peers
peers[msg["name"]] = msg
def on_peer_dropped(msg):
print("===> PeerDropped: Dropping", msg["name"])
global peers
del peers[msg["name"]]
def on_sending_failed(msg):
print("===> SendingFailed: For Bundle", msg['bid'])
# Remove from delivered if sending failed
global delivered
del delivered[msg['bid']]
def on_sending_timeout(msg):
print("===> Timeout: For Bundle", msg["bp"]["id"])
# Remove from delivered if sending was not received fast enough
global delivered
del delivered[msg["bp"]["id"]]
#
# Start WebSocket Client
#
wsapp = websocket.WebSocketApp("ws://127.0.0.1:3000/ws/erouting", on_message=on_message, on_open=on_open)
wsapp.run_forever()