milestone 17: real deadlines, validator holes, upstream editor, trusted proxies, contract samples, badvers
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 4m55s
CI / frontend (push) Successful in 39s
CI / cross (push) Successful in 7m57s
CI / docker (push) Failing after 1h10m42s

This commit is contained in:
2026-08-07 17:55:59 +02:00
parent 9b12dbaaa0
commit c50c6d285a
57 changed files with 2926 additions and 126 deletions
+9 -4
View File
@@ -15,7 +15,9 @@
//!
//! The route is rate-limit exempt (a long-lived stream must not drain its
//! address's token bucket) but pays the per-address SSE connection cap, which
//! binds loopback too: hub slots are a fixed resource.
//! binds loopback too: hub slots are a fixed resource. The address it keys on is
//! `client_addr`, so behind a trusted proxy each remote client holds its own
//! budget rather than all of them sharing the proxy's.
const std = @import("std");
@@ -79,12 +81,15 @@ pub fn stream(
const hub = state.hub orelse
return http_util.respondError(request, .service_unavailable, "live stream unavailable");
const peer = address.NetAddress.fromIp(request.peer);
// The effective client, not the socket peer: behind a trusted proxy every
// stream would otherwise share one address's budget. Acquire and release
// read the same value, so a release can never miss the slot it took.
const client = address.NetAddress.fromIp(request.client_addr);
if (state.limiter) |limiter| {
if (!limiter.tryAcquireSse(io, std.Io.Clock.awake.now(io), peer))
if (!limiter.tryAcquireSse(io, std.Io.Clock.awake.now(io), client))
return http_util.respondError(request, .too_many_requests, "too many live streams from this address");
}
defer if (state.limiter) |limiter| limiter.releaseSse(io, peer);
defer if (state.limiter) |limiter| limiter.releaseSse(io, client);
const id = hub.subscribe(io) orelse
return http_util.respondError(request, .service_unavailable, "live stream is full");
+2
View File
@@ -213,6 +213,7 @@ const WebView = struct {
api_rate_limit_per_min: u32,
api_localhost_exempt: bool,
sse_max_connections_per_ip: u16,
trusted_proxies: []const u8,
/// Derived, not stored: the hash itself is never serialized, and the UI
/// still has to know whether a password is set.
auth_enabled: bool,
@@ -246,6 +247,7 @@ pub fn view(cfg: model.Config) View {
.api_rate_limit_per_min = cfg.web.api_rate_limit_per_min,
.api_localhost_exempt = cfg.web.api_localhost_exempt,
.sse_max_connections_per_ip = cfg.web.sse_max_connections_per_ip,
.trusted_proxies = cfg.web.trusted_proxies,
.auth_enabled = auth.authEnabled(cfg.web),
},
.doh_server = cfg.doh_server,
+4 -1
View File
@@ -88,7 +88,10 @@ fn testEntry(url: []const u8, enabled: bool) pool_mod.Entry {
}
fn testPool(entries: []pool_mod.Entry) pool_mod.Pool {
return .init(entries, .{}, .{ .raw = .fromMilliseconds(50), .clock = .awake }, 1);
return .init(entries, .{}, .{
.attempt = .{ .raw = .fromMilliseconds(50), .clock = .awake },
.total = .{ .raw = .fromMilliseconds(100), .clock = .awake },
}, 1);
}
test "every upstream is copied, counted and owned by the arena" {