Access control
Peer ACL, accept rate limits, and quota trackers in hopf-core. These sit on TcpListenerConfig (and related accept paths) so unwanted peers never reach ProtocolHandler::connected. Authentication (identity) is separate — see Auth.
Contents
Architecture
accept()
│
├─ PeerAcl::allows(peer)? else close + TelemetryHook::on_error
├─ AcceptRateLimit::…? else close + on_error
└─ ProtocolHandler::connected
Deny wins over allow. Empty allow list means allow-all (subject to deny). IPv4 and IPv6 CIDR supported via IpNet.
CIDR allow / deny (PeerAcl)
use hopf_core::{IpNet, PeerAcl, TcpListenerConfig};
let acl = PeerAcl {
allow: vec![], // empty allow = allow all (unless denied)
deny: vec![IpNet::parse("10.0.0.0/8").unwrap()],
};
let config = TcpListenerConfig::new(addr, factory).with_acl(acl);| Property | Type | Default | Notes |
|---|---|---|---|
allow |
Vec<IpNet> |
empty | If non-empty, peer must match ≥1 |
deny |
Vec<IpNet> |
empty | Any match → reject (deny wins) |
| Method | Notes |
|---|---|
PeerAcl::open() / Default |
Allow all |
PeerAcl::allows(peer: SocketAddr) -> bool |
Evaluate |
IpNet::parse("addr/prefix") |
IPv4 / IPv6 |
Rejection closes the accepted socket before ProtocolHandler::connected.
Accept rate limits
Token-bucket style window counters:
use std::time::Duration;
use hopf_core::AcceptRateLimit;
let config = TcpListenerConfig::new(addr, factory).with_rate_limit(
AcceptRateLimit::new(
/* per_source */ 100,
Duration::from_secs(1),
/* global */ 0, // 0 = unlimited on that axis
),
);AcceptRateLimit
| Property | Type | Default / meaning | Notes |
|---|---|---|---|
per_source |
u32 |
caller | Max accepts per window from one source IP |
window |
Duration |
caller | Window length |
global |
u32 |
caller | Max accepts per window overall; 0 = unlimited |
AcceptRateLimit::new(per_source, window, global). Axis value 0 disables that limit.
Quotas
Application / protocol policy skeleton — not auto-enforced on every codec datapath yet.
| Type | Role |
|---|---|
QuotaTracker |
Trait: add_inbound / add_outbound / add_message |
QuotaVerdict |
Allow / deny outcome |
CounterQuota |
Atomic counters with caps |
UnlimitedQuota |
Always allow |
CounterQuota
| Constructor | Notes |
|---|---|
CounterQuota::new(max_in, max_out, max_messages) -> Arc<Self> |
0 disables that axis |
Protocols may call the tracker as policy when ready; do not assume Hopf closes sockets for you on quota alone today.
Listener configuration
Relevant TcpListenerConfig fields / builders (see also Runtime options):
| Property | Type | Default | Notes |
|---|---|---|---|
acl |
PeerAcl |
open (allow all) | .with_acl |
rate_limit |
Option<AcceptRateLimit> |
None |
.with_rate_limit |
max_net_in |
usize |
1 MiB (DEFAULT_MAX_NET_IN) |
Buffer cap → close |
max_net_out |
usize |
4 MiB (DEFAULT_MAX_NET_OUT) |
Buffer cap → close |
idle_timeout |
Option<Duration> |
None |
Partially wired (Tranche 1) |
Buffer caps are connection safety limits (not identity ACL), but they are part of the same listener hardening surface.
Examples
Compose ACL + rate limit on any service listener:
use std::sync::Arc;
use std::time::Duration;
use hopf_core::{
AcceptRateLimit, IpNet, PeerAcl, Runtime, RuntimeConfig, TcpListenerConfig,
};
let rt = Arc::new(Runtime::start(RuntimeConfig::default())?);
let acl = PeerAcl {
allow: vec![IpNet::parse("192.168.0.0/16").unwrap()],
deny: vec![],
};
let listener = TcpListenerConfig::new(addr, factory)
.with_acl(acl)
.with_rate_limit(AcceptRateLimit::new(50, Duration::from_secs(1), 500));
rt.add_tcp_listener(listener)?;FTP/SMTP services build a TcpListenerConfig internally — prefer their config builders where available, or obtain control_listener and layer ACL before Runtime::add_tcp_listener when the service exposes that path (FTP, SMTP).
Pair with Telemetry to observe on_error for denials.
Limitations
- Quotas are a skeleton — not automatically wired into every protocol codec.
idle_timeoutis only partially wired.- ACL is IP-based only; user/role RBAC belongs in Auth / protocol
is_authorizedhooks (e.g. FTP). - No distributed / cluster ACL store — process-local configuration.
- WebDAV has no in-crate ACL; put
PeerAclon the HTTP listener (WebDAV).