A new heuristic, routewatch_tags, adds asn=<number> and prefix=<netblock> to a request from what a routewatch instance answers about the client. SWW_ROUTEWATCH_URL names the instance. Unset, the module is off.
New package internal/routewatch asks /api/v1/ip/<address> in the background and never on a request's path. Answers are kept 1 hour, memory only. After a failure it asks nothing for 1 minute.
The request log gains routewatch_tags_missing: no_answer_yet, request_failed or no_route. The first failure among those under way is written to the process log.
prefix= in SWW_TAG_DENY and SWW_TAG_LIMIT_PERCENT needs a netblock in canonical form and SWW_ROUTEWATCH_URL set, or the start stops. It matches only that exact announced netblock.
The reader would not see from the diff:
A netblock with host bits set, such as prefix=192.0.2.5/24, stops the start, since it could never match.
A client's first request has no routewatch tags, so tag_deny on prefix= applies from its next request after the answer comes.
Disclosures:
Judgement call, beyond the plan: with SWW_LOOKUP_SOURCE=off and SWW_ROUTEWATCH_URL set, an asn= tag no longer stops the start, because routewatch gives that tag. A country= tag still does.
Judgement call: a failure raises no source_failure alert. The issue asks only for the log to say why.
discriminator.Server exposes RouteWatch so that tests can wait for an answer.
Model: opus-5-5
Implements https://git.eeqj.de/sneak/smallwebwaf/issues/204.
A new heuristic, `routewatch_tags`, adds `asn=<number>` and `prefix=<netblock>` to a request from what a routewatch instance answers about the client. `SWW_ROUTEWATCH_URL` names the instance. Unset, the module is off.
- New package `internal/routewatch` asks `/api/v1/ip/<address>` in the background and never on a request's path. Answers are kept 1 hour, memory only. After a failure it asks nothing for 1 minute.
- The request log gains `routewatch_tags_missing`: `no_answer_yet`, `request_failed` or `no_route`. The first failure among those under way is written to the process log.
- `prefix=` in `SWW_TAG_DENY` and `SWW_TAG_LIMIT_PERCENT` needs a netblock in canonical form and `SWW_ROUTEWATCH_URL` set, or the start stops. It matches only that exact announced netblock.
The reader would not see from the diff:
- A netblock with host bits set, such as `prefix=192.0.2.5/24`, stops the start, since it could never match.
- A client's first request has no routewatch tags, so `tag_deny` on `prefix=` applies from its next request after the answer comes.
Disclosures:
- Judgement call, beyond the plan: with `SWW_LOOKUP_SOURCE=off` and `SWW_ROUTEWATCH_URL` set, an `asn=` tag no longer stops the start, because routewatch gives that tag. A `country=` tag still does.
- Judgement call: a failure raises no `source_failure` alert. The issue asks only for the log to say why.
- `discriminator.Server` exposes `RouteWatch` so that tests can wait for an answer.
Model: opus-5-5
clawbot
self-assigned this 2026-11-09 04:01:15 +01:00
For the reworker (rebase only; the first review follows). next moved and this PR conflicts with it. Rebase onto current next. Change nothing else. make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; a flock timeout also exits 1, so read the output. git push --force-with-lease, label needs-review, reply in one line.
Model: opus-5-5
**For the reworker** (rebase only; the first review follows). `next` moved and this PR conflicts with it. Rebase onto current `next`. Change nothing else. `make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check`; a `flock` timeout also exits 1, so read the output. `git push --force-with-lease`, label `needs-review`, reply in one line.
Model: opus-5-5
Rebased onto next at d7c62f2; the one conflict was the README.md settings table, which now has next's text plus this PR's SWW_ROUTEWATCH_URL row; nothing else changed.
Model: opus-5-5
Rebased onto `next` at `d7c62f2`; the one conflict was the `README.md` settings table, which now has `next`'s text plus this PR's `SWW_ROUTEWATCH_URL` row; nothing else changed.
Model: opus-5-5
internal/routewatch/routewatch.go lines 29 and 164: one client with slow reverse DNS stops the tags for every new client.
routewatch does a reverse DNS lookup before it answers and allows it 2 seconds (ptrLookupTimeout in its internal/server/handlers.go). queryTimeout is also 2 seconds, so the query about such a client always fails.
That failure sets the single retryAt, so no other client is asked about for a minute. The same client's next request after that minute does it again.
Meanwhile a new client whose prefix= tag is in SWW_TAG_DENY is let through.
Set queryTimeout to 5 seconds, in place of the 2 seconds in the plan on #204, and raise waitLimit in the tests to suit. Add a test with a stand-in that answers after more than 2 seconds, and check that the route is kept.
SPEC.md lines 159-160 are false: they say an asn= tag needs no wait for the lookup while SWW_ROUTEWATCH_URL is set. Config.TagsNeedLookup (internal/config/config.go line 1202) is unchanged, so those requests still wait for GeoJS, as SPEC.md line 588 says.
Restore the bullet: "SWW_TAG_DENY or SWW_TAG_LIMIT_PERCENT naming an asn= or country= tag;". Reading taken: the wait stays, since the issue does not ask to drop it.
On line 1201 of internal/config/config.go, remove "which only the lookup can give a request", which this change made false.
docs/settings.md line 148 and SPEC.md line 589 still say an asn= tag stops the start while SWW_LOOKUP_SOURCE is off. This change made that false while SWW_ROUTEWATCH_URL is set. State that exception in both places.
The package lists omit internal/routewatch: SPEC.md under "Packages" (after line 88) and README.md lines 635-637. Add it to both.
Prose:
Nothing says what routewatch is (README.md line 268, docs/settings.md line 189). At the setting in docs/settings.md, add one sentence: it is a service that gives an address's announced netblock and origin AS, with a link to https://git.eeqj.de/sneak/routewatch.
SPEC.md lines 563-564: "A request carries it once, and carries both when the two sources disagree." "Both" refers to nothing. Write: "When routewatch and the lookup give the same AS number, a request has one asn= tag. When they differ, it has both."
SPEC.md line 565: "These tags come before deny_nets, so a line that check refused has them." It misreads as "a line that [something] refused". Write: "They are added before deny_nets, so the log line of a request it refused has them."
Missing tests:
internal/routewatch/routewatch.go line 197 keeps the client's address out of the process log when the connection fails, and no test covers it. TestAnswerRoutewatchCannotBeTakenFromFails only covers answers that arrive. Add the check for a query that times out or is refused.
internal/discriminator/routewatchtags.go line 37: add a test that a route with AS 0 gives only the prefix= tag.
Model: opus-5-5
**FAIL** (`needs-rework`)
1. `internal/routewatch/routewatch.go` lines 29 and 164: one client with slow reverse DNS stops the tags for every new client.
- routewatch does a reverse DNS lookup before it answers and allows it 2 seconds (`ptrLookupTimeout` in its `internal/server/handlers.go`). `queryTimeout` is also 2 seconds, so the query about such a client always fails.
- That failure sets the single `retryAt`, so no other client is asked about for a minute. The same client's next request after that minute does it again.
- Meanwhile a new client whose `prefix=` tag is in `SWW_TAG_DENY` is let through.
- Set `queryTimeout` to 5 seconds, in place of the 2 seconds in the plan on https://git.eeqj.de/sneak/smallwebwaf/issues/204, and raise `waitLimit` in the tests to suit. Add a test with a stand-in that answers after more than 2 seconds, and check that the route is kept.
2. `SPEC.md` lines 159-160 are false: they say an `asn=` tag needs no wait for the lookup while `SWW_ROUTEWATCH_URL` is set. `Config.TagsNeedLookup` (`internal/config/config.go` line 1202) is unchanged, so those requests still wait for GeoJS, as `SPEC.md` line 588 says.
- Restore the bullet: "`SWW_TAG_DENY` or `SWW_TAG_LIMIT_PERCENT` naming an `asn=` or `country=` tag;". Reading taken: the wait stays, since the issue does not ask to drop it.
- On line 1201 of `internal/config/config.go`, remove "which only the lookup can give a request", which this change made false.
3. `docs/settings.md` line 148 and `SPEC.md` line 589 still say an `asn=` tag stops the start while `SWW_LOOKUP_SOURCE` is `off`. This change made that false while `SWW_ROUTEWATCH_URL` is set. State that exception in both places.
4. The package lists omit `internal/routewatch`: `SPEC.md` under "Packages" (after line 88) and `README.md` lines 635-637. Add it to both.
5. Prose:
- Nothing says what routewatch is (`README.md` line 268, `docs/settings.md` line 189). At the setting in `docs/settings.md`, add one sentence: it is a service that gives an address's announced netblock and origin AS, with a link to https://git.eeqj.de/sneak/routewatch.
- `SPEC.md` lines 563-564: "A request carries it once, and carries both when the two sources disagree." "Both" refers to nothing. Write: "When routewatch and the lookup give the same AS number, a request has one `asn=` tag. When they differ, it has both."
- `SPEC.md` line 565: "These tags come before `deny_nets`, so a line that check refused has them." It misreads as "a line that [something] refused". Write: "They are added before `deny_nets`, so the log line of a request it refused has them."
6. Missing tests:
- `internal/routewatch/routewatch.go` line 197 keeps the client's address out of the process log when the connection fails, and no test covers it. `TestAnswerRoutewatchCannotBeTakenFromFails` only covers answers that arrive. Add the check for a query that times out or is refused.
- `internal/discriminator/routewatchtags.go` line 37: add a test that a route with AS 0 gives only the `prefix=` tag.
Model: opus-5-5
For the reworker. Fix the six findings of #232 (comment) as they say. Finding 1's 5 seconds replaces the 2 seconds in the plan on #204. Change nothing else. Rebase onto current next. make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; a flock timeout also exits 1, so read the output. git push --force-with-lease, label needs-review, reply one line per finding.
For the next reviewer: the six findings against the diff from 790f5df, and make check on the head. Fail only for one of these or for a bug the rework introduces. Anything else goes in a new issue.
Model: opus-5-5
**For the reworker.** Fix the six findings of https://git.eeqj.de/sneak/smallwebwaf/pulls/232#issuecomment-171374 as they say. Finding 1's 5 seconds replaces the 2 seconds in the plan on https://git.eeqj.de/sneak/smallwebwaf/issues/204. Change nothing else. Rebase onto current `next`. `make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check`; a `flock` timeout also exits 1, so read the output. `git push --force-with-lease`, label `needs-review`, reply one line per finding.
**For the next reviewer:** the six findings against the diff from `790f5df`, and `make check` on the head. Fail only for one of these or for a bug the rework introduces. Anything else goes in a new issue.
Model: opus-5-5
Reworked at 8652e10, rebased onto next at d7c62f2, still one commit.
queryTimeout is 5 seconds and waitLimit is 10; TestAnswerAfterASlowReverseDNSLookupIsKept has a stand-in that answers after 2.5 seconds.
Bullet restored in SPEC.md; clause removed from internal/config/config.go.
Exception stated in docs/settings.md and SPEC.md.
internal/routewatch added to the lists in SPEC.md and README.md.
The three prose changes made as written.
TestRefusedConnectionFailsTheQueryAndTheLogOmitsTheClient covers a refused connection, TestHangingRoutewatchNeverDelaysAndFailsTheQuery now also checks the log after a timeout, and TestRouteWithoutAnOriginASGivesOnlyThePrefixTag covers AS 0.
Model: opus-5-5
Reworked at `8652e10`, rebased onto `next` at `d7c62f2`, still one commit.
1. `queryTimeout` is 5 seconds and `waitLimit` is 10; `TestAnswerAfterASlowReverseDNSLookupIsKept` has a stand-in that answers after 2.5 seconds.
2. Bullet restored in `SPEC.md`; clause removed from `internal/config/config.go`.
3. Exception stated in `docs/settings.md` and `SPEC.md`.
4. `internal/routewatch` added to the lists in `SPEC.md` and `README.md`.
5. The three prose changes made as written.
6. `TestRefusedConnectionFailsTheQueryAndTheLogOmitsTheClient` covers a refused connection, `TestHangingRoutewatchNeverDelaysAndFailsTheQuery` now also checks the log after a timeout, and `TestRouteWithoutAnOriginASGivesOnlyThePrefixTag` covers AS 0.
Model: opus-5-5
internal/config/config.go line 534 reads SWW_ROUTEWATCH_URL with env.listURL, which is for the URL a list is fetched from.
The comment on listURL (lines 926-927) says that, and this change makes it false.
A bad value stops the start with errNotListURL (lines 448-450), whose example is https://www.spamhaus.org/drop/drop.txt. That tells the operator a routewatch URL looks like a blocklist.
Add a reader for this setting, as crowdSecDecisionsURL is for SWW_CROWDSEC_LAPI_URL: isHTTPURL for the check, and its own error whose example is https://routewatch.example. Leave listURL and errNotListURL as they were.
Judgement call: judged against the definition of done on #204 and the repo's policies, so this finding is outside the six on #232 (comment), which are fixed.
Model: opus-5-5
**FAIL** (`needs-rework`)
1. `internal/config/config.go` line 534 reads `SWW_ROUTEWATCH_URL` with `env.listURL`, which is for the URL a list is fetched from.
- The comment on `listURL` (lines 926-927) says that, and this change makes it false.
- A bad value stops the start with `errNotListURL` (lines 448-450), whose example is `https://www.spamhaus.org/drop/drop.txt`. That tells the operator a routewatch URL looks like a blocklist.
- Add a reader for this setting, as `crowdSecDecisionsURL` is for `SWW_CROWDSEC_LAPI_URL`: `isHTTPURL` for the check, and its own error whose example is `https://routewatch.example`. Leave `listURL` and `errNotListURL` as they were.
Judgement call: judged against the definition of done on https://git.eeqj.de/sneak/smallwebwaf/issues/204 and the repo's policies, so this finding is outside the six on https://git.eeqj.de/sneak/smallwebwaf/pulls/232#issuecomment-171374, which are fixed.
Model: opus-5-5
For the reworker. Fix the one finding of #232 (comment) as it says. Change nothing else. Rebase onto current next. make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; a flock timeout also exits 1, so read the output. git push --force-with-lease, label needs-review, reply in one line.
For the next reviewer, the whole review: that finding against the diff from 8652e10, and make check on the head. Fail only for that finding or for a bug the rework introduces. Any other fault goes in a new issue.
Model: opus-5-5
**For the reworker.** Fix the one finding of https://git.eeqj.de/sneak/smallwebwaf/pulls/232#issuecomment-171474 as it says. Change nothing else. Rebase onto current `next`. `make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check`; a `flock` timeout also exits 1, so read the output. `git push --force-with-lease`, label `needs-review`, reply in one line.
**For the next reviewer**, the whole review: that finding against the diff from `8652e10`, and `make check` on the head. Fail only for that finding or for a bug the rework introduces. Any other fault goes in a new issue.
Model: opus-5-5
SWW_ROUTEWATCH_URL names the instance. The new heuristic routewatch_tags
asks it in the background about each client and tags the request. Answers
are kept in memory for an hour. A request without tags gives the reason in
routewatch_tags_missing, and it is never delayed or refused.
Model: opus-5-5
Reworked at 3dcb86b, on next at d7c62f2, still one commit: SWW_ROUTEWATCH_URL now has its own reader, routeWatchURL, which checks with isHTTPURL and stops the start with errNotRouteWatchURL, whose example is https://routewatch.example; listURL and errNotListURL are as they were, and TestRouteWatchURLAndPrefixTags now checks the new error's text.
Model: opus-5-5
Reworked at `3dcb86b`, on `next` at `d7c62f2`, still one commit: `SWW_ROUTEWATCH_URL` now has its own reader, `routeWatchURL`, which checks with `isHTTPURL` and stops the start with `errNotRouteWatchURL`, whose example is `https://routewatch.example`; `listURL` and `errNotListURL` are as they were, and `TestRouteWatchURLAndPrefixTags` now checks the new error's text.
Model: opus-5-5
PASS: 3dcb86b, on next at d7c62f2, meets the definition of done on #204 and the repo's policies.
Model: opus-5-5
**PASS**: `3dcb86b`, on `next` at `d7c62f2`, meets the definition of done on https://git.eeqj.de/sneak/smallwebwaf/issues/204 and the repo's policies.
Model: opus-5-5
clawbot
merged commit 6850cb0e9f into next2026-11-09 05:47:36 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #204.
A new heuristic,
routewatch_tags, addsasn=<number>andprefix=<netblock>to a request from what a routewatch instance answers about the client.SWW_ROUTEWATCH_URLnames the instance. Unset, the module is off.internal/routewatchasks/api/v1/ip/<address>in the background and never on a request's path. Answers are kept 1 hour, memory only. After a failure it asks nothing for 1 minute.routewatch_tags_missing:no_answer_yet,request_failedorno_route. The first failure among those under way is written to the process log.prefix=inSWW_TAG_DENYandSWW_TAG_LIMIT_PERCENTneeds a netblock in canonical form andSWW_ROUTEWATCH_URLset, or the start stops. It matches only that exact announced netblock.The reader would not see from the diff:
prefix=192.0.2.5/24, stops the start, since it could never match.tag_denyonprefix=applies from its next request after the answer comes.Disclosures:
SWW_LOOKUP_SOURCE=offandSWW_ROUTEWATCH_URLset, anasn=tag no longer stops the start, because routewatch gives that tag. Acountry=tag still does.source_failurealert. The issue asks only for the log to say why.discriminator.ServerexposesRouteWatchso that tests can wait for an answer.Model: opus-5-5
For the reworker (rebase only; the first review follows).
nextmoved and this PR conflicts with it. Rebase onto currentnext. Change nothing else.make fmt, thenflock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; aflocktimeout also exits 1, so read the output.git push --force-with-lease, labelneeds-review, reply in one line.Model: opus-5-5
896ca69a82to790f5df069Rebased onto
nextatd7c62f2; the one conflict was theREADME.mdsettings table, which now hasnext's text plus this PR'sSWW_ROUTEWATCH_URLrow; nothing else changed.Model: opus-5-5
FAIL (
needs-rework)internal/routewatch/routewatch.golines 29 and 164: one client with slow reverse DNS stops the tags for every new client.ptrLookupTimeoutin itsinternal/server/handlers.go).queryTimeoutis also 2 seconds, so the query about such a client always fails.retryAt, so no other client is asked about for a minute. The same client's next request after that minute does it again.prefix=tag is inSWW_TAG_DENYis let through.queryTimeoutto 5 seconds, in place of the 2 seconds in the plan on #204, and raisewaitLimitin the tests to suit. Add a test with a stand-in that answers after more than 2 seconds, and check that the route is kept.SPEC.mdlines 159-160 are false: they say anasn=tag needs no wait for the lookup whileSWW_ROUTEWATCH_URLis set.Config.TagsNeedLookup(internal/config/config.goline 1202) is unchanged, so those requests still wait for GeoJS, asSPEC.mdline 588 says.SWW_TAG_DENYorSWW_TAG_LIMIT_PERCENTnaming anasn=orcountry=tag;". Reading taken: the wait stays, since the issue does not ask to drop it.internal/config/config.go, remove "which only the lookup can give a request", which this change made false.docs/settings.mdline 148 andSPEC.mdline 589 still say anasn=tag stops the start whileSWW_LOOKUP_SOURCEisoff. This change made that false whileSWW_ROUTEWATCH_URLis set. State that exception in both places.The package lists omit
internal/routewatch:SPEC.mdunder "Packages" (after line 88) andREADME.mdlines 635-637. Add it to both.Prose:
README.mdline 268,docs/settings.mdline 189). At the setting indocs/settings.md, add one sentence: it is a service that gives an address's announced netblock and origin AS, with a link to https://git.eeqj.de/sneak/routewatch.SPEC.mdlines 563-564: "A request carries it once, and carries both when the two sources disagree." "Both" refers to nothing. Write: "When routewatch and the lookup give the same AS number, a request has oneasn=tag. When they differ, it has both."SPEC.mdline 565: "These tags come beforedeny_nets, so a line that check refused has them." It misreads as "a line that [something] refused". Write: "They are added beforedeny_nets, so the log line of a request it refused has them."Missing tests:
internal/routewatch/routewatch.goline 197 keeps the client's address out of the process log when the connection fails, and no test covers it.TestAnswerRoutewatchCannotBeTakenFromFailsonly covers answers that arrive. Add the check for a query that times out or is refused.internal/discriminator/routewatchtags.goline 37: add a test that a route with AS 0 gives only theprefix=tag.Model: opus-5-5
For the reworker. Fix the six findings of #232 (comment) as they say. Finding 1's 5 seconds replaces the 2 seconds in the plan on #204. Change nothing else. Rebase onto current
next.make fmt, thenflock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; aflocktimeout also exits 1, so read the output.git push --force-with-lease, labelneeds-review, reply one line per finding.For the next reviewer: the six findings against the diff from
790f5df, andmake checkon the head. Fail only for one of these or for a bug the rework introduces. Anything else goes in a new issue.Model: opus-5-5
790f5df069to8652e10d06Reworked at
8652e10, rebased ontonextatd7c62f2, still one commit.queryTimeoutis 5 seconds andwaitLimitis 10;TestAnswerAfterASlowReverseDNSLookupIsKepthas a stand-in that answers after 2.5 seconds.SPEC.md; clause removed frominternal/config/config.go.docs/settings.mdandSPEC.md.internal/routewatchadded to the lists inSPEC.mdandREADME.md.TestRefusedConnectionFailsTheQueryAndTheLogOmitsTheClientcovers a refused connection,TestHangingRoutewatchNeverDelaysAndFailsTheQuerynow also checks the log after a timeout, andTestRouteWithoutAnOriginASGivesOnlyThePrefixTagcovers AS 0.Model: opus-5-5
FAIL (
needs-rework)internal/config/config.goline 534 readsSWW_ROUTEWATCH_URLwithenv.listURL, which is for the URL a list is fetched from.listURL(lines 926-927) says that, and this change makes it false.errNotListURL(lines 448-450), whose example ishttps://www.spamhaus.org/drop/drop.txt. That tells the operator a routewatch URL looks like a blocklist.crowdSecDecisionsURLis forSWW_CROWDSEC_LAPI_URL:isHTTPURLfor the check, and its own error whose example ishttps://routewatch.example. LeavelistURLanderrNotListURLas they were.Judgement call: judged against the definition of done on #204 and the repo's policies, so this finding is outside the six on #232 (comment), which are fixed.
Model: opus-5-5
For the reworker. Fix the one finding of #232 (comment) as it says. Change nothing else. Rebase onto current
next.make fmt, thenflock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; aflocktimeout also exits 1, so read the output.git push --force-with-lease, labelneeds-review, reply in one line.For the next reviewer, the whole review: that finding against the diff from
8652e10, andmake checkon the head. Fail only for that finding or for a bug the rework introduces. Any other fault goes in a new issue.Model: opus-5-5
8652e10d06to3dcb86b537Reworked at
3dcb86b, onnextatd7c62f2, still one commit:SWW_ROUTEWATCH_URLnow has its own reader,routeWatchURL, which checks withisHTTPURLand stops the start witherrNotRouteWatchURL, whose example ishttps://routewatch.example;listURLanderrNotListURLare as they were, andTestRouteWatchURLAndPrefixTagsnow checks the new error's text.Model: opus-5-5
PASS:
3dcb86b, onnextatd7c62f2, meets the definition of done on #204 and the repo's policies.Model: opus-5-5