The nine findings: the heap figure keeps the old README's meaning; the Makefile sentence names the three targets that call no script; the five fields /api/v1/stats adds are a list; the test comment names README "JSON API"; the X-Forwarded-For readers are a list, with short sentences after it; the AS page's peers are the ASes next to it in AS paths; CIDR ranges and the default route are explained; handle and description are field names; the TODO entry is one sentence. The sweep of every sentence defined feed, carry, IANA, multicast, handler, streamer, reverse proxy and goroutine at first use, split the list of parts, and named the 404 of an unknown page path. Model: fable-5-1
This commit is contained in:
@@ -15,7 +15,8 @@ path.
|
|||||||
|
|
||||||
RouteWatch does three things:
|
RouteWatch does three things:
|
||||||
|
|
||||||
- It streams BGP updates from the RIPE RIS Live service.
|
- It streams BGP updates from the RIPE RIS Live service. This document calls
|
||||||
|
that stream the feed.
|
||||||
- It applies them to a routing table that it keeps in memory, with a copy in a
|
- It applies them to a routing table that it keeps in memory, with a copy in a
|
||||||
SQLite database.
|
SQLite database.
|
||||||
- It serves web pages and a JSON API that answer from that table.
|
- It serves web pages and a JSON API that answer from that table.
|
||||||
@@ -27,9 +28,11 @@ about IP addresses. A service asks RouteWatch instead of working the answer out
|
|||||||
for itself.
|
for itself.
|
||||||
|
|
||||||
RIPE runs route collectors around the world. Many routers, the RIS peers, send
|
RIPE runs route collectors around the world. Many routers, the RIS peers, send
|
||||||
the collectors their routes over BGP sessions. RIS Live streams every change the
|
the collectors their routes. The connection between a peer and a collector is a
|
||||||
collectors receive, as it happens. RouteWatch applies those changes to its own
|
BGP session. A peer carries a prefix when the peer has a route to that prefix.
|
||||||
routing table. From that table it sees directly:
|
RIS Live streams every change the collectors receive, as it happens. RouteWatch
|
||||||
|
applies those changes to its own routing table. From that table it sees
|
||||||
|
directly:
|
||||||
|
|
||||||
- which AS each IP address is routed to
|
- which AS each IP address is routed to
|
||||||
- which announced prefix holds the address, and how big that prefix is
|
- which announced prefix holds the address, and how big that prefix is
|
||||||
@@ -89,8 +92,8 @@ database keeps the table across restarts.
|
|||||||
|
|
||||||
## HTTP
|
## HTTP
|
||||||
|
|
||||||
The server listens on `PORT`, `8080` by default. Every route answers `HEAD` as
|
The server listens on `PORT`, `8080` by default. Every page and API path answers
|
||||||
it answers `GET`, without the body.
|
`HEAD` as it answers `GET`, without the body.
|
||||||
|
|
||||||
The pages and the API read routes from the routing table in memory. For each
|
The pages and the API read routes from the routing table in memory. For each
|
||||||
prefix and origin AS, the table keeps the two newest routes in full, with their
|
prefix and origin AS, the table keeps the two newest routes in full, with their
|
||||||
@@ -104,7 +107,7 @@ and origin AS can have two kept routes, one or none.
|
|||||||
- `/`: the home page. Its top row shows these figures, refreshed every 5
|
- `/`: the home page. Its top row shows these figures, refreshed every 5
|
||||||
seconds:
|
seconds:
|
||||||
- whether the feed is connected
|
- whether the feed is connected
|
||||||
- the number of live routes, ASes, prefixes and BGP peers
|
- how many routes, ASes, prefixes and peers there are
|
||||||
- the route updates per second
|
- the route updates per second
|
||||||
|
|
||||||
Below the figures are a lookup by AS number, a search of AS names and a
|
Below the figures are a lookup by AS number, a search of AS names and a
|
||||||
@@ -118,22 +121,24 @@ and origin AS can have two kept routes, one or none.
|
|||||||
- how many ASes originate a single prefix, and the newest kept route of
|
- how many ASes originate a single prefix, and the newest kept route of
|
||||||
those ASes
|
those ASes
|
||||||
- the number of bogon announcements, linked to `/bogons`. A bogon
|
- the number of bogon announcements, linked to `/bogons`. A bogon
|
||||||
announcement is a route whose prefix or origin AS is reserved.
|
announcement is a route whose prefix or origin AS is reserved, as the
|
||||||
|
rules below say.
|
||||||
- the number of prefixes with more than one origin AS, linked to
|
- the number of prefixes with more than one origin AS, linked to
|
||||||
`/multiple-origins`
|
`/multiple-origins`
|
||||||
- fifteen well-known networks, such as Apple, Google, Cogent and MIT, each
|
- fifteen well-known networks, such as Apple, Google, Cogent and MIT, each
|
||||||
with its number of prefixes and the age of its newest kept route, newest
|
with its number of prefixes and the age of its newest kept route, newest
|
||||||
first
|
first
|
||||||
|
|
||||||
RouteWatch works these figures out once a minute, so they can be a minute
|
RouteWatch works out the figures on these cards once a minute, so they can
|
||||||
old. Until the first figures are ready, the page says that they are being
|
be a minute old. Until the first figures are ready, the page says that they
|
||||||
worked out.
|
are being worked out.
|
||||||
|
|
||||||
- `/as/{asn}`: an AS, the prefixes it originates and the ASes it peers with. The
|
- `/as/{asn}`: an AS, the prefixes it originates and the ASes next to it in AS
|
||||||
prefixes come in two lists, IPv4 and IPv6, shortest mask length first. An AS
|
paths. The prefixes come in two lists, IPv4 and IPv6, shortest mask length
|
||||||
has a handle, a short name such as `GOOGLE`, and a description, such as
|
first. An AS has a handle, a short name such as `GOOGLE`, and a description,
|
||||||
`Google LLC`. When the database has no record of the AS, both come from the AS
|
such as `Google LLC`. When the database has no record of the AS, both come
|
||||||
list built into RouteWatch. The page then shows no first-seen date.
|
from the AS list built into RouteWatch. The page then shows no first-seen
|
||||||
|
date.
|
||||||
- `/prefix/{prefix}/{length}`: a prefix, such as `/prefix/192.0.2.0/24`, with
|
- `/prefix/{prefix}/{length}`: a prefix, such as `/prefix/192.0.2.0/24`, with
|
||||||
its origin ASes and every peer that carries it. Only the kept routes show an
|
its origin ASes and every peer that carries it. Only the kept routes show an
|
||||||
AS path, a next hop and a time. The next hop is the router the peer sends the
|
AS path, a next hop and a time. The next hop is the router the peer sends the
|
||||||
@@ -157,11 +162,13 @@ and origin AS can have two kept routes, one or none.
|
|||||||
A prefix is reserved when it lies inside one of these:
|
A prefix is reserved when it lies inside one of these:
|
||||||
|
|
||||||
- a block that the IANA special-purpose address registries mark as not globally
|
- a block that the IANA special-purpose address registries mark as not globally
|
||||||
reachable
|
reachable. The IANA is the body that hands out the internet's address space
|
||||||
- the multicast space, `224.0.0.0/4` and `ff00::/8`
|
and AS numbers.
|
||||||
|
- the multicast blocks, `224.0.0.0/4` and `ff00::/8`. A multicast address stands
|
||||||
|
for a group of receivers, not for one host.
|
||||||
|
|
||||||
A few blocks that may be announced lie inside reserved blocks. The most specific
|
A few blocks that may be announced lie inside reserved blocks. Of the blocks
|
||||||
block that holds the prefix decides.
|
that hold the prefix, the one with the longest mask length decides.
|
||||||
|
|
||||||
An AS number is reserved when it is one of these:
|
An AS number is reserved when it is one of these:
|
||||||
|
|
||||||
@@ -185,7 +192,7 @@ also carries an `@meta` object:
|
|||||||
Every value with a unit is a number, and the unit ends the field name. Examples
|
Every value with a unit is a number, and the unit ends the field name. Examples
|
||||||
are `_ms`, `_seconds`, `_bytes`, `_per_sec` and `_per_min`.
|
are `_ms`, `_seconds`, `_bytes`, `_per_sec` and `_per_min`.
|
||||||
|
|
||||||
The routes:
|
The paths:
|
||||||
|
|
||||||
- `GET /api/v1/ip/{ip}`: what RouteWatch knows about each address in `{ip}`. See
|
- `GET /api/v1/ip/{ip}`: what RouteWatch knows about each address in `{ip}`. See
|
||||||
IP lookup below.
|
IP lookup below.
|
||||||
@@ -194,15 +201,16 @@ The routes:
|
|||||||
the request came from. For a request from a trusted proxy, it is the client
|
the request came from. For a request from a trusted proxy, it is the client
|
||||||
address in `X-Forwarded-For` (see `TRUSTED_PROXIES` in Configuration).
|
address in `X-Forwarded-For` (see `TRUSTED_PROXIES` in Configuration).
|
||||||
- `GET /api/v1/as/{asn}`: the AS and the prefixes it originates. `data` holds:
|
- `GET /api/v1/as/{asn}`: the AS and the prefixes it originates. `data` holds:
|
||||||
- `asn`: the AS, with its handle, description, `first_seen` and `last_seen`
|
- `asn`: the AS, with its `handle`, `description`, `first_seen` and
|
||||||
|
`last_seen`
|
||||||
- `ipv4_prefixes` and `ipv6_prefixes`: the prefixes it originates, each with
|
- `ipv4_prefixes` and `ipv6_prefixes`: the prefixes it originates, each with
|
||||||
`last_updated`, the time of its newest kept route with the AS
|
`last_updated`, the time of its newest kept route with the AS
|
||||||
- `total_count`: how many prefixes the two lists hold together
|
- `total_count`: how many prefixes the two lists hold together
|
||||||
|
|
||||||
`last_updated` is left out when the kept routes of the prefix with the AS
|
`last_updated` is left out when the kept routes of the prefix with the AS
|
||||||
were all withdrawn. When the database has no record of the AS, its handle
|
were all withdrawn. When the database has no record of the AS, its `handle`
|
||||||
and description come from the AS list built into RouteWatch. The answer then
|
and `description` come from the AS list built into RouteWatch. The answer
|
||||||
has no `first_seen` or `last_seen`.
|
then has no `first_seen` or `last_seen`.
|
||||||
|
|
||||||
- `GET /api/v1/prefix/{prefix}/{length}`: the prefix and its routes. `data`
|
- `GET /api/v1/prefix/{prefix}/{length}`: the prefix and its routes. `data`
|
||||||
holds:
|
holds:
|
||||||
@@ -212,7 +220,9 @@ The routes:
|
|||||||
- `peer_count` and `origin_count`: how many routes and how many origin ASes
|
- `peer_count` and `origin_count`: how many routes and how many origin ASes
|
||||||
there are
|
there are
|
||||||
|
|
||||||
Only the kept routes have `as_path`, `next_hop` and `last_updated`.
|
A route has `prefix`, `mask_length`, `ip_version`, `origin_asn` and
|
||||||
|
`peer_ip`. Only the kept routes also have `as_path`, `next_hop` and
|
||||||
|
`last_updated`.
|
||||||
|
|
||||||
- `GET /api/v1/search/as?q={text}`: the ASes whose handle or description
|
- `GET /api/v1/search/as?q={text}`: the ASes whose handle or description
|
||||||
contains `{text}`, ignoring case. `data` holds:
|
contains `{text}`, ignoring case. `data` holds:
|
||||||
@@ -226,8 +236,8 @@ The routes:
|
|||||||
[ipverse/as-metadata](https://github.com/ipverse/as-metadata). So a match
|
[ipverse/as-metadata](https://github.com/ipverse/as-metadata). So a match
|
||||||
can be an AS that the database has no record of.
|
can be an AS that the database has no record of.
|
||||||
|
|
||||||
- `GET /status.json`: figures of the feed, the memory, the database and the
|
- `GET /status.json`: the daemon's figures. The figures whose names do not say
|
||||||
routing table. The figures whose names do not say what they mean:
|
what they mean:
|
||||||
- `uptime_seconds`: the seconds since the feed last connected, or 0 when it
|
- `uptime_seconds`: the seconds since the feed last connected, or 0 when it
|
||||||
has never connected
|
has never connected
|
||||||
- `memory_usage_bytes`: the size of the Go heap in use
|
- `memory_usage_bytes`: the size of the Go heap in use
|
||||||
@@ -239,12 +249,17 @@ The routes:
|
|||||||
that peers carry but whose kept routes were all withdrawn
|
that peers carry but whose kept routes were all withdrawn
|
||||||
- `whois_stats.current_interval_seconds`: the WHOIS fetcher's wait between
|
- `whois_stats.current_interval_seconds`: the WHOIS fetcher's wait between
|
||||||
two fetches, which can include a fraction of a second
|
two fetches, which can include a fraction of a second
|
||||||
- `GET /api/v1/stats`: the `/status.json` figures plus `reconnect_count`, and
|
- `GET /api/v1/stats`: the `/status.json` figures and five more:
|
||||||
figures of the garbage collector in `gc`, of the stream in `stream` and of the
|
- `reconnect_count`: how many times the feed has reconnected since the start
|
||||||
handler queues in `handler_stats`. `connection_duration_seconds` is the
|
- `connection_duration_seconds`: the seconds the feed has been connected, or
|
||||||
seconds the feed has been connected, or null while it is not connected.
|
null while it is not connected
|
||||||
`stream.route_churn_per_min` is the number of prefix announcements and
|
- `gc`: figures of the garbage collector, the part of the Go runtime that
|
||||||
withdrawals, from all peers, in the last full clock minute.
|
frees memory no longer in use
|
||||||
|
- `stream`: figures of the feed. `stream.route_churn_per_min` is the number
|
||||||
|
of prefix announcements and withdrawals, from all peers, in the last full
|
||||||
|
clock minute.
|
||||||
|
- `handler_stats`: figures of the handlers' queues. The handlers are the
|
||||||
|
four parts that take the feed's messages (see Design).
|
||||||
- `GET /.well-known/healthcheck.json`: the health check. It answers 200 when the
|
- `GET /.well-known/healthcheck.json`: the health check. It answers 200 when the
|
||||||
database answers and the feed is connected, otherwise 503. `data.status` is
|
database answers and the feed is connected, otherwise 503. `data.status` is
|
||||||
`ok` or `error`, and `data.checks` says which check failed. The 503 answer
|
`ok` or `error`, and `data.checks` says which check failed. The 503 answer
|
||||||
@@ -266,7 +281,8 @@ address. Every entry has the same fields:
|
|||||||
Every other field is about the address's live route. Each is null when the
|
Every other field is about the address's live route. Each is null when the
|
||||||
address has no live route:
|
address has no live route:
|
||||||
|
|
||||||
- `netblock`: the most specific prefix that holds the address
|
- `netblock`: the most specific prefix that holds the address, the one with the
|
||||||
|
longest mask length
|
||||||
- `mask_length`: the netblock's mask length
|
- `mask_length`: the netblock's mask length
|
||||||
- `ip_version`: the netblock's IP version, 4 or 6
|
- `ip_version`: the netblock's IP version, 4 or 6
|
||||||
- `num_peers`: the number of peers that carry the netblock
|
- `num_peers`: the number of peers that carry the netblock
|
||||||
@@ -290,7 +306,8 @@ database does not have it:
|
|||||||
- `address`: the organisation's postal address
|
- `address`: the organisation's postal address
|
||||||
- `country_code`: the organisation's country code
|
- `country_code`: the organisation's country code
|
||||||
- `abuse_email`: the address that takes abuse reports for the AS
|
- `abuse_email`: the address that takes abuse reports for the AS
|
||||||
- `rir`: the regional internet registry of the AS
|
- `rir`: the regional internet registry that holds the AS, one of `ARIN`,
|
||||||
|
`RIPE`, `APNIC`, `LACNIC` and `AFRINIC`
|
||||||
|
|
||||||
When a host name fails, `errors` lists it with the reason. A name that does not
|
When a host name fails, `errors` lists it with the reason. A name that does not
|
||||||
resolve is reported as `could not resolve host name`. A name with no public
|
resolve is reported as `could not resolve host name`. A name with no public
|
||||||
@@ -320,14 +337,15 @@ shortest blocklist interval. The answer for the caller's own address (`/ip/`) is
|
|||||||
- an AS that originates no prefix and has no record in the database or in
|
- an AS that originates no prefix and has no record in the database or in
|
||||||
the AS list built into RouteWatch
|
the AS list built into RouteWatch
|
||||||
- a prefix that no peer carries
|
- a prefix that no peer carries
|
||||||
- under `/api/v1` and `/ip/`, a path that matches no route, with the message
|
- under `/api/v1` and `/ip/`, a path that matches nothing, with the message
|
||||||
`Not found`
|
`Not found`
|
||||||
|
- any other path that matches nothing, with the text `404 page not found`
|
||||||
- 405, with the message `Method not allowed`, for `POST`, `PUT`, `PATCH`,
|
- 405, with the message `Method not allowed`, for `POST`, `PUT`, `PATCH`,
|
||||||
`DELETE`, `OPTIONS`, `TRACE` or `CONNECT` on these routes:
|
`DELETE`, `OPTIONS`, `TRACE` or `CONNECT` on these paths:
|
||||||
- the routes under `/api/v1` and `/ip/`
|
- the paths under `/api/v1` and `/ip/`
|
||||||
- `/status.json`
|
- `/status.json`
|
||||||
- `/.well-known/healthcheck.json`
|
- `/.well-known/healthcheck.json`
|
||||||
- 500: an internal error. The JSON routes say only `Internal server error`. The
|
- 500: an internal error. The JSON paths say only `Internal server error`. The
|
||||||
detail goes to the log.
|
detail goes to the log.
|
||||||
- 504: `Request timeout`. A request may take 30 seconds. The database reads
|
- 504: `Request timeout`. A request may take 30 seconds. The database reads
|
||||||
behind `/status.json` and `/api/v1/stats` may take 4 seconds.
|
behind `/status.json` and `/api/v1/stats` may take 4 seconds.
|
||||||
@@ -425,38 +443,45 @@ command-line flags.
|
|||||||
- `PORT`: the port the HTTP server listens on. A whole number from 1 to 65535.
|
- `PORT`: the port the HTTP server listens on. A whole number from 1 to 65535.
|
||||||
Default `8080`.
|
Default `8080`.
|
||||||
- `DEBUG`: set it to `routewatch` for debug logging. The daemon then also logs a
|
- `DEBUG`: set it to `routewatch` for debug logging. The daemon then also logs a
|
||||||
`System stats` line every 60 seconds, with the goroutine count and the Go
|
`System stats` line every 60 seconds, with the Go memory figures and the
|
||||||
memory figures. Default empty.
|
number of goroutines, Go's lightweight threads. Default empty.
|
||||||
- `TRUSTED_PROXIES`: the reverse proxies whose `X-Forwarded-For` header the
|
- `TRUSTED_PROXIES`: the reverse proxies whose `X-Forwarded-For` header the
|
||||||
server believes. See below. Default: the private ranges of RFC 1918.
|
server believes. A reverse proxy is a server in front of RouteWatch that
|
||||||
- `XDG_DATA_HOME`: the base of the state directory on Linux and the BSDs, when
|
passes requests on to it. See below. Default: the private ranges of RFC 1918.
|
||||||
RouteWatch does not run as root. It must be an absolute path. The Docker image
|
- `XDG_DATA_HOME`: the base of the state directory (see below) on Linux and the
|
||||||
sets `/var/lib`.
|
BSDs, when RouteWatch does not run as root. It must be an absolute path. The
|
||||||
- `GOMEMLIMIT`: the Go runtime's soft memory limit. The Docker image sets
|
Docker image sets `/var/lib`.
|
||||||
`2560MiB`. See Memory.
|
- `GOMEMLIMIT`: the ceiling the Go runtime keeps the program's memory under. The
|
||||||
|
Docker image sets `2560MiB`. See Memory.
|
||||||
- `MALLOC_ARENA_MAX`: how many memory pools, called arenas, the C library may
|
- `MALLOC_ARENA_MAX`: how many memory pools, called arenas, the C library may
|
||||||
create. The Docker image sets `2`. See Memory.
|
create. The Docker image sets `2`. See Memory.
|
||||||
|
|
||||||
`PORT`, `XDG_DATA_HOME` or `TRUSTED_PROXIES` set to an invalid value stops the
|
The start fails, with an error and a non-zero exit, when `PORT`, `XDG_DATA_HOME`
|
||||||
start with an error and a non-zero exit. The Docker image's entrypoint refuses a
|
or `TRUSTED_PROXIES` has an invalid value. In the Docker image, `entrypoint.sh`
|
||||||
`MALLOC_ARENA_MAX` that is not a positive whole number. An empty variable counts
|
refuses a `MALLOC_ARENA_MAX` that is not a positive whole number. An empty
|
||||||
as unset. `TRUSTED_PROXIES` is the one exception: set but empty, it makes the
|
variable counts as unset. `TRUSTED_PROXIES` is the one exception: set but empty,
|
||||||
server trust no proxy.
|
it makes the server trust no proxy.
|
||||||
|
|
||||||
`TRUSTED_PROXIES` works like this:
|
`TRUSTED_PROXIES` works like this:
|
||||||
|
|
||||||
- When it is not set, the server trusts `10.0.0.0/8`, `172.16.0.0/12` and
|
- When it is not set, the server trusts `10.0.0.0/8`, `172.16.0.0/12` and
|
||||||
`192.168.0.0/16`.
|
`192.168.0.0/16`.
|
||||||
- When it lists addresses and CIDR ranges, comma-separated, such as
|
- When it lists addresses and prefixes, comma-separated, such as
|
||||||
`10.0.0.0/8,192.0.2.1`, the server trusts those instead.
|
`10.0.0.0/8,192.0.2.1`, the server trusts those instead.
|
||||||
- When it is set but empty (`TRUSTED_PROXIES=`), the server trusts no proxy.
|
- When it is set but empty (`TRUSTED_PROXIES=`), the server trusts no proxy.
|
||||||
|
|
||||||
For a request from a trusted proxy, the request log, `/ip/` and the home page's
|
Three parts of RouteWatch read `X-Forwarded-For`:
|
||||||
`curl` card use the last address in `X-Forwarded-For` that is not a trusted
|
|
||||||
proxy. Each proxy adds the address it received the request from to the end of
|
- the request log
|
||||||
the header. The entries before that are whatever the client sent. For a request
|
- `/ip/`
|
||||||
from any other address, they use the address the request came from, whatever
|
- the home page's `curl` card
|
||||||
headers it carries. `X-Real-IP` and `True-Client-IP` are not read.
|
|
||||||
|
For a request from a trusted proxy, they use the last address in the header that
|
||||||
|
is not a trusted proxy. Each proxy adds, at the end of the header, the address
|
||||||
|
that sent it the request. The entries left of the client's address were written
|
||||||
|
by the client. For a request from any other address, they use the address the
|
||||||
|
request came from, whatever headers it carries. `X-Real-IP` and `True-Client-IP`
|
||||||
|
are not read.
|
||||||
|
|
||||||
The state directory holds the database, `db.sqlite`, and the blocklist copies:
|
The state directory holds the database, `db.sqlite`, and the blocklist copies:
|
||||||
|
|
||||||
@@ -480,24 +505,27 @@ a memory limit of 5 GiB and swap disabled:
|
|||||||
docker run --memory=5g --memory-swap=5g ...
|
docker run --memory=5g --memory-swap=5g ...
|
||||||
```
|
```
|
||||||
|
|
||||||
That leaves room above the ceilings for spikes and for the kernel's page cache.
|
The 5 GiB leaves room above the ceilings for spikes and for the kernel's cache
|
||||||
|
of the database file.
|
||||||
|
|
||||||
The ceilings add up to 3.7 GiB:
|
The ceilings add up to 3.7 GiB:
|
||||||
|
|
||||||
- The Go heap: a 2.5 GiB soft limit, `GOMEMLIMIT=2560MiB`, set by the image. The
|
- The Go heap, the memory the Go program allocates: a 2.5 GiB soft limit,
|
||||||
routing table takes about 700 bytes for each prefix and origin AS pair. A full
|
`GOMEMLIMIT=2560MiB`, set by the image. The routing table takes about 700
|
||||||
table has about 1.4 million pairs, so it takes about 1 GB. The handlers'
|
bytes for each prefix and origin AS pair. A full table has about 1.4 million
|
||||||
batches and queues take up to about 0.5 GiB. The remaining 1 GiB is room for
|
pairs, so it takes about 1 GB. The rest of the heap takes up to about 0.5 GiB,
|
||||||
the garbage collector. With much less, it would run almost all the time.
|
most of it the handlers' batches and queues (see below). The remaining 1 GiB
|
||||||
|
is room for the garbage collector, which frees memory the program no longer
|
||||||
|
uses. With much less, it would run almost all the time.
|
||||||
- SQLite: 1 GiB, set in the code. The connection that does every write has a 512
|
- SQLite: 1 GiB, set in the code. The connection that does every write has a 512
|
||||||
MiB page cache, the database pages it keeps in memory. It keeps the pages the
|
MiB page cache: the database pages it keeps in memory from one write to the
|
||||||
writes touch from one write to the next. The `routes` table of a full routing
|
next. The `routes` table of a full routing table takes about 450 MB, so its
|
||||||
table takes about 450 MB, so its pages fit. Each of the 9 reading connections
|
pages fit. Each of the 9 reading connections has 16 MiB. A 768 MiB soft heap
|
||||||
has 16 MiB. A 768 MiB soft heap limit sits above the caches, so it does not
|
limit sits above the caches, so it does not shrink them. A 1 GiB hard heap
|
||||||
shrink them. A 1 GiB hard heap limit caps the SQLite library.
|
limit caps the SQLite library.
|
||||||
- About 0.2 GiB for everything else in the runtime.
|
- About 0.2 GiB for everything else in the runtime.
|
||||||
|
|
||||||
The SQLite library gets its memory from the C library, glibc. glibc keeps freed
|
The SQLite library gets its memory from glibc, the C library. glibc keeps freed
|
||||||
memory in pools called arenas, and hands it back to the kernel slowly. It
|
memory in pools called arenas, and hands it back to the kernel slowly. It
|
||||||
creates up to eight arenas per processor core. So on a host with many cores the
|
creates up to eight arenas per processor core. So on a host with many cores the
|
||||||
process holds far more memory than SQLite uses. The image sets
|
process holds far more memory than SQLite uses. The image sets
|
||||||
@@ -510,15 +538,17 @@ What happens at each limit:
|
|||||||
garbage more often rather than growing further.
|
garbage more often rather than growing further.
|
||||||
- The SQLite soft heap limit: SQLite recycles its page cache instead of
|
- The SQLite soft heap limit: SQLite recycles its page cache instead of
|
||||||
allocating more.
|
allocating more.
|
||||||
- The SQLite hard heap limit: a statement fails with an out-of-memory error,
|
- The SQLite hard heap limit: a database statement fails with an out-of-memory
|
||||||
which the handler logs. The prefix handler writes the routing table's changes
|
error, which the handler logs. The prefix handler writes the routing table's
|
||||||
again in a later write. Everything else in the failed write is dropped. The
|
changes again in a later write. Everything else in the failed write is
|
||||||
process keeps running.
|
dropped. The process keeps running.
|
||||||
- The handler queues: each of the four handlers has a queue of at most 20,000
|
- The handler queues: the handlers are the four parts that take the feed's
|
||||||
messages. When a queue fills, the streamer drops messages instead of waiting.
|
messages (see Design). Each has a queue of at most 20,000 messages. When a
|
||||||
The exception is a `STATE` message, which it holds, in feed order, until there
|
queue fills, the streamer drops messages instead of waiting. The streamer is
|
||||||
is room. A `STATE` message says that a peer's BGP session changed state, for
|
the part that reads the feed. The exception is a `STATE` message, which it
|
||||||
example went down. These are a few hundred bytes each, about ten a second.
|
holds, in feed order, until there is room. A `STATE` message says that a
|
||||||
|
peer's BGP session changed state, for example went down. These are a few
|
||||||
|
hundred bytes each, about ten a second.
|
||||||
- The handler batches: while the AS, peer or prefix handler writes a batch to
|
- The handler batches: while the AS, peer or prefix handler writes a batch to
|
||||||
the database, the updates that arrive collect in its next batch. That batch
|
the database, the updates that arrive collect in its next batch. That batch
|
||||||
stops at ten times the batch size:
|
stops at ten times the batch size:
|
||||||
@@ -538,7 +568,7 @@ many cores.
|
|||||||
|
|
||||||
## Running under upaas
|
## Running under upaas
|
||||||
|
|
||||||
What the [upaas](https://git.eeqj.de/sneak/upaas) app needs:
|
What [upaas](https://git.eeqj.de/sneak/upaas), the deployment tool, needs:
|
||||||
|
|
||||||
- Container port: `8080`.
|
- Container port: `8080`.
|
||||||
- Volume: one, at the container path `/var/lib/berlin.sneak.app.routewatch`.
|
- Volume: one, at the container path `/var/lib/berlin.sneak.app.routewatch`.
|
||||||
@@ -558,19 +588,25 @@ What the [upaas](https://git.eeqj.de/sneak/upaas) app needs:
|
|||||||
cannot reach RIS Live never becomes healthy.
|
cannot reach RIS Live never becomes healthy.
|
||||||
- Stop grace: upaas gives the container 10 seconds between `SIGTERM` and
|
- Stop grace: upaas gives the container 10 seconds between `SIGTERM` and
|
||||||
`SIGKILL`, as `docker stop` does by default. The daemon gives its parts 8
|
`SIGKILL`, as `docker stop` does by default. The daemon gives its parts 8
|
||||||
seconds to stop and then exits. A stop still not done after 9 seconds ends
|
seconds to stop and then exits with status 0. A stop that is still running
|
||||||
with status 1. See Shutdown under Design for what a stop writes and what it
|
after 9 seconds exits with status 1 instead. See Shutdown under Design for
|
||||||
can lose.
|
what a stop writes and what it can lose.
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
### Parts
|
### Parts
|
||||||
|
|
||||||
The parts are built in two steps. The `fx` library, which wires a Go program's
|
The parts are built in two steps:
|
||||||
parts together, builds the database, the routing table, the streamer and the
|
|
||||||
HTTP server. `RouteWatch.Run` in `internal/routewatch/app.go` builds the
|
- The `fx` library, which wires a Go program's parts together, builds these:
|
||||||
handlers, the WHOIS fetcher, the blocklists and the database maintainer, and
|
- the database
|
||||||
starts every part. The parts:
|
- the routing table in memory
|
||||||
|
- the streamer
|
||||||
|
- the HTTP server
|
||||||
|
- `RouteWatch.Run` in `internal/routewatch/app.go` builds the other parts and
|
||||||
|
starts them all.
|
||||||
|
|
||||||
|
The parts:
|
||||||
|
|
||||||
- The streamer connects to RIS Live, reads the JSON stream and parses the BGP
|
- The streamer connects to RIS Live, reads the JSON stream and parses the BGP
|
||||||
messages. It gives each message to the handlers that want it, through one
|
messages. It gives each message to the handlers that want it, through one
|
||||||
@@ -593,8 +629,8 @@ starts every part. The parts:
|
|||||||
address.
|
address.
|
||||||
- The database maintainer checkpoints the database every 5 seconds: it folds the
|
- The database maintainer checkpoints the database every 5 seconds: it folds the
|
||||||
write-ahead log, where SQLite first writes changes, into the database file. It
|
write-ahead log, where SQLite first writes changes, into the database file. It
|
||||||
reclaims free space every 10 minutes and refreshes the query statistics every
|
reclaims free space every 10 minutes. Every hour it refreshes the statistics
|
||||||
hour.
|
SQLite uses to plan its queries.
|
||||||
- The HTTP server serves the pages and the API. Every route it shows comes from
|
- The HTTP server serves the pages and the API. Every route it shows comes from
|
||||||
the routing table in memory. AS names, WHOIS details and first-seen times come
|
the routing table in memory. AS names, WHOIS details and first-seen times come
|
||||||
from the database. The AS name search reads the AS list built into RouteWatch.
|
from the database. The AS name search reads the AS list built into RouteWatch.
|
||||||
@@ -623,8 +659,10 @@ The prefix handler applies each update as it handles it:
|
|||||||
|
|
||||||
The address lookup finds the most specific prefix that holds the address. It
|
The address lookup finds the most specific prefix that holds the address. It
|
||||||
looks the address up at each mask length, longest first: at most 25 lookups for
|
looks the address up at each mask length, longest first: at most 25 lookups for
|
||||||
IPv4 and 113 for IPv6. Prefixes shorter than `/8` for IPv4 or `/16` for IPv6,
|
IPv4 and 113 for IPv6. Prefixes shorter than `/8` for IPv4 or `/16` for IPv6 are
|
||||||
such as a default route, are not used.
|
not used. Such a prefix says only where a peer sends traffic that matches no
|
||||||
|
better route; it does not say who holds the address. One of them is the default
|
||||||
|
route, `0.0.0.0/0` or `::/0`, which matches every address.
|
||||||
|
|
||||||
The database's `routes` table has one row for each prefix and origin AS. Every
|
The database's `routes` table has one row for each prefix and origin AS. Every
|
||||||
second the prefix handler writes the pairs that changed, at most 20,000 in one
|
second the prefix handler writes the pairs that changed, at most 20,000 in one
|
||||||
@@ -693,8 +731,9 @@ On `SIGTERM` or `SIGINT` the daemon stops its parts in this order:
|
|||||||
6. the HTTP server
|
6. the HTTP server
|
||||||
|
|
||||||
Last, the daemon checkpoints and closes the database. It gives all this 8
|
Last, the daemon checkpoints and closes the database. It gives all this 8
|
||||||
seconds and then exits with status 0. A stop still not done after 9 seconds ends
|
seconds and then exits with status 0. A stop that is still running after 9
|
||||||
with status 1. When RouteWatch stops on an error, it exits with status 1.
|
seconds exits with status 1 instead. When RouteWatch stops on an error, it exits
|
||||||
|
with status 1.
|
||||||
|
|
||||||
What a stop can lose:
|
What a stop can lose:
|
||||||
|
|
||||||
@@ -782,7 +821,8 @@ The `make` targets:
|
|||||||
- `make fmt`: format the Go code and the markdown
|
- `make fmt`: format the Go code and the markdown
|
||||||
- `make fmt-check`: check the formatting without writing
|
- `make fmt-check`: check the formatting without writing
|
||||||
- `make check`: run the tests, the linter and the format check
|
- `make check`: run the tests, the linter and the format check
|
||||||
- `make hooks`: install the git pre-commit hook
|
- `make hooks`: install the git hook that runs `script/precommit` before each
|
||||||
|
commit
|
||||||
- `make build`: build `bin/routewatch`
|
- `make build`: build `bin/routewatch`
|
||||||
- `make run`: build the binary and run it with debug logging
|
- `make run`: build the binary and run it with debug logging
|
||||||
- `make clean`: delete `bin/`
|
- `make clean`: delete `bin/`
|
||||||
@@ -793,8 +833,9 @@ The `make` targets:
|
|||||||
|
|
||||||
The tests and the linter run only in Docker, as the `test` and `lint` phases of
|
The tests and the linter run only in Docker, as the `test` and `lint` phases of
|
||||||
the `Dockerfile`. The image cannot be built unless both pass. Every
|
the `Dockerfile`. The image cannot be built unless both pass. Every
|
||||||
`docker build` in `script/` passes a new `CACHEBUST` value, so each run executes
|
`docker build` in `script/` passes a new value of the `CACHEBUST` build
|
||||||
every check. Go's build cache and the linter's cache are kept between builds.
|
argument, so each run executes every check. Go's build cache and the linter's
|
||||||
|
cache are kept between builds.
|
||||||
|
|
||||||
The test suite runs with `-short`, which skips `TestRouteWatchLiveFeed`. That
|
The test suite runs with `-short`, which skips `TestRouteWatchLiveFeed`. That
|
||||||
test streams the live feed for a few seconds. No `make` target runs it.
|
test streams the live feed for a few seconds. No `make` target runs it.
|
||||||
@@ -804,12 +845,13 @@ test streams the live feed for a few seconds. No `make` target runs it.
|
|||||||
This repository adheres to the
|
This repository adheres to the
|
||||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
||||||
standard: the scripts in `script/` are the entrypoints of the development
|
standard: the scripts in `script/` are the entrypoints of the development
|
||||||
workflow, and the `Makefile` targets call them. The scripts:
|
workflow. Every `make` target except `make build`, `make run` and `make clean`
|
||||||
|
calls one of them. The scripts:
|
||||||
|
|
||||||
- `script/bootstrap`: install the development dependencies:
|
- `script/bootstrap`: install the development dependencies:
|
||||||
- `git`, `make` and Go, when they are missing
|
- `git`, `make` and Go, when they are missing
|
||||||
- the pinned node and yarn
|
- the pinned versions of node and yarn
|
||||||
- prettier, from `yarn.lock`
|
- prettier, the markdown formatter, from `yarn.lock`
|
||||||
- the Go modules
|
- the Go modules
|
||||||
- `script/setup`: make a fresh clone ready for development. It runs
|
- `script/setup`: make a fresh clone ready for development. It runs
|
||||||
`script/bootstrap`, then `script/install-precommit`.
|
`script/bootstrap`, then `script/install-precommit`.
|
||||||
@@ -830,8 +872,8 @@ workflow, and the `Makefile` targets call them. The scripts:
|
|||||||
- `script/precommit`: the pre-commit gate. It runs `go mod tidy` and `go fmt`,
|
- `script/precommit`: the pre-commit gate. It runs `go mod tidy` and `go fmt`,
|
||||||
then `script/check`. `go mod tidy` must leave `go.mod` and `go.sum` unchanged;
|
then `script/check`. `go mod tidy` must leave `go.mod` and `go.sum` unchanged;
|
||||||
`go fmt` writes.
|
`go fmt` writes.
|
||||||
- `script/install-precommit`: install the git pre-commit hook that runs
|
- `script/install-precommit`: install the git hook that runs `script/precommit`
|
||||||
`script/precommit`.
|
before each commit.
|
||||||
- `script/asupdate`: rebuild the AS list built into RouteWatch,
|
- `script/asupdate`: rebuild the AS list built into RouteWatch,
|
||||||
`pkg/asinfo/asdata.json.gz`, from the `as.csv` file of
|
`pkg/asinfo/asdata.json.gz`, from the `as.csv` file of
|
||||||
[ipverse/as-metadata](https://github.com/ipverse/as-metadata). It needs the
|
[ipverse/as-metadata](https://github.com/ipverse/as-metadata). It needs the
|
||||||
|
|||||||
@@ -35,12 +35,8 @@ tested.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-11-02: `README.md` rewritten from scratch, sentence by sentence, against
|
- 2026-11-02: `README.md` rewritten from scratch, sentence by sentence, and
|
||||||
the rules of https://git.eeqj.de/sneak/routewatch/issues/157. It leads with
|
checked against the code (part of #157)
|
||||||
what RouteWatch is and how to run it, then the pages and the API, then the
|
|
||||||
settings, memory and upaas, and ends with the design and development. Every
|
|
||||||
statement was checked against the code (part of
|
|
||||||
https://git.eeqj.de/sneak/routewatch/issues/157)
|
|
||||||
- 2026-10-27: the tests of `internal/routewatch` no longer fail on a loaded
|
- 2026-10-27: the tests of `internal/routewatch` no longer fail on a loaded
|
||||||
host. Five of them gave `HandleMessage` two seconds to apply a full prefix
|
host. Five of them gave `HandleMessage` two seconds to apply a full prefix
|
||||||
batch, which under the race detector can take longer. A test now waits up to
|
batch, which under the race detector can take longer. A test now waits up to
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ func checkAnswerCarriesMeta(
|
|||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// The values README "API v1" gives the two fields that never change.
|
// The values README "JSON API" gives the two fields that never change.
|
||||||
const (
|
const (
|
||||||
wantTimeZone = "UTC"
|
wantTimeZone = "UTC"
|
||||||
wantAPIVersion = 1
|
wantAPIVersion = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user