Commit Graph

3653 Commits

Author SHA1 Message Date
Wesley Rosenblum 6997d2d2a9 Move stats types into a separate module 2021-08-19 15:23:31 +02:00
Matthias Einwag 78d8619dd9 Fix cubic minimum congestion window handling
`config.minimum_window` is already in bytes - it doesn't have to be multiplied by them..
2021-08-19 08:04:31 +02:00
Matthias Einwag 4e95796240 Log and ignore transmit errors
Some more transient transmit errors have been observed. E.g. for some
peers transmission failed with
```
error: Os { code: 101, kind: Other, message: "Network is unreachable" }
```

That lead the endpoint to shut down and not process any messages for any
other clients.

To prevent this, this changes the behavior to ignore errors inside the endpoint.
In order to not lose all visibility, the change however adds low frequency
logging for the errors.
2021-08-14 16:34:34 -07:00
Marcel Märtens b224d1d0fa switch to futures_utils in tests and benches, also use futures_utils isntead of futures_core 2021-08-11 10:27:32 +02:00
Marcel Märtens 1c30201a47 rather than importing futures only import the crates we really need. This way we can streamline our dependencies, e.g. futures-executor is not used at all 2021-08-11 10:27:32 +02:00
Benjamin Saunders e4f68fd6e7 Remove dead code 2021-08-09 06:23:31 +02:00
Benjamin Saunders 5425389540 Encode token lifetime configuration as a Duration
This is less error-prone and consistent with other configuration
values.
2021-07-31 07:25:52 +02:00
Dirkjan Ochtman d78b3187a9 Apply clippy suggestions from Rust 1.54 2021-07-30 13:29:32 -07:00
Max Davitt f97cb904c2 Fix example in "Self Signed Certificates" section of book 2021-07-19 16:38:34 +02:00
Benjamin Saunders 1c257aba1b Default to Cubic congestion control rather than NewReno
Reportedly increases throughput over realistic WAN paths dramatically.
2021-07-17 20:45:13 +02:00
Benjamin Saunders 459f56973c Ensure Connecting::handshake_data completes on connection errors 2021-07-16 23:36:33 +02:00
Benjamin Saunders c8736e869b Don't falsely report end-of-stream when reading into an empty slice 2021-07-13 06:59:06 +02:00
Matthias Einwag 9b98c69197 Send pending ACKs as part of CONNECTION_CLOSE
When the connection is closed while some ACKs are outstanding,
the peer might have a more pessimistic impression of what was actually
processed and e.g. report various requests that run to completion as failed.

To improve this, we send all pending ACKs as part of the
CONNECTION_CLOSE frame.
2021-07-12 10:18:53 +02:00
Matthias Einwag 6ee488a586 Report ACK delay to the peer
This change enables reporting ack delay to the peer, which will give the peer
a better estimate about actual path latency. The actual delay will later on be be
be determined between the time we first receive a packet and an event
(eg another packet or a timer) unblocked the ACK. This part is not yet implemented,
so the delay is 0 for now.

To enable sending ACK delay information, the default TransportParameters
has been changed to use a non 0 max ack delay value (since otherwise that
value will be picked up).

This also fixes a tiny bug where ack_delay wasn't considered in the path for
a RTT of 0ns.
2021-07-12 10:18:53 +02:00
Matthias Einwag f51a39f71d Move tracking of outgoing ACKs into a dedicated PendingAcks struct
This will make it easier to add additional logic for  ACK suppression.
2021-07-12 10:18:53 +02:00
Matthias Einwag 9e9d1c09b1 Drop outgoing packets on permission errors
When packets failed to transmit - e.g. due to being blocked by iptables - the
quinn endpoint kept them around and tried to retransmit them later on. This
usually failed for the same reason, which resulted in an infinitely growing list
of packets to transmit.

To prevent this, this change drops all packets where sending fails for a
a non-fatal reason.
2021-07-07 19:33:51 -07:00
BiagioFesta 5a9d362937 Change constant INITIAL_MAX_UDP_PAYLOAD_SIZE to 1200 2021-07-07 13:26:01 +02:00
BiagioFesta 0215b5b0aa Rename path.mtu property as max_udp_payload_size 2021-07-07 13:26:01 +02:00
Dirkjan Ochtman a21f010bad Silence clippy false positive 2021-07-07 06:33:05 +02:00
Dirkjan Ochtman 07377c6af3 Remove quinn-h3 2021-07-07 06:33:05 +02:00
Dirkjan Ochtman 53b063b9cd interop: remove H3 support 2021-07-07 06:33:05 +02:00
Frank Spitulski 3f908a2c8c feat(cca): cubic (#1122) 2021-06-22 22:38:52 +02:00
Matthias Einwag 3a30aa17c8 Prevent sending of ACK-only packets
The priority-based send state implementation had an issue where if returned
`self.can_send() == true` even if no data was available for sending.
This condition lead the `poll_transmit` method to initiate a transmission which
passed the ACK-only check since it assumed there was stream data to send.
However when trying to write stream frames no actual stream data could be
written.

The reason for this was that the `can_send()` condition only checked for the
length of the binary heap. However the length of this one was only reduced
in `write_stream_frames()` when the level was inspected the next time, and
not after data of one level was actually written.

This change fixes that, and drops the unused levels immediately after data
was written. There is on exception however: If only one level is left, it is
kept around and reused for future transmits to avoid deallocating and
reallocating the transmit queue continuously. However the `can_send` check
was updated to account for the empty level.

I also added some additional debug asserts which cross-checks
if an operation which announced to write more than just ACKs
still ended up only writing ACKs. That should make it easier to
determine similar issues in the future.

**Ack only transmissions in benchmark before change:** 1462

**Ack only transmissions in benchmark after change:** 45
2021-06-22 21:16:36 +02:00
Matthias Einwag 65ab28ec14 Relax anti-ampflication check
As described in https://github.com/quinn-rs/quinn/issues/1082,
the current version of the anti-ampflication check is extremely strict
and usually only allows 2 outgoing datagrams to be sent during a handshake
due to rounding down.

This change relaxes the check to allow sending another datagram if any
anti-amplification budget is left. This method is used by a variety of
other implementations too.

Fixes #1082
2021-06-22 06:45:50 +02:00
Matthias Einwag 575f7746c4 Fix Rust 1.53 clippy issues
Fixes two instances of
> warning: all if blocks contain the same code at the start

as well as a couple of

https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
2021-06-21 19:21:34 -07:00
Matthias Einwag 62ed3240d1 Add a WorkLimiter, and limit the amount of time sppent in Endpoint::drive_recv dynamically
This change adds a `WorkLimiter` component, which measures the amount of
time required to perform some work items and will limit work based on time
instead of pure iterations.

It also changes the `Endpoint`s  `drive_recv` method to limit receive operations
based on the amount of spent time (to 50µs) using the `WorkLimiter`, instead
of using the hardcoded `IO_LOOP_BOUND` counter.

Performance differences are negligible on this machine (probably because
`IO_LOOP_BOUND` was set to a number which works for it), but it can improve
things on less known environments.

I instrumented the endpoints receive method to see how much time it spends
on average in `drive_recv`.

**Baseline:**
```
Recv time: AvgTime { total: 3.280880841s, calls: 34559, avg: 94.935µs, min: 3.146µs, max: 312.574µs }
path: PathStats {
    rtt: 511.656µs,
```

**With this change:**
```
Recv time: AvgTime { total: 3.333642823s, calls: 54627, avg: 61.024µs, min: 2.645µs, max: 319.147µs }
path: PathStats {
    rtt: 446.641µs,
```
Note that 50µs are not reached because a single `recvmmsg` batch takes about 30µs, so this is just rounding up to 2 batches.

**When set to 200µs (for comparison purposes):**
```
Recv time: AvgTime { total: 3.243954076s, calls: 19558, avg: 165.862µs, min: 2.525µs, max: 358.711µs }
path: PathStats {
    rtt: 700.34µs,
}
```
2021-06-16 13:19:25 +02:00
Matthias Einwag e8ee0da51e Fix some h3 test races
The response body in those tests was never read. Therefore it was possible
for the test to drop the connection before the stream was actually read to
completion.
2021-06-14 21:02:45 -07:00
Benjamin Saunders ab9794ae94 Remove incorrect spurious migration handling
This case is handled by the usual path validation timeout, and by the
ACK elicited from the peer by a PATH_CHALLENGE on a legitimate path.
2021-06-14 07:16:12 +02:00
Matthias Einwag f6519b01f6 Account received data for the most recent path
When path migration happened, the data received was accounted for
the old path, and provided the server less anti-amplification budget
to respond to the client.
By accessing path only after the first packet had been processed and
migration was initiated we make sure the new path gets the budget.
2021-06-12 16:20:58 -07:00
Matthias Einwag 2adf815d51 Handle STOP_SENDING only once
This avoids duplicate emission of various events
2021-06-12 22:34:03 +02:00
Matthias Einwag 39335cb405 Derive PartialEq/Eq for StreamEvent
Its useful being able to compare events
2021-06-12 22:34:03 +02:00
Matthias Einwag eb6ee64f1a Add rt-multi-thread tokio feature to quinn-h3
This got lost when #1137 was merged and breaks the build
2021-06-12 09:30:08 +02:00
lberrymage 4b9481e906 Add must_use warnings for all Future types
The warning message is taken verbatim from the futures crate.
2021-06-10 11:56:32 +02:00
Dirkjan Ochtman 7f1aa1ead3 quinn: only depend on rt-multi-thread as a dev-dependency 2021-06-02 14:04:08 -07:00
Ryo Ota 79c3153e2a Update h3_server.rs 2021-05-31 06:48:51 +02:00
Matthias Einwag e3060d3ff2 Print connection statistics in perf client and server
This adds the `--conn-stats` flag to both CLI tools, which will print
the connection statistics together with other data every 2s.
2021-05-28 09:05:53 +02:00
Luca Barbato 5ab895ba19 Add a mean to set the local address in the perf_client 2021-05-24 13:20:38 +02:00
Matthias Einwag 8c89bfb751 Limit the amount of datagrams produced per connection driver iteration
Creating datagrams using `poll_transmit` is rather CPU intensive,
and while doing that no other events can be handled (e.g. processing new
incoming ACKs). This change places an upper bound on the amount of
datagrams produced.

This significantly reduces RTT for a loopback connection since the connection
is no longer busy producing packets.

Raw throughput can be a bit slower since a lot more ACK packets are now
processed in the loopback test. However RTT is halfed. The difference
in ACK packets (7200 vs 83000) and RTT (800us vs 400us) is very
visible in the benchmark:

**Before:**

```
Overall stats:

Sent 1073741824 bytes on 1 streams in 1.70s (603.88 MiB/s)

Server connection stats:
ConnectionStats {
    frame_tx: FrameStats {
        ACK: 7199,
    },
    frame_rx: FrameStats {
        ACK: 903129,
    },
    path: PathStats {
        rtt: 459.755µs,
        cwnd: 12320,
    },
}

Client connection stats:
ConnectionStats {
    frame_tx: FrameStats {
        ACK: 903129,
    },
    frame_rx: FrameStats {
        ACK: 7199,
    },
    path: PathStats {
        rtt: 769.84µs,
        cwnd: 1094264975,
    },
}
```

**After:**
```
Sent 5389680640 bytes on 1 streams in 8.51s (603.90 MiB/s)

Server connection stats:
ConnectionStats {
    frame_tx: FrameStats {
        ACK: 83555,
    },
    frame_rx: FrameStats {
        ACK: 4563602,
        STREAM: 4562497,
    },
    path: PathStats {
        rtt: 205.582µs,
        cwnd: 12320,
    },
}

Client connection stats:
ConnectionStats {
    frame_tx: FrameStats {
        ACK: 4563602,
        STREAM: 4562497,
    },
    frame_rx: FrameStats {
        ACK: 83555,
    },
    path: PathStats {
        rtt: 393.448µs,
        cwnd: 495821204,
    },
}
```
2021-05-23 23:52:25 +02:00
Matthias Einwag d350b87981 Limit the amount of endpoint events handled per iteration
This places a bound on the mount of events handled in `Endpoint::handle_events`.
It equals the amount of send events, because thats the upper limit
of packets that are transmitteable per iteration anyway.
2021-05-23 23:52:25 +02:00
Benjamin Saunders d573c7f8db Misc. doc fixes 2021-05-22 23:15:33 +02:00
Luca Barbato bd2a795dd9 Bind to the right ip family the local socket 2021-05-20 11:14:13 -07:00
Matthias Einwag fa0ac1f85a Yield to the executor in the the Endpoint
As long as there is data to send or receive the `Endpoint` `Future`
will currently continue to execute and thereby retain the eventloop.

In order to allow other code to execute in between we yield back
to the executor after each iteration. Instead of doing mulitiple
iterations in one `EndpointDriver::poll` call we can just increase the
maximum amount of packets to send or receive in a single iteration
and pick a number which optimizes performance.
Therefore the loop gets completely removed.
2021-05-16 17:47:53 -07:00
Matthias Einwag 304e5e4ea3 Unify IO_BOUND meaning for transmits and receives
The endpoint driver is currently utilizing a different behavior for transmits and receives:
- For receives it allows a certain number of datagrams to be received per Endpoint iteration
- For transmits it allows a certain number of transmit calls to be issued per Endpoint iteration.
  Each transmit call can transmit up to `BATCH_SIZE` transmits. Those might
  contain even more datagrams due to GSO.

This change unifies the behavior, and the bound will always limit the amount
of datagrams instead of `sendmsg/sendmmsg/recvmsg/recvmmsg` calls.
Given that a `sendmmsg` call for N datagrams is still roughly N times as
expensive as a sending a single datagram this makes sense.

The overall `IO_LOOP_BOUND` was adjusted to accomodate the new behavior.
`BATCH_SIZE` was modified to
2021-05-16 17:47:53 -07:00
Matthias Einwag fc950da8aa Avoid allocations for CIDs
When reading CIDs from packets the current code makes some short-lived
allocations due to the use of `copy_to_bytes`, which allocate for 1.5% of
CPU samples. This change avoids this.

**Before:**
```
Sent 1073741824 bytes on 1 streams in 1.72s (594.74 MiB/s)
```

**After:**
```
Sent 1073741824 bytes on 1 streams in 1.70s (602.78 MiB/s)
```
2021-05-14 08:31:01 +02:00
Matthias Einwag ce66efc045 Use an ArrayRangeSet to store new ACKs
This seems slightly more efficient than extending a `Vec`.
Would probably be even more efficient if there would be an intersection
method on `SentMap` insert of returning packet numbers one by one.

Peak perf before:
```
Sent 1073741824 bytes on 1 streams in 1.77s (577.02 MiB/s)
```

After:
```
Sent 1073741824 bytes on 1 streams in 1.75s (584.25 MiB/s)
```
2021-05-08 16:27:12 -07:00
Matthias Einwag a2af4c03b5 Rust 1.52 lint fix
Fix the new clippy finding
2021-05-08 20:26:23 +02:00
Matthias Einwag eb89e468ef Ack frame Debug implementation
This adds a manual `Debug` implementation for Ack frames, to make them understandable
in logs.

Old:
```
got frame Ack(Ack { largest: 11, delay: 0, additional: b"\x02", ecn: None })
```

New:
```
got frame Ack(Ack { largest: 9, delay: 0, ecn: None, ranges: "[8..=9]" }
```
2021-05-07 14:42:54 -07:00
Matthias Einwag c70763d040 Maximize CRYPTO data
This change increases the amount of `CRYPTO` data sent in packets during handshakes.

Instead of reserving a fixed amount of 17 bytes for frame overhead,
we reserve the exactly required amount of bytes to encode frame type
and offset, and only 2 bytes for the frame length. This leads to an
additional 12-13 bytes of payload data being used.
2021-05-04 21:49:34 +02:00
David Craven 6671d14032 Allow overriding supported versions in EndpointConfig (#1106) 2021-05-03 10:04:59 +02:00
Matthias Einwag 7d2e07f86f Reserve the maximum transmit buffer capacity upfront
The current implementation continously resizes the datagram buffer if
GSO is enabled and further datagrams are appended. Some benchmarking
and profiling showed that this doesn't have too much of an impact with
the glibc allocator, the strategy proved rather inefficient with pooling
memory allocators like jemalloc and mimalloc. For those, the cost of
calling `realloc` is rather high.

I benchmarked a bunch of strategies to determine the most efficient way forward:
1. Continously resize output buffer (Current approach)
2. Reserve maximum buffer size upfront
3. Reserve space for a single datagram to minimize over-allocation for
  tiny transmits. If this is not enough, reallocate once for maximum size

Based on the results of those, I am proposing to go for approach 2) and
simply allocate the maximum buffer size upfront, which yields maximum
efficiency for mimalloc + jemalloc.

## Benchmark results

### Glibc:

Baseline:

> Sent 1073741824 bytes on 1 streams in 1.79s (572.62 MiB/s)

Allocate for 1 MTU, then for `max_datagrams`:

> Sent 1073741824 bytes on 1 streams in 1.78s (576.31 MiB/s)

Allocate for `max_datagrams` upfront:

> Sent 1073741824 bytes on 1 streams in 1.79s (572.28 MiB/s)

### Mimalloc:

Baseline:

> Sent 1073741824 bytes on 1 streams in 1.84s (557.34 MiB/s)

Allocate for 1 MTU, then for `max_datagrams`:

> Sent 1073741824 bytes on 1 streams in 1.74s (587.76 MiB/s)

Allocate for `max_datagrams` upfront:

> Sent 1073741824 bytes on 1 streams in 1.71s (600.06 MiB/s)

### Jemalloc:

Baseline:

> Sent 1073741824 bytes on 1 streams in 1.86s (551.75 MiB/s)

Allocate for 1 MTU, then for `max_datagrams`:

> Sent 1073741824 bytes on 1 streams in 1.73s (592.50 MiB/s)

Allocate for `max_datagrams` upfront:

> Sent 1073741824 bytes on 1 streams in 1.72s (596.29 MiB/s)
2021-05-03 07:22:47 +02:00