fix(dns): size the per-client limit for a browser, not against one - #103
Merged
Conversation
The rate limit prices reflection: a forged source address turns our answers into someone else's inbound traffic, so a client's budget is the most we will ever send a victim who never asked. At 50 qps / 100 burst it priced ordinary browsing too. A client here is an address, not a person, and behind one address is a laptop with tabs open — or a household, or an office. A single page asks A, AAAA and HTTPS for every hostname it touches, so a few tabs clear 100 queries in a burst without trying. The excess is dropped rather than refused, which is right for a spoofing victim and the worst possible answer for a browser: nothing comes back, the stub waits out its full timeout, retries, and the page finishes with subresources that never resolved. Loopback stopped paying this in #102, which covers a machine resolving through the bridge locally. It does not cover the case this deployment actually serves — a laptop pointed at dns.moshcode.sh is a remote client and still paid. 200/600 is still a bound, drawn around a browser instead of inside it. It lets through ~200 answers/sec to a spoofed victim, well under 100KB/s and no kind of amplifier: ANY is already refused, which keeps the amplification factor small enough for this to be the right trade. Both knobs stay configurable via MOSHPIT_DNS_QPS / MOSHPIT_DNS_BURST. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #102, for the case that PR did not cover.
#102 exempted loopback from the rate limiter, which fixes a machine resolving through the bridge locally. But this deployment serves
dns.moshcode.shpublicly, and a laptop pointed at it is a remote client — so it still paid a 50 qps / 100 burst budget.That budget exists to price reflection: a forged source address turns our answers into someone else's inbound traffic, so a client's budget is the most we will ever send a victim who never asked. It was never meant to price browsing, and at 50/100 it did.
A client here is an address, not a person, and behind one address is a laptop with tabs open — or a household, or an office. A single page asks A, AAAA and HTTPS for every hostname it touches, so a few tabs clear 100 queries in a burst without trying. Over-budget queries are dropped rather than refused — right for a spoofing victim, worst possible for a browser: nothing comes back, the stub waits out its full timeout, retries, and the page finishes with subresources that never resolved.
Measured against the live bridge before #102: a 150-query burst lost 20.
Why 200/600
Still a bound, just drawn around a browser instead of inside it. It lets through ~200 answers/sec to a spoofed victim — well under 100KB/s, and no kind of amplifier, since
ANYis already refused and that keeps the amplification factor small. Both knobs remain configurable viaMOSHPIT_DNS_QPS/MOSHPIT_DNS_BURST; this only changes the default, which is what themoshpit-dns.serviceunit relies on (it sets neither).191 tests pass;
tsc --noEmitclean.🤖 Generated with Claude Code