Commit eaa67d4f authored by jan.koester's avatar jan.koester
Browse files

hystart

parent 4d62db9f
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1483,6 +1483,16 @@ uint32_t aes256::subWord_ct(uint32_t w) {
           (uint32_t(b3));
}

// Deliberately always the constant-time software S-box (subWord_ct), even
// when AES-NI is available: key expansion runs once per encryption
// level/direction/connection (see deriveInitialKeys()/deriveHandshakeKeys()/
// deriveApplicationKeys() in quic.cpp), never in the per-packet hot path —
// AES-NI's _mm_aeskeygenassist_si128 keygen path would only shorten an
// already-sub-microsecond, one-time-per-key operation, and AES-256's
// alternating (SubWord only on every other word) key schedule makes that
// path meaningfully more intricate to get right than AES-128's. Not worth
// the correctness risk for a speedup that never shows up in steady-state
// throughput.
void aes256::keyExpansion(const std::vector<uint8_t>& key) {
    if (key.size() != 32)
        throw std::invalid_argument("aes256::keyExpansion: key must be 32 bytes");
+11 −0
Original line number Diff line number Diff line
@@ -530,6 +530,17 @@ size_t udp::recvBatchAddr(std::vector<std::vector<uint8_t>>& out,
#else
    // Use thread_local static buffers to avoid 64×65KB heap allocation per call.
    // These are reused across calls on the same thread.
    //
    // Per-slot size is 65535, not the ~1472-byte PMTU ceiling a QUIC
    // connection actually negotiates: this buffer is shared (thread_local)
    // across every udp/quic instance serviced by this thread, and when
    // _gro_enabled is set, a single recvmmsg slot can hold a UDP_GRO
    // coalesced batch of multiple datagrams — up to the kernel's ~64KB GRO
    // limit, not one MTU-sized packet. Sizing slots down to PMTU+margin
    // would silently truncate/corrupt any GRO-coalesced receive on a thread
    // that also happens to service a GRO-enabled socket. Safe to shrink only
    // if GRO support were removed, or reworked to size per-socket instead of
    // per-thread.
    static constexpr int MAX_BATCH = 64;
    static thread_local std::vector<uint8_t> flat_buf(MAX_BATCH * 65535);
    static thread_local struct iovec iovecs[MAX_BATCH];
+8 −2
Original line number Diff line number Diff line
@@ -502,9 +502,15 @@ void quic::onPacketAckedCC(size_t packet_size, uint64_t pn) {
    }
    _in_recovery = false;

    // Slow start: cwnd grows by bytes acked
    // Slow start: cwnd grows by bytes acked (or, under HyStart++
    // Conservative Slow Start, by a fraction of that — see
    // hystartOnRttSample()).
    if (_cwnd < _ssthresh) {
        if (_hystart_css_active) {
            _cwnd += packet_size / HYSTART_CSS_GROWTH_DIVISOR;
        } else {
            _cwnd += packet_size;
        }
    } else {
        // Congestion avoidance: cwnd grows by ~1 MTU per RTT (RFC 9002
        // Appendix B.5). Uses the actual confirmed usable datagram size
+48 −1
Original line number Diff line number Diff line
@@ -1142,7 +1142,21 @@ namespace netplus {
		std::vector<uint8_t> _c_hs_traffic_secret;
		std::vector<uint8_t> _s_hs_traffic_secret;

		// TLS handshake state
		// TLS handshake state.
		//
		// _tls_transcript grows across the handshake (ClientHello,
		// ServerHello, ... Finished) and is re-hashed from byte 0 on each of
		// the ~8-10 sha256_hash()/sha384_hash() calls over it, rather than
		// fed into an incremental/streaming hash state that only processes
		// the newly-appended bytes each time. This is O(n^2) in transcript
		// hash calls rather than O(n), but n is a handshake's worth of
		// messages (typically low single-digit KB), the whole thing happens
		// once per connection (never in the per-packet hot path), and
		// sha256_hash()/sha384_hash() (crypto/sha.h) only expose a one-shot
		// API — adding and validating a new incremental variant carries real
		// correctness risk (this feeds Finished/CertificateVerify; a subtly
		// wrong incremental state would silently break every handshake) for
		// a gain that only shows up with unusually large certificate chains.
		std::vector<uint8_t> _tls_transcript;
		std::vector<uint8_t> _client_random;
		std::vector<uint8_t> _server_random;
@@ -1578,6 +1592,39 @@ namespace netplus {
		void onPacketAckedCC(size_t packet_size, uint64_t pn);
		void onPacketLostCC(size_t packet_size, uint64_t pn);

		// ---- HyStart++ (RFC 9438) — slow-start overshoot protection ----
		// Plain NewReno slow start only ever exits via an actual loss,
		// letting cwnd overshoot the path's real capacity on high-BDP/high-
		// latency paths before the first loss event corrects it. HyStart++
		// watches for a sustained per-round RTT increase (a sign of queue
		// buildup ahead of loss) and reacts by capping growth (Conservative
		// Slow Start) instead of continuing to double cwnd every RTT.
		// MIN_RTT_THRESH guards against false triggers from ordinary
		// scheduling jitter on very-low-RTT paths (loopback, LAN) — the
		// same class of problem the loss-detection 5ms floor elsewhere in
		// this file was added for.
		static constexpr double HYSTART_MIN_RTT_THRESH = 0.004;  // 4ms
		static constexpr double HYSTART_MAX_RTT_THRESH = 0.016;  // 16ms
		static constexpr int HYSTART_N_RTT_SAMPLE = 8;
		static constexpr int HYSTART_CSS_ROUNDS = 5;
		static constexpr uint64_t HYSTART_CSS_GROWTH_DIVISOR = 4;

		bool _hystart_css_active = false;
		int _hystart_css_rounds_remaining = 0;
		uint64_t _hystart_window_end = 0;         // PN marking current round's end
		double _hystart_curr_round_min_rtt = -1.0;
		double _hystart_last_round_min_rtt = -1.0;
		int _hystart_rtt_sample_count = 0;

		// Called from processAckFrame() for every RTT sample while in slow
		// start or CSS (a no-op once in ordinary congestion avoidance).
		void hystartOnRttSample(double rtt_sample, uint64_t acked_pn);
		// Resets all HyStart++ round-tracking state — called whenever a
		// loss/recovery event redefines cwnd growth from scratch, so stale
		// round data from before the loss can't influence the decision
		// after it.
		void hystartReset();

		// Reused across the congestion-window-blocked wait retries in
		// sendStreamData() instead of constructing a fresh socketwait (and
		// its underlying epoll fd) on every ~1ms poll attempt. Profiling a