Add Route REST API (fixes #96)

Consists of a single, one-shot request, GET /restful/route/all.json, for
the time being.  A "newsince" request can be added later.

Add a Markdown tech doc that specifies the new Route REST API and update
the MDP tech doc a little.

Add the 'routerestful' test script, which uses some test utility
fuctions that have been factored out of the 'routing' test script into
the new testdefs_routing.sh.

Add the new 'allrestful' test script.
This commit is contained in:
Andrew Bettison 2018-03-22 10:13:08 +10:30
parent 1a091aa8a1
commit 8345d896a6
16 changed files with 711 additions and 99 deletions

View File

@ -8,11 +8,11 @@ make it particularly suitable for use in Ad Hoc wireless networks, which can
suffer high levels of packet loss due to weak signal, interference and
congestion.
MDP carries [messages](#mdp-message) from [sender](#sender) to
[recipient](#recipient) [node](#node)s, or [broadcasts](#broadcast) to all
nodes. MDP guarantees that message contents will be correct if delivered, but
does not guarantee one delivery (messages may be lost or delivered more than
once), arrival time, or message order.
MDP carries [messages](#mdp-message) from [sender](#sender) to [recipient](#recipient)
[node](#node)s, or [broadcasts](#broadcast) to all nodes. MDP guarantees that
message contents will be correct if delivered, but does not guarantee exactly
one delivery (messages may be lost or delivered more than once), arrival time,
or message order.
MDP can be carried over any wireless or wired data link, whether a shared
medium (eg, [CSMA/CA][] used in [Wi-Fi][]) or a dedicated medium (eg, [AX.25
@ -78,7 +78,7 @@ called a *subscriber*), then strictly speaking, the Serval mesh network could
be said to carry messages between *users* not between *devices*. There is
nothing to prevent a [keyring][] entry from being copied from one device to
another, thus it is possible for two or more devices to have the same MDP
Address. At present, Serval routing does not handle this case, so it could
Address. At present, Serval [routing][] does not handle this case, so it could
cause unwanted effects such as route flapping or dropped messages.
In practice, the “duplicate MDP Address” problem is rare for the time being,
@ -261,15 +261,9 @@ MDP transmits a [MDP message](#mdp-message) over a [link](#link) by
encapsulating it into an **overlay packet** (also called **MDP packet** or
**overlay frame**). The MDP overlay packet is a [layer 2][] concept; it is
only concerned with transporting MDP messages across a single link to a
neighbouring *peer* [node](#node). Once an overlay packet arrives, the
receiver unpacks all of its MDP messages, consumes those for which it (or one
of its zero-hop identities) is the [recipient](#recipient) and independently
routes each of the remaining messages to its next appropriate peer.
Every overlay packet contains the [MDP address](#mdp-address)es of its
[transmitter](#transmitter) and [receiver](#receiver).
An overlay packet may contain many MDP messages. The header of each MDP
neighbouring [peer][] node. Once an overlay packet arrives, the receiver
unpacks all of its MDP messages, consumes those for which it (or one of its
[zero-hop][] identities) is the [recipient](#recipient) and independently
message in an overlay packet is constructed afresh when it is embedded into the
packet, setting its [flag bits](#mdp-message-flags) and re-writing the [address
fields](#mdp-address-fields) within the context of the overlay packet, in order
@ -340,6 +334,7 @@ Available under the [Creative Commons Attribution 4.0 International licence][CC
[layer 2]: https://en.wikipedia.org/wiki/Data_link_layer
[layer 3]: https://en.wikipedia.org/wiki/Network_layer
[layer 4]: https://en.wikipedia.org/wiki/Transport_layer
[peer]: ./REST-API-Route.md#peer
[UDP]: http://en.wikipedia.org/wiki/User_Datagram_Protocol
[MTU]: http://en.wikipedia.org/wiki/Maximum_transmission_unit
[SID]: ./REST-API-Keyring.md#serval-id
@ -349,6 +344,8 @@ Available under the [Creative Commons Attribution 4.0 International licence][CC
[configured]: ./Servald-Configuration.md
[network interfaces]: ./Servald-Configuration.md#network-interfaces
[keyring]: ./REST-API-Keyring.md
[routing]: ./REST-API-Route.md
[zero-hop]: ./REST-API-Route.md#zero-hop
[encrypted]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:security_framework
[signed]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:security_framework
[local sockets]: https://en.wikipedia.org/wiki/Unix_domain_socket

View File

@ -43,6 +43,7 @@ DNA][] component of the [Serval mesh network][].
REST APIs through which applications access the services of the Serval mesh
network, including:
* [Keyring REST API](./REST-API-Keyring.md) -- identity management
* [Route REST API](./REST-API-Route.md) -- network routing and peers
* [Rhizome REST API](./REST-API-Rhizome.md) -- decentralised content distribution
* [MeshMS REST API](./REST-API-MeshMS.md) -- secure, one-to-one messaging

208
doc/REST-API-Route.md Normal file
View File

@ -0,0 +1,208 @@
Route REST API
==============
[Serval Project][], September 2016
Introduction
------------
Every [Serval DNA][] daemon running on a node in the [Serval mesh network][]
maintains its own dynamic *routing table*, which it uses to choose the [network
interface][] on which to send each outgoing [MDP message][]. The mesh routing
algorithm updates the routing table whenever Serval DNA receives an [overlay
packet][] and whenever system time advances. For more details, see [Mesh
Datagram Protocol][].
The [Serval DNA][] daemon gives applications access to its routing table via
the **Route REST API** described in this document. Applications can use this
information to:
* discover the current [network neighbours](#neighbour)
* discover all currently (and recently) [reachable nodes](#reachable)
* estimate the quality of service to any given [reachable node](#reachable)
Basic concepts
--------------
### Routing table
Every [node](#node) in the [Serval mesh network][] maintains its own *routing
table*, which identifies a single [path](#path) to every [reachable](#reachable)
[node](#node). The mesh routing algorithm chooses the best path based on
routing information received from other nodes along the path.
Whenever a [node](#node) receives an [overlay packet][], it knows that, at the
time the packet was received, there existed a direct incoming [link](#link)
from the [transmitting node][transmitter]. It updates its routing table to
mark the transmitter's [primary SID](#primary-sid) as a [neighbour](#neighbour).
Whenever a [node](#node) receives nothing from a given neighbour for longer
than a [configured][] time interval, it presumes that the [link](#link) is
broken. It updates its routing table to mark the neighbour's [primary
SID](#primary-sid) as no longer a neighbour.
### Tick
Every daemon ensures that all of its nearby nodes remain aware of its presence
by sending regular [overlay packet][]s to every [neighbour](#neighbour). Every
[overlay packet][] contains its own [primary SID](#primary-sid) as the sender
address.
Every [network interface][] has a [configured][] *tick interval*, which is the
maximum time period that may elapse between messages sent to any neighbour on
that interface. If no message has been sent to a given neighbour for a whole
tick interval, then the daemon sends an empty [overlay packet][], called a
*tick packet*, to the neighbour.
As a result, while an interface is quiescent (no traffic), depending on whether
the [link](#link) to each neighbour is *broadcast* or *unicast*, at every tick
the daemon will send either a single broadcast Wi-Fi packet, or several unicast
Wi-Fi packets, or a mixture.
### Node
A *node* in the [Serval mesh network][] is any device with its own link-layer
address (eg, a UDP/IP address or a Wi-Fi MAC address) that is running a [Serval
DNA][] daemon configured to use that network interface.
### Neighbour
A [node's](#node) *neighbour* in the [Serval mesh network][] is any node from
which its [Serval DNA][] daemon directly receives [overlay packet][]s through a
[network interface][].
Note that a neighbour is not necessarily [reachable](#reachable), because
wireless links are not always symmetrical; even though station A receives from
station B, it does not necessarily mean that B can receive from A, because of
factors like different transmitter power and antenna gain.
### Link
A *link* is a one-way connection from a [node](#node) to one of its [neighbour
nodes](#neighbour). Links are represented in the routing table by the [primary
SID](#primary-sid) of their receiving end.
A link may be either *broadcast* or *unicast*, which is chosen by the receiver
[node](#node) during link negotiation.
Each link is characterised by a metric that represents the dynamic link quality
(eg, the proportion of recent packets successfully received). Every
[node](#node) dynamically computes the quality of all its incoming links by
counting the gaps in the sequence numbers on received [overlay packet][]s. It
continuously informs each [neighbour node](#neighbour) of the measured quality
of its incoming link by periodically sending a [routing
message](#routing-message) to each neighbour at a [configured][] time interval.
### Primary SID
Every [node](#node) identifies itself by its *primary SID*, which is usually
the [SID][] of the first identity that was [unlocked][] since the daemon was
started.
### Secondary SID
A [node](#node) may have more than one [SID][], ie more than one [unlocked][]
identity. All its SIDs except the [primary SID](#primary-sid) are called
*secondary*.
A [node](#node) announces all of its secondary SIDs by representing them in its
[routing messages](#routing-message) as [reachable](#reachable)
[neighbours](#neighbour) on a private network interface (ie, not available to
other nodes) with a 100% link quality.
### Routing message
Every [node](#node) in the [Serval mesh network][] informs other nodes of the
presence and quality of all of its incoming and outgoing [links](#link) by
sending *routing messages*. A routing message is a one-hop message that goes
to all [neighbouring](#neighbour) nodes but no further:
* whenever a node detects the presence (received packet) or absence (timeout)
of an incoming link, or revises the measured quality of an incoming link,
it sends a routing message with the single link's state/quality to the
single [neighbour](#neighbour) at the transmitting end of the link;
* whenever a node receives a routing message from one of its neighbours, it
incorporates the new link state (up/down) and quality information into its own
routing table, re-evaluates its [paths](#path), and, shortly afterwards,
sends a routing message containing the state/quality information of all its
outgoing and incoming links to all of its [neighbours](#neighbour).
Routing messages are not forwarded directly, but the information they carry
propagates beyond the node's immediate neighbours because each neighbour, upon
receiving a routing message, updates its own [routing table](#routing-table)
and sends out its own routing messages that arise as a result of the update.
### Path
A *path* is a one-way route from a *sender* [node](#node) to a *recipient*
[node](#node), expressed as a sequence of non-repeating [links](#link). The
first link in a path always leads to a [reachable](#reachable) [neighbour
node](#neighbour).
The routing algorithm constructs paths by choosing links whose existence and
quality has been revealed by a recently-received *routing message* from a
neighbour.
### Reachable
A [node](#node) in the [Serval mesh network][] considers another node to be
*reachable* if its own [routing table](#routing-table) contains a [path](#path)
to the second node.
REST Requests
-------------
### GET /restful/route/all.json
Returns a list of all currently known identities, in [JSON table][] format.
The table columns are:
| heading | type | content |
|:--------------------- |:---------------- |:------------------------------------------------------------------------------ |
| `sid` | string | the [SID][] of the identity, as 64 uppercase hex digits |
| `did` | string or `null` | the [DID][] of the identity if known (eg, for a local [keyring][] identity) |
| `name` | string or `null` | the [Name][] of the identity if known (eg, for a local [keyring][] identity) |
| `is_self` | boolean | true if the identity is a self-identity, ie, in the local [keyring][] |
| `reachable_broadcast` | boolean | true if the identity is [reachable](#reachable) by broadcast [link](#link) |
| `reachable_unicast` | boolean | true if the identity is [reachable](#reachable) by unicast [link](#link) |
| `reachable_indirect` | boolean | true if the identity is [reachable](#reachable) only via another [node](#node) |
| `interface` | string or `null` | the name of the local network interface on which the identity is reachable |
| `hop_count` | integer | the number of hops to reach the identity |
| `first_hop` | string or `null` | if `hop_count > 1`, then the [SID][] of the first identity in the route |
| `penultimate_hop` | string or `null` | if `hop_count > 2`, then the [SID][] of the penultimate identity in the route |
-----
**Copyright 2015 Serval Project Inc.**
**Copyright 2016-2018 Flinders University**
![CC-BY-4.0](./cc-by-4.0.png)
Available under the [Creative Commons Attribution 4.0 International licence][CC BY 4.0].
[Serval Project]: http://www.servalproject.org/
[CC BY 4.0]: ../LICENSE-DOCUMENTATION.md
[Serval Mesh network]: http://developer.servalproject.org/dokuwiki/doku.php?id=content:tech:mesh_network
[Serval DNA]: ../README.md
[REST-API]: ./REST-API.md
[keyring]: ./REST-API-Keyring.md
[SID]: ./REST-API-Keyring.md#serval-id
[DID]: ./REST-API-Keyring.md#did
[Name]: ./REST-API-Keyring.md#name
[unlocked]: ./REST-API-Keyring.md#identity-unlocking
[overlay packet]: ./Mesh-Datagram-Protocol.md#overlay-packet
[sender]: ./Mesh-Datagram-Protocol.md#sender
[transmitter]: ./Mesh-Datagram-Protocol.md#transmitter
[MDP message]: ./Mesh-Datagram-Protocol.md#mdp-message
[broadcast]: ./Mesh-Datagram-Protocol.md#broadcast
[Mesh Datagram Protocol]: ./Mesh-Datagram-Protocol.md
[JSON table]: ./REST-API.md#json-table
[configured]: ./Servald-Configuration.md
[network interface]: ./Servald-Configuration.md#network-interfaces
[200]: ./REST-API.md#200-ok
[201]: ./REST-API.md#201-created
[202]: ./REST-API.md#202-accepted
[400]: ./REST-API.md#400-bad-request
[404]: ./REST-API.md#404-not-found
[419]: ./REST-API.md#419-authentication-timeout
[422]: ./REST-API.md#422-unprocessable-entity
[423]: ./REST-API.md#423-locked
[500]: ./REST-API.md#500-server-error

11
httpd.h
View File

@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "http_server.h"
#include "keyring.h"
#include "overlay_address.h"
#include "meshms.h"
#include "os.h"
@ -161,7 +162,7 @@ typedef struct httpd_request
*/
struct rhizome_read read_state;
/* For responses that list SIDs.
/* For responses that list identities in the keyring.
*/
struct {
enum list_phase phase;
@ -169,6 +170,14 @@ typedef struct httpd_request
}
sidlist;
/* For responses that list known subscribers, eg, routing table.
*/
struct {
enum list_phase phase;
subscriber_iterator it;
}
subscriberlist;
/* For responses that list manifests.
*/
struct {

View File

@ -152,9 +152,6 @@ static int restful_keyring_identitylist_json_content(struct http_request *hr, un
static int restful_keyring_identitylist_json_content_chunk(struct http_request *hr, strbuf b)
{
httpd_request *r = (httpd_request *) hr;
// The "my_sid" and "their_sid" per-conversation fields allow the same JSON structure to be used
// in a future, non-SID-specific request, eg, to list all conversations for all currently open
// identities.
const char *headers[] = {
"sid",
"identity",

View File

@ -173,6 +173,33 @@ struct subscriber *find_subscriber(const uint8_t *sidp, int len, int create)
return result;
}
// iterate over subscribers in ascending binary order
void subscriber_iterator_start(subscriber_iterator *it)
{
tree_iterator_start(&it->tree_iterator, &root);
}
void subscriber_iterator_advance_to(subscriber_iterator *it, const sid_t *sid)
{
tree_iterator_advance_to(&it->tree_iterator, sid->binary, sizeof sid->binary);
}
struct subscriber **subscriber_iterator_get_current(subscriber_iterator *it)
{
return (struct subscriber **) tree_iterator_get_node(&it->tree_iterator);
}
void subscriber_iterator_advance(subscriber_iterator *it)
{
tree_iterator_advance(&it->tree_iterator);
}
void subscriber_iterator_free(subscriber_iterator *it)
{
tree_iterator_free(&it->tree_iterator);
}
/*
walk the tree, starting at start inclusive, calling the supplied callback function
*/

View File

@ -1,6 +1,7 @@
/*
Serval DNA MDP addressing
Copyright (C) 2012-2013 Serval Project Inc.
Copyright (C) 2018 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -125,6 +126,16 @@ extern __thread struct subscriber *directory_service;
struct subscriber *find_subscriber(const uint8_t *sid, int len, int create);
typedef struct subscriber_iterator {
tree_iterator tree_iterator;
} subscriber_iterator;
void subscriber_iterator_start(subscriber_iterator *it);
void subscriber_iterator_advance_to(subscriber_iterator *it, const sid_t *sid);
struct subscriber **subscriber_iterator_get_current(subscriber_iterator *it);
void subscriber_iterator_advance(subscriber_iterator *it);
void subscriber_iterator_free(subscriber_iterator *it);
void enum_subscribers(struct subscriber *start, walk_callback callback, void *context);
int set_reachable(struct subscriber *subscriber, struct network_destination *destination, struct subscriber *next_hop, int hop_count, struct subscriber *prior_hop);
struct network_destination *load_subscriber_address(struct subscriber *subscriber);

179
route_restful.c Normal file
View File

@ -0,0 +1,179 @@
/*
Serval DNA Routing HTTP RESTful interface
Copyright (C) 2018 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "lang.h" // for bool_t, FALLTHROUGH
#include "serval.h"
#include "conf.h"
#include "httpd.h"
#include "server.h"
#include "strbuf_helpers.h"
#include "dataformats.h"
#include "overlay_address.h"
#include "overlay_interface.h"
DEFINE_FEATURE(http_rest_route);
DECLARE_HANDLER("/restful/route/", restful_route_);
static HTTP_HANDLER restful_route_list_json;
static int restful_route_(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
if (r->http.verb == HTTP_VERB_GET && strcmp(remainder, "all.json") == 0)
return restful_route_list_json(r, "");
return 404;
}
static void finalise_union_subscriberlist(httpd_request *r)
{
subscriber_iterator_free(&r->u.subscriberlist.it);
}
static HTTP_CONTENT_GENERATOR restful_route_list_json_content;
static int restful_route_list_json(httpd_request *r, const char *remainder)
{
if (*remainder)
return 404;
r->u.subscriberlist.phase = LIST_HEADER;
subscriber_iterator_start(&r->u.subscriberlist.it);
r->finalise_union = finalise_union_subscriberlist;
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, restful_route_list_json_content);
return 1;
}
static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_route_list_json_content_chunk;
static int restful_route_list_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
{
return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_route_list_json_content_chunk);
}
static int restful_route_list_json_content_chunk(struct http_request *hr, strbuf b)
{
httpd_request *r = (httpd_request *) hr;
const char *headers[] = {
"sid",
"did",
"name",
"is_self",
"reachable_broadcast",
"reachable_unicast",
"reachable_indirect",
"interface",
"hop_count",
"first_hop",
"penultimate_hop"
};
switch (r->u.subscriberlist.phase) {
case LIST_HEADER:
strbuf_puts(b, "{\n\"header\":[");
unsigned i;
for (i = 0; i != NELS(headers); ++i) {
if (i)
strbuf_putc(b, ',');
strbuf_json_string(b, headers[i]);
}
strbuf_puts(b, "],\n\"rows\":[");
if (!strbuf_overrun(b)){
r->u.subscriberlist.phase = LIST_FIRST;
if (!subscriber_iterator_get_current(&r->u.subscriberlist.it))
r->u.subscriberlist.phase = LIST_END;
}
return 1;
case LIST_ROWS:
strbuf_putc(b, ',');
FALLTHROUGH;
case LIST_FIRST:
r->u.subscriberlist.phase = LIST_ROWS;
struct subscriber **subscriberp = subscriber_iterator_get_current(&r->u.subscriberlist.it);
assert(subscriberp);
struct subscriber *subscriber = *subscriberp;
const char *did = NULL;
const char *name = NULL;
if (subscriber->identity)
keyring_identity_extract(subscriber->identity, &did, &name);
// sid
strbuf_puts(b, "\n[");
strbuf_json_string(b, alloca_tohex_sid_t(subscriber->sid));
// did
strbuf_puts(b, ",");
strbuf_json_string(b, did);
// name
strbuf_puts(b, ",");
strbuf_json_string(b, name);
// is_self
strbuf_puts(b, ",");
strbuf_json_boolean(b, subscriber->reachable & REACHABLE_SELF);
// reachable_broadcast
strbuf_puts(b, ",");
strbuf_json_boolean(b, subscriber->reachable & REACHABLE_BROADCAST);
// reachable_unicast
strbuf_puts(b, ",");
strbuf_json_boolean(b, subscriber->reachable & REACHABLE_UNICAST);
// reachable_indirect
strbuf_puts(b, ",");
strbuf_json_boolean(b, subscriber->reachable & REACHABLE_INDIRECT);
// interface
strbuf_puts(b, ",");
if (subscriber->destination && subscriber->destination->interface)
strbuf_json_string(b, subscriber->destination->interface->name);
else
strbuf_json_null(b);
// hop_count
strbuf_puts(b, ",");
strbuf_json_integer(b, subscriber->hop_count);
// first_hop
strbuf_puts(b, ",");
if (subscriber->hop_count > 1)
strbuf_json_string(b, alloca_tohex_sid_t(subscriber->next_hop->sid));
else
strbuf_json_null(b);
// penultimate_hop
strbuf_puts(b, ",");
if (subscriber->hop_count > 2)
strbuf_json_string(b, alloca_tohex_sid_t(subscriber->prior_hop->sid));
else
strbuf_json_null(b);
strbuf_puts(b, "]");
if (!strbuf_overrun(b)) {
subscriber_iterator_advance(&r->u.subscriberlist.it);
if (!subscriber_iterator_get_current(&r->u.subscriberlist.it))
r->u.subscriberlist.phase = LIST_END;
}
return 1;
case LIST_END:
strbuf_puts(b, "\n]\n}\n");
if (strbuf_overrun(b))
return 1;
r->u.subscriberlist.phase = LIST_DONE;
// fall through...
case LIST_DONE:
return 0;
}
abort();
return 0;
}

View File

@ -54,6 +54,7 @@ void servald_features()
USE_FEATURE(http_rhizome);
USE_FEATURE(http_rhizome_direct);
USE_FEATURE(http_rest_keyring);
USE_FEATURE(http_rest_route);
USE_FEATURE(http_rest_rhizome);
USE_FEATURE(http_rest_meshms);
USE_FEATURE(http_rest_meshmb);

View File

@ -99,6 +99,7 @@ SERVAL_DAEMON_SOURCES = \
overlay_packetformats.c \
overlay_payload.c \
route_link.c \
route_restful.c \
rhizome.c \
rhizome_bundle.c \
rhizome_crypto.c \

View File

@ -424,9 +424,9 @@ servald_start() {
# Utility function:
# - fetch the daemon's primary SID
get_servald_primary_sid() {
local _instance="$2"
local _instance="$1"
[ -z "$_instance" ] || push_and_set_instance $_instance || return $?
local _var="$1"
local _var="$2"
local _sid=$(<"$SERVALINSTANCE_PATH/proc/primary_sid")
assert --message="instance $instance_name primary SID is known" [ -n "$_sid" ]
if [ -n "$_var" ]; then

95
testdefs_routing.sh Normal file
View File

@ -0,0 +1,95 @@
# Common definitions for routing tests.
#
# Copyright 2012-2015 Serval Project, Inc.
# Copyright 2016-2018 Flinders University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
interface_is_up() {
$GREP "Interface .* is up" $instance_servald_log || return 1
return 0
}
link_matches() {
local interface_ex=".*"
local link_type="(BROADCAST|UNICAST)"
local via=".*"
while [ $# -ne 0 ]; do
case "$1" in
--interface) interface_ex="$2"; shift 2;;
--broadcast) link_type="BROADCAST"; shift;;
--unicast) link_type="UNICAST"; shift;;
--via) link_type="INDIRECT"; via="$2"; interface_ex=""; shift 2;;
--any) via=".*"; link_type=".*"; shift;;
*) break;;
esac
done
local oIFS="$IFS"
IFS='|'
local sids="$*"
IFS="$oIFS"
local rexp="^(${sids}):${link_type}:${interface_ex}:${via}:"
tfw_log "Looking for $rexp"
if ! $GREP -E "$rexp" "$TFWSTDOUT"; then
tfw_log "Link not found"
tfw_cat --stdout
return 1
fi
}
has_link() {
executeOk_servald route print
link_matches $@
}
has_no_link() {
has_link --any $@ || return 0
return 1
}
path_exists() {
local dest
eval dest=\$$#
local dest_sidvar=SID${dest#+}
local dest_sids
eval 'dest_sids=("${'$dest_sidvar'[@]}")'
local first_inst=$1
local next_inst=$first_inst
shift
local I
for I; do
local sidvar=SID${I#+}
local sids
eval 'sids=("${'$sidvar'[@]}")'
[ "${#sids[@]}" -gt 0 ] || error "no SIDs known for identity $I"
set_instance $next_inst
executeOk_servald route print
link_matches "${sids[@]}" || return 1
[ $I = $dest ] && break
link_matches --via ${!sidvar} "${dest_sids[@]}" || return 1
next_inst=$I
done
# so we think this path should exist, check that it works
set_instance $first_inst
executeOk_servald --stderr mdp trace --timeout=20 "${dest_sids[0]}"
# assertStdoutGrep "^[0-9]+:$dest_sids\$"
tfw_cat --stdout
return 0
}
log_routing_table() {
executeOk_servald route print
tfw_cat --stdout --stderr
}

View File

@ -3,7 +3,7 @@
# Aggregation of all tests except high-load stress tests.
#
# Copyright 2012-2014 Serval Project Inc.
# Copyright 2017 Flinders University
# Copyright 2017-2018 Flinders University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -37,10 +37,9 @@ includeTests meshms
includeTests meshmb
includeTests directory_service
includeTests vomp
includeTests keyringrestful
includeTests rhizomerestful
includeTests meshmsrestful
includeTests meshmbrestful
includeTests allrestful
if type -p "$JAVAC" >/dev/null; then
includeTests alljava
fi

30
tests/allrestful Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# Aggregation of all REST API tests.
#
# Copyright 2018 Flinders University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
source "${0%/*}/../testframework.sh"
source "${0%/*}/../testdefs.sh"
includeTests keyringrestful
includeTests routerestful
includeTests rhizomerestful
includeTests meshmsrestful
includeTests meshmbrestful
runTests "$@"

125
tests/routerestful Executable file
View File

@ -0,0 +1,125 @@
#!/bin/bash
# Tests for Serval DNA Route REST API
#
# Copyright 2018 Flinders University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
source "${0%/*}/../testframework.sh"
source "${0%/*}/../testdefs.sh"
source "${0%/*}/../testdefs_rest.sh"
source "${0%/*}/../testdefs_routing.sh"
setup() {
setup_rest_utilities
setup_servald
assert_no_servald_processes
setup_rest_config +A
}
configure_servald_server() {
executeOk_servald config \
set debug.mdprequests yes \
set debug.linkstate yes \
set debug.subscriber yes \
set debug.verbose yes \
set debug.overlayrouting yes \
set log.console.level debug \
set log.console.show_pid on \
set log.console.show_time on \
set rhizome.enable no
}
finally() {
stop_all_servald_servers
}
teardown() {
foreach_instance_with_pidfile log_routing_table
stop_all_servald_servers
kill_all_servald_processes
assert_no_servald_processes
report_all_servald_servers
}
doc_AuthBasicMissing="REST API missing Basic Authentication credentials"
setup_AuthBasicMissing() {
setup
set_instance +A
start_servald_server
wait_until_rest_server_ready
}
test_AuthBasicMissing() {
rest_request GET "/restful/route/all.json" 401 --no-auth
assertGrep response.headers "^WWW-Authenticate: Basic realm=\"Serval RESTful API\"$CR\$"
assertJq response.json 'contains({"http_status_code": 401})'
assertJq response.json 'contains({"http_status_message": ""})'
}
doc_AuthBasicWrong="REST API incorrect Basic Authentication credentials"
setup_AuthBasicWrong() {
setup
set_instance +A
start_servald_server
wait_until_rest_server_ready
}
test_AuthBasicWrong() {
rest_request GET "/restful/route/all.json" 401 --user=fred:nurks
assertGrep response.headers "^WWW-Authenticate: Basic realm=\"Serval RESTful API\"$CR\$"
assertJq response.json 'contains({"http_status_code": 401})'
assertJq response.json 'contains({"http_status_message": ""})'
rest_request GET "/restful/route/all.json" 200 --user=ron:weasley
}
doc_RouteListAll="REST API list entire routing table"
setup_RouteListAll() {
setup
foreach_instance +A +B create_identities 2
foreach_instance +A +B add_servald_interface 1
foreach_instance +A +B start_servald_server
wait_until_rest_server_ready +A
get_servald_primary_sid +B PRIMARY_SIDB
wait_until --timeout=20 path_exists +A +B
wait_until --timeout=10 path_exists +B +A
set_instance +A
}
test_RouteListAll() {
rest_request GET "/restful/route/all.json"
transform_list_json response.json routes.json
assert [ "$(jq 'length' routes.json)" = 4 ]
assertJq routes.json 'contains([{"sid": "'$SIDA1'",
"is_self": true,
"hop_count": 0}])'
assertJq routes.json 'contains([{"sid": "'$SIDA2'",
"is_self": true,
"hop_count": 0}])'
assertJq routes.json 'contains([{"sid": "'$PRIMARY_SIDB'",
"is_self": false,
"reachable_unicast": true,
"reachable_indirect": false,
"hop_count": 1}])'
for SID in "${SIDB[@]}"; do
if [ "$SID" != "$PRIMARY_SIDB" ]; then
assertJq routes.json 'contains([{"sid": "'$SID'",
"is_self": false,
"reachable_unicast": false,
"reachable_indirect": true,
"hop_count": 2}])'
fi
done
}
runTests "$@"

View File

@ -2,7 +2,8 @@
# Tests for Route discovery
#
# Copyright 2012 Serval Project, Inc.
# Copyright 2012-2015 Serval Project, Inc.
# Copyright 2016-2018 Flinders University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -18,73 +19,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
source "${0%/*}/../testframework.sh"
source "${0%/*}/../testdefs.sh"
interface_up() {
$GREP "Interface .* is up" $instance_servald_log || return 1
return 0
}
link_matches() {
local interface_ex=".*"
local link_type="\(BROADCAST\|UNICAST\)"
local via="0*"
local sid=""
while [ $# -ne 0 ]; do
case "$1" in
--interface) interface_ex="$2"; shift 2;;
--broadcast) link_type="BROADCAST"; shift;;
--unicast) link_type="UNICAST"; shift;;
--via) link_type="INDIRECT"; via="$2"; interface_ex=""; shift 2;;
--any) via=".*"; link_type=".*"; shift;;
*) break;;
esac
done
sid="$1"
tfw_log "Looking for link ${sid}, ${link_type}, ${interface_ex}, ${via}"
if ! $GREP "^${sid}:${link_type}:${interface_ex}:${via}:" "$TFWSTDOUT"; then
tfw_log "Link not found"
return 1
fi
}
has_link() {
executeOk_servald route print
link_matches $@
}
has_no_link() {
has_link --any $@ || return 0
return 1
}
path_exists() {
local dest
eval dest=\$$#
local dest_sidvar=SID${dest#+}
local first_inst=$1
local next_inst=$first_inst
shift
local I
for I; do
local sidvar=SID${I#+}
[ -n "${!sidvar}" ] || break
set_instance $next_inst
executeOk_servald route print
link_matches ${!sidvar} || return 1
[ $I = $dest ] && break
link_matches --via ${!sidvar} ${!dest_sidvar} || return 1
next_inst=$I
done
# so we think this path should exist, check that it works
set_instance $first_inst
executeOk_servald mdp trace --timeout=20 "${!dest_sidvar}"
# assertStdoutGrep "^[0-9]+:${!dest_sidvar}\$"
tfw_cat --stdout
return 0
}
source "${0%/*}/../testdefs_routing.sh"
configure_servald_server() {
executeOk_servald config \
@ -99,11 +36,6 @@ configure_servald_server() {
set rhizome.enable no
}
log_routing_table() {
executeOk_servald route print
tfw_cat --stdout --stderr
}
teardown() {
foreach_instance_with_pidfile log_routing_table
stop_all_servald_servers
@ -406,7 +338,7 @@ setup_scan() {
set interfaces.1.broadcast.drop on
foreach_instance +A +B +C start_servald_server
foreach_instance +A +B +C \
wait_until interface_up
wait_until interface_is_up
}
test_scan() {
set_instance +A
@ -470,7 +402,7 @@ setup_fixedAddress() {
touch "$SERVALD_VAR/dummy1"
foreach_instance +A +B start_servald_server
foreach_instance +A +B \
wait_until interface_up
wait_until interface_is_up
}
doc_fixedAddress="Establish a link from a configured fixed address"
test_fixedAddress() {