An allowance that resets whenever the tunnel reconnects is not a limit, and that is exactly what you get when a byte counter is bolted onto a VPN server without a place to put durable state. The counter belongs to a session; the allowance belongs to a person or a device contract. Everything difficult about the feature comes from that mismatch, so the checks below are about where state lives and which layer is allowed to make a decision.

Pin the allowance to an account, not a tunnel

Start by naming the subject of the quota. One person may hold three device certificates, and none of them should get an independent monthly budget simply because they authenticate separately. A stable account identifier that survives reconnects, new tunnel addresses and a move to a second gateway is the only anchor a quota can use. A certificate common name works as that key only when your issuance policy guarantees one account per name.

An assigned tunnel address is never the answer. Addresses are recycled, pools are shared, and the same account can appear twice on two gateways in the same minute. If the ledger keys on an address, disconnecting and reconnecting is enough to refill the tank.

Record enough per session to make the arithmetic auditable: the account, the billing period, the gateway instance, the session identity, the last observed counters and the committed total. Include a daemon-start generation in the session identity, because a restarted server reuses client IDs and a naive match would inherit the previous baseline.

Fix the unit, direction and reset boundary

Four definitions have to be settled in writing before any collection starts. Does the allowance count bytes the client sends, bytes it receives, or the sum of both? Is a gigabyte 10^9 bytes or 2^30? At which local hour and in which timezone does the period roll over? And what exactly does the counter measure, given that tunnel overhead and retransmissions are included while application file size and a provider's invoice are not?

None of those choices is wrong, but the implementation and the customer-facing number have to agree. Two halves of the same system disagreeing about GiB, or about a rollover that happens at midnight UTC instead of local time, produces exactly the kind of ticket that erodes trust in the whole quota.

Turn byte totals into one charged delta

The management interface reports periodic BYTECOUNT_CLI lines carrying a client ID plus cumulative byte totals in each direction, and the bytecount command sets how often those arrive. From the server's point of view, the received figure counts bytes the client sent and the sent figure counts bytes delivered to it. Set the interval to something the ledger can absorb; a very short interval buys precision you do not actually get.

One constraint shapes the whole design: the management socket accepts a single control connection. A monitoring exporter, a GUI and your controller cannot each open their own. Decide which process owns the channel and let the others read state from it, because this interface grants administrative actions and should not be exposed beyond the host anyway.

rx_delta = sample.rx_now - session.prev_rx
tx_delta = sample.tx_now - session.prev_tx
charge  = rx_delta + tx_delta
# same transaction:
ledger.total += charge
session.prev_rx, session.prev_tx = sample.rx_now, sample.tx_now

Commit the new baselines and the usage increment together, so replaying a sample charges nothing instead of charging twice. A counter that moves backwards is a signal, not a debt: it means the session was replaced or the daemon restarted, and the correct response is to reconcile the session identity rather than to credit a negative amount.

The disconnect path needs the same care. If a final event carries end-of-session totals, charge only the difference from the last committed sample. Adding the full total after periodic sampling has already been charged is the most common double-count in home-grown quota systems. Sessions that straddle a period boundary also need a rule, since one final total carries no information about when the bytes crossed midnight.

Make exhaustion deny admission, not just kill the tunnel

When an account reaches its limit, two actions have to happen: existing sessions end, and new ones are refused. The management protocol provides client-kill by client ID, which handles the first half as long as you enumerate every live session for the account instead of killing the one address you happened to notice.

The second half is where quota designs fail. A kill alone is a race against the client's reconnect loop, which will happily come back seconds later. Admission has to consult the shared ledger, and on a multi-gateway deployment that check cannot be a per-machine file. Every instance that can accept a login must see the same exhausted state.

Token-based reconnects deserve their own test. OpenVPN 2.6 can validate a session token internally with auth-gen-token, skipping the external authentication call that your controller was using to enforce the cap. A token that is still valid therefore proves identity, not remaining allowance, and each new client instance must still consult the ledger. If you enable management-client-auth instead, the controller becomes the authenticator and inherits the obligation to parse session state and reject expired or invalid tokens on its own.

Accept the overshoot the sampling creates

Periodic observation plus disconnection gives you a sampled allowance with a known tolerance. A client can overshoot by roughly one reporting interval plus the time it takes the controller to act, and concurrent sessions on one account multiply that window. Say so in the interface instead of advertising an exact last-byte cutoff you cannot deliver.

A real hard ceiling is a different engineering problem: enforcement inside the forwarding path, tied to the same account across sessions. Reading counters faster is not the same thing, and it does not survive a client that simply opens more tunnels.

Before rollout, run the acceptance cases that break naive implementations: repeated reconnects, two devices at once, controller and gateway restarts, a lost disconnect event, a rollover mid-session, and a token reconnect attempted right after exhaustion. Then decide the failure mode for accounting outages. A durable ledger protects the usage you recorded; it cannot resurrect observations that never arrived, and the customer deserves to know which of those two situations they are in.

ບົດຄວາມທີ່ກ່ຽວຂ້ອງ