



Looking Glass API documentation
Contents
- Changes
- General
- Get the available looking glass hosts
- Get the status of a measurement
- Search for measurements
- Get the results of a measurement
- Create a new measurement
- Publications
- Code samples
Changes
API v2.1 (2021-04-12)
No features or data were removed. All API queries that worked with v2 will continue to work with v2.1.
- The preferred base URL has changed from
http://periscope.caida.org/api/v2/
tohttps://api.periscope.caida.org/v2/
. The old URL will continue to work for now, but may be retired eventually. - The host/list endpoint now accepts a server search parameter and includes server attributes in its response.
- In requests to the measurement/ endpoint, items in the hosts array may now contain a server attribute in place of the asn attribute.
General
All requests to the Periscope API must be made via HTTP[S] GET and POST requests. All responses to API requests are in JSON format. Responses to successful requests will include an HTTP response code in the range 200-299. Responses to failed requests will include an HTTP response code outside that range, and a body that is a JSON object with the attribute "errors" containing a list of human-readable error messages.
All measurement data collected by Periscope are public and therefore API requests that query the results of measurements (GET requests) do not require authentication, and may be made by HTTP or HTTPS.
Requests to create measurements require a POST request over HTTPS (not HTTP), and authentication with HMAC SHA-256 (Hash Message Authentication Code). Each authorized user is issued a public key and a private key. The private key is used to calculate the HMAC string of the request body. If the request authenticaion fails, the API returns a HTTP 401 error. POST requests must contain the following HTTP headers:
-
Content-type:
"application/json; charset=utf-8"
(other charset values will also work; just make sure your content matches. Non-ASCII characters occur in Periscope data only in city names, and then only rarely.) - X-Public: public key
- X-Hash: base64-encoded HMAC-SHA-256 signature of request body
The Code samples section provides Python and PHP examples on how to perform the authentication.
To obtain a public and a private key please Request a Periscope account.
Get the available looking glass measurement hosts
API Endpoint: https://api.periscope.caida.org/v2/host/listHTTP Method: GET
Parameters:
Name | Type | Value | Notes |
---|---|---|---|
command | string | command that the host must allow | REQUIRED |
server | string | Looking Glass server name | optional; new in v2.1 |
asn | string | Autonomous System number of host | optional |
router | string | name of measurement host | optional |
city | string | host's city | optional |
country | string | host's country | optional |
A GET request to this endpoint returns a JSON-encoded array of objects describing the available looking glass hosts matching the parameters. The host objects have the following attributes:
- server [string]: The name of the looking glass server (new in v2.1)
- asn [integer]: The Autonomous System Number of the looking glass measurement host
- router [string or integer]: The name of the LG measurement host if the LG server offers one or more named hosts, or integer 0 if the LG server has a single anonymous host
- label [optional string]: A description of the host that may be more informative than just the name
- city [optional string]: The city where the host is located, if known
- country [optional string]: The country (in ISO-3166-1 alpha-2 format) where the host is located, if known
When the looking glass provides the city and the country of the host we use this information, otherwise we infer the host location based on geographical DNS hints in the first hop, or the NetAcuity geolocation database. Therefore the geolocation may not always be accurate.
Example
Request:https://api.periscope.caida.org/v2/host/list?command=bgp&city=san+diego
Response:
[
{
"server": "example.com",
"asn": 64496,
"router": "san01",
"label": "San Diego router 01",
"city": "San Diego",
"country": "US"
},
{
"server": "lg.example.net",
"asn": 64511,
"router": "sandiego.example.net",
"city": "San Diego",
"country": "US"
},
{
"server": "lg.as65551.net",
"asn": 65551,
"router": 0,
"city": "San Diego",
"country": "US"
}
]
Get the status of a measurement
API Endpoint: https://api.periscope.caida.org/v2/measurement/idHTTP Method: GET
A GET request to this endpoint returns a JSON-encoded object with the status of the measurement with id id, with the following attributes:
- id [integer]: The unique identifier of the measurement (same as the id parameter)
- argument [string]: The IP address used as target in this measurement's looking glass queries
- command [string]: The looking glass command used in this measurement's queries.
- name [string]: The name of the measurement specified when the measurement was created.
- timestamp [string]: The date and time when the measurement was created.
- queries [integer]: The number of queries requested as part of this measurement
-
status: object with the following attributes:
- completed [integer]: number of completed queries
- failed [integer]: number of failed queries
- pending [integer]: number of queries still in progress
Example
Request:
https://api.periscope.caida.org/v2/measurement/1230021
Response:
{
"id":"1230021",
"argument":"198.51.100.7",
"command":"traceroute",
"name":"test",
"timestamp":"2018-12-05 12:33:11",
"queries":4,
"status":{"completed":4, "failed":0, "pending":0}
}
Search for measurements
API Endpoint: https://api.periscope.caida.org/v2/measurement/find?name=name&command=command&argument=argumentHTTP Method: GET
Parameters:
Name | Type | Value | Notes |
---|---|---|---|
name | string | The name of the measurement | optional |
command | string | "traceroute" | "bgp" | optional |
argument | string | The target of the measurement | optional |
To search for specific measurements send a GET request to
https://api.periscope.caida.org/v2/measurement/find
All parameters are optional but at least one is required.
The response is a JSON array of measurement status objects.
Example
Request:https://api.periscope.caida.org/v2/measurement/find?name=test
Response:
[
{
"id":"1230021",
"argument":"198.51.100.7",
"command":"traceroute",
"name":"test",
"timestamp":"2018-12-05 12:33:11",
"queries":4,
"status":{"completed":4, "failed":0, "pending":0}
},
{
"id":"1230022",
"argument":"203.0.113.22",
"command":"bgp",
"name":"test",
"timestamp":"2018-12-05 13:21:47",
"queries":5,
"status":{"completed":5, "failed":1, "pending":0}
},
{
"id":"1230023",
"argument":"192.0.2.250",
"command":"traceroute",
"name":"test",
"timestamp":"2018-12-05 13:37:29",
"queries":8,
"status":{"completed":8, "failed":0, "pending":0}
},
]
Get the results of a measurement
API Endpoint: https://api.periscope.caida.org/v2/measurement/id/result?format=formatHTTP Method: GET
Parameters:
Name | Type | Value | Notes |
---|---|---|---|
format | string | "json" | "raw" | optional, default "json" |
A GET request to this endpoint returns a JSON-encoded object with the status and results of the measurement with id id. The object has all the same attributes as a status object, plus a "queries" attribute:
- id [integer]: The unique identifier of the measurement (same as the id parameter)
- argument [string]: The IP address used as target in this measurement's looking glass queries
- command [string]: The looking glass command used in this measurement's queries.
- name [string]: The name of the measurement specified when the measurement was created.
- timestamp [string]: The date and time when the measurement was submitted to Periscope, in "YYYY-MM-DD HH:MM:SS" format.
-
status: object with the following attributes:
- completed [integer]: number of completed queries
- failed [integer]: number of failed queries
- pending [integer]: number of queries still in progress
-
queries: array of objects with the following attributes:
- id [integer]: The unique identifier of the looking glass query.
- server [string]: The name of the looking glass server (new in v2.1)
- asn [integer]: The ASN of the measurement host
-
router [string or integer]: The measurement host used
to run the query, or
0
if this LG server has only one anonymous host. - city [string]: The city where the measurement host is located
- country [string]: The country (in ISO-3166-1 alpha-2 format) where the measurement host is located
- starttime [string]: The date and time when the query was sent to the LG server, in "YYYY-MM-DD HH:MM:SS" format.
- endtime [string]: The date and time when the response was received from the LG server, in "YYYY-MM-DD HH:MM:SS" format.
-
status [string]: The status of the query:
"completed"
,"failed"
, or"pending"
-
result [optional]: If status is
"completed"
, this contains the result of the query, formatted according to the format parameter (see below). -
raw_result [optional string]: If status is
"pending"
or"failed"
, this field may contain a raw result string of unknown quality. -
error [optional string]: If status is
"failed"
, this will contain an error message.
The format of the result attribute in a completed query depends on the format parameter and the command:
- format="json", command="traceroute":
a JSON-encoded object with the following attributes:
- dst_addr [string]: destination address
-
hops: array of objects with the following attributes:
- hop [integer]: traceroute hop number
-
responses: array of objects (usually 3) describing the
response to probes at this hop. If there was a response, it will
contain the following attributes:
- from [string]: IP address of response
- rtt [number]: round trip time of response, in milliseconds
-
err [optional string]: 1-letter code for an ICMP error
like "
(!N)
" in the raw trace output
-
x:
"*"
- size [optional integer]: packet size in bytes, if known
- format="json", command="bgp":
a JSON-encoded array of objects with the following attributes:
- dst_net [optional string]: destination network in CIDR notation (almost always present)
- AS_path [array of strings]: list of ASNs (e.g. "111") or AS-sets (e.g. "{111,222}"). Always ASPLAIN, even if raw output was ASDOT. Confederations are omitted.
- best [optional boolean]: Whether this path is the best for the given network. Present only if the raw format supports it.
- communities [optional string]: one or more communities in "xxx:yyy" format with optional parenthesised additional information (output from Bird routers was converted from "(xxx,yyy)" to "xxx:yyy" format)
- localpref [optional string]: local preference value
- format="raw": a string containing the original text returned by the looking glass server. Because raw format differs between looking glasses, you are advised to use the "json" format instead.
Example: traceroute json
Request:
https://api.periscope.caida.org/v2/measurement/1230062/result
Response:
{
"argument": "192.51.100.7",
"command": "traceroute",
"id": 1230062,
"name": "example1",
"queries": [
{
"server": "lg.example.net",
"asn": 64496,
"city": "New York",
"country": "US",
"endtime": "2018-12-10 22:57:52",
"id": 5516730,
"result": {
"dst_addr": "198.51.100.7",
"hops": [
{
"hop": 1,
"responses": [
{ "from": "203.0.113.97", "rtt": 0.592 },
{ "from": "203.0.113.97", "rtt": 0.581 },
{ "from": "203.0.113.97", "rtt": 0.575 }
]
},
{
"hop": 2,
"responses": [
{ "from": "192.0.2.42", "rtt": 0.214 },
{ "from": "192.0.2.42", "rtt": 0.212 },
{ "from": "192.0.2.42", "rtt": 0.206 }
]
},
{
"hop": 3,
"responses": [
{ "from": "10.255.255.9", "rtt": 0.202 },
{ "from": "10.255.255.9", "rtt": 0.272 },
{ "from": "10.255.255.9", "rtt": 0.188 }
]
},
{
"hop": 4,
"responses": [
{ "from": "198.51.100.7", "rtt": 0.34 },
{ "from": "198.51.100.7", "rtt": 0.331 },
{ "from": "198.51.100.7", "rtt": 0.332 }
]
}
],
"size": 60,
},
"router": "r1-newyork",
"starttime": "2018-12-10 22:57:30",
"status": "completed"
}
],
"status": { "completed": 1, "failed": 0, "pending": 0 },
"timestamp": "2018-12-10 22:57:07"
}
Example: traceroute raw
Request:https://api.periscope.caida.org/v2/measurement/1230062/result?format=raw
Response:
{
"argument": "192.51.100.7",
"command": "traceroute",
"id": 1230062,
"name": "example1",
"queries": [
{
"server": "lg.example.net",
"asn": 64496,
"city": "New York",
"country": "US",
"endtime": "2018-12-10 22:57:52",
"id": 5516730,
"result": "traceroute to 198.51.100.7 (198.51.100.7), 30 hops max, 60 byte packets\n 1 abcdefg.example.org (203.0.113.97) 0.592 ms 0.581 ms 0.575 ms\n 2 wxyz.example.com (192.0.2.42) 0.214 ms 0.212 ms 0.206 ms\n 3 10.255.255.19 (10.255.255.9) 0.202 ms 0.272 ms 0.188 ms\n 4 target.example.net (198.51.100.7) 0.340 ms 0.331 ms 0.332 ms\n",
"router": "r1-newyork",
"starttime": "2018-12-10 22:57:30",
"status": "completed"
}
],
"status": { "completed": 1, "failed": 0, "pending": 0 },
"timestamp": "2018-12-10 22:57:07"
}
Example: bgp json
Request:
https://api.periscope.caida.org/v2/measurement/1230071/result
Response:
{
"argument": "198.51.100.7",
"command": "bgp",
"id": 1230071,
"name": "test",
"queries": [
{
"server": "lg.example.net",
"asn": 12741,
"city": "Warsaw",
"country": "PL",
"endtime": "2019-01-22 19:49:49",
"id": 5516949,
"result": {
"AS_path": [ "64496", "65536", "64511", "65551" ],
"best": true,
"dst_net": "198.51.100.0/24",
"localpref": "100"
},
"router": "warsaw2",
"starttime": "2019-01-22 19:48:27",
"status": "completed"
}
],
"status": {
"completed": 1, "failed": 0, "pending": 0
},
"timestamp": "2019-01-22 19:48:27"
}
Example: bgp raw
Request:https://api.periscope.caida.org/v2/measurement/1230071/result?format=raw
Response:
{
"argument": "198.51.100.7",
"command": "bgp",
"id": 1230071,
"name": "test",
"queries": [
{
"server": "lg.example.net",
"asn": 12741,
"city": "Warsaw",
"country": "PL",
"endtime": "2019-01-22 19:49:49",
"id": 5516949,
"result": "BGP routing table entry for 198.51.100.0/24\nPaths: (1 available, best #1, table Default-IP-Routing-Table)\n Not advertised to any peer\n 64496 65536 64511 65551\n 192.0.2.42 from 203.0.113.78 (203.0.113.78)\n Origin IGP, metric 0, localpref 100, valid, internal, best\n Last update: Wed Jan 16 15:47:40 2019\n",
"router": "warsaw2",
"starttime": "2019-01-22 19:48:27",
"status": "completed"
}
],
"status": {
"completed": 1, "failed": 0, "pending": 0
},
"timestamp": "2019-01-22 19:48:27"
}
Create a new measurement
API Endpoint: https://api.periscope.caida.org/v2/measurement/HTTP Method: POST
Parameters:
To create a new measurement, send a POST request to
https://api.periscope.caida.org/v2/measurement/
with the payload encoded as JSON dictionary with the following attributes:
- argument [string]: The IP address to probe (not hostname)
- command [string]: "traceroute" | "bgp"
- name [string]: A user-chosen name for the measurement that can be used to find it later. Multiple measurements can have the same name.
-
hosts A JSON-encoded array of objects with the following attributes, identifying the looking glass hosts that will be used to execute command with argument:
- server [string]: The name of the LG server. Exactly one of server or asn is required. (New in v2.1)
- asn [integer]: The Autonomous System Number of the host. Exactly one of server or asn is required.
- router [string or integer]: The name of the measurement host, or the 0-based index of the host within the LG server (all supported LG servers have at least one host with index 0).
If the request is accepted, the response will have an HTTP status of 201 (Created), a Location header containing a URL for the measurement, and a payload with a JSON-encoded object with the following attributes:
- message [string]: "Measurement created"
- id [integer]: a unique identifier for the newly created measurement
Example
Example request for a new measurement that will attempt to execute a traceroute to 192.51.100.7 from 5 different looking glass hosts belonging to 3 different ASes.
Request body:
{
"command":"traceroute",
"argument":"192.51.100.7",
"name":"example1",
"hosts":[
{"server":"lg.as64496.net", "router":"r1-newyork" },
{"server":"lg.as64496.net", "router":"r2-paris" },
{"server":"lg.as64496.net", "router":"r3-london" },
{"server":"lg.example.net", "router":"lax001.example.net" },
{"asn":65550, "router":0 },
]
}
Response:
{"message":"Measurement created", "id":"1230021"}
Publications
Please cite the Periscope paper if you use the Periscope Looking Glass API in a publcation:V. Giotsas, A. Dhamdhere, k. claffy. "Periscope: Unifying Looking Glass Querying". In proceedings of the 2016 Passive and Active Measurements Conference (PAM'16) 31 March - 1 April 2016, Heraklion, Greece
Also, please email us your publication at data-info AT caida DOT org
to add it in this page
Code Samples
Sample 1
The following Python script creates a measurement like that in the previous example:
#!/usr/bin/env python3
# python character encoding: utf-8
import sys
import requests
import json
import hmac, hashlib, base64 # for calculating the HMAC signature
api_url = "https://api.periscope.caida.org/v2"
# Construct the measurement request
measurement = dict()
measurement["command"] = "traceroute"
measurement["argument"] = "192.51.100.7"
measurement["name"] = "example1"
measurement["hosts"] = [
{"server": "lg.as64496.net", "router": "r1-newyork" },
{"server": "lg.as64496.net", "router": "r2-paris" },
{"server": "lg.as64496.net", "router": "r3-london" },
{"server": "lg.example.net", "router": "lax001.example.net" },
{"asn": 65550, "router": 0 },
]
data = bytes(json.dumps(measurement), "utf8")
# Calculate the HMAC signature
public_key = "my_public_key"
private_key = "my_private_key"
signature = base64.b64encode(hmac.new(private_key, msg=data,
digestmod=hashlib.sha256).digest())
# Set the headers
headers = {
'Content-type': 'application/json; charset=utf-8',
'X-Public': public_key,
'X-Hash': signature
}
print("Request headers: %r" % (headers,))
print("Request data: %r" % (data,))
# Post the data and headers
response = requests.post(api_url + "/measurement", data=data, headers=headers)
print("HTTP response status: %r" % (response.status_code,))
print("HTTP response headers: %r" % (response.headers,))
print("HTTP response text: %r" % (response.text,))
if response.status_code == 201:
decoded_response = response.json()
print("Measurement ID: %r" % (decoded_response["id"],))
else:
sys.exit(1)
The same example in PHP:
<?php
$api_url = "https://api.periscope.caida.org/v2";
// Construct the measurement request
$measurement = [
"command" => "traceroute",
"argument" => "192.51.100.7",
"name" => "example1",
"hosts" => [
["server" => "lg.as64496.net", "router" => "r1-newyork" ],
["server" => "lg.as64496.net", "router" => "r2-paris" ],
["server" => "lg.as64496.net", "router" => "r3-london" ],
["server" => "lg.example.net", "router" => "lax001.example.net" ],
["asn" => 65550, "router" => 0 ],
]
];
$data = json_encode($measurement);
# Calculate the HMAC signature
$public_key = 'my_public_key';
$private_key = 'my_private_key';
$signature = base64_encode(hash_hmac('sha256', $data, $private_key, TRUE));
# Set the headers
$headers = [
'Content-type:application/json; charset=utf-8',
'X-Public:' . $public_key,
'X-Hash:' . $signature
];
print "Request headers:";
print_r($headers);
print "Request data: " . $data . "\n";
# Post the data and headers
$ch = curl_init($api_url . "/measurement");
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
$response_code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
print "HTTP response status: " . curl_getinfo($ch, CURLINFO_RESPONSE_CODE) . "\n";
print "HTTP response text: " . $response . "\n";
if ($response_code == 201) {
$decoded_response = json_decode($response, 1);
print "Measurement ID: " . $decoded_response["id"] . "\n";
} else {
exit(1);
}
Sample 2
The following Python script gets the list of the available looking glass nodes, selects 30 nodes randomly and creates a new traceroute measurement to 192.51.100.7:
#!/usr/bin/env python3
import requests
import json
import random
api_url = "https://api.periscope.caida.org/v2"
# Get the available looking glass nodes
response = requests.get(api_url + "/host/list?command=traceroute")
available_hosts = response.json()
print("Number of available hosts: %d", (len(available_hosts),))
# Select 30 random looking glass hosts for the measurement
hosts = random.sample(available_hosts, 30)
# Construct the measurement request
measurement = dict()
measurement["argument"] = "192.51.100.7"
measurement["command"]= "traceroute"
measurement["name"] = "test"
measurement["hosts"] = list()
for host in hosts:
measurement["hosts"].append({"server": host["server"], "router": host["router"]})
# Send the data encoded as JSON string
data = bytes(json.dumps(measurement), "utf8")
print(data)
response = requests.post(api_url + "/measurement", data=data)
# Convert the response from JSON string to array
decoded_response = response.json()
print("Measurement ID is %s", (decoded_response["id"],))