RPKI web test tool testing valid ROA and invalid ROA http endpoint.
This is the repo that backs RPKI WEB TEST
This small tool tests whether the client running it is connected to the internet through upstream router that performs Route Origin Validation according (ROA) in accordance with the Routing Public Key Infrastructure (RPKI). You can read more about RPKI here.
This tool will try to retrieve information from a specially conifgured mutli-homed webserver through two different IP addresses. One of these IP addresses is part of a prefix that is part of a valid ROA and the other IP address is part of a prefix that explicitly has an invalid ROA. If the tool can reach the webserver through the prefix with the valid ROA, but it times out connecting to the the prefix with the invalid ROA, it will conclude that somewhere along the route from the client to the server the prefix with ROA invalid is actively being dropped. Be aware that we cannot infer where along the route this packet is dropped, nor can we infer which along which AS path the packet travels.
The specially configured webserver this test uses is kindly hosted by Job Snijders. You can however configure this tool to use any webserver that's configured to do this, provided you have access to IP addresses within a valid ROA and an invalid ROA.
This tool consists of three parts:
- a web-client (that you can use here) that will run in a browser and show a frown, meh or smiley face based on the outcome of the test. You can also host it yourself if you wish.
- a javascript library that can be run from both node.js and from webbrowsers. It can be used through ES6 modules or CJS module (for node.js).
- a CLI tool that performs the test and outputs the result in JSON. It is rather clunky now, since it has a complete nodejs environment embedded in it. given enough interest I can port this to a compiled language, so you can have a small binary. Also, I could add a traceroute engine to this CLI tool, so we could obtain more information about where along the route the invalid ROAs are dropped.
You can build the webclient yourself. The prerequisites for this are node 12.x or higher. The you can:
npm install
npm run web
This will create a file /build/bundle.js
that you'll need together with index.html
, manifest.webmanifest
from the /html
directory. Note that for this to correctly function it should be hosted on a TLS backed website, i.e. https.
The cli tool is also available under the Releases. I've only included it for MacOs. Deployments for Linux and Windows can be made with the excellent pkg:
npm -g install pkg@4
npm install
TARGET=<YOU_TARGET_ARCH> npm run cli
Under the Releases tab you can find a file librpkitest.js
that contains a CJS library for use with webbrowsers (with require/r.js) and nodejs.
const fetch = require('cross-fetch');
const librpkitest = require('./librpkitest');
If you're using ES6 modules you can use the file librpkitest.js
in /src
directly, like so:
import { testRpkiInvalids } from "./librpkitest";
For node.js you will have to bring your own fetch
implementation. The cross-fetch
package seems to be a good choice. You'll have to include the package and then pass it as an argument to the librpkitest library:
const rpkiResult = testRpkiInvalids({fetch: fetch}).then(...);
The Library has only one public function named testRpkiInvalids
.
The testRpkiInvalids
function accepts an arguments
object with the following fields:
name | type | default | description |
---|---|---|---|
enrich | bool | false | Sets whether the prefix and ASN belonging to the client IP address will be retrieved |
postResult | bool | false | Sets whether the result will be posted to a logging server |
invalidTimeout | Number | 5000 | Timeout in milliseconds after which the invalid ROA request is considered blocked. |
fetch | function | window.fetch* | The fetch used to make the HTTP requests. |
callBacks | object with stageName :callBack Function pairs |
{} | At the end of each stage a callBack can be invoked (i.e., to render stuff to the clientscreen) |
The tesRpkiInvalids
function returns a Promise wrapping a Result object.
// require:
// const librpkitest = require("./librpkitest");
// librpkitest.testRpkiInvalids(...)
// es6:
// import { testRpkiInvalids } from "librpkitest";
testRpkiInvalids({
enrich: true,
postResult: true
}).then(
rpkiResult => {
console.log("rpki test result came in.");
console.log(rpkiResult);
},
err => {
console.log("errored out");
console.log(err);
}
);
the testRpkiInvalids
function returns a Promise that resolved into a Result object:
- rpki-valid-passed (bool) whether all rpki valid requests returned a non-error response
- rpki-invalid-passed (bool) whether all rpki invalid requests returned a non-error response
- events: (array of Event objects) all events of all stages appended. See down below for details
- lastStage: (stageId) the last stage before returning the result, should be "finished"
- lastErrorStage: (stageId) the stage where the last error occurred
- lastError: (Error) the last encountered error
- pfx: (string) Prefix that the client IP address is routed from
- asn: (string) Autonomous System Number that the prefix is announced from
- ip: ((string) (public) IP address of the connection of the client as seen by the consulted webserver
- stage (string) id of the final state of the stage
- error (Error) error if thrown, otherwise
null
- successs (bool) if true stage finished without errors
- data (object) if any data returned by this stage, otherwise
null
The data object for each stage, will always have either a startDateTime
field that represents the start of a timer or a duration
field, the time in ms from the start of that timer.
The testRpkiInvalids
function will go concurrently through a few stages and will report on the final state of each of these stages.
Each stage will go through one or more states and will setlle on a final state that will end up in the events
object in the returned result.
Here are the stages and their encapsulated states, like [state A] -> [state B | state C]. Stages are encapsulated in square brackets, are unnamed and can either have one possible final state (the names inside the brackets). So, [initialized] in a stage with one possible final state, whereas [validAwait | validReceived] is a stage with two possible final states.
[initialized]
|
+ --------- + --------- +
| |
[validAwait | validReceived] [invalidAwait | invalidReceived | invalidBlocked]
| |
+ --------- + --------- +
|
[enrichedAwait | enrichedReceived]
|
[resultPostedAwait | resultPostedConfirmed]
|
[finished]
{
"rpki-valid-passed": true,
"rpki-invalid-passed": false,
"lastStage": "finished",
"lastErrorStage": null,
"lastError": null,
"ip": "83.160.104.137",
"asn": ["3265"],
"pfx": "83.160.0.0/14",
"events": [
{
"stage": "initialized",
"error": null,
"success": true,
"data": {
"testUrls": [
{
"url": "https://038c8427-6c0e-4ad3-b42c-152d1f8c7343.rpki-valid-beacon.meerval.net/valid.json",
"addressFamily": 4
},
{
"url": "https://038c8427-6c0e-4ad3-b42c-152d1f8c7343.rpki-invalid-beacon.meerval.net/invalid.json",
"addressFamily": 4
}
],
"startDateTime": "2019-10-31T17:06:28.153Z",
"originLocation": null,
"userAgent": "rpki-web-test-0.0.1",
"options": {
"enrich": true,
"invalidTimeout": 5000,
"postResult": true
}
}
},
{
"stage": "validReceived",
"error": null,
"data": {
"ip": "83.160.104.137",
"rpki-valid-passed": true,
"duration": 562,
"testUrl": "https://038c8427-6c0e-4ad3-b42c-152d1f8c7343.rpki-valid-beacon.meerval.net/valid.json",
"addressFamily": 4
},
"success": true
},
{
"stage": "invalidBlocked",
"data": {
"rpki-invalid-passed": false,
"lastError": null,
"duration": 5026,
"testUrl": "https://038c8427-6c0e-4ad3-b42c-152d1f8c7343.rpki-invalid-beacon.meerval.net/invalid.json",
"addressFamily": 4
},
"success": true,
"error": null
},
{
"stage": "enrichedReceived",
"data": {
"ip": "83.160.104.137",
"asns": ["3265"],
"prefix": "83.160.0.0/14",
"enrichUrl": "https://stat.ripe.net/data/network-info/data.json?resource=83.160.104.137",
"duration": 5420
},
"error": null,
"success": true
},
{
"stage": "resultPostedConfirmed",
"error": null,
"successs": true,
"data": {
"postUrl": "https://rpki-browser.webmeasurements.net/results/",
"duration": 6262
}
},
{
"stage": "finished",
"data": { "duration": 6262 },
"error": null,
"success": true
}
]
}