mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-18 20:57:56 +00:00
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:
parent
1a091aa8a1
commit
8345d896a6
@ -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
|
||||
|
@ -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
208
doc/REST-API-Route.md
Normal 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
11
httpd.h
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
179
route_restful.c
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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 \
|
||||
|
@ -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
95
testdefs_routing.sh
Normal 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
|
||||
}
|
@ -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
30
tests/allrestful
Executable 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
125
tests/routerestful
Executable 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 "$@"
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user