Bring README and TODO.md up to date with the code (closes #72)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
README "HTTP Endpoints" lists every route setupRoutes registers with what it answers, and "Database Schema" matches schema.sql. Other sentences the code contradicts are corrected: the handlers' batch sizes, the stream client, the macOS state directory, the build command and what script/precommit checks; "Code Structure" gains the version and whois packages. TODO.md gets one line for each issue landed on next since 2026-10-03, and a current Status, Next Step and Future Steps. Model: opus-5-5
This commit is contained in:
@@ -38,21 +38,44 @@ DEBUG=routewatch ./routewatch
|
||||
|
||||
### Web Interface
|
||||
|
||||
- `GET /` - Redirects to /status
|
||||
- `GET /status` - HTML status dashboard
|
||||
- `GET /status.json` - JSON statistics
|
||||
- `GET /as/{asn}` - AS detail page (HTML)
|
||||
- `GET /prefix/{prefix}` - Prefix detail page (HTML)
|
||||
- `GET /prefixlength/{length}` - IPv4 prefixes by mask length
|
||||
- `GET /prefixlength6/{length}` - IPv6 prefixes by mask length
|
||||
- `GET /ip/{ip}` - Redirects to prefix containing the IP
|
||||
- `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, or the one a proxy names
|
||||
in `True-Client-IP`, `X-Real-IP` or `X-Forwarded-For`
|
||||
|
||||
### API v1
|
||||
|
||||
- `GET /api/v1/stats` - Detailed statistics with handler metrics
|
||||
- `GET /api/v1/ip/{ip}` - Look up AS information for an IP address
|
||||
- `GET /api/v1/as/{asn}` - Get prefixes announced by an AS
|
||||
- `GET /api/v1/prefix/{prefix}` - Get routes for a specific prefix
|
||||
- `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 (each address it resolves to
|
||||
is looked up), or a comma-separated list of these. `results` has one entry per
|
||||
address found and `errors` one per address or name that failed; the answer is
|
||||
404 when no address was found.
|
||||
- `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.
|
||||
|
||||
An origin AS of 0 (`origin_asn` in a route, `asn` in an IP lookup, the key `0`
|
||||
in a prefix's `origins`) means the route has no known origin: its AS path ends
|
||||
@@ -76,7 +99,9 @@ routewatch/
|
||||
│ ├── logger/ # Structured logging wrapper
|
||||
│ ├── metrics/ # Performance metrics tracking
|
||||
│ ├── config/ # Configuration management
|
||||
│ └── templates/ # HTML templates
|
||||
│ ├── templates/ # HTML templates
|
||||
│ ├── version/ # Build version information
|
||||
│ └── whois/ # WHOIS lookups of AS details
|
||||
└── pkg/
|
||||
└── asinfo/ # AS information lookup (public API)
|
||||
```
|
||||
@@ -95,7 +120,7 @@ routewatch/
|
||||
│ │ Streamer │───▶│ Handlers │───▶│ Database │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ RIS Live │ │ - ASHandler │ │ SQLite with │ │
|
||||
│ │ WebSocket │ │ - PeerHandler│ │ WAL mode │ │
|
||||
│ │ HTTP stream │ │ - PeerHandler│ │ WAL mode │ │
|
||||
│ │ client │ │ - PrefixHdlr │ │ │ │
|
||||
│ │ │ │ - PeeringHdlr│ │ Tables: │ │
|
||||
│ └──────────────┘ └──────────────┘ │ - asns │ │
|
||||
@@ -131,7 +156,10 @@ routewatch/
|
||||
- Parses BGP UPDATE messages from JSON stream
|
||||
- Dispatches to registered handlers based on message type
|
||||
- Each handler has its own queue with backpressure handling
|
||||
- Handlers batch writes for efficiency (25K-30K ops, 1-2s timeout)
|
||||
- Handlers batch writes for efficiency: the AS, peer and prefix handlers
|
||||
write 30,000, 10,000 and 25,000 updates at a time, or after 2, 2 and 1
|
||||
seconds; the peering handler writes the peerings it has collected every 30
|
||||
seconds
|
||||
|
||||
4. **Handler Details**
|
||||
- **ASHandler**: Tracks all ASNs seen in AS paths
|
||||
@@ -150,7 +178,8 @@ routewatch/
|
||||
|
||||
### Key Design Patterns
|
||||
|
||||
- **Batched Writes**: All database operations are batched for performance
|
||||
- **Batched Writes**: The handlers write the feed's updates to the database in
|
||||
batches for performance
|
||||
- **Backpressure**: Probabilistic message dropping when queues exceed 50%
|
||||
capacity
|
||||
- **Graceful Shutdown**: 60-second timeout, flushes all pending batches
|
||||
@@ -169,21 +198,35 @@ routewatch/
|
||||
|
||||
```sql
|
||||
-- Core tables
|
||||
asns(id, number, handle, description, first_seen, last_seen)
|
||||
prefixes_v4(prefix, first_seen)
|
||||
prefixes_v6(prefix, first_seen)
|
||||
asns(asn PRIMARY KEY, handle, description,
|
||||
as_name, org_name, org_id, address, country_code, abuse_email,
|
||||
abuse_phone, tech_email, tech_phone, rir, rir_registration_date,
|
||||
rir_last_modified, whois_raw,
|
||||
first_seen, last_seen, whois_updated_at)
|
||||
prefixes_v4(prefix PRIMARY KEY, first_seen) WITHOUT ROWID
|
||||
prefixes_v6(prefix PRIMARY KEY, first_seen) WITHOUT ROWID
|
||||
|
||||
-- Live routing tables (one per IP version)
|
||||
live_routes_v4(id, prefix, mask_length, origin_asn, peer_ip, as_path,
|
||||
next_hop, last_updated)
|
||||
live_routes_v6(id, prefix, mask_length, origin_asn, peer_ip, as_path,
|
||||
next_hop, last_updated)
|
||||
-- Live routing tables (one per IP version), one route per peer and prefix
|
||||
live_routes_v4(id INTEGER PRIMARY KEY, prefix, mask_length, origin_asn,
|
||||
peer_ip, as_path, next_hop, last_updated,
|
||||
UNIQUE(peer_ip, prefix))
|
||||
live_routes_v6(id INTEGER PRIMARY KEY, prefix, mask_length, origin_asn,
|
||||
peer_ip, as_path, next_hop, last_updated,
|
||||
UNIQUE(peer_ip, prefix))
|
||||
|
||||
-- Relationship tracking
|
||||
peerings(id, as_a, as_b, first_seen, last_seen)
|
||||
bgp_peers(id, peer_ip, peer_asn, last_message_type, last_seen)
|
||||
peerings(id PRIMARY KEY, as_a, as_b, first_seen, last_seen,
|
||||
UNIQUE(as_a, as_b))
|
||||
bgp_peers(id PRIMARY KEY, peer_ip UNIQUE, peer_asn, first_seen, last_seen,
|
||||
last_message_type)
|
||||
|
||||
-- Nothing in the daemon writes this table
|
||||
announcements(id PRIMARY KEY, prefix_id, peer_asn, origin_asn, path,
|
||||
next_hop, timestamp, is_withdrawal)
|
||||
```
|
||||
|
||||
The indexes are in `internal/database/schema.sql`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is handled via environment variables and OS-specific paths:
|
||||
@@ -201,7 +244,7 @@ non-zero exit. An empty variable counts as unset.
|
||||
|
||||
State directory (database location):
|
||||
|
||||
- macOS: `~/Library/Application Support/routewatch/`
|
||||
- macOS: `~/Library/Application Support/berlin.sneak.app.routewatch/`
|
||||
- Linux: `/var/lib/berlin.sneak.app.routewatch/` when running as root, otherwise
|
||||
`$XDG_DATA_HOME/berlin.sneak.app.routewatch/` (with `XDG_DATA_HOME` unset,
|
||||
`~/.local/share/berlin.sneak.app.routewatch/`). In the Docker image this is
|
||||
@@ -301,7 +344,7 @@ make fmt
|
||||
make lint
|
||||
|
||||
# Build
|
||||
make
|
||||
make build
|
||||
```
|
||||
|
||||
## Entrypoints
|
||||
@@ -330,8 +373,8 @@ provide:
|
||||
- `script/docker` — build the Docker image tagged via `script/projectname`
|
||||
- `script/cibuild` — CI entrypoint: runs `script/bootstrap` and `script/check`,
|
||||
then builds the image
|
||||
- `script/precommit` — pre-commit gate: `go mod tidy` + `go fmt` (must not
|
||||
change files), then `script/check`
|
||||
- `script/precommit` — pre-commit gate: `go mod tidy` (must not change `go.mod`
|
||||
or `go.sum`) and `go fmt` (writes), then `script/check`
|
||||
- `script/install-precommit` — install the git pre-commit hook that runs
|
||||
`script/precommit`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user