Loading debian/changelog +8 −0 Original line number Diff line number Diff line libnetplus (20260404+8) unstable; urgency=medium * Fix QUIC stream limit exhaustion: increase initial_max_streams from 100 to 10M * Auto-cleanup completed streams and send MAX_STREAMS frames to replenish peer budget * Server sends MAX_STREAMS(bidi/uni) when peer-initiated streams are fully closed -- Jan Koester <jan.koester@tuxist.de> Fri, 04 Apr 2026 23:00:00 +0200 libnetplus (20260404+7) unstable; urgency=medium * Remove [QUIC] server-side Initial/ServerHello debug logging Loading src/quic.cpp +57 −2 Original line number Diff line number Diff line Loading @@ -2298,7 +2298,7 @@ std::vector<uint8_t> quic::buildTransportParameters() { // initial_max_streams_bidi (0x08) params.push_back(0x08); uint8_t max_streams_buf[8]; size_t max_streams_len = encodeVarInt(100, max_streams_buf); size_t max_streams_len = encodeVarInt(10000000, max_streams_buf); uint8_t max_streams_len_buf[8]; size_t max_streams_len_size = encodeVarInt(max_streams_len, max_streams_len_buf); params.insert(params.end(), max_streams_len_buf, max_streams_len_buf + max_streams_len_size); Loading Loading @@ -3684,6 +3684,36 @@ void quic::sendStreamData(uint64_t stream_id, const std::vector<uint8_t>& data, done: if (fin) { stream.send_fin = true; // Auto-cleanup: both sides FIN'd → erase stream & replenish peer's stream budget if (stream.recv_fin) { bool peer_bidi = _is_server ? (stream_id % 4 == 0) : (stream_id % 4 == 1); bool peer_uni = _is_server ? (stream_id % 4 == 2) : (stream_id % 4 == 3); _streams.erase(stream_id); if (peer_bidi) { _max_streams_bidi_local++; // Send MAX_STREAMS(bidi) to allow peer to open more streams std::vector<uint8_t> ms_frame; ms_frame.push_back(0x12); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_bidi_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } else if (peer_uni) { _max_streams_uni_local++; std::vector<uint8_t> ms_frame; ms_frame.push_back(0x13); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_uni_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } } } } Loading Loading @@ -4982,9 +5012,34 @@ void quic::closeStream(uint64_t stream_id) { stream.send_fin = true; } // If both sides have sent FIN, remove stream // If both sides have sent FIN, remove stream and replenish peer's budget if (stream.recv_fin && stream.send_fin) { bool peer_bidi = _is_server ? (stream_id % 4 == 0) : (stream_id % 4 == 1); bool peer_uni = _is_server ? (stream_id % 4 == 2) : (stream_id % 4 == 3); _streams.erase(it); if (peer_bidi) { _max_streams_bidi_local++; std::vector<uint8_t> ms_frame; ms_frame.push_back(0x12); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_bidi_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } else if (peer_uni) { _max_streams_uni_local++; std::vector<uint8_t> ms_frame; ms_frame.push_back(0x13); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_uni_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } } } Loading src/socket.h +4 −4 Original line number Diff line number Diff line Loading @@ -832,10 +832,10 @@ namespace netplus { uint64_t _data_recv = 0; // Stream count limits (MAX_STREAMS) uint64_t _max_streams_bidi_local = 100; // we allow peer to open uint64_t _max_streams_bidi_remote = 100; // peer allows us to open uint64_t _max_streams_uni_local = 100; uint64_t _max_streams_uni_remote = 100; uint64_t _max_streams_bidi_local = 10000000; // we allow peer to open uint64_t _max_streams_bidi_remote = 10000000; // peer allows us to open uint64_t _max_streams_uni_local = 10000000; uint64_t _max_streams_uni_remote = 10000000; uint64_t _streams_bidi_opened = 0; // streams we opened uint64_t _streams_uni_opened = 0; uint64_t _peer_streams_bidi_opened = 0; // streams peer opened Loading Loading
debian/changelog +8 −0 Original line number Diff line number Diff line libnetplus (20260404+8) unstable; urgency=medium * Fix QUIC stream limit exhaustion: increase initial_max_streams from 100 to 10M * Auto-cleanup completed streams and send MAX_STREAMS frames to replenish peer budget * Server sends MAX_STREAMS(bidi/uni) when peer-initiated streams are fully closed -- Jan Koester <jan.koester@tuxist.de> Fri, 04 Apr 2026 23:00:00 +0200 libnetplus (20260404+7) unstable; urgency=medium * Remove [QUIC] server-side Initial/ServerHello debug logging Loading
src/quic.cpp +57 −2 Original line number Diff line number Diff line Loading @@ -2298,7 +2298,7 @@ std::vector<uint8_t> quic::buildTransportParameters() { // initial_max_streams_bidi (0x08) params.push_back(0x08); uint8_t max_streams_buf[8]; size_t max_streams_len = encodeVarInt(100, max_streams_buf); size_t max_streams_len = encodeVarInt(10000000, max_streams_buf); uint8_t max_streams_len_buf[8]; size_t max_streams_len_size = encodeVarInt(max_streams_len, max_streams_len_buf); params.insert(params.end(), max_streams_len_buf, max_streams_len_buf + max_streams_len_size); Loading Loading @@ -3684,6 +3684,36 @@ void quic::sendStreamData(uint64_t stream_id, const std::vector<uint8_t>& data, done: if (fin) { stream.send_fin = true; // Auto-cleanup: both sides FIN'd → erase stream & replenish peer's stream budget if (stream.recv_fin) { bool peer_bidi = _is_server ? (stream_id % 4 == 0) : (stream_id % 4 == 1); bool peer_uni = _is_server ? (stream_id % 4 == 2) : (stream_id % 4 == 3); _streams.erase(stream_id); if (peer_bidi) { _max_streams_bidi_local++; // Send MAX_STREAMS(bidi) to allow peer to open more streams std::vector<uint8_t> ms_frame; ms_frame.push_back(0x12); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_bidi_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } else if (peer_uni) { _max_streams_uni_local++; std::vector<uint8_t> ms_frame; ms_frame.push_back(0x13); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_uni_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } } } } Loading Loading @@ -4982,9 +5012,34 @@ void quic::closeStream(uint64_t stream_id) { stream.send_fin = true; } // If both sides have sent FIN, remove stream // If both sides have sent FIN, remove stream and replenish peer's budget if (stream.recv_fin && stream.send_fin) { bool peer_bidi = _is_server ? (stream_id % 4 == 0) : (stream_id % 4 == 1); bool peer_uni = _is_server ? (stream_id % 4 == 2) : (stream_id % 4 == 3); _streams.erase(it); if (peer_bidi) { _max_streams_bidi_local++; std::vector<uint8_t> ms_frame; ms_frame.push_back(0x12); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_bidi_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } else if (peer_uni) { _max_streams_uni_local++; std::vector<uint8_t> ms_frame; ms_frame.push_back(0x13); uint8_t ms_buf[8]; size_t ms_bytes = encodeVarInt(_max_streams_uni_local, ms_buf); ms_frame.insert(ms_frame.end(), ms_buf, ms_buf + ms_bytes); std::vector<uint8_t> ms_pkt = buildShortHeaderPacket(ms_frame); const uint8_t* ms_hp = _is_server ? _app_hp_server : _app_hp_client; applyHeaderProtection(ms_pkt, ms_hp); sendPacket(ms_pkt.data(), ms_pkt.size()); } } } Loading
src/socket.h +4 −4 Original line number Diff line number Diff line Loading @@ -832,10 +832,10 @@ namespace netplus { uint64_t _data_recv = 0; // Stream count limits (MAX_STREAMS) uint64_t _max_streams_bidi_local = 100; // we allow peer to open uint64_t _max_streams_bidi_remote = 100; // peer allows us to open uint64_t _max_streams_uni_local = 100; uint64_t _max_streams_uni_remote = 100; uint64_t _max_streams_bidi_local = 10000000; // we allow peer to open uint64_t _max_streams_bidi_remote = 10000000; // peer allows us to open uint64_t _max_streams_uni_local = 10000000; uint64_t _max_streams_uni_remote = 10000000; uint64_t _streams_bidi_opened = 0; // streams we opened uint64_t _streams_uni_opened = 0; uint64_t _peer_streams_bidi_opened = 0; // streams peer opened Loading