Skip to content

Commit af93246

Browse files
committed
Make it go fast
- Adds a retransmit timer to resend on drop - Properly advanced the stream so that we don't stall at the default 64kB limit - Remove some debugging and unused/unneeded methods
1 parent 22a3e52 commit af93246

File tree

5 files changed

+68
-36
lines changed

5 files changed

+68
-36
lines changed

connection.cpp

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "log.h"
1111
#include "uvw/async.h"
1212
#include "uvw/poll.h"
13+
#include "uvw/timer.h"
1314

1415
#include <oxenmq/hex.h>
1516
#include <oxenmq/bt_serialize.h>
@@ -91,7 +92,6 @@ namespace {
9192
break;
9293

9394
case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
94-
9595
if (!ngtcp2_conn_is_server(conn)) {
9696
if (auto rv = conn.recv_transport_params(data); rv != 0)
9797
return rv;
@@ -176,6 +176,7 @@ namespace {
176176
uint64_t offset, uint64_t datalen, void* user_data,
177177
void* stream_user_data) {
178178
Debug("######################", __func__);
179+
Debug("Ack [", offset, ",", offset+datalen, ")");
179180
return static_cast<Connection*>(user_data)->stream_ack({stream_id}, datalen);
180181
}
181182

@@ -201,13 +202,6 @@ namespace {
201202
// FIXME
202203
return 0;
203204
}
204-
int extend_max_local_streams_bidi(ngtcp2_conn* conn, uint64_t max_streams, void* user_data) {
205-
Debug("######################", __func__);
206-
Error("FIXME UNIMPLEMENTED ", __func__);
207-
Warn("new max streams: ", max_streams);
208-
// FIXME
209-
return 0;
210-
}
211205
int rand(
212206
uint8_t* dest, size_t destlen,
213207
const ngtcp2_rand_ctx* rand_ctx,
@@ -312,6 +306,18 @@ std::tuple<ngtcp2_settings, ngtcp2_transport_params, ngtcp2_callbacks> Connectio
312306
io_trigger = endpoint.loop->resource<uvw::AsyncHandle>();
313307
io_trigger->on<uvw::AsyncEvent>([this] (auto&, auto&) { on_io_ready(); });
314308

309+
retransmit_timer = endpoint.loop->resource<uvw::TimerHandle>();
310+
retransmit_timer->on<uvw::TimerEvent>([this] (auto&, auto&) {
311+
Debug("Retransmit timer fired!");
312+
if (auto rv = ngtcp2_conn_handle_expiry(*this, get_timestamp()); rv != 0) {
313+
Warn("expiry handler invocation returned an error: ", ngtcp2_strerror(rv));
314+
endpoint.close_connection(*this, ngtcp2_err_infer_quic_transport_error_code(rv), false);
315+
} else {
316+
flush_streams();
317+
}
318+
});
319+
retransmit_timer->start(0ms, 0ms);
320+
315321
auto result = std::tuple<ngtcp2_settings, ngtcp2_transport_params, ngtcp2_callbacks>{};
316322
auto& [settings, tparams, cb] = result;
317323
cb.recv_crypto_data = recv_crypto_data;
@@ -422,7 +428,6 @@ Connection::Connection(Client& c, const ConnectionID& scid, const Path& path, ui
422428

423429
cb.client_initial = client_initial;
424430
cb.recv_retry = recv_retry;
425-
cb.extend_max_local_streams_bidi = extend_max_local_streams_bidi;
426431
//cb.extend_max_local_streams_bidi = extend_max_local_streams_bidi;
427432
//cb.recv_new_token = recv_new_token;
428433

@@ -454,11 +459,6 @@ void Connection::on_io_ready() {
454459
Debug("done ", __func__);
455460
}
456461

457-
void Connection::on_read(bstring_view data) {
458-
Debug("FIXME UNIMPLEMENTED ", __func__, ", data size: ", data.size());
459-
// FIXME
460-
}
461-
462462
void Connection::flush_streams() {
463463
// conn, path, pi, dest, destlen, and ts
464464
std::optional<uint64_t> ts;
@@ -491,12 +491,9 @@ void Connection::flush_streams() {
491491

492492
// FIXME: update remote addr? ecn?
493493
auto sent = send();
494-
if (sent.blocked()) {
495-
// FIXME: somewhere (maybe here?) should be setting up a write poll so that, once
496-
// writing becomes available again (and the pending packet gets sent), we get back here.
497-
// FIXME 2: I think this is already done by send() itself.
498-
return false;
499-
}
494+
if (sent.blocked())
495+
return false; // We'll get called again when the socket becomes writable
496+
500497
send_buffer_size = 0;
501498
if (!sent) {
502499
Warn("I/O error while trying to send packet: ", sent.str());
@@ -562,6 +559,7 @@ void Connection::flush_streams() {
562559
switch (nwrite) {
563560
case 0:
564561
Debug("Done stream writing to ", stream.id(), " (either stream is congested or we have nothing else to send right now)");
562+
assert(consumed <= 0);
565563
break;
566564
case NGTCP2_ERR_WRITE_MORE:
567565
Debug("consumed ", consumed, " bytes from stream ", stream.id(), " and have space left");
@@ -587,8 +585,8 @@ void Connection::flush_streams() {
587585
}
588586
}
589587

590-
// Now try more with stream id -1 and no data: this will take care of initial handshake packets,
591-
// and should finish off any partially-filled packet from above.
588+
// Now try more with stream id -1 and no data: this takes care of things like initial handshake
589+
// packets, and also finishes off any partially-filled packet from above.
592590
for (;;) {
593591
auto [nwrite, consumed] = add_stream_data(StreamID{}, nullptr, 0);
594592
Debug("add_stream_data for non-stream returned [", nwrite, ",", consumed, "]");
@@ -598,18 +596,39 @@ void Connection::flush_streams() {
598596
continue;
599597
} else if (nwrite < 0) {
600598
Warn("Error writing non-stream data: ", ngtcp2_strerror(nwrite));
601-
return;
599+
break;
602600
} else if (nwrite == 0) {
603601

604602
// FIXME: Check whether this is actually possible for the -1 streamid?
605-
Warn("Unable to continue non-stream writing: we are congested");
606-
return;
603+
Debug("Nothing else to write for non-stream data for now (or we are congested)");
604+
ngtcp2_conn_stat cstat;
605+
ngtcp2_conn_get_conn_stat(*this, &cstat);
606+
Debug("Current unacked bytes in flight: ", cstat.bytes_in_flight);
607+
break;
607608
}
608609

609610
Debug("Sending non-stream data packet");
610611
if (!send_packet(nwrite))
611612
return;
612613
}
614+
615+
schedule_retransmit();
616+
}
617+
618+
void Connection::schedule_retransmit() {
619+
auto expiry = std::chrono::nanoseconds{ngtcp2_conn_get_expiry(*this)};
620+
Debug("SCHEDULE RETRANSMIT exp ", expiry.count());
621+
if (expiry < 0ns) {
622+
retransmit_timer->repeat(0ms);
623+
return;
624+
}
625+
auto expires_in = std::chrono::duration_cast<std::chrono::milliseconds>(
626+
expiry - get_time().time_since_epoch());
627+
Debug("Next retransmit in ", expires_in.count(), "ms");
628+
if (expires_in < 1ms)
629+
expires_in = 1ms;
630+
retransmit_timer->repeat(expires_in);
631+
retransmit_timer->again();
613632
}
614633

615634
int Connection::stream_opened(StreamID id) {
@@ -638,7 +657,7 @@ int Connection::stream_opened(StreamID id) {
638657
return 0;
639658
}
640659

641-
int Connection::stream_receive(StreamID id, bstring_view data, bool fin) {
660+
int Connection::stream_receive(StreamID id, const bstring_view data, bool fin) {
642661
auto str = get_stream(id);
643662
if (!str->data_callback)
644663
Debug("Dropping incoming data on stream ", str->id(), ": stream has no data callback set");
@@ -662,6 +681,9 @@ int Connection::stream_receive(StreamID id, bstring_view data, bool fin) {
662681
str->close_callback(*str, std::nullopt);
663682
streams.erase(id);
664683
io_ready();
684+
} else {
685+
ngtcp2_conn_extend_max_stream_offset(*this, id.id, data.size());
686+
ngtcp2_conn_extend_max_offset(*this, data.size());
665687
}
666688
return 0;
667689
}

connection.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <ngtcp2/ngtcp2.h>
1717
#include <uvw/async.h>
1818
#include <uvw/poll.h>
19+
#include <uvw/timer.h>
1920

2021
namespace quic {
2122

@@ -108,6 +109,10 @@ class Connection : public std::enable_shared_from_this<Connection> {
108109
// Event trigger used to queue packet processing for this connection
109110
std::shared_ptr<uvw::AsyncHandle> io_trigger;
110111

112+
// Schedules a retransmit in the event loop (according to when ngtcp2 tells us we should)
113+
void schedule_retransmit();
114+
std::shared_ptr<uvw::TimerHandle> retransmit_timer;
115+
111116
// The port the client wants to connect to on the server
112117
uint16_t tunnel_port = 0;
113118

@@ -184,7 +189,6 @@ class Connection : public std::enable_shared_from_this<Connection> {
184189
// Called (via libuv) when it wants us to do our stuff. Call io_ready() to schedule this.
185190
void on_io_ready();
186191

187-
void on_read(bstring_view data);
188192
int setup_server_crypto_initial();
189193

190194
// Flush any streams with pending data. Note that, depending on available ngtcp2 state, we may
@@ -226,6 +230,7 @@ class Connection : public std::enable_shared_from_this<Connection> {
226230
// Accesses the stream via its StreamID; throws std::out_of_range if the stream doesn't exist.
227231
const std::shared_ptr<Stream>& get_stream(StreamID s) const;
228232

233+
// Internal methods that need to be publicly callable because we call them from C functions:
229234
int init_client();
230235
int recv_initial_crypto(std::basic_string_view<uint8_t> data);
231236
int recv_transport_params(std::basic_string_view<uint8_t> data);

endpoint.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ class Endpoint {
4242
std::shared_ptr<uvw::Loop> loop;
4343

4444
// How many messages (at most) we recv per callback:
45-
static constexpr size_t N_msgs = 8;
45+
static constexpr int N_msgs = 8;
4646
#ifdef LOKINET_HAVE_RECVMMSG
47-
static constexpr size_t N_mmsg = N_msgs;
47+
static constexpr int N_mmsg = N_msgs;
4848
std::array<mmsghdr, N_mmsg> msgs;
4949
#else
50-
static constexpr size_t N_mmsg = 1;
50+
static constexpr int N_mmsg = 1;
5151
std::array<msghdr, N_mmsg> msgs;
5252
#endif
5353

@@ -206,6 +206,7 @@ class Endpoint {
206206
// Default stream buffer size for streams opened through this endpoint.
207207
size_t default_stream_buffer_size = 64*1024;
208208

209+
// Gets a reference to the UV event loop
209210
uvw::Loop& get_loop() { return *loop; }
210211
};
211212

tunnel-server.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#include "connection.h"
22
#include "server.h"
33
#include "log.h"
4-
54
#include "tunnel.h"
6-
#include "uvw/tcp.h"
75

8-
#include <uvw.hpp>
6+
#include <set>
7+
8+
#include <uvw/tcp.h>
99

1010
using namespace std::literals;
1111

@@ -16,7 +16,7 @@ int usage(std::string_view arg0, std::string_view msg) {
1616

1717
int main(int argc, char *argv[]) {
1818
uint16_t listen_port = 4242;
19-
std::unordered_set<uint16_t> allowed_ports{{22, 80, 4444, 8080}};
19+
std::set<uint16_t> allowed_ports{{22, 80, 4444, 8080}};
2020

2121
if (argc >= 2 && !tunnel::parse_int(argv[1], listen_port))
2222
return usage(argv[0], "Invalid port "s + argv[1]);
@@ -101,6 +101,12 @@ int main(int argc, char *argv[]) {
101101
}
102102
};
103103
quic::Debug("Initialized server");
104+
std::cout << "Listening on localhost:" << listen_port << " with tunnel(s) to localhost port(s):";
105+
if (allowed_ports.empty())
106+
std::cout << " (any)";
107+
for (auto p : allowed_ports)
108+
std::cout << ' ' << p;
109+
std::cout << '\n';
104110

105111
loop->run();
106112

tunnel.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ namespace tunnel {
66

77
// Takes data from the tcp connection and pushes it down the quic tunnel
88
void on_outgoing_data(uvw::DataEvent& event, uvw::TCPHandle& client) {
9-
quic::Warn("on outgoing data");
109
auto stream = client.data<quic::Stream>();
1110
assert(stream);
1211
std::string_view data{event.data.get(), event.length};
@@ -44,7 +43,6 @@ void on_outgoing_data(uvw::DataEvent& event, uvw::TCPHandle& client) {
4443

4544
// Received data from the quic tunnel and sends it to the TCP connection
4645
void on_incoming_data(quic::Stream& stream, quic::bstring_view bdata) {
47-
quic::Error("on incoming data");
4846
auto tcp = stream.data<uvw::TCPHandle>();
4947
assert(tcp);
5048
std::string_view data{reinterpret_cast<const char*>(bdata.data()), bdata.size()};

0 commit comments

Comments
 (0)