Commit Graph
52 Commits
Author SHA1 Message Date
clawbot a671b8a0b0 Give every IP lookup result the same fields (closes #200)
check / check (push) Canceled after 0s
Every result of /ip/, /ip/{addr} and /api/v1/ip/{ip} now has every
field. A field without a value is null instead of left out, so an
address with no live route has all its route and AS fields set to null.
ptr is an empty list when there are no PTR names.

Two new fields, num_origins and origin_asns, give how many origin ASes
peers carry the netblock with, and which. The routing table's prefix
summary now lists them. asn still names the origin that most peers
carry.

first_seen is null, no longer the zero time, when the database has not
stored the netblock yet.

Model: opus-5-5
2026-10-27 19:54:02 +01:00
clawbot 281d162e27 Show figures of the routing table on the home page (closes #154)
check / check (push) Canceled after 0s
The home page gets cards for the newest and oldest kept route, the newest
and oldest updated AS, the AS with the most prefixes, the ASes with a
single prefix and fifteen well-known networks. Two more cards count the
bogon announcements and the prefixes with several origin ASes. They link
to the new pages /bogons and /multiple-origins, which list up to 1,000
entries each.

The new package internal/bogon tells reserved prefixes and AS numbers,
from the IANA special-purpose and address space registries. Table.Overview
walks the table and lets the read lock go every 10,000 prefixes. The
server works the cards out once a minute and serves that copy. A new index
on asns(last_seen) finds the newest and oldest updated AS.

Model: opus-5-5
2026-10-27 18:27:01 +01:00
clawbot 1c65656b58 Remove the per-peer route tables (closes #122)
check / check (push) Canceled after 0s
The routing table in memory now holds every route, so the two per-peer route
tables go, with every write, delete, count and read that served them. The
prefix handler applies each update to the table, writes the table's changes
every second, and stores each new prefix with its first-seen time. The peer
delete, its retry on the next flush and the updates held for it go too. The
types and errors of the server's answers move to internal/server.

The image's memory settings fit a full table of about 1 GB: GOMEMLIMIT of
2.5 GiB, a 512 MiB page cache for the writing connection, and SQLite heap
limits of 768 MiB and 1 GiB. The README describes the new table, its
database copy and the memory budget.

Model: opus-5-5
2026-10-27 17:22:32 +01:00
clawbot 506e289a63 Read every route from the routing table in memory (part of #122)
check / check (push) Canceled after 0s
The IP lookups, the AS and prefix pages and their API answers, the prefix-length pages and the stats read routes only from the routing table the prefix handler keeps. fx reads the table at startup and gives it to both. AS names, WHOIS details and first-seen times still come from the database.

The IP lookup names the origin AS most peers carry, with the time of the newest kept route. The prefix answers list peers whose routes are not kept without a path or time. The stats gain origins_without_kept_route. They no longer read route times from the per-peer tables, so the two helpers that did are gone.

Model: opus-5-5
2026-10-27 16:21:59 +01:00
clawbot 7e13f28aa0 Report which public blocklists list an address in the IP lookup (closes #158)
check / check (push) Canceled after 0s
A new package, internal/blocklist, downloads seven public lists on a
schedule: DShield, Feodo Tracker, FireHOL abusers and level 3, Spamhaus
DROP for IPv4 and IPv6, and the Tor exit list. It keeps the last good
copy of each in memory and on disk. A download sends back the
Last-Modified and ETag the publisher gave for the copy, also kept on
disk.

Every IP lookup result carries blocklists. An address with no live route
now gets a result too, without route fields, so the lookup no longer
answers 404. A successful answer carries Cache-Control, private for the
caller's own address. The address sets come from go4.org/netipx.

Model: opus-5-5
2026-10-27 06:35:17 +01:00
clawbot ad67e3ca1e Show the IP information API on the home page (closes #156)
check / check (push) Canceled after 0s
The home page gets an IP information API card. It shows the curl
command that looks up the visitor's own address at /ip/{addr}. The page
then makes that same request and shows the JSON answer, pretty-printed.
An error answer, such as a 404 for an address with no route, is shown
the same way. A request that fails shows an error line.

The server gives the page the visitor's address, found as /ip/ finds
it, so a trusted proxy's X-Forwarded-For is honoured. The command takes
its origin from the browser, so it names the host the visitor used.

Model: opus-5-5
2026-10-27 05:27:23 +01:00
clawbot 2375480e2e List an AS's prefixes by mask length, shortest first (closes #153)
check / check (push) Canceled after 0s
The AS page sorted its IPv4 and IPv6 prefix lists by network address,
then by mask length. It now sorts them by mask length, shortest first,
and by network address within one mask length, so a /16 comes before a
/24. The sort function is renamed from sortRoutesByNetwork to
sortRoutesByMaskLength to say so.

The handler of /api/v1/as/{asn} does not sort its lists, so it is
unchanged.

Model: opus-5-5
2026-10-27 04:37:17 +01:00
clawbot 355cd37496 Show route churn as a whole count per minute (closes #160)
check / check (push) Canceled after 0s
The status page showed route churn per second as a one-minute moving
average, so a quiet feed gave a fraction such as 0.1. The average was
correct.

The metrics tracker now also counts prefix announcements and
withdrawals, from all peers, in each clock minute. /api/v1/stats
reports the count for the last full minute as route_churn_per_min in
stream, in place of route_churn_per_sec. The status page shows it as
"Route Churn/min".

The status and home pages round messages and updates per second to
whole numbers.

Model: opus-5-5
2026-10-27 00:14:24 +01:00
clawbot 99c7efc156 Make the home page's AS name search work (closes #151)
check / check (push) Canceled after 0s
The AS Name Search card on the home page said the search was coming soon.
It now asks the new GET /api/v1/search/as?q=<text> and lists the matches
as links to their AS pages. asinfo.Search ignores case, returns the first
matches up to a limit in AS number order, and counts all matches. An
empty q answers 400.

When the database has no record of an AS, its page and /api/v1/as/{asn}
take its handle and description from asinfo and still list its prefixes.
They answer 404 only for an AS with no record, no asinfo entry and no
prefix. AS 0 means no known origin: it always answers 404, and the search
never lists it.

Model: opus-5-5
2026-10-26 22:51:12 +01:00
clawbot c7f2e5db1d Bound the IP lookup's address list and name lookups (closes #110)
check / check (push) Canceled after 0s
An IP lookup took any number of addresses and host names, gave each PTR
lookup until the request's 30-second limit, and looked up and named
every address a host name resolved to. It now answers 400 for more than
10 entries, or more than 10 addresses once the names are resolved,
before reading the database. Each PTR lookup gets 2 seconds; one that
runs out leaves the address's result without PTR names. Of a host
name's addresses only public ones are kept: global unicast and not
private. A name with none is reported as having no public address. The
tests give the server a fake resolver.

Model: opus-5-5
2026-10-20 02:23:49 +02:00
clawbot 237f73fc49 Answer 500, not 404, when the IP lookup cannot read the database (closes #109)
check / check (push) Canceled after 0s
A failed database read for an address was listed in the answer's errors
as if the address had no route, so the lookup answered 404, or 200 next
to a routed address. Only a missing route or an invalid address is
listed now; any other read error is logged and answers 500 with the JSON
error body, as the AS and prefix JSON routes do.

Model: opus-5-5
2026-10-20 01:49:34 +02:00
clawbot 89bdda18a5 Add the HTTP hardening the policy requires before 1.0 (closes #108)
check / check (push) Canceled after 0s
Every answer carries the six security headers REPO_POLICIES.md lists.
The Content-Security-Policy allows inline content only with a nonce made
for that answer; the pages put it on their style and script blocks and
drop their four style attributes.

The request log and /ip/ take a forwarded address only from a proxy
listed in TRUSTED_PROXIES, which stops the start when set but invalid:
the last X-Forwarded-For entry that is not a trusted proxy. X-Real-IP
and True-Client-IP are no longer read.

The JSON routes and the health check send fixed messages in place of
database, parser and resolver errors, and log the detail.

Model: opus-5-5
2026-10-20 01:36:18 +02:00
clawbot b89987e356 Re-vendor the shared files from sneak/prompts at 0653579 (closes #52)
check / check (push) Canceled after 0s
Takes .golangci.yml, .dockerignore, .gitignore, .editorconfig, the
workflow and REPO_POLICIES.md from sneak/prompts 0653579, with this
repository's own entries after the shared content. Lint (golangci-lint
v2.14.0) and test are Dockerfile phases that script/lint and script/test
build alone with a new CACHEBUST value, keeping Go's and golangci-lint's
caches; the image build depends on both. make fmt and make fmt-check also
format the markdown with prettier, pinned in yarn.lock. The agent
guidance moves to AGENTS.md. The Go code is fixed for what the new
configuration finds, and the AS and prefix detail reads now return the
rows' error after their loops.

Model: opus-5-5
2026-10-19 12:10:11 +02:00
clawbot ae6d885a5a Answer 504, not 408, when a request outlasts its time limit (closes #70)
check / check (push) Canceled after 0s
A request past the server's 30-second limit answered 408, which tells the
client it was too slow sending its request, and /api/v1/stats answered 500
when its 4-second read ran out. The limit is now chi's middleware.Timeout,
which answers 504 and never writes from a second goroutine while the
handler is writing.

Right after each database read or host name lookup, whatever came back, a
handler asks deadlinePassed whether its time limit has passed and, if so,
answers 504, with the JSON error body on JSON routes: a cut-off read can
return part of its rows without an error. The health check is unchanged.

Model: opus-5-5
2026-10-19 10:02:23 +02:00
clawbot ad4c3d91f1 Show the prefix page's origin names from the prefix query (closes #67)
check / check (push) Canceled after 0s
The prefix page looked up each origin AS's details only to show its handle and description, and that lookup reads every live route the AS originates. `GetPrefixDetailsContext` already selected the handle and description and dropped them; it now returns them keyed by AS number, and the page uses them. The origin list now also shows the description after the handle, which it never rendered. The JSON prefix answer is unchanged. The test serves the page from a store whose AS lookup always fails.

Model: opus-5-5
2026-10-14 19:08:58 +02:00
clawbot 616f13071f Parse the prefix on the prefix pages before looking it up (closes #71)
check / check (push) Canceled after 0s
`/prefix/{prefix}/{len}` and `/api/v1/prefix/{prefix}/{len}` passed the URL text to the lookup as typed: a prefix that does not parse answered 500, and a valid prefix written in another form (`2001:DB8:0:0::/32`, `192.0.2.1/24`) answered 404. Both now parse the prefix with `net/netip`, mask it and look up the form the tables store; one that does not parse answers 400. The answer shows the stored form of the prefix. The test drives both pages through the router against a real database.

Model: opus-5-5
2026-10-14 17:24:02 +02:00
clawbot df9e23d503 Serve /api/v1/stats counts from realtime in-memory counters (closes #27)
check / check (push) Successful in 2m36s
Realtime in-memory counters seeded at startup and adjusted on every insert, update and delete; no periodic recompute. Independent review passed: #29 (comment)

model: claude-opus-4-8 (implementation and review); merged by claude-fable-5
2026-09-22 09:41:16 +02:00
clawbot 63d62b7bc1 Fix two goroutine leaks: stats handlers on timeout, streamer tickers on reconnect (closes #12)
check / check (push) Failing after 1s
The stats handlers ran the database query in a goroutine that sent on an
unbuffered channel. When the 4s request timeout won, nothing received and
the goroutine blocked forever; the status page polls every 2s, so once the
query exceeds the timeout every poll leaked one goroutine. Give both
channels capacity 1 so the send always completes.

The streamer started two ticker goroutines per connection that exited only
with the streamer's lifetime context, leaking two on every reconnect. Scope
them to a per-connection context cancelled when the stream call returns.

Tests force the stats timeout repeatedly and drive many reconnects, then
assert the goroutine count settles back to its starting value. The streamer
gains an internal endpoint field so a test can point it at a local server.

Model: opus-4-8
2026-09-21 16:29:32 +02:00
sneak 583c16a65c Adopt scripts-to-rule-them-all: script/ entrypoints, Makefile shims 2026-07-07 01:53:25 +02:00
sneak a163449a28 Improve request logging and make health check lightweight
- Log slow requests (>1s) at WARNING level with slow=true flag
- Log request timeouts at WARNING level in TimeoutMiddleware
- Replace heavy GetStatsContext with lightweight Ping in health check
- Add Ping method to database interface (SELECT 1)
2026-01-01 06:06:20 -08:00
sneak aebdd1b23e Add oldest and newest route timestamps to status page
Display oldest and newest route timestamps in the Routing Table card
on the /status page. Timestamps are shown as relative times (e.g.,
"5m ago", "2h 30m ago").

Changes:
- Add OldestRoute and NewestRoute fields to database.Stats
- Query MIN/MAX of last_updated across live_routes_v4 and v6 tables
- Include timestamps in both /status.json and /api/v1/stats responses
- Add formatRelativeTime JavaScript function for display
2025-12-31 15:47:57 -08:00
sneak 4284e923a6 Add navbar and home page with search functionality
- Create new home page (/) with overview stats, ASN lookup,
  AS name search, and IP address lookup with JSON display
- Add responsive navbar to all pages with app branding
- Navbar shows "routewatch by @sneak" with link to author
- Status page accessible via navbar link
- Remove redirect from / to /status, serve home page instead
2025-12-31 14:56:02 -08:00
sneak 45810e3fc8 Fix prefix URL routing for encoded CIDR notation
Change route from wildcard /prefix/* to explicit /prefix/{prefix}/{len}
to properly handle URL-encoded IPv6 addresses with CIDR notation.

- Separate prefix and length into individual path parameters
- Add prefixURL template function for generating correct links
- Remove url.QueryUnescape from handlers (chi handles decoding)
2026-01-01 05:37:37 +07:00
sneak c116b035bd Add status page enhancements with new metrics and footer
- Add GC statistics (run count, total/last pause, heap usage)
- Add BGP peer count tracking from RIS Live OPEN/NOTIFICATION messages
- Add route churn rate metric (announcements + withdrawals per second)
- Add announcement and withdrawal counters
- Add footer with attribution, license, and git revision
- Embed git revision at build time via ldflags
- Update HTML template to display all new metrics
2025-12-30 14:50:54 +07:00
sneak 1115954827 Fix prefix URL routing to handle CIDR notation with slashes
- Use wildcard route pattern for /prefix/* endpoints
- Extract prefix parameter using chi.URLParam(r, "*")
- Fixes 400 error when accessing /prefix/x.x.x.x/32 directly
2025-12-30 14:41:57 +07:00
sneak 9043cf9bc0 Add connection duration and reconnect count to status page
- Track reconnection count in metrics tracker
- Display connection duration under Stream Statistics
- Display reconnect count since app startup
- Update both JSON API and HTML status page
2025-12-30 14:33:37 +07:00
sneak da6d605e4d Add production hardening: health check, streamer panic fix, db maintenance
- Add health check endpoint at /.well-known/healthcheck.json that
  verifies database and RIS Live connectivity, returns 200/503

- Fix panic in streamer when encountering unknown RIS message types
  by logging a warning and continuing instead of crashing

- Add DBMaintainer for periodic database maintenance:
  - VACUUM every 6 hours to reclaim space
  - ANALYZE every hour to update query statistics
  - Graceful shutdown support

- Add Vacuum() and Analyze() methods to database interface
2025-12-29 15:55:54 +07:00
sneak d2041a5a55 Add WHOIS stats to status page with adaptive fetcher improvements
- Add WHOIS Fetcher card showing fresh/stale/never-fetched ASN counts
- Display hourly success/error counts and current fetch interval
- Increase max WHOIS rate to 1/sec (down from 10 sec minimum)
- Select random stale ASN instead of oldest for better distribution
- Add index on whois_updated_at for query performance
- Track success/error timestamps for hourly stats
- Add GetWHOISStats database method for freshness statistics
2025-12-27 16:20:09 +07:00
sneak f8b7d3b773 Unify IP lookup response structure and add PTR lookups
- Always return consistent JSON structure with query and results array
- Add PTR field to IPInfo for reverse DNS records
- Support comma-separated IPs and hostnames in single query
- Do PTR lookup for all IPs (direct, resolved from hostname, or listed)
- Remove trailing dots from PTR records
2025-12-27 15:56:10 +07:00
sneak cb75409647 Add hostname resolution support to IP lookup endpoint
- Accept hostnames in addition to IP addresses for /ip endpoints
- Resolve A and AAAA records for hostnames
- Return list of results with info for each resolved IP
- Include hostname in response when resolving hostnames
- Report per-IP errors while still returning successful lookups
2025-12-27 15:53:14 +07:00
sneak 3b159454eb Add IP information API with background WHOIS fetcher
- Add /ip and /ip/{addr} JSON endpoints returning comprehensive IP info
- Include ASN, netblock, country code, org name, abuse contact, RIR data
- Extend ASN schema with WHOIS fields (country, org, abuse contact, etc)
- Create background WHOIS fetcher for rate-limited ASN info updates
- Store raw WHOIS responses for debugging and data preservation
- Queue on-demand WHOIS lookups when stale data is requested
- Refactor handleIPInfo to serve all IP endpoints consistently
2025-12-27 15:47:35 +07:00
sneak ab392d874c Track wire bytes separately from decompressed stream bytes
The stream stats were showing decompressed data sizes, not actual wire
bandwidth. This change adds wire byte tracking by disabling automatic
gzip decompression in the HTTP client and wrapping the response body
with a counting reader before decompression. Both wire (compressed) and
decompressed bytes are now tracked and exposed in the API responses.
2025-12-27 12:56:57 +07:00
sneak c292fef0ac Add comprehensive godoc documentation to handler.go
Expand documentation comments for SimpleHandler type and its methods
to better explain their purpose, parameters, and behavior.
2025-12-27 12:24:36 +07:00
sneak e1d0ab5ea6 Add detailed godoc documentation to CLIEntry function
Expand the documentation comment for CLIEntry to provide more context
about what the function does, including its use of the fx dependency
injection framework, signal handling, and blocking behavior.
2025-12-27 12:24:22 +07:00
sneak 2f96141e48 Fix IPv6 prefix length links to use separate /prefixlength6/<len> route
The prefix length links for IPv6 prefixes were incorrectly pointing to
/prefixlength/<len> which would show IPv4 prefixes. Added a new route
/prefixlength6/<len> specifically for IPv6 prefixes and updated the
template to use the correct URL based on whether displaying IPv4 or IPv6
prefix distributions.

Also refactored handlePrefixLength to explicitly handle only IPv4 prefixes
and created handlePrefixLength6 for IPv6 prefixes, removing the ambiguous
auto-detection based on mask length value.
2025-08-09 11:37:14 +02:00
sneak 1fded42651 Quadruple all HTTP timeouts to prevent timeout errors
- HTTP request timeout: 2s -> 8s
- Stats collection context timeout: 1s -> 4s
- HTTP read header timeout: 10s -> 40s

This should prevent timeout errors when the database is under load
or when complex queries take longer than expected (slow query
threshold is 50ms).
2025-07-29 04:18:07 +02:00
sneak 7aec01c499 Add AS peers display to AS detail page
- Added GetASPeers method to database to fetch all peering relationships
- Updated AS detail handler to fetch and pass peers to template
- Added peers section to AS detail page showing all peer ASNs with their info
- Added peer count to the info cards at the top of the page
- Shows handle, description, and first/last seen dates for each peer
2025-07-29 03:58:09 +02:00
sneak 23127b86e9 Add queue high water marks to handler statistics
- Track the maximum queue length seen for each handler
- Display high water marks on the status page with percentage
- Helps identify which handlers are experiencing queue pressure
2025-07-29 02:46:53 +02:00
sneak c9da20e630 Major schema refactoring: simplify ASN and prefix tracking
- Remove UUID primary keys from ASNs table, use ASN number as primary key
- Update announcements table to reference ASN numbers directly
- Rename asns.number column to asns.asn for consistency
- Add prefix tracking to PrefixHandler to populate prefixes_v4/v6 tables
- Add UpdatePrefixesBatch method for efficient batch updates
- Update all database methods and models to use new schema
- Fix all references in code to use ASN field instead of Number
- Update test mocks to match new interfaces
2025-07-28 22:58:55 +02:00
sneak fc32090483 Fix JavaScript UI and complete database table migration
- Update status page JavaScript to reset all fields to '-' on error
- Fix status page to not show 'Connected' when API returns error
- Update remaining database methods to use new live_routes_v4/v6 tables
- Fix GetStatsContext to count routes from both IPv4 and IPv6 tables
- Fix UpsertLiveRoute to insert into correct table based on IP version
- Fix DeleteLiveRoute to determine table from prefix IP version
2025-07-28 22:39:01 +02:00
sneak 9518519208 Fix prefix links on prefix length page with URL encoding
- Add urlEncode template function to properly encode prefix URLs
- Move prefix_length.html to embedded templates with function map
- Prevents broken links for prefixes containing slashes
2025-07-28 22:00:27 +02:00
sneak 7d39bd18bc Fix concurrent map write panic in timeout middleware
- Add thread-safe header wrapper in timeoutWriter
- Check context cancellation before writing responses in handlers
- Protect header access after timeout with mutex
- Prevents race condition when requests timeout while handlers are still running
2025-07-28 21:54:58 +02:00
sneak e0a4c8642e Add context cancellation support to database operations
- Add context-aware versions of all read operations in the database
- Update handlers to use context from HTTP requests
- Allows database queries to be cancelled when HTTP requests timeout
- Prevents database operations from continuing after client disconnects
2025-07-28 19:27:55 +02:00
sneak 0196251906 Fix race condition crash in timeout middleware
- Remove duplicate http.Error call when context times out
- The timeout middleware already handles writing the response
- Prevents "concurrent write to websocket connection" panic
2025-07-28 19:07:30 +02:00
sneak ba13c76c53 Fix prefix distribution bug and add prefix length pages
- Fix GetPrefixDistribution to count unique prefixes using COUNT(DISTINCT prefix) instead of COUNT(*)
- Add /prefixlength/<length> route showing random sample of 500 prefixes
- Make prefix counts on status page clickable links to prefix length pages
- Add GetRandomPrefixesByLength database method
- Create prefix_length.html template with sortable table
- Show prefix age and origin AS with descriptions
2025-07-28 18:42:38 +02:00
sneak 1dcde74a90 Update AS path display to show handles with clickable links
- Change AS path from descriptions to handles (short names)
- Make each AS in the path a clickable link to /as/<asn>
- Add font-weight to AS links in path for better visibility
- Prevent word wrapping on all table columns except AS path
- Remove unused maxASDescriptionLength constant
2025-07-28 18:31:35 +02:00
sneak dc3ceb8d94 Show AS descriptions in AS path on prefix detail page
- Display AS descriptions alongside AS numbers in format: Description (ASN)
- Truncate descriptions longer than 20 characters with ellipsis
- Increase container max width to 1600px for better display
- Enable word wrapping for AS path cells to handle long paths
- Update mobile responsive styles for AS path display
2025-07-28 18:25:26 +02:00
sneak 9b649c98c9 Fix AS detail view and add prefix sorting
- Fix GetASDetails to properly handle timestamp from MAX(last_updated)
- Parse timestamp string from SQLite aggregate function result
- Add natural sorting of prefixes by IP address in AS detail view
- Sort IPv4 and IPv6 prefixes separately by network address
- Remove SQL ORDER BY since we're sorting in Go
- This fixes the issue where AS detail pages showed no prefixes
2025-07-28 04:42:10 +02:00
sneak df31cf880a Fix prefix URL routing by using URL encoding
- Replace slash-to-dash conversion with proper URL encoding
- Update handlePrefixDetail and handlePrefixDetailJSON to URL decode prefix parameter
- Update handleIPRedirect to URL encode the prefix in the redirect
- Add urlEncode template function for use in templates
- Update AS detail template to URL encode prefix links
- This properly handles the slash in CIDR notation (e.g., /prefix/192.168.1.0%2F24)
2025-07-28 04:34:34 +02:00
sneak af9ff258b1 Add /ip/<ip> route that redirects to prefix detail page
- Implement handleIPRedirect handler that looks up the prefix containing an IP
- Add /ip/{ip} route to routes.go
- Reuse existing GetASInfoForIP database method which returns prefix info
- Redirect to /prefix/<prefix> page with HTTP 303 See Other status
- Handle invalid IPs (400) and IPs with no route (404)
2025-07-28 04:31:22 +02:00