# RouteWatch RouteWatch is an MIT-licensed Go daemon by @sneak that monitors the BGP routing table in real time: it streams BGP UPDATE messages from the RIPE RIS Live service, maintains a live routing table in SQLite, and provides HTTP APIs for querying routing information. ## Rationale RIPE RIS Live streams the BGP updates that RIPE's route collectors receive, as they happen, from the moment a client connects. It reports changes, not the routing table they add up to, so it cannot say which AS an IP address is routed to now or which routes a prefix has. RouteWatch applies the feed's updates to live routing tables in its own SQLite database and answers IP-to-AS lookups and prefix pages from them. The feed sends no table when a client connects, so these tables hold only the routes the feed has announced since RouteWatch first started: an IP-to-AS lookup finds nothing for an address whose routes have not been announced since then. The tables also miss the updates RouteWatch drops when it falls behind under load and those sent while it is stopped, so a lookup or a prefix page can show a route the feed has withdrawn. ## Features - Real-time streaming of BGP updates from RIPE RIS Live - Maintains live IPv4 and IPv6 routing tables - Tracks AS peering relationships - HTTP API for IP-to-AS lookups, prefix details, and AS information - Automatic reconnection with exponential backoff - Batched database writes for high performance - Backpressure handling to prevent memory exhaustion ## Installation ```bash go build -o routewatch ./cmd/routewatch ``` ## Usage ```bash # Run the daemon (listens on port 8080 by default) ./routewatch # Set custom port PORT=3000 ./routewatch # Enable debug logging DEBUG=routewatch ./routewatch ``` ## HTTP Endpoints Every route answers `HEAD` as it answers `GET`, without the body. ### Web Interface - `GET /` - Home page (HTML): headline figures, and lookups by AS number and by IP address - `GET /status` - HTML status dashboard, which reads `/api/v1/stats` - `GET /status.json` - JSON statistics: feed, memory, database and route figures - `GET /.well-known/healthcheck.json` - Health check (JSON): 200 when the database answers and the RIS Live feed is connected, otherwise 503; `data.status` is `ok` or `error` and `data.checks` says which check failed - `GET /as/{asn}` - AS detail page (HTML): the prefixes the AS originates and its peers - `GET /prefix/{prefix}/{length}` - Prefix detail page (HTML), such as `/prefix/192.0.2.0/24`: every live route for the prefix, one per peer, and its origin ASes - `GET /prefixlength/{length}` - Up to 500 IPv4 prefixes with that mask length, picked at random, each with its newest route (HTML) - `GET /prefixlength6/{length}` - The same for IPv6 - `GET /ip/{addr}` - The same JSON answer as `/api/v1/ip/{ip}` - `GET /ip/` - The same, for the caller's own address: the address the request came from or, when that is a trusted proxy (`TRUSTED_PROXIES`, see Configuration), the client address in its `X-Forwarded-For` ### API v1 - `GET /api/v1/stats` - The `/status.json` figures plus connection, garbage collector, stream and handler queue statistics - `GET /api/v1/ip/{ip}` - Looks up each address: its most specific live route (`netblock`), that route's origin AS with its WHOIS details, and the address's PTR names. `{ip}` is an IP address, a host name, or a comma-separated list of at most 10 of these. Of the addresses a host name resolves to, only public ones are looked up: a name with none, such as one that resolves to a private address, is reported as `host name has no public address`, without its addresses. `results` has one entry per address with a live route and `errors` one per address or name that failed. The answer is 400 when `{ip}` lists more than 10, when it comes to more than 10 addresses once its names are resolved, or when nothing in it is an IP address or a host name with a public address; and 404 when none of the addresses has a live route. A PTR lookup gets 2 seconds; an address whose lookup takes longer is answered without PTR names. - `GET /api/v1/as/{asn}` - The AS and the prefixes it originates, IPv4 and IPv6 apart, each with when its newest route was updated - `GET /api/v1/prefix/{prefix}/{length}` - Every live route for the prefix, one per peer, and the same routes grouped by origin AS in `origins` A JSON answer is `{"status": "ok", "data": ...}`, or for an error `{"status": "error", "error": {"msg": ..., "code": ...}}`, and carries an `@meta` object with the time zone, the API version and the time the request took. The health check is the exception: its 503 answer is also `{"status": "ok", "data": ...}`, with `error` in `data.status`. A request that runs past its time limit answers 504: the limit is 30 seconds, and 4 seconds for the database reads behind `/status.json` and `/api/v1/stats`. An AS number, prefix or mask length that does not parse answers 400, as does a mask length out of range for its IP version. An AS the database does not hold, or a prefix with no live route, answers 404. The HTML pages give these errors as plain text. Under `/api/v1` and `/ip/`, a path no route matches answers 404 with the message `Not found`, and `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `TRACE` or `CONNECT` answers 405 with `Method not allowed`. A 500 answer says only `Internal server error`, and a host name that does not resolve only `could not resolve host name`; the detail goes to the log. Every answer carries `Strict-Transport-Security`, `Content-Security-Policy`, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` and `Permissions-Policy`. The Content-Security-Policy lets a page load content only from the server, and does not allow inline content with `'unsafe-inline'`: the pages keep their CSS and JavaScript in inline `