Gives every browser on the machine ad and tracker blocking, including WebKitGTK ones like surf that have no extension mechanism and so cannot run uBlock Origin. 80,002 rules from the StevenBlack list load in 82ms and sit in 9MB resident. A proxy receives CONNECT doubleclick.net:443 before any TLS handshake, so a blocked host is refused without decrypting anything. Ads and trackers are third-party hosts, which is why host-level refusal captures nearly all of the weight while leaving traffic sealed: no certificate authority in the trust store, no CA private key on disk, and no visibility into a bank session this program merely relays. What that cannot do is cosmetic filtering and first-party ads, which need TLS interception. That is omitted deliberately, and the README records the reasoning and where the seam would be, rather than leaving it looking like an oversight. CONNECT to a blocked host 403, refused before the handshake CONNECT to anything else tunnelled bytes, untouched plain HTTP to a blocked host 204, so a beacon looks empty not failed plain HTTP otherwise forwarded and relayed Matching is by domain suffix, so a rule for doubleclick.net covers stats.g.doubleclick.net; lookups walk the labels of the requested host rather than the list. Hosts files, bare domain lists and the ||domain^ subset of Adblock syntax are accepted, while rules needing response inspection are skipped rather than half-applied. Allow rules win at any depth, and IP addresses are never blocked, since hosts files are full of them as addresses. Lists cache for a week and fall back to a stale copy when a refresh fails. Connections are capped at 96 with a Drop guard releasing the slot even on panic, and only the loopback interface is bound.
117 lines
5.3 KiB
Markdown
117 lines
5.3 KiB
Markdown
# furst
|
||
|
||
A toolset for browsing the modern web on hardware that cannot run a modern
|
||
browser. Built for a Core2 Duo with 4GB of RAM and a minimal Arch install,
|
||
where Firefox is the bottleneck.
|
||
|
||
## The idea
|
||
|
||
A news page is 2–5MB across 80+ requests with 1–3MB of JavaScript to parse and
|
||
JIT. The same article, extracted, is ~20KB. Choosing a lighter browser engine
|
||
buys 2–3×. **Not loading the payload at all buys considerably more.**
|
||
|
||
So the goal is not a faster browser. It is to keep a browser engine out of the
|
||
loop wherever possible, and to make the cases where you still need one rare and
|
||
deliberate.
|
||
|
||
```
|
||
link click
|
||
│
|
||
┌────▼─────┐ video ──────────────► mpv (H.264 forced)
|
||
│ furst │ pdf / image ────────► zathura / nsxiv
|
||
│ router │ mailto / magnet ────► xdg-email / transmission
|
||
└────┬─────┘ a short "heavy" list ► firefox ──┐
|
||
│ │
|
||
▼ everything else │
|
||
┌──────────────┐ ┌──────────────┐ │
|
||
│ furst-serve │───────►│ furst-read │ │
|
||
│ local reader │ │ library │ │
|
||
└──────┬───────┘ └──────────────┘ │
|
||
│ minimal HTML, no JS/CSS/fonts │
|
||
▼ ▼
|
||
a light browser (surf, luakit, …) ──────► furst-proxy
|
||
ad and tracker hosts refused
|
||
before the TLS handshake
|
||
```
|
||
|
||
## The pieces
|
||
|
||
| | |
|
||
|---|---|
|
||
| [`furst`](src/) | the router. Sits where the default browser used to and dispatches each URL to the cheapest tool that can handle it. Two dependencies, 541K binary. |
|
||
| [`furst-read`](furst-read/README.md) | fetch, extract the article, render it as a self-contained document. Library and CLI. |
|
||
| [`furst-serve`](furst-serve/README.md) | the local reader. Rewrites in-page links so browsing stays in reader mode, caches pages, and handles what extraction cannot: search, feeds, listings, comment threads. |
|
||
| [`furst-proxy`](furst-proxy/README.md) | filtering proxy for the times you do use a browser. Refuses ad and tracker hosts at `CONNECT`, before any TLS handshake, so nothing is decrypted. 80k rules in 9MB. |
|
||
|
||
## Install
|
||
|
||
```sh
|
||
cargo build --release --workspace
|
||
install -Dm755 target/release/furst target/release/furst-read \
|
||
target/release/furst-serve -t ~/.local/bin/
|
||
|
||
furst --init # rules tuned for low-end hardware
|
||
furst --install # become the system default browser
|
||
furst-serve --init # home page links and feeds
|
||
furst-proxy --init && furst-proxy --update # 80k blocklist rules
|
||
|
||
sudo pacman -S mpv yt-dlp zathura zathura-pdf-mupdf nsxiv surf
|
||
```
|
||
|
||
To filter what a browser still loads directly, run `furst-proxy` and
|
||
`eval "$(furst-proxy --env)"`. This is what gives `surf` ad blocking, since
|
||
WebKitGTK has no extension mechanism.
|
||
|
||
Then every link click in every application goes through the router, and most
|
||
of them never reach a browser engine.
|
||
|
||
## Measured
|
||
|
||
| page | raw HTML | scripts | stylesheets | through furst |
|
||
|---|---|---|---|---|
|
||
| wiki.archlinux.org/title/Zram | 64K | 4 | 2 | 16K |
|
||
| en.wikipedia.org/wiki/Core_2 | 207K | 5 | 2 | 47K |
|
||
| theverge.com | 770K | 55 | 71 | 2.4K |
|
||
| lwn.net headlines (feed) | — | — | — | 11K, 15 entries |
|
||
|
||
The HTML column understates it. The script and stylesheet columns are the real
|
||
story: those become zero, along with every webfont and tracker, which is where
|
||
an old CPU actually spends its time. A cached revisit is ~0.4ms.
|
||
|
||
## Two details that matter on this hardware
|
||
|
||
**Video is forced to H.264.** A Core2 handles 720p `avc1` in software but
|
||
stalls on VP9 and AV1, which is what YouTube serves by default. The format
|
||
string in the `video` rule is doing more work than the resolution cap, and
|
||
playing it in mpv skips the browser entirely.
|
||
|
||
**Blocking needs no certificate authority.** A proxy sees `CONNECT
|
||
doubleclick.net:443` before any TLS handshake, so it can refuse the connection
|
||
without decrypting anything. Ads and trackers are third-party hosts, so that
|
||
captures nearly all of the weight while leaving your traffic sealed.
|
||
|
||
**The reader is the catch-all, not a special case.** Rules fall through when
|
||
their command is missing, so the last rule can be "send it to the reader" while
|
||
`default` stays a plain browser. Firefox is left for a short list of sites that
|
||
genuinely need it — and every reader page has a **browser** link for the rest.
|
||
|
||
## Limits
|
||
|
||
- Anything that renders client-side arrives empty. There is no JavaScript here;
|
||
that is the point, not an oversight.
|
||
- Free search endpoints rate-limit repeat visitors. The reader detects a
|
||
challenge page and tells you which engine refused, rather than showing an
|
||
empty list. See [furst-serve](furst-serve/README.md#on-search).
|
||
- Paywalls and DRM are not defeated, only rendered plainly or handed off.
|
||
|
||
## Development
|
||
|
||
```sh
|
||
cargo test --workspace # note: --workspace, the root package is just furst
|
||
cargo clippy --workspace --all-targets
|
||
```
|
||
|
||
## License
|
||
|
||
MIT
|