<feed xmlns='http://www.w3.org/2005/Atom'>
<title>accel-ppp.git/accel-pppd/extra, branch sstp-alloc-invariant</title>
<subtitle>High performance PPTP/L2TP/SSTP/PPPoE/IPoE server for Linux (mirror of https://github.com/accel-ppp/accel-ppp.git)
</subtitle>
<id>https://git.amelek.net/accel-ppp/accel-ppp.git/atom?h=sstp-alloc-invariant</id>
<link rel='self' href='https://git.amelek.net/accel-ppp/accel-ppp.git/atom?h=sstp-alloc-invariant'/>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/'/>
<updated>2026-06-10T18:28:29+00:00</updated>
<entry>
<title>openssl: suppress deprecated API warnings</title>
<updated>2026-06-10T18:28:29+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-06-10T14:15:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=c1689506bbc27f498612ca69648876599ded7d7c'/>
<id>urn:sha1:c1689506bbc27f498612ca69648876599ded7d7c</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Potential fix for pull request finding</title>
<updated>2026-06-09T14:57:58+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>nuclearcat@nuclearcat.com</email>
</author>
<published>2026-06-09T14:57:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=e3618955ebcfd8ef0bce15bb862405d4fa44e121'/>
<id>urn:sha1:e3618955ebcfd8ef0bce15bb862405d4fa44e121</id>
<content type='text'>
Co-authored-by: Copilot Autofix powered by AI &lt;175728472+Copilot@users.noreply.github.com&gt;</content>
</entry>
<entry>
<title>ippool/ipv6pool: bitmap allocator + online reconfiguration</title>
<updated>2026-06-03T11:57:45+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-06-02T23:23:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=8afec90643a6e6d923032ae5c50d6c18e7a8b0c3'/>
<id>urn:sha1:8afec90643a6e6d923032ae5c50d6c18e7a8b0c3</id>
<content type='text'>
Replace the pre-generated free-list (one heap node per allocatable
address/prefix) with a per-pool bitmap. Each pool holds a list of
contiguous ranges, each range owning one bitmap (1 bit per unit);
a lease is a small per-session malloc wrapper around the ipdb item,
so pool memory is no longer shared or mutated by sessions.

Memory now scales with capacity at ~1 bit/unit instead of ~96B (v4) /
~128B (v6) per unit; startup, `show ippool`, and backup-restore are
O(1) instead of O(N)/O(N*M). Oversized IPv6 ranges (prefix_len-mask
&gt; 24) are rejected at parse time instead of OOMing in the malloc loop.

The ipdb_t vtable, the owner-based put dispatch, ipdb.h structs, and
struct ap_session are unchanged; RADIUS and chap-secrets backends are
untouched (reconcile filters by owner).

Preserved behavior: p2p/net30 allocators (via a step/gw_offset/
peer_offset geometry triple), shuffle (randomized scan start), named
pools, next-chains, gw-ip-address, RADIUS pool-name attrs, and the
USE_BACKUP save/restore path. `gw=` is now accepted-and-ignored (its
per-address local gateway was already overridden at allocation time).

New: online reconfiguration. An EV_CONFIG_RELOAD handler rebuilds the
pool set and reconciles live sessions (sessions are the source of
truth, the bitmap is rebuilt from them) under
pool_set_rwlock(write) -&gt; ses_lock(read) -&gt; per-pool spinlock.
Sessions whose address left the pools are handled per a new
`reload-orphan = keep|disconnect` knob (default keep); foreign
in-range addresses are reserved to avoid duplicate assignment.

Adds extra/bitpool.h (shared bit-array helpers) and a standalone
extra/bitpool_test.c (not wired into cmake) covering the bitmap and
the v4/v6 address&lt;-&gt;bit math, cross-checked against an __int128
reference.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;

</content>
</entry>
<entry>
<title>metrics: resolve protocol stat symbols via dlsym</title>
<updated>2026-05-14T09:24:21+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-14T08:54:15+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=3776ae87bf363535aa5883008a922a7dcb1e2b86'/>
<id>urn:sha1:3776ae87bf363535aa5883008a922a7dcb1e2b86</id>
<content type='text'>
The previous weak-symbol workaround let the module load on musl (which
treats RTLD_LAZY as RTLD_NOW) but introduced a silent failure: weak
undefined references are bound to NULL at our own dlopen time and are
not updated when a later RTLD_GLOBAL dlopen brings the protocol module
in. In any [modules] ordering where metrics comes before pppoe / l2tp /
pptp / sstp / ipoe, the function pointers stay NULL and per-protocol
session metrics silently disappear from both Prometheus and JSON
output, with no log to indicate why.

Replace the weak declarations with a small table and resolve each
protocol's stat_starting/stat_active pair via dlsym(RTLD_DEFAULT, ...)
the first time we render after the module is seen as loaded. dlsym
walks the live global scope at call time, so it picks up symbols
regardless of dlopen order; the resolved pointers are cached so
subsequent scrapes do not re-walk the loader. The five near-identical
render blocks in render_prometheus() and render_json() collapse into
table-driven loops.

libdl is already a transitive dependency of accel-pppd via triton, so
no build-system changes are needed.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>metrics: queue partial response writes</title>
<updated>2026-05-14T07:04:56+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-13T23:29:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=391edc5c59b4c66ef97ef435a64eff4f68c32e63'/>
<id>urn:sha1:391edc5c59b4c66ef97ef435a64eff4f68c32e63</id>
<content type='text'>
write_all() previously did a blocking-style loop on a O_NONBLOCK
socket and bailed on the first EAGAIN. With a slow scrape client or
a small kernel send buffer that meant the response was truncated and
the connection dropped mid-flight.

Allocate one contiguous xmit_buf per response holding header + body,
then drain it in xmit_flush():

  * full write → mark the client for disconnect on the next event
    loop tick;
  * EAGAIN/EWOULDBLOCK → enable MD_MODE_WRITE so cln_write() resumes
    the drain when the socket becomes writable;
  * hard error → mark for disconnect, caller tears down.

cln_read() now stops reading once a response is queued (read events
during the response phase are uninteresting since we'll close on
flush), and cln_write() finishes the drain and disconnects when the
last byte is out. The existing per-client read timer doubles as a
write deadline, so a peer that opens the connection and never reads
still gets cleaned up after read_timeout seconds.

Smoke-tested with a python client that uses SO_RCVBUF=256 and
sleep(0.05) between recv()s — it now reads the entire ~2.8 KiB body
across many short reads. Five concurrent slow readers plus a fast
scrape all complete successfully.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
</content>
</entry>
<entry>
<title>metrics: back off accept loop on persistent errors</title>
<updated>2026-05-14T07:04:56+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-13T23:24:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=0197e6e96ea391d6b44e9440f2add84a5dc70645'/>
<id>urn:sha1:0197e6e96ea391d6b44e9440f2add84a5dc70645</id>
<content type='text'>
Previously the only path out of serv_read()'s accept loop was an
EAGAIN/EWOULDBLOCK return; every other failure logged once and fell
back into `continue`. With a level-readable listening fd, that means
EMFILE/ENFILE/ENOBUFS/ENOMEM pin the worker thread spinning on
accept() and saturate the log.

Detect that class of error and pause the listener: disable
MD_MODE_READ on serv_hnd, arm a one-shot triton timer for one
second, and on expiry re-enable the handler. EINTR and ECONNABORTED
are kept as transient retries — those are normal and short-lived.

Verified by running the daemon under `prlimit --nofile=24` and
opening enough slow connections to exhaust the limit. The first
accept failure logs

  metrics: accept failed: Too many open files; backing off 1s

then the daemon idles at 0% CPU instead of spinning; once fds free
up it resumes accepting.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
</content>
</entry>
<entry>
<title>metrics: per-client read timeout and max-clients cap</title>
<updated>2026-05-14T07:04:56+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-13T23:16:57+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=555ad5446199c9bd5258466f75e235d3efecbb84'/>
<id>urn:sha1:555ad5446199c9bd5258466f75e235d3efecbb84</id>
<content type='text'>
A scrape client that opens a TCP connection and never sends a full
request line+headers used to keep its accel-pppd-side fd registered
indefinitely. Combined with the default `allowed_ips` (= allow all),
a single peer could exhaust the daemon's file descriptors
slowloris-style.

Give every accepted connection a triton timer armed for
`read_timeout` seconds (default 5). On expiry, disconnect_client()
tears down the fd, the timer, and the buffer. The timer is canceled
implicitly when the client is disconnected for any other reason
because disconnect_client() now deletes the timer before freeing the
client.

Also cap the number of in-flight clients at `max_clients`
(default 64). Excess connections are accepted and immediately
closed so the kernel listen backlog still drains.

Both knobs accept 0 to disable. The default values are documented in
accel-ppp.conf(5) alongside the existing [metrics] options.

Smoke-tested:

  * a connection that sends nothing is dropped from the daemon's fd
    table when read_timeout elapses; subsequent scrapes still
    succeed;
  * with max_clients=3 and five concurrent silent connections, the
    daemon holds exactly three ESTAB sockets, the others are closed.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
</content>
</entry>
<entry>
<title>metrics: render JSON when format=json</title>
<updated>2026-05-14T07:04:56+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-13T22:06:49+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=5358369d7b7965deda010267076b25e25c4c044a'/>
<id>urn:sha1:5358369d7b7965deda010267076b25e25c4c044a</id>
<content type='text'>
Replace the previous `{}` stub with a structured JSON document carrying
the same fields as the Prometheus exposition: build info, uptime,
CPU%, RSS/virtual memory, the triton core counters, session counts by
state, and a `protocols` object whose keys are only present for
modules that are actually loaded.

Strings are emitted through a small helper that escapes the JSON
control characters (\b, \f, \n, \r, \t, \", \\) and falls back to
\u00XX for other bytes below 0x20, so the version string and any
future textual labels survive without producing invalid JSON. The
output passes `python3 -m json.tool` against a running daemon.

Content-Type is already set to application/json by content_type(), so
no transport changes are needed.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
</content>
</entry>
<entry>
<title>metrics: render Prometheus exposition for /metrics</title>
<updated>2026-05-14T07:04:56+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-13T22:01:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=d200bdcb39f754ac360b78bf408a3c66f50e8a8c'/>
<id>urn:sha1:d200bdcb39f754ac360b78bf408a3c66f50e8a8c</id>
<content type='text'>
Fill in the body that GET /metrics returns when format=prometheus.
The exposed series mirror what `show stat` prints over the CLI today:

  * accel_ppp_build_info{version="..."} 1
  * accel_ppp_uptime_seconds
  * accel_ppp_cpu_percent
  * accel_ppp_memory_{rss,virt}_bytes (read from /proc/&lt;pid&gt;/statm)
  * accel_ppp_core_mempool_{allocated,available}_bytes
  * accel_ppp_core_threads{,_active}
  * accel_ppp_core_contexts{,_sleeping,_pending}
  * accel_ppp_core_md_handlers{,_pending}
  * accel_ppp_core_timers{,_pending}
  * accel_ppp_sessions{state="starting|active|finishing"}
  * accel_ppp_protocol_sessions{protocol=...,state=...} for every
    protocol module that is currently loaded — pppoe, l2tp, pptp,
    sstp, ipoe — gated by triton_module_loaded() so we never call a
    stat helper from a module that wasn't loaded.

A small growing strbuf helper coalesces the rendering into a single
buffer that is passed to send_response() in one shot. Per-protocol
forward declarations rely on the existing RTLD_LAZY|RTLD_GLOBAL load
behaviour used by net-snmp, so the metrics .so does not need to link
against pppoe.so, l2tp.so, etc.

json format remains a stubbed `{}` body — the next commit replaces
that with a real renderer.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
</content>
</entry>
<entry>
<title>metrics: gate listener with allowed_ips ACL</title>
<updated>2026-05-14T07:04:56+00:00</updated>
<author>
<name>Denys Fedoryshchenko</name>
<email>denys.f@collabora.com</email>
</author>
<published>2026-05-13T19:44:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.amelek.net/accel-ppp/accel-ppp.git/commit/?id=d5a0c7da380ff2efc359b939ac9abd5ffc10df69'/>
<id>urn:sha1:d5a0c7da380ff2efc359b939ac9abd5ffc10df69</id>
<content type='text'>
Parse the `allowed_ips` option in [metrics] as a comma-separated list
of IPv4 CIDR entries. Both the bracketed form

    allowed_ips = ["1.2.3.4/32", "5.6.7.0/24"]

and a bare comma-separated form are accepted; surrounding whitespace,
matched quotes, and the optional [ ] are stripped. A bare address
without a prefix is treated as /32.

When the list is empty (option missing or empty value), all peers are
allowed and behavior is unchanged. Otherwise serv_read() rejects any
peer that doesn't match a configured CIDR by closing the freshly
accepted socket before allocating client state, so scanners get
nothing more than a TCP reset.

Signed-off-by: Denys Fedoryshchenko &lt;denys.f@collabora.com&gt;
</content>
</entry>
</feed>
