litespeed-quic/src/liblsquic/lsquic_engine.c

3362 lines
110 KiB
C
Raw Normal View History

2022-05-06 16:49:46 +00:00
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
2017-09-22 21:00:03 +00:00
/*
* lsquic_engine.c - QUIC engine
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
2017-09-22 21:00:03 +00:00
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>
#ifndef WIN32
#include <sys/time.h>
2017-09-22 21:00:03 +00:00
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#endif
2017-09-22 21:00:03 +00:00
#ifndef NDEBUG
#include <sys/types.h>
2020-06-03 04:13:30 +00:00
#endif
#if defined(WIN32) || defined(NDEBUG)
#define CAN_LOSE_PACKETS 0
#else
#define CAN_LOSE_PACKETS 1
#endif
#if CAN_LOSE_PACKETS
#include <regex.h> /* For code that loses packets */
#endif
#if LOG_PACKET_CHECKSUM
#include <zlib.h>
#endif
2017-09-22 21:00:03 +00:00
#include <openssl/aead.h>
2017-09-22 21:00:03 +00:00
#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_sizes.h"
2018-08-15 19:06:31 +00:00
#include "lsquic_parse_common.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_parse.h"
#include "lsquic_packet_in.h"
#include "lsquic_packet_out.h"
#include "lsquic_senhist.h"
#include "lsquic_rtt.h"
#include "lsquic_cubic.h"
#include "lsquic_pacer.h"
#include "lsquic_bw_sampler.h"
#include "lsquic_minmax.h"
#include "lsquic_bbr.h"
#include "lsquic_adaptive_cc.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_set.h"
#include "lsquic_conn_flow.h"
#include "lsquic_sfcw.h"
#include "lsquic_hash.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_conn.h"
#include "lsquic_full_conn.h"
#include "lsquic_util.h"
#include "lsquic_qtags.h"
#include "lsquic_enc_sess.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lsquic_eng_hist.h"
#include "lsquic_ev_log.h"
#include "lsquic_version.h"
#include "lsquic_pr_queue.h"
#include "lsquic_mini_conn.h"
#include "lsquic_trechist.h"
#include "lsquic_mini_conn_ietf.h"
#include "lsquic_stock_shi.h"
#include "lsquic_purga.h"
#include "lsquic_tokgen.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_attq.h"
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
#include "lsquic_min_heap.h"
#include "lsquic_http1x_if.h"
#include "lsquic_handshake.h"
#include "lsquic_crand.h"
#include "lsquic_ietf.h"
#include "lsquic_handshake.h"
2017-09-22 21:00:03 +00:00
#define LSQUIC_LOGGER_MODULE LSQLM_ENGINE
#include "lsquic_logger.h"
#ifndef LSQUIC_DEBUG_NEXT_ADV_TICK
#define LSQUIC_DEBUG_NEXT_ADV_TICK 1
#endif
#if LSQUIC_DEBUG_NEXT_ADV_TICK || LSQUIC_CONN_STATS
#include "lsquic_alarmset.h"
#endif
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
2017-09-22 21:00:03 +00:00
/* The batch of outgoing packets grows and shrinks dynamically */
/* Batch sizes do not have to be powers of two */
2017-09-22 21:00:03 +00:00
#define MAX_OUT_BATCH_SIZE 1024
#define MIN_OUT_BATCH_SIZE 4
#define INITIAL_OUT_BATCH_SIZE 32
2017-09-22 21:00:03 +00:00
2018-04-02 19:16:01 +00:00
struct out_batch
{
lsquic_conn_t *conns [MAX_OUT_BATCH_SIZE];
struct lsquic_out_spec outs [MAX_OUT_BATCH_SIZE];
unsigned pack_off[MAX_OUT_BATCH_SIZE];
lsquic_packet_out_t *packets[MAX_OUT_BATCH_SIZE * 2];
struct iovec iov [MAX_OUT_BATCH_SIZE * 2];
2018-04-02 19:16:01 +00:00
};
2017-09-22 21:00:03 +00:00
typedef struct lsquic_conn * (*conn_iter_f)(struct lsquic_engine *);
static void
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
process_connections (struct lsquic_engine *engine, conn_iter_f iter,
lsquic_time_t now);
2017-09-22 21:00:03 +00:00
static void
engine_incref_conn (lsquic_conn_t *conn, enum lsquic_conn_flags flag);
static lsquic_conn_t *
engine_decref_conn (lsquic_engine_t *engine, lsquic_conn_t *conn,
enum lsquic_conn_flags flag);
static void
force_close_conn (lsquic_engine_t *engine, lsquic_conn_t *conn);
#if LSQUIC_CONN_STATS
static void
update_busy_detector (struct lsquic_engine *, struct lsquic_conn *, int);
#endif
#if LSQUIC_COUNT_ENGINE_CALLS
#define ENGINE_CALLS_INCR(e) do { ++(e)->n_engine_calls; } while (0)
#else
#define ENGINE_CALLS_INCR(e)
#endif
/* Nested calls to some LSQUIC functions are not supported. Functions that
* iterate over connections cannot be nested.
*/
2017-09-22 21:00:03 +00:00
#define ENGINE_IN(e) do { \
assert(!((e)->pub.enp_flags & ENPUB_PROC)); \
(e)->pub.enp_flags |= ENPUB_PROC; \
ENGINE_CALLS_INCR(e); \
2017-09-22 21:00:03 +00:00
} while (0)
#define ENGINE_OUT(e) do { \
assert((e)->pub.enp_flags & ENPUB_PROC); \
(e)->pub.enp_flags &= ~ENPUB_PROC; \
} while (0)
/* A connection can be referenced from one of six places:
2017-09-22 21:00:03 +00:00
*
* 1. A hash is used to find connections in order to dispatch an incoming
* packet. Connections can be hashed by CIDs or by address. In the
* former case, each connection has one or more mappings in the hash
* table. IETF QUIC connections have up to eight (in our implementation)
* source CIDs and each of those would have a mapping. In client mode,
* depending on QUIC versions and options selected, it is may be
* necessary to hash connections by address, in which case incoming
* packets are delivered to connections based on the address.
2017-09-22 21:00:03 +00:00
*
* 2. Outgoing queue.
*
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
* 3. Tickable queue
2017-09-22 21:00:03 +00:00
*
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
* 4. Advisory Tick Time queue.
2017-09-22 21:00:03 +00:00
*
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
* 5. Closing connections queue. This is a transient queue -- it only
2017-09-22 21:00:03 +00:00
* exists for the duration of process_connections() function call.
*
* 6. Ticked connections queue. Another transient queue, similar to (5).
*
2017-09-22 21:00:03 +00:00
* The idea is to destroy the connection when it is no longer referenced.
* For example, a connection tick may return TICK_SEND|TICK_CLOSE. In
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
* that case, the connection is referenced from two places: (2) and (5).
* After its packets are sent, it is only referenced in (5), and at the
* end of the function call, when it is removed from (5), reference count
2017-09-22 21:00:03 +00:00
* goes to zero and the connection is destroyed. If not all packets can
* be sent, at the end of the function call, the connection is referenced
* by (2) and will only be removed once all outgoing packets have been
* sent.
*/
#define CONN_REF_FLAGS (LSCONN_HASHED \
|LSCONN_HAS_OUTGOING \
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
|LSCONN_TICKABLE \
|LSCONN_TICKED \
2017-09-22 21:00:03 +00:00
|LSCONN_CLOSING \
|LSCONN_ATTQ)
struct cid_update_batch
{
lsquic_cids_update_f cub_update_cids;
void *cub_update_ctx;
unsigned cub_count;
lsquic_cid_t cub_cids[20];
void *cub_peer_ctxs[20];
};
static void
cub_init (struct cid_update_batch *, lsquic_cids_update_f, void *);
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
2017-09-22 21:00:03 +00:00
struct lsquic_engine
{
struct lsquic_engine_public pub;
enum {
ENG_SERVER = LSENG_SERVER,
ENG_HTTP = LSENG_HTTP,
ENG_COOLDOWN = (1 << 7), /* Cooldown: no new connections */
ENG_PAST_DEADLINE
= (1 << 8), /* Previous call to a processing
* function went past time threshold.
*/
ENG_CONNS_BY_ADDR
= (1 << 9), /* Connections are hashed by address */
2017-09-22 21:00:03 +00:00
#ifndef NDEBUG
ENG_COALESCE = (1 << 24), /* Packet coalescing is enabled */
2020-06-03 04:13:30 +00:00
#endif
#if CAN_LOSE_PACKETS
ENG_LOSE_PACKETS= (1 << 25), /* Lose *some* outgoing packets */
2020-06-03 04:13:30 +00:00
#endif
#ifndef NDEBUG
2017-09-22 21:00:03 +00:00
ENG_DTOR = (1 << 26), /* Engine destructor */
#endif
} flags;
lsquic_packets_out_f packets_out;
void *packets_out_ctx;
lsquic_cids_update_f report_new_scids;
lsquic_cids_update_f report_live_scids;
lsquic_cids_update_f report_old_scids;
void *scids_ctx;
struct lsquic_hash *conns_hash;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
struct min_heap conns_tickable;
struct min_heap conns_out;
2017-09-22 21:00:03 +00:00
struct eng_hist history;
unsigned batch_size;
unsigned min_batch_size, max_batch_size;
struct lsquic_conn *curr_conn;
struct pr_queue *pr_queue;
2017-09-22 21:00:03 +00:00
struct attq *attq;
/* Track time last time a packet was sent to give new connections
* priority lower than that of existing connections.
*/
lsquic_time_t last_sent;
2020-06-03 04:13:30 +00:00
#if CAN_LOSE_PACKETS
regex_t lose_packets_re;
const char *lose_packets_str;
#endif
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
unsigned n_conns;
2017-09-22 21:00:03 +00:00
lsquic_time_t deadline;
lsquic_time_t resume_sending_at;
unsigned mini_conns_count;
struct lsquic_purga *purga;
#if LSQUIC_CONN_STATS
struct {
unsigned conns;
} stats;
struct conn_stats conn_stats_sum;
FILE *stats_fh;
#endif
struct cid_update_batch new_scids;
2018-04-02 19:16:01 +00:00
struct out_batch out_batch;
#if LSQUIC_COUNT_ENGINE_CALLS
unsigned long n_engine_calls;
#endif
#if LSQUIC_DEBUG_NEXT_ADV_TICK
uintptr_t last_logged_conn;
unsigned last_logged_ae_why;
int last_tick_diff;
#endif
struct crand crand;
EVP_AEAD_CTX retry_aead_ctx[N_IETF_RETRY_VERSIONS];
#if LSQUIC_CONN_STATS
struct {
uint16_t immed_ticks; /* bitmask */
#define MAX_IMMED_TICKS UINT16_MAX
struct lsquic_conn *last_conn, /* from last call */
*pin_conn, /* last connection with packet in */
*current; /* currently busy connection */
lsquic_time_t last_log;
} busy;
#endif
2017-09-22 21:00:03 +00:00
};
void
lsquic_engine_init_settings (struct lsquic_engine_settings *settings,
unsigned flags)
{
memset(settings, 0, sizeof(*settings));
settings->es_versions = LSQUIC_DF_VERSIONS;
if (flags & ENG_SERVER)
{
settings->es_cfcw = LSQUIC_DF_CFCW_SERVER;
settings->es_sfcw = LSQUIC_DF_SFCW_SERVER;
settings->es_init_max_data
= LSQUIC_DF_INIT_MAX_DATA_SERVER;
settings->es_init_max_stream_data_bidi_remote
= LSQUIC_DF_INIT_MAX_STREAM_DATA_BIDI_REMOTE_SERVER;
settings->es_init_max_stream_data_bidi_local
= LSQUIC_DF_INIT_MAX_STREAM_DATA_BIDI_LOCAL_SERVER;
settings->es_init_max_stream_data_uni
= LSQUIC_DF_INIT_MAX_STREAM_DATA_UNI_SERVER;
settings->es_init_max_streams_uni
= LSQUIC_DF_INIT_MAX_STREAMS_UNI_SERVER;
settings->es_ping_period = 0;
settings->es_noprogress_timeout
= LSQUIC_DF_NOPROGRESS_TIMEOUT_SERVER;
2017-09-22 21:00:03 +00:00
}
else
{
settings->es_cfcw = LSQUIC_DF_CFCW_CLIENT;
settings->es_sfcw = LSQUIC_DF_SFCW_CLIENT;
settings->es_init_max_data
= LSQUIC_DF_INIT_MAX_DATA_CLIENT;
settings->es_init_max_stream_data_bidi_remote
= LSQUIC_DF_INIT_MAX_STREAM_DATA_BIDI_REMOTE_CLIENT;
settings->es_init_max_stream_data_bidi_local
= LSQUIC_DF_INIT_MAX_STREAM_DATA_BIDI_LOCAL_CLIENT;
settings->es_init_max_stream_data_uni
= LSQUIC_DF_INIT_MAX_STREAM_DATA_UNI_CLIENT;
settings->es_init_max_streams_uni
= LSQUIC_DF_INIT_MAX_STREAMS_UNI_CLIENT;
settings->es_ping_period = LSQUIC_DF_PING_PERIOD;
settings->es_noprogress_timeout
= LSQUIC_DF_NOPROGRESS_TIMEOUT_CLIENT;
2017-09-22 21:00:03 +00:00
}
settings->es_max_streams_in = LSQUIC_DF_MAX_STREAMS_IN;
settings->es_idle_conn_to = LSQUIC_DF_IDLE_CONN_TO;
settings->es_idle_timeout = LSQUIC_DF_IDLE_TIMEOUT;
2017-09-22 21:00:03 +00:00
settings->es_handshake_to = LSQUIC_DF_HANDSHAKE_TO;
settings->es_silent_close = LSQUIC_DF_SILENT_CLOSE;
settings->es_max_header_list_size
= LSQUIC_DF_MAX_HEADER_LIST_SIZE;
settings->es_ua = LSQUIC_DF_UA;
settings->es_ecn = LSQUIC_DF_ECN;
2017-09-22 21:00:03 +00:00
settings->es_pdmd = QTAG_X509;
settings->es_aead = QTAG_AESG;
settings->es_kexs = QTAG_C255;
settings->es_support_push = LSQUIC_DF_SUPPORT_PUSH;
settings->es_support_tcid0 = LSQUIC_DF_SUPPORT_TCID0;
settings->es_support_nstp = LSQUIC_DF_SUPPORT_NSTP;
settings->es_honor_prst = LSQUIC_DF_HONOR_PRST;
settings->es_progress_check = LSQUIC_DF_PROGRESS_CHECK;
settings->es_rw_once = LSQUIC_DF_RW_ONCE;
settings->es_proc_time_thresh= LSQUIC_DF_PROC_TIME_THRESH;
settings->es_pace_packets = LSQUIC_DF_PACE_PACKETS;
settings->es_clock_granularity = LSQUIC_DF_CLOCK_GRANULARITY;
settings->es_max_inchoate = LSQUIC_DF_MAX_INCHOATE;
settings->es_send_prst = LSQUIC_DF_SEND_PRST;
settings->es_sttl = LSQUIC_DF_STTL;
settings->es_init_max_streams_bidi
= LSQUIC_DF_INIT_MAX_STREAMS_BIDI;
settings->es_scid_len = LSQUIC_DF_SCID_LEN;
settings->es_scid_iss_rate = LSQUIC_DF_SCID_ISS_RATE;
settings->es_qpack_dec_max_size = LSQUIC_DF_QPACK_DEC_MAX_SIZE;
settings->es_qpack_dec_max_blocked = LSQUIC_DF_QPACK_DEC_MAX_BLOCKED;
settings->es_qpack_enc_max_size = LSQUIC_DF_QPACK_ENC_MAX_SIZE;
settings->es_qpack_enc_max_blocked = LSQUIC_DF_QPACK_ENC_MAX_BLOCKED;
settings->es_allow_migration = LSQUIC_DF_ALLOW_MIGRATION;
settings->es_ql_bits = LSQUIC_DF_QL_BITS;
settings->es_spin = LSQUIC_DF_SPIN;
settings->es_delayed_acks = LSQUIC_DF_DELAYED_ACKS;
settings->es_timestamps = LSQUIC_DF_TIMESTAMPS;
settings->es_grease_quic_bit = LSQUIC_DF_GREASE_QUIC_BIT;
settings->es_mtu_probe_timer = LSQUIC_DF_MTU_PROBE_TIMER;
settings->es_dplpmtud = LSQUIC_DF_DPLPMTUD;
settings->es_cc_algo = LSQUIC_DF_CC_ALGO;
settings->es_cc_rtt_thresh = LSQUIC_DF_CC_RTT_THRESH;
settings->es_optimistic_nat = LSQUIC_DF_OPTIMISTIC_NAT;
settings->es_ext_http_prio = LSQUIC_DF_EXT_HTTP_PRIO;
settings->es_ptpc_periodicity= LSQUIC_DF_PTPC_PERIODICITY;
settings->es_ptpc_max_packtol= LSQUIC_DF_PTPC_MAX_PACKTOL;
settings->es_ptpc_dyn_target = LSQUIC_DF_PTPC_DYN_TARGET;
settings->es_ptpc_target = LSQUIC_DF_PTPC_TARGET;
settings->es_ptpc_prop_gain = LSQUIC_DF_PTPC_PROP_GAIN;
settings->es_ptpc_int_gain = LSQUIC_DF_PTPC_INT_GAIN;
settings->es_ptpc_err_thresh = LSQUIC_DF_PTPC_ERR_THRESH;
settings->es_ptpc_err_divisor= LSQUIC_DF_PTPC_ERR_DIVISOR;
settings->es_delay_onclose = LSQUIC_DF_DELAY_ONCLOSE;
settings->es_check_tp_sanity = LSQUIC_DF_CHECK_TP_SANITY;
2017-09-22 21:00:03 +00:00
}
/* Note: if returning an error, err_buf must be valid if non-NULL */
int
lsquic_engine_check_settings (const struct lsquic_engine_settings *settings,
unsigned flags,
char *err_buf, size_t err_buf_sz)
{
if (settings->es_cfcw < LSQUIC_MIN_FCW ||
settings->es_sfcw < LSQUIC_MIN_FCW)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "%s",
"flow control window set too low");
return -1;
}
if (0 == (settings->es_versions & LSQUIC_SUPPORTED_VERSIONS))
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "%s",
"No supported QUIC versions specified");
return -1;
}
if (settings->es_versions & ~LSQUIC_SUPPORTED_VERSIONS)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "%s",
"one or more unsupported QUIC version is specified");
return -1;
}
if (flags & ENG_SERVER)
{
if (settings->es_handshake_to >
MAX_MINI_CONN_LIFESPAN_IN_USEC)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "handshake timeout %lu"
" usec is too large. The maximum for server is %u usec",
settings->es_handshake_to, MAX_MINI_CONN_LIFESPAN_IN_USEC);
return -1;
}
}
if (settings->es_idle_timeout > 600)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "%s",
"The maximum value of idle timeout is 600 seconds");
return -1;
}
if (settings->es_scid_len > MAX_CID_LEN)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "Source connection ID cannot be %u "
"bytes long; it must be between 0 and %u.",
settings->es_scid_len, MAX_CID_LEN);
return -1;
}
if (settings->es_cc_algo > 3)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "Invalid congestion control "
"algorithm value %u", settings->es_cc_algo);
return -1;
}
if (!(settings->es_ql_bits >= 0 && settings->es_ql_bits <= 2))
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "Invalid QL bits value %d ",
settings->es_ql_bits);
return -1;
}
if (!(settings->es_spin == 0 || settings->es_spin == 1))
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "Invalid spin value %d",
settings->es_spin);
return -1;
}
if (settings->es_mtu_probe_timer && settings->es_mtu_probe_timer < 1000)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "mtu probe timer is too small: "
"%u ms", settings->es_mtu_probe_timer);
return -1;
}
if (settings->es_max_batch_size > MAX_OUT_BATCH_SIZE)
{
if (err_buf)
snprintf(err_buf, err_buf_sz, "max batch size is greater than "
"the allowed maximum of %u", (unsigned) MAX_OUT_BATCH_SIZE);
return -1;
}
2017-09-22 21:00:03 +00:00
return 0;
}
static void
free_packet (void *ctx, void *conn_ctx, void *packet_data, char is_ipv6)
2017-09-22 21:00:03 +00:00
{
free(packet_data);
}
static void *
2020-10-03 05:48:01 +00:00
malloc_buf (void *ctx, void *peer_ctx, lsquic_conn_ctx_t *conn_ctx, unsigned short size, char is_ipv6)
2017-09-22 21:00:03 +00:00
{
return malloc(size);
}
static const struct lsquic_packout_mem_if stock_pmi =
{
malloc_buf, free_packet, free_packet,
2017-09-22 21:00:03 +00:00
};
2018-08-15 19:06:31 +00:00
static int
hash_conns_by_addr (const struct lsquic_engine *engine)
{
if (engine->flags & ENG_SERVER)
return 0;
2018-08-15 19:06:31 +00:00
if (engine->pub.enp_settings.es_versions & LSQUIC_FORCED_TCID0_VERSIONS)
return 1;
if ((engine->pub.enp_settings.es_versions & LSQUIC_GQUIC_HEADER_VERSIONS)
&& engine->pub.enp_settings.es_support_tcid0)
return 1;
if (engine->pub.enp_settings.es_scid_len == 0)
return 1;
2018-08-15 19:06:31 +00:00
return 0;
}
2017-09-22 21:00:03 +00:00
lsquic_engine_t *
lsquic_engine_new (unsigned flags,
const struct lsquic_engine_api *api)
{
lsquic_engine_t *engine;
size_t alpn_len;
unsigned i;
2017-09-22 21:00:03 +00:00
char err_buf[100];
if (!api->ea_packets_out)
{
LSQ_ERROR("packets_out callback is not specified");
return NULL;
}
if (!api->ea_stream_if)
{
LSQ_ERROR("stream interface is not specified");
return NULL;
}
if (!(flags & LSENG_HTTP) && api->ea_alpn)
{
alpn_len = strlen(api->ea_alpn);
if (alpn_len < 1 || alpn_len > 255)
{
LSQ_ERROR("ALPN string length invalid: %zd bytes", alpn_len);
return NULL;
}
}
else
alpn_len = 0;
2017-09-22 21:00:03 +00:00
if (api->ea_settings &&
0 != lsquic_engine_check_settings(api->ea_settings, flags,
err_buf, sizeof(err_buf)))
{
LSQ_ERROR("cannot create engine: %s", err_buf);
return NULL;
}
engine = calloc(1, sizeof(*engine));
if (!engine)
return NULL;
if (0 != lsquic_mm_init(&engine->pub.enp_mm))
{
free(engine);
return NULL;
}
if (api->ea_settings)
engine->pub.enp_settings = *api->ea_settings;
else
lsquic_engine_init_settings(&engine->pub.enp_settings, flags);
int tag_buf_len;
tag_buf_len = lsquic_gen_ver_tags(engine->pub.enp_ver_tags_buf,
sizeof(engine->pub.enp_ver_tags_buf),
engine->pub.enp_settings.es_versions);
if (tag_buf_len <= 0)
{
LSQ_ERROR("cannot generate version tags buffer");
free(engine);
return NULL;
}
engine->pub.enp_ver_tags_len = tag_buf_len;
engine->pub.enp_flags = ENPUB_CAN_SEND;
engine->pub.enp_stream_if = api->ea_stream_if;
engine->pub.enp_stream_if_ctx = api->ea_stream_if_ctx;
2017-09-22 21:00:03 +00:00
engine->flags = flags;
#ifndef NDEBUG
engine->flags |= ENG_COALESCE;
#endif
2017-09-22 21:00:03 +00:00
engine->packets_out = api->ea_packets_out;
engine->packets_out_ctx = api->ea_packets_out_ctx;
engine->report_new_scids = api->ea_new_scids;
engine->report_live_scids = api->ea_live_scids;
engine->report_old_scids = api->ea_old_scids;
engine->scids_ctx = api->ea_cids_update_ctx;
cub_init(&engine->new_scids, engine->report_new_scids, engine->scids_ctx);
engine->pub.enp_lookup_cert = api->ea_lookup_cert;
engine->pub.enp_cert_lu_ctx = api->ea_cert_lu_ctx;
engine->pub.enp_get_ssl_ctx = api->ea_get_ssl_ctx;
2020-09-26 18:11:28 +00:00
if (api->ea_generate_scid)
{
engine->pub.enp_generate_scid = api->ea_generate_scid;
engine->pub.enp_gen_scid_ctx = api->ea_gen_scid_ctx;
}
else
engine->pub.enp_generate_scid = lsquic_generate_scid;
2020-09-26 18:11:28 +00:00
if (api->ea_shi)
{
engine->pub.enp_shi = api->ea_shi;
engine->pub.enp_shi_ctx = api->ea_shi_ctx;
}
else
{
engine->pub.enp_shi = &stock_shi;
engine->pub.enp_shi_ctx = lsquic_stock_shared_hash_new();
if (!engine->pub.enp_shi_ctx)
{
free(engine);
return NULL;
}
}
if (api->ea_hsi_if)
{
engine->pub.enp_hsi_if = api->ea_hsi_if;
engine->pub.enp_hsi_ctx = api->ea_hsi_ctx;
}
else
{
engine->pub.enp_hsi_if = lsquic_http1x_if;
engine->pub.enp_hsi_ctx = NULL;
}
2017-09-22 21:00:03 +00:00
if (api->ea_pmi)
{
engine->pub.enp_pmi = api->ea_pmi;
engine->pub.enp_pmi_ctx = api->ea_pmi_ctx;
}
else
{
engine->pub.enp_pmi = &stock_pmi;
engine->pub.enp_pmi_ctx = NULL;
}
engine->pub.enp_verify_cert = api->ea_verify_cert;
engine->pub.enp_verify_ctx = api->ea_verify_ctx;
2017-09-22 21:00:03 +00:00
engine->pub.enp_engine = engine;
if (hash_conns_by_addr(engine))
engine->flags |= ENG_CONNS_BY_ADDR;
engine->conns_hash = lsquic_hash_create();
engine->pub.enp_tokgen = lsquic_tg_new(&engine->pub);
if (!engine->pub.enp_tokgen)
return NULL;
if (engine->flags & ENG_SERVER)
for (i = 0; i < sizeof(engine->pub.enp_quic_ctx_sz)
/ sizeof(engine->pub.enp_quic_ctx_sz[0]); ++i)
{
int sz = lsquic_enc_sess_ietf_gen_quic_ctx(
&engine->pub.enp_settings,
i == 0 ? LSQVER_ID27 : LSQVER_ID29,
engine->pub.enp_quic_ctx_buf[i],
sizeof(engine->pub.enp_quic_ctx_buf));
if (sz < 0)
{
free(engine);
return NULL;
}
engine->pub.enp_quic_ctx_sz[i] = (unsigned) sz;
}
engine->pub.enp_crand = &engine->crand;
if (engine->pub.enp_settings.es_noprogress_timeout)
engine->pub.enp_noprog_timeout
= engine->pub.enp_settings.es_noprogress_timeout * 1000000;
engine->pub.enp_mtu_probe_timer = 1000
* (engine->pub.enp_settings.es_mtu_probe_timer
? engine->pub.enp_settings.es_mtu_probe_timer
: LSQUIC_DF_MTU_PROBE_TIMER);
if (flags & ENG_SERVER)
{
engine->pr_queue = lsquic_prq_create(
10000 /* TODO: make configurable */, MAX_OUT_BATCH_SIZE,
&engine->pub);
if (!engine->pr_queue)
{
lsquic_tg_destroy(engine->pub.enp_tokgen);
return NULL;
}
engine->purga = lsquic_purga_new(30 * 1000 * 1000,
engine->report_old_scids, engine->scids_ctx);
if (!engine->purga)
{
lsquic_tg_destroy(engine->pub.enp_tokgen);
lsquic_prq_destroy(engine->pr_queue);
return NULL;
}
}
engine->attq = lsquic_attq_create();
2017-09-22 21:00:03 +00:00
eng_hist_init(&engine->history);
if (engine->pub.enp_settings.es_max_batch_size)
{
engine->max_batch_size = engine->pub.enp_settings.es_max_batch_size;
engine->min_batch_size = MIN(4, engine->max_batch_size);
engine->batch_size = MAX(engine->max_batch_size / 4,
engine->min_batch_size);
}
else
{
engine->min_batch_size = MIN_OUT_BATCH_SIZE;
engine->max_batch_size = MAX_OUT_BATCH_SIZE;
engine->batch_size = INITIAL_OUT_BATCH_SIZE;
}
if (engine->pub.enp_settings.es_honor_prst)
{
engine->pub.enp_srst_hash = lsquic_hash_create();
if (!engine->pub.enp_srst_hash)
{
lsquic_engine_destroy(engine);
return NULL;
}
}
if ((flags & LSENG_SERVER) && 0 != lsquic_init_gquic_crypto(&engine->pub))
{
lsquic_engine_destroy(engine);
return NULL;
}
2017-09-22 21:00:03 +00:00
if (alpn_len)
{
engine->pub.enp_alpn = malloc(alpn_len + 1);
if (!engine->pub.enp_alpn)
{
lsquic_engine_destroy(engine);
return NULL;
}
engine->pub.enp_alpn[0] = alpn_len;
memcpy(engine->pub.enp_alpn + 1, api->ea_alpn, alpn_len);
}
if (flags & LSENG_HTTP)
engine->pub.enp_flags |= ENPUB_HTTP;
#ifndef NDEBUG
{
const char *env;
env = getenv("LSQUIC_LOSE_PACKETS_RE");
2020-06-03 04:13:30 +00:00
#if CAN_LOSE_PACKETS
if (env)
{
if (0 != regcomp(&engine->lose_packets_re, env,
REG_EXTENDED|REG_NOSUB))
{
LSQ_ERROR("could not compile lost packet regex `%s'", env);
return NULL;
}
engine->flags |= ENG_LOSE_PACKETS;
engine->lose_packets_str = env;
LSQ_WARN("will lose packets that match the following regex: %s",
env);
}
2020-06-03 04:13:30 +00:00
#endif
env = getenv("LSQUIC_COALESCE");
if (env)
{
engine->flags &= ~ENG_COALESCE;
if (atoi(env))
{
engine->flags |= ENG_COALESCE;
LSQ_NOTICE("will coalesce packets");
}
else
LSQ_NOTICE("will not coalesce packets");
}
}
#endif
#if LSQUIC_CONN_STATS
engine->stats_fh = api->ea_stats_fh;
#endif
for (i = 0; i < sizeof(engine->retry_aead_ctx)
/ sizeof(engine->retry_aead_ctx[0]); ++i)
if (1 != EVP_AEAD_CTX_init(&engine->retry_aead_ctx[i],
EVP_aead_aes_128_gcm(), lsquic_retry_key_buf[i],
IETF_RETRY_KEY_SZ, 16, NULL))
{
LSQ_ERROR("could not initialize retry AEAD ctx #%u", i);
lsquic_engine_destroy(engine);
return NULL;
}
engine->pub.enp_retry_aead_ctx = engine->retry_aead_ctx;
2017-09-22 21:00:03 +00:00
LSQ_INFO("instantiated engine");
return engine;
}
#if LOG_PACKET_CHECKSUM
static void
log_packet_checksum (const lsquic_cid_t *cid, const char *direction,
const unsigned char *buf, size_t bufsz)
{
EV_LOG_CONN_EVENT(cid, "packet %s checksum: %08X", direction,
(uint32_t) crc32(0, buf, bufsz));
}
#endif
2017-09-22 21:00:03 +00:00
static void
grow_batch_size (struct lsquic_engine *engine)
{
engine->batch_size = MIN(engine->batch_size * 2, engine->max_batch_size);
2017-09-22 21:00:03 +00:00
}
static void
shrink_batch_size (struct lsquic_engine *engine)
{
engine->batch_size = MAX(engine->batch_size / 2, engine->min_batch_size);
2017-09-22 21:00:03 +00:00
}
struct cce_cid_iter
{
const struct lsquic_conn *conn;
unsigned todo, n;
};
static struct conn_cid_elem *
cce_iter_next (struct cce_cid_iter *citer)
{
struct conn_cid_elem *cce;
while (citer->todo)
if (citer->todo & (1 << citer->n))
{
citer->todo &= ~(1 << citer->n);
cce = &citer->conn->cn_cces[ citer->n++ ];
if (!(cce->cce_flags & CCE_PORT))
return cce;
}
else
++citer->n;
return NULL;
}
static struct conn_cid_elem *
cce_iter_first (struct cce_cid_iter *citer, const struct lsquic_conn *conn)
{
citer->conn = conn;
citer->todo = conn->cn_cces_mask;
citer->n = 0;
return cce_iter_next(citer);
}
#if LSQUIC_CONN_STATS
void
update_stats_sum (struct lsquic_engine *engine, struct lsquic_conn *conn)
{
unsigned long *const dst = (unsigned long *) &engine->conn_stats_sum;
const unsigned long *src;
const struct conn_stats *stats;
unsigned i;
if (conn->cn_if->ci_get_stats && (stats = conn->cn_if->ci_get_stats(conn)))
{
++engine->stats.conns;
src = (unsigned long *) stats;
for (i = 0; i < sizeof(*stats) / sizeof(unsigned long); ++i)
dst[i] += src[i];
}
}
#endif
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
/* Wrapper to make sure important things occur before the connection is
* really destroyed.
*/
2017-09-22 21:00:03 +00:00
static void
destroy_conn (struct lsquic_engine *engine, struct lsquic_conn *conn,
lsquic_time_t now)
2017-09-22 21:00:03 +00:00
{
struct cce_cid_iter citer;
const struct conn_cid_elem *cce;
lsquic_time_t drain_time;
struct purga_el *puel;
engine->mini_conns_count -= !!(conn->cn_flags & LSCONN_MINI);
if (engine->purga
/* Blacklist all CIDs except for promoted mini connections */
&& (conn->cn_flags & (LSCONN_MINI|LSCONN_PROMOTED))
!= (LSCONN_MINI|LSCONN_PROMOTED))
{
if (!(conn->cn_flags & LSCONN_IMMED_CLOSE)
&& conn->cn_if->ci_drain_time &&
(drain_time = conn->cn_if->ci_drain_time(conn), drain_time))
{
for (cce = cce_iter_first(&citer, conn); cce;
cce = cce_iter_next(&citer))
{
puel = lsquic_purga_add(engine->purga, &cce->cce_cid,
lsquic_conn_get_peer_ctx(conn, NULL),
PUTY_CONN_DRAIN, now);
if (puel)
puel->puel_time = now + drain_time;
}
}
else
{
for (cce = cce_iter_first(&citer, conn); cce;
cce = cce_iter_next(&citer))
{
puel = lsquic_purga_add(engine->purga, &cce->cce_cid,
lsquic_conn_get_peer_ctx(conn, NULL),
PUTY_CONN_DELETED, now);
if (puel)
{
puel->puel_time = now;
puel->puel_count = 0;
}
}
}
}
#if LSQUIC_CONN_STATS
update_stats_sum(engine, conn);
if (engine->busy.last_conn == conn)
engine->busy.last_conn = NULL;
if (engine->busy.current == conn)
{
char cidstr[MAX_CID_LEN * 2 + 1];
lsquic_logger_log1(LSQ_LOG_NOTICE, LSQLM_CONN_STATS,
"busy connection %s is destroyed",
(lsquic_cid2str(lsquic_conn_log_cid(conn), cidstr), cidstr));
engine->busy.current = NULL;
engine->busy.last_log = 0;
}
if (engine->busy.pin_conn == conn)
engine->busy.pin_conn = NULL;
#endif
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
--engine->n_conns;
conn->cn_flags |= LSCONN_NEVER_TICKABLE;
2017-09-22 21:00:03 +00:00
conn->cn_if->ci_destroy(conn);
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
static int
maybe_grow_conn_heaps (struct lsquic_engine *engine)
{
struct min_heap_elem *els;
unsigned count;
if (engine->n_conns < lsquic_mh_nalloc(&engine->conns_tickable))
return 0; /* Nothing to do */
if (lsquic_mh_nalloc(&engine->conns_tickable))
count = lsquic_mh_nalloc(&engine->conns_tickable) * 2 * 2;
else
count = 8;
els = malloc(sizeof(els[0]) * count);
if (!els)
{
LSQ_ERROR("%s: malloc failed", __func__);
return -1;
}
LSQ_DEBUG("grew heaps to %u elements", count / 2);
memcpy(&els[0], engine->conns_tickable.mh_elems,
sizeof(els[0]) * lsquic_mh_count(&engine->conns_tickable));
memcpy(&els[count / 2], engine->conns_out.mh_elems,
sizeof(els[0]) * lsquic_mh_count(&engine->conns_out));
free(engine->conns_tickable.mh_elems);
engine->conns_tickable.mh_elems = els;
engine->conns_out.mh_elems = &els[count / 2];
engine->conns_tickable.mh_nalloc = count / 2;
engine->conns_out.mh_nalloc = count / 2;
return 0;
}
static void
remove_cces_from_hash (struct lsquic_hash *hash, struct lsquic_conn *conn,
unsigned todo)
{
unsigned n;
for (n = 0; todo; todo &= ~(1 << n++))
if ((todo & (1 << n)) &&
(conn->cn_cces[n].cce_hash_el.qhe_flags & QHE_HASHED))
lsquic_hash_erase(hash, &conn->cn_cces[n].cce_hash_el);
}
static void
remove_all_cces_from_hash (struct lsquic_hash *hash, struct lsquic_conn *conn)
{
remove_cces_from_hash(hash, conn, conn->cn_cces_mask);
}
static void
cub_add (struct cid_update_batch *cub, const lsquic_cid_t *cid, void *peer_ctx);
static int
insert_conn_into_hash (struct lsquic_engine *engine, struct lsquic_conn *conn,
void *peer_ctx)
{
struct conn_cid_elem *cce;
unsigned todo, done, n;
for (todo = conn->cn_cces_mask, done = 0, n = 0; todo; todo &= ~(1 << n++))
if (todo & (1 << n))
{
cce = &conn->cn_cces[n];
assert(!(cce->cce_hash_el.qhe_flags & QHE_HASHED));
if (lsquic_hash_insert(engine->conns_hash, cce->cce_cid.idbuf,
cce->cce_cid.len, conn, &cce->cce_hash_el))
done |= 1 << n;
else
goto err;
if ((engine->flags & ENG_SERVER) && 0 == (cce->cce_flags & CCE_REG))
{
cce->cce_flags |= CCE_REG;
cub_add(&engine->new_scids, &cce->cce_cid, peer_ctx);
}
}
return 0;
err:
remove_cces_from_hash(engine->conns_hash, conn, done);
return -1;
}
2017-09-22 21:00:03 +00:00
static lsquic_conn_t *
new_full_conn_server (lsquic_engine_t *engine, lsquic_conn_t *mini_conn,
lsquic_time_t now)
2017-09-22 21:00:03 +00:00
{
const lsquic_cid_t *cid;
server_conn_ctor_f ctor;
2017-09-22 21:00:03 +00:00
lsquic_conn_t *conn;
unsigned flags;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (0 != maybe_grow_conn_heaps(engine))
return NULL;
2017-09-22 21:00:03 +00:00
flags = engine->flags & (ENG_SERVER|ENG_HTTP);
if (mini_conn->cn_flags & LSCONN_IETF)
ctor = lsquic_ietf_full_conn_server_new;
else
ctor = lsquic_gquic_full_conn_server_new;
conn = ctor(&engine->pub, flags, mini_conn);
2017-09-22 21:00:03 +00:00
if (!conn)
{
/* Otherwise, full_conn_server_new prints its own warnings */
if (ENOMEM == errno)
{
cid = lsquic_conn_log_cid(mini_conn);
LSQ_WARNC("could not allocate full connection for %"CID_FMT": %s",
CID_BITS(cid), strerror(errno));
}
2017-09-22 21:00:03 +00:00
return NULL;
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
++engine->n_conns;
if (0 != insert_conn_into_hash(engine, conn, lsquic_conn_get_peer_ctx(conn, NULL)))
{
cid = lsquic_conn_log_cid(conn);
LSQ_WARNC("cannot add connection %"CID_FMT" to hash - destroy",
CID_BITS(cid));
destroy_conn(engine, conn, now);
return NULL;
}
assert(!(conn->cn_flags & CONN_REF_FLAGS));
conn->cn_flags |= LSCONN_HASHED;
2017-09-22 21:00:03 +00:00
return conn;
}
static enum
{
VER_NOT_SPECIFIED,
VER_SUPPORTED,
VER_UNSUPPORTED,
}
version_matches (lsquic_engine_t *engine, const lsquic_packet_in_t *packet_in,
enum lsquic_version *pversion)
{
lsquic_ver_tag_t ver_tag;
enum lsquic_version version;
if (!packet_in->pi_quic_ver)
{
LSQ_DEBUG("packet does not specify version");
return VER_NOT_SPECIFIED;
}
memcpy(&ver_tag, packet_in->pi_data + packet_in->pi_quic_ver, sizeof(ver_tag));
version = lsquic_tag2ver(ver_tag);
if (version < N_LSQVER)
{
if (engine->pub.enp_settings.es_versions & (1 << version))
{
LSQ_DEBUG("client-supplied version %s is supported",
lsquic_ver2str[version]);
*pversion = version;
return VER_SUPPORTED;
}
else
LSQ_DEBUG("client-supplied version %s is not supported",
lsquic_ver2str[version]);
}
else
LSQ_DEBUG("client-supplied version tag 0x%08X is not recognized",
ver_tag);
return VER_UNSUPPORTED;
}
static void
schedule_req_packet (struct lsquic_engine *engine, enum packet_req_type type,
const struct lsquic_packet_in *packet_in, const struct sockaddr *sa_local,
const struct sockaddr *sa_peer, void *peer_ctx)
{
assert(engine->pr_queue);
if (0 == lsquic_prq_new_req(engine->pr_queue, type, packet_in, peer_ctx,
sa_local, sa_peer))
LSQ_DEBUGC("scheduled %s packet for cid %"CID_FMT,
lsquic_preqt2str[type], CID_BITS(&packet_in->pi_conn_id));
else
LSQ_DEBUG("cannot schedule %s packet", lsquic_preqt2str[type]);
}
static unsigned short
sa2port (const struct sockaddr *sa)
{
if (sa->sa_family == AF_INET)
{
struct sockaddr_in *const sa4 = (void *) sa;
return sa4->sin_port;
}
else
{
struct sockaddr_in6 *const sa6 = (void *) sa;
return sa6->sin6_port;
}
}
static struct lsquic_hash_elem *
find_conn_by_addr (struct lsquic_hash *hash, const struct sockaddr *sa)
{
unsigned short port;
port = sa2port(sa);
return lsquic_hash_find(hash, &port, sizeof(port));
}
/* When connections are identified by the local address, we need to drop
* packets that use DCIDs that do not correspond to any of SCIDs. This
* can happen when peer retires a SCID. This mimics the normal behavior,
* when connections are looked up in engine->conns_hash by ID: when there
* is no match, the packet is dropped.
*/
static int
dcid_checks_out (const struct lsquic_conn *conn, const lsquic_cid_t *dcid)
{
const struct conn_cid_elem *cce;
if (LSQUIC_CIDS_EQ(CN_SCID(conn), dcid))
return 1;
/* Slow check for those rare cases */
for (cce = conn->cn_cces; cce < END_OF_CCES(conn); ++cce)
if ((conn->cn_cces_mask & (1 << (cce - conn->cn_cces)))
&& !(cce->cce_flags & CCE_PORT)
&& LSQUIC_CIDS_EQ(&cce->cce_cid, dcid))
{
LSQ_DEBUG("connection checks out");
return 1;
}
return 0;
}
2017-09-22 21:00:03 +00:00
static lsquic_conn_t *
find_conn (lsquic_engine_t *engine, lsquic_packet_in_t *packet_in,
struct packin_parse_state *ppstate, const struct sockaddr *sa_local)
2017-09-22 21:00:03 +00:00
{
struct lsquic_hash_elem *el;
2017-09-22 21:00:03 +00:00
lsquic_conn_t *conn;
if (engine->flags & ENG_CONNS_BY_ADDR)
{
el = find_conn_by_addr(engine->conns_hash, sa_local);
if ((packet_in->pi_flags & PI_CONN_ID)
&& !dcid_checks_out(lsquic_hashelem_getdata(el),
&packet_in->pi_conn_id))
{
LSQ_DEBUGC("DCID matches no SCID in connection %"CID_FMT": drop it",
CID_BITS(&packet_in->pi_conn_id));
return NULL;
}
}
else if (packet_in->pi_flags & PI_CONN_ID)
el = lsquic_hash_find(engine->conns_hash,
packet_in->pi_conn_id.idbuf, packet_in->pi_conn_id.len);
else
{
LSQ_DEBUG("packet header does not have connection ID: discarding");
return NULL;
}
if (!el)
2017-09-22 21:00:03 +00:00
return NULL;
conn = lsquic_hashelem_getdata(el);
conn->cn_pf->pf_parse_packet_in_finish(packet_in, ppstate);
if ((engine->flags & ENG_CONNS_BY_ADDR)
&& !(conn->cn_flags & LSCONN_IETF)
&& (packet_in->pi_flags & PI_CONN_ID)
&& !LSQUIC_CIDS_EQ(CN_SCID(conn), &packet_in->pi_conn_id))
2017-09-22 21:00:03 +00:00
{
LSQ_DEBUG("connection IDs do not match");
2017-09-22 21:00:03 +00:00
return NULL;
}
return conn;
}
static const char *const puty2str[] = {
[PUTY_CONN_DELETED] = "deleted",
[PUTY_CONN_DRAIN] = "being drained",
[PUTY_CID_RETIRED] = "retired",
};
static lsquic_conn_t *
find_or_create_conn (lsquic_engine_t *engine, lsquic_packet_in_t *packet_in,
struct packin_parse_state *ppstate, const struct sockaddr *sa_local,
const struct sockaddr *sa_peer, void *peer_ctx, size_t packet_in_size)
{
struct lsquic_hash_elem *el;
struct purga_el *puel;
lsquic_conn_t *conn;
if (!(packet_in->pi_flags & PI_CONN_ID))
{
LSQ_DEBUG("packet header does not have connection ID: discarding");
return NULL;
}
el = lsquic_hash_find(engine->conns_hash,
packet_in->pi_conn_id.idbuf, packet_in->pi_conn_id.len);
if (el)
{
conn = lsquic_hashelem_getdata(el);
conn->cn_pf->pf_parse_packet_in_finish(packet_in, ppstate);
return conn;
}
if (engine->flags & ENG_COOLDOWN)
{ /* Do not create incoming connections during cooldown */
LSQ_DEBUG("dropping inbound packet for unknown connection (cooldown)");
return NULL;
}
if (engine->mini_conns_count >= engine->pub.enp_settings.es_max_inchoate)
{
LSQ_DEBUG("reached limit of %u inchoate connections",
engine->pub.enp_settings.es_max_inchoate);
return NULL;
}
if (engine->purga
&& (puel = lsquic_purga_contains(engine->purga,
&packet_in->pi_conn_id), puel))
{
switch (puel->puel_type)
{
case PUTY_CID_RETIRED:
case PUTY_CONN_DRAIN:
if (puel->puel_time > packet_in->pi_received)
{
LSQ_DEBUGC("CID %"CID_FMT" is %s for another %"PRIu64
"usec, ignore packet", CID_BITS(&packet_in->pi_conn_id),
puty2str[puel->puel_type],
puel->puel_time - packet_in->pi_received);
return NULL;
}
LSQ_DEBUGC("CID %"CID_FMT" is no longer %s",
CID_BITS(&packet_in->pi_conn_id), puty2str[puel->puel_type]);
break;
case PUTY_CONN_DELETED:
LSQ_DEBUGC("Connection with CID %"CID_FMT" was deleted",
CID_BITS(&packet_in->pi_conn_id));
if (puel->puel_time < packet_in->pi_received)
{
if (puel->puel_count < 4)
{
puel->puel_time = packet_in->pi_received
/* Exponential back-off */
+ 1000000ull * (1 << MIN(puel->puel_count, 4));
++puel->puel_count;
goto maybe_send_prst;
}
else
break;
}
return NULL;
default:
assert(0);
return NULL;
}
}
if (engine->pub.enp_settings.es_send_prst
&& !(packet_in->pi_flags & PI_GQUIC)
&& HETY_NOT_SET == packet_in->pi_header_type)
goto maybe_send_prst;
if (0 != maybe_grow_conn_heaps(engine))
return NULL;
const struct parse_funcs *pf;
enum lsquic_version version;
switch (version_matches(engine, packet_in, &version))
{
case VER_UNSUPPORTED:
if (engine->flags & ENG_SERVER)
schedule_req_packet(engine, PACKET_REQ_VERNEG, packet_in,
sa_local, sa_peer, peer_ctx);
return NULL;
case VER_NOT_SPECIFIED:
maybe_send_prst:
if ((engine->flags & ENG_SERVER) &&
engine->pub.enp_settings.es_send_prst)
schedule_req_packet(engine, PACKET_REQ_PUBRES, packet_in,
sa_local, sa_peer, peer_ctx);
return NULL;
case VER_SUPPORTED:
pf = select_pf_by_ver(version);
pf->pf_parse_packet_in_finish(packet_in, ppstate);
break;
}
if ((1 << version) & LSQUIC_IETF_VERSIONS)
{
conn = lsquic_mini_conn_ietf_new(&engine->pub, packet_in, version,
sa_peer->sa_family == AF_INET, NULL, packet_in_size);
}
else
{
conn = lsquic_mini_conn_new(&engine->pub, packet_in, version);
}
if (!conn)
return NULL;
++engine->mini_conns_count;
++engine->n_conns;
if (0 != insert_conn_into_hash(engine, conn, peer_ctx))
{
const lsquic_cid_t *cid = lsquic_conn_log_cid(conn);
LSQ_WARNC("cannot add connection %"CID_FMT" to hash - destroy",
CID_BITS(cid));
destroy_conn(engine, conn, packet_in->pi_received);
return NULL;
}
assert(!(conn->cn_flags & CONN_REF_FLAGS));
conn->cn_flags |= LSCONN_HASHED;
eng_hist_inc(&engine->history, packet_in->pi_received, sl_new_mini_conns);
conn->cn_last_sent = engine->last_sent;
return conn;
}
lsquic_conn_t *
lsquic_engine_find_conn (const struct lsquic_engine_public *engine,
const lsquic_cid_t *cid)
{
struct lsquic_hash_elem *el;
lsquic_conn_t *conn = NULL;
el = lsquic_hash_find(engine->enp_engine->conns_hash, cid->idbuf, cid->len);
if (el)
conn = lsquic_hashelem_getdata(el);
return conn;
}
2017-09-22 21:00:03 +00:00
#if !defined(NDEBUG) && __GNUC__
__attribute__((weak))
#endif
void
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
lsquic_engine_add_conn_to_tickable (struct lsquic_engine_public *enpub,
lsquic_conn_t *conn)
2017-09-22 21:00:03 +00:00
{
if (0 == (enpub->enp_flags & ENPUB_PROC) &&
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
0 == (conn->cn_flags & (LSCONN_TICKABLE|LSCONN_NEVER_TICKABLE)))
2017-09-22 21:00:03 +00:00
{
lsquic_engine_t *engine = (lsquic_engine_t *) enpub;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
lsquic_mh_insert(&engine->conns_tickable, conn, conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
2017-09-22 21:00:03 +00:00
}
}
void
lsquic_engine_add_conn_to_attq (struct lsquic_engine_public *enpub,
lsquic_conn_t *conn, lsquic_time_t tick_time, unsigned why)
2017-09-22 21:00:03 +00:00
{
lsquic_engine_t *const engine = (lsquic_engine_t *) enpub;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (conn->cn_flags & LSCONN_TICKABLE)
2017-09-22 21:00:03 +00:00
{
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
/* Optimization: no need to add the connection to the Advisory Tick
* Time Queue: it is about to be ticked, after which it its next tick
* time may be queried again.
*/;
2017-09-22 21:00:03 +00:00
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
else if (conn->cn_flags & LSCONN_ATTQ)
2017-09-22 21:00:03 +00:00
{
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (lsquic_conn_adv_time(conn) != tick_time)
{
lsquic_attq_remove(engine->attq, conn);
if (0 != lsquic_attq_add(engine->attq, conn, tick_time, why))
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
engine_decref_conn(engine, conn, LSCONN_ATTQ);
}
2017-09-22 21:00:03 +00:00
}
else if (0 == lsquic_attq_add(engine->attq, conn, tick_time, why))
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
engine_incref_conn(conn, LSCONN_ATTQ);
2017-09-22 21:00:03 +00:00
}
static struct lsquic_conn *
find_conn_by_srst (struct lsquic_engine *engine,
const struct lsquic_packet_in *packet_in)
{
struct lsquic_hash_elem *el;
struct lsquic_conn *conn;
if (packet_in->pi_data_sz < IQUIC_MIN_SRST_SIZE
|| (packet_in->pi_data[0] & 0xC0) != 0x40)
return NULL;
el = lsquic_hash_find(engine->pub.enp_srst_hash,
packet_in->pi_data + packet_in->pi_data_sz - IQUIC_SRESET_TOKEN_SZ,
IQUIC_SRESET_TOKEN_SZ);
if (!el)
return NULL;
conn = lsquic_hashelem_getdata(el);
return conn;
}
/* Return 0 if packet is being processed by a real connection (mini or full),
* otherwise return 1.
*/
2017-09-22 21:00:03 +00:00
static int
process_packet_in (lsquic_engine_t *engine, lsquic_packet_in_t *packet_in,
struct packin_parse_state *ppstate, const struct sockaddr *sa_local,
const struct sockaddr *sa_peer, void *peer_ctx, size_t packet_in_size)
2017-09-22 21:00:03 +00:00
{
lsquic_conn_t *conn;
const unsigned char *packet_in_data;
2017-09-22 21:00:03 +00:00
2018-08-15 19:06:31 +00:00
if (lsquic_packet_in_is_gquic_prst(packet_in)
&& !engine->pub.enp_settings.es_honor_prst)
{
lsquic_mm_put_packet_in(&engine->pub.enp_mm, packet_in);
LSQ_DEBUG("public reset packet: discarding");
return 1;
}
if (engine->flags & ENG_SERVER)
{
conn = find_or_create_conn(engine, packet_in, ppstate, sa_local,
sa_peer, peer_ctx, packet_in_size);
if (!engine->curr_conn)
engine->curr_conn = conn;
}
else
conn = find_conn(engine, packet_in, ppstate, sa_local);
2017-09-22 21:00:03 +00:00
if (!conn)
{
if (engine->pub.enp_settings.es_honor_prst
&& packet_in_size == packet_in->pi_data_sz /* Full UDP packet */
&& !(packet_in->pi_flags & PI_GQUIC)
&& engine->pub.enp_srst_hash
&& (conn = find_conn_by_srst(engine, packet_in)))
{
LSQ_DEBUGC("got stateless reset for connection %"CID_FMT,
CID_BITS(lsquic_conn_log_cid(conn)));
conn->cn_if->ci_stateless_reset(conn);
if (!(conn->cn_flags & LSCONN_TICKABLE)
&& conn->cn_if->ci_is_tickable(conn))
{
lsquic_mh_insert(&engine->conns_tickable, conn,
conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
}
/* Even though the connection processes this packet, we return
* 1 so that the caller does not add reset packet's random
* bytes to the list of valid CIDs.
*/
}
2017-09-22 21:00:03 +00:00
lsquic_mm_put_packet_in(&engine->pub.enp_mm, packet_in);
return 1;
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (0 == (conn->cn_flags & LSCONN_TICKABLE))
{
lsquic_mh_insert(&engine->conns_tickable, conn, conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
2017-09-22 21:00:03 +00:00
}
packet_in->pi_path_id = lsquic_conn_record_sockaddr(conn, peer_ctx,
sa_local, sa_peer);
2017-09-22 21:00:03 +00:00
lsquic_packet_in_upref(packet_in);
#if LOG_PACKET_CHECKSUM
log_packet_checksum(lsquic_conn_log_cid(conn), "in", packet_in->pi_data,
packet_in->pi_data_sz);
#endif
/* Note on QLog:
* For the PACKET_RX QLog event, we are interested in logging these things:
* - raw packet (however it comes in, encrypted or not)
* - frames (list of frame names)
* - packet type and number
* - packet rx timestamp
*
* Since only some of these items are available at this code
* juncture, we will wait until after the packet has been
* decrypted (if necessary) and parsed to call the log functions.
*
* Once the PACKET_RX event is finally logged, the timestamp
* will come from packet_in->pi_received. For correct sequential
* ordering of QLog events, be sure to process the QLogs downstream.
* (Hint: Use the qlog_parser.py tool in tools/ for full QLog processing.)
*/
packet_in_data = packet_in->pi_data;
packet_in_size = packet_in->pi_data_sz;
2017-09-22 21:00:03 +00:00
conn->cn_if->ci_packet_in(conn, packet_in);
#if LSQUIC_CONN_STATS
engine->busy.pin_conn = conn;
#endif
QLOG_PACKET_RX(lsquic_conn_log_cid(conn), packet_in, packet_in_data, packet_in_size);
2017-09-22 21:00:03 +00:00
lsquic_packet_in_put(&engine->pub.enp_mm, packet_in);
return 0;
}
void
lsquic_engine_destroy (lsquic_engine_t *engine)
{
struct lsquic_hash_elem *el;
2017-09-22 21:00:03 +00:00
lsquic_conn_t *conn;
unsigned i;
2017-09-22 21:00:03 +00:00
LSQ_DEBUG("destroying engine");
#ifndef NDEBUG
engine->flags |= ENG_DTOR;
#endif
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
while ((conn = lsquic_mh_pop(&engine->conns_out)))
2017-09-22 21:00:03 +00:00
{
assert(conn->cn_flags & LSCONN_HAS_OUTGOING);
(void) engine_decref_conn(engine, conn, LSCONN_HAS_OUTGOING);
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
while ((conn = lsquic_mh_pop(&engine->conns_tickable)))
{
assert(conn->cn_flags & LSCONN_TICKABLE);
(void) engine_decref_conn(engine, conn, LSCONN_TICKABLE);
}
2017-09-22 21:00:03 +00:00
for (el = lsquic_hash_first(engine->conns_hash); el;
el = lsquic_hash_next(engine->conns_hash))
{
conn = lsquic_hashelem_getdata(el);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
force_close_conn(engine, conn);
}
lsquic_hash_destroy(engine->conns_hash);
2017-09-22 21:00:03 +00:00
while ((conn = lsquic_attq_pop(engine->attq, UINT64_MAX)))
(void) engine_decref_conn(engine, conn, LSCONN_ATTQ);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
assert(0 == engine->n_conns);
assert(0 == engine->mini_conns_count);
if (engine->pr_queue)
lsquic_prq_destroy(engine->pr_queue);
if (engine->purga)
lsquic_purga_destroy(engine->purga);
lsquic_attq_destroy(engine->attq);
2017-09-22 21:00:03 +00:00
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
assert(0 == lsquic_mh_count(&engine->conns_out));
assert(0 == lsquic_mh_count(&engine->conns_tickable));
if (engine->pub.enp_shi == &stock_shi)
lsquic_stock_shared_hash_destroy(engine->pub.enp_shi_ctx);
lsquic_mm_cleanup(&engine->pub.enp_mm);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
free(engine->conns_tickable.mh_elems);
2020-06-03 04:13:30 +00:00
#if CAN_LOSE_PACKETS
if (engine->flags & ENG_LOSE_PACKETS)
regfree(&engine->lose_packets_re);
#endif
if (engine->pub.enp_tokgen)
lsquic_tg_destroy(engine->pub.enp_tokgen);
if (engine->flags & LSENG_SERVER)
lsquic_cleanup_gquic_crypto(&engine->pub);
#if LSQUIC_CONN_STATS
if (engine->stats_fh)
{
const struct conn_stats *const stats = &engine->conn_stats_sum;
fprintf(engine->stats_fh, "Aggregate connection stats collected by engine:\n");
fprintf(engine->stats_fh, "Connections: %u\n", engine->stats.conns);
fprintf(engine->stats_fh, "Ticks: %lu\n", stats->n_ticks);
fprintf(engine->stats_fh, "In:\n");
fprintf(engine->stats_fh, " Total bytes: %lu\n", stats->in.bytes);
fprintf(engine->stats_fh, " packets: %lu\n", stats->in.packets);
fprintf(engine->stats_fh, " undecryptable packets: %lu\n", stats->in.undec_packets);
fprintf(engine->stats_fh, " duplicate packets: %lu\n", stats->in.dup_packets);
fprintf(engine->stats_fh, " error packets: %lu\n", stats->in.err_packets);
fprintf(engine->stats_fh, " STREAM frame count: %lu\n", stats->in.stream_frames);
fprintf(engine->stats_fh, " STREAM payload size: %lu\n", stats->in.stream_data_sz);
fprintf(engine->stats_fh, " Header bytes: %lu; uncompressed: %lu; ratio %.3lf\n",
stats->in.headers_comp, stats->in.headers_uncomp,
stats->in.headers_uncomp ?
(double) stats->in.headers_comp / (double) stats->in.headers_uncomp
: 0);
fprintf(engine->stats_fh, " ACK frames: %lu\n", stats->in.n_acks);
fprintf(engine->stats_fh, " ACK frames processed: %lu\n", stats->in.n_acks_proc);
fprintf(engine->stats_fh, " ACK frames merged: %lu\n", stats->in.n_acks_merged);
fprintf(engine->stats_fh, "Out:\n");
fprintf(engine->stats_fh, " Total bytes: %lu\n", stats->out.bytes);
fprintf(engine->stats_fh, " packets: %lu\n", stats->out.packets);
fprintf(engine->stats_fh, " acked via loss record: %lu\n", stats->out.acked_via_loss);
fprintf(engine->stats_fh, " acks: %lu\n", stats->out.acks);
fprintf(engine->stats_fh, " retx packets: %lu\n", stats->out.retx_packets);
fprintf(engine->stats_fh, " STREAM frame count: %lu\n", stats->out.stream_frames);
fprintf(engine->stats_fh, " STREAM payload size: %lu\n", stats->out.stream_data_sz);
fprintf(engine->stats_fh, " Header bytes: %lu; uncompressed: %lu; ratio %.3lf\n",
stats->out.headers_comp, stats->out.headers_uncomp,
stats->out.headers_uncomp ?
(double) stats->out.headers_comp / (double) stats->out.headers_uncomp
: 0);
fprintf(engine->stats_fh, " ACKs: %lu\n", stats->out.acks);
}
#endif
if (engine->pub.enp_srst_hash)
lsquic_hash_destroy(engine->pub.enp_srst_hash);
#if LSQUIC_COUNT_ENGINE_CALLS
LSQ_NOTICE("number of calls into the engine: %lu", engine->n_engine_calls);
#endif
for (i = 0; i < sizeof(engine->retry_aead_ctx)
/ sizeof(engine->retry_aead_ctx[0]); ++i)
EVP_AEAD_CTX_cleanup(&engine->pub.enp_retry_aead_ctx[i]);
free(engine->pub.enp_alpn);
free(engine);
}
static struct conn_cid_elem *
find_free_cce (struct lsquic_conn *conn)
{
struct conn_cid_elem *cce;
for (cce = conn->cn_cces; cce < END_OF_CCES(conn); ++cce)
if (!(conn->cn_cces_mask & (1 << (cce - conn->cn_cces))))
return cce;
return NULL;
}
static int
add_conn_to_hash (struct lsquic_engine *engine, struct lsquic_conn *conn,
const struct sockaddr *local_sa, void *peer_ctx)
{
struct conn_cid_elem *cce;
if (engine->flags & ENG_CONNS_BY_ADDR)
{
cce = find_free_cce(conn);
if (!cce)
{
LSQ_ERROR("cannot find free CCE");
return -1;
}
cce->cce_port = sa2port(local_sa);
cce->cce_flags = CCE_PORT;
if (lsquic_hash_insert(engine->conns_hash, &cce->cce_port,
sizeof(cce->cce_port), conn, &cce->cce_hash_el))
{
conn->cn_cces_mask |= 1 << (cce - conn->cn_cces);
return 0;
}
else
return -1;
}
else
return insert_conn_into_hash(engine, conn, peer_ctx);
2017-09-22 21:00:03 +00:00
}
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
lsquic_conn_t *
lsquic_engine_connect (lsquic_engine_t *engine, enum lsquic_version version,
const struct sockaddr *local_sa,
const struct sockaddr *peer_sa,
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
void *peer_ctx, lsquic_conn_ctx_t *conn_ctx,
const char *hostname, unsigned short base_plpmtu,
const unsigned char *sess_resume, size_t sess_resume_len,
const unsigned char *token, size_t token_sz)
2017-09-22 21:00:03 +00:00
{
lsquic_conn_t *conn;
unsigned flags, versions;
int is_ipv4;
ENGINE_CALLS_INCR(engine);
2017-09-22 21:00:03 +00:00
if (engine->flags & ENG_SERVER)
{
LSQ_ERROR("`%s' must only be called in client mode", __func__);
goto err;
2017-09-22 21:00:03 +00:00
}
if (engine->flags & ENG_CONNS_BY_ADDR
&& find_conn_by_addr(engine->conns_hash, local_sa))
{
LSQ_ERROR("cannot have more than one connection on the same port");
goto err;
}
if (0 != maybe_grow_conn_heaps(engine))
return NULL;
flags = engine->flags & (ENG_SERVER|ENG_HTTP);
is_ipv4 = peer_sa->sa_family == AF_INET;
if (sess_resume && sess_resume_len)
{
version = lsquic_sess_resume_version(sess_resume, sess_resume_len);
if (version >= N_LSQVER)
{
LSQ_INFO("session resumption version is bad, won't use");
sess_resume = NULL;
sess_resume_len = 0;
}
}
if (version >= N_LSQVER)
{
if (version > N_LSQVER)
LSQ_WARN("invalid version specified, engine will pick");
versions = engine->pub.enp_settings.es_versions;
}
else
versions = 1u << version;
if (versions & LSQUIC_IETF_VERSIONS)
conn = lsquic_ietf_full_conn_client_new(&engine->pub, versions,
flags, hostname, base_plpmtu,
is_ipv4, sess_resume, sess_resume_len, token, token_sz, peer_ctx);
else
conn = lsquic_gquic_full_conn_client_new(&engine->pub, versions,
flags, hostname, base_plpmtu, is_ipv4,
sess_resume, sess_resume_len);
2017-09-22 21:00:03 +00:00
if (!conn)
goto err;
EV_LOG_CREATE_CONN(lsquic_conn_log_cid(conn), local_sa, peer_sa);
EV_LOG_VER_NEG(lsquic_conn_log_cid(conn), "proposed",
lsquic_ver2str[conn->cn_version]);
++engine->n_conns;
lsquic_conn_record_sockaddr(conn, peer_ctx, local_sa, peer_sa);
if (0 != add_conn_to_hash(engine, conn, local_sa, peer_ctx))
{
const lsquic_cid_t *cid = lsquic_conn_log_cid(conn);
LSQ_WARNC("cannot add connection %"CID_FMT" to hash - destroy",
CID_BITS(cid));
destroy_conn(engine, conn, lsquic_time_now());
goto err;
}
assert(!(conn->cn_flags &
(CONN_REF_FLAGS
& ~LSCONN_TICKABLE /* This flag may be set as effect of user
callbacks */
)));
conn->cn_flags |= LSCONN_HASHED;
if (!(conn->cn_flags & LSCONN_TICKABLE))
{
lsquic_mh_insert(&engine->conns_tickable, conn, conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
}
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
lsquic_conn_set_ctx(conn, conn_ctx);
conn->cn_if->ci_client_call_on_new(conn);
end:
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
return conn;
err:
conn = NULL;
goto end;
2017-09-22 21:00:03 +00:00
}
static void
remove_conn_from_hash (lsquic_engine_t *engine, lsquic_conn_t *conn)
{
remove_all_cces_from_hash(engine->conns_hash, conn);
2017-09-22 21:00:03 +00:00
(void) engine_decref_conn(engine, conn, LSCONN_HASHED);
}
static void
refflags2str (enum lsquic_conn_flags flags, char s[7])
2017-09-22 21:00:03 +00:00
{
*s = 'C'; s += !!(flags & LSCONN_CLOSING);
*s = 'H'; s += !!(flags & LSCONN_HASHED);
*s = 'O'; s += !!(flags & LSCONN_HAS_OUTGOING);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
*s = 'T'; s += !!(flags & LSCONN_TICKABLE);
2017-09-22 21:00:03 +00:00
*s = 'A'; s += !!(flags & LSCONN_ATTQ);
*s = 'K'; s += !!(flags & LSCONN_TICKED);
2017-09-22 21:00:03 +00:00
*s = '\0';
}
static void
engine_incref_conn (lsquic_conn_t *conn, enum lsquic_conn_flags flag)
{
char str[2][7];
2017-09-22 21:00:03 +00:00
assert(flag & CONN_REF_FLAGS);
assert(!(conn->cn_flags & flag));
conn->cn_flags |= flag;
LSQ_DEBUGC("incref conn %"CID_FMT", '%s' -> '%s'",
CID_BITS(lsquic_conn_log_cid(conn)),
(refflags2str(conn->cn_flags & ~flag, str[0]), str[0]),
(refflags2str(conn->cn_flags, str[1]), str[1]));
2017-09-22 21:00:03 +00:00
}
static lsquic_conn_t *
engine_decref_conn (lsquic_engine_t *engine, lsquic_conn_t *conn,
enum lsquic_conn_flags flags)
{
char str[2][7];
lsquic_time_t now;
2017-09-22 21:00:03 +00:00
assert(flags & CONN_REF_FLAGS);
assert(conn->cn_flags & flags);
#ifndef NDEBUG
if (flags & LSCONN_CLOSING)
assert(0 == (conn->cn_flags & LSCONN_HASHED));
#endif
conn->cn_flags &= ~flags;
LSQ_DEBUGC("decref conn %"CID_FMT", '%s' -> '%s'",
CID_BITS(lsquic_conn_log_cid(conn)),
(refflags2str(conn->cn_flags | flags, str[0]), str[0]),
(refflags2str(conn->cn_flags, str[1]), str[1]));
2017-09-22 21:00:03 +00:00
if (0 == (conn->cn_flags & CONN_REF_FLAGS))
{
now = lsquic_time_now();
if (conn->cn_flags & LSCONN_MINI)
eng_hist_inc(&engine->history, now, sl_del_mini_conns);
else
eng_hist_inc(&engine->history, now, sl_del_full_conns);
destroy_conn(engine, conn, now);
2017-09-22 21:00:03 +00:00
return NULL;
}
else
return conn;
}
/* This is not a general-purpose function. Only call from engine dtor. */
static void
force_close_conn (lsquic_engine_t *engine, lsquic_conn_t *conn)
{
assert(engine->flags & ENG_DTOR);
const enum lsquic_conn_flags flags = conn->cn_flags;
assert(conn->cn_flags & CONN_REF_FLAGS);
assert(!(flags & LSCONN_HAS_OUTGOING)); /* Should be removed already */
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
assert(!(flags & LSCONN_TICKABLE)); /* Should be removed already */
2017-09-22 21:00:03 +00:00
assert(!(flags & LSCONN_CLOSING)); /* It is in transient queue? */
if (flags & LSCONN_ATTQ)
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
{
lsquic_attq_remove(engine->attq, conn);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
(void) engine_decref_conn(engine, conn, LSCONN_ATTQ);
}
2017-09-22 21:00:03 +00:00
if (flags & LSCONN_HASHED)
remove_conn_from_hash(engine, conn);
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
/* Iterator for tickable connections (those on the Tickable Queue). Before
* a connection is returned, it is removed from the Advisory Tick Time queue
* if necessary.
2017-09-22 21:00:03 +00:00
*/
static lsquic_conn_t *
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
conn_iter_next_tickable (struct lsquic_engine *engine)
2017-09-22 21:00:03 +00:00
{
lsquic_conn_t *conn;
if (engine->flags & ENG_SERVER)
while (1)
{
conn = lsquic_mh_pop(&engine->conns_tickable);
if (conn && (conn->cn_flags & LSCONN_SKIP_ON_PROC))
(void) engine_decref_conn(engine, conn, LSCONN_TICKABLE);
else
break;
}
else
conn = lsquic_mh_pop(&engine->conns_tickable);
2017-09-22 21:00:03 +00:00
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (conn)
conn = engine_decref_conn(engine, conn, LSCONN_TICKABLE);
if (conn && (conn->cn_flags & LSCONN_ATTQ))
2017-09-22 21:00:03 +00:00
{
lsquic_attq_remove(engine->attq, conn);
2017-09-22 21:00:03 +00:00
conn = engine_decref_conn(engine, conn, LSCONN_ATTQ);
}
return conn;
}
static void
cub_init (struct cid_update_batch *cub, lsquic_cids_update_f update,
void *update_ctx)
2017-09-22 21:00:03 +00:00
{
cub->cub_update_cids = update;
cub->cub_update_ctx = update_ctx;
cub->cub_count = 0;
2017-09-22 21:00:03 +00:00
}
static void
cub_flush (struct cid_update_batch *cub)
2017-09-22 21:00:03 +00:00
{
if (cub->cub_count > 0 && cub->cub_update_cids)
cub->cub_update_cids(cub->cub_update_ctx, cub->cub_peer_ctxs,
cub->cub_cids, cub->cub_count);
cub->cub_count = 0;
}
2017-09-22 21:00:03 +00:00
static void
cub_add (struct cid_update_batch *cub, const lsquic_cid_t *cid, void *peer_ctx)
{
cub->cub_cids [ cub->cub_count ] = *cid;
cub->cub_peer_ctxs[ cub->cub_count ] = peer_ctx;
++cub->cub_count;
if (cub->cub_count == sizeof(cub->cub_cids) / sizeof(cub->cub_cids[0]))
cub_flush(cub);
2017-09-22 21:00:03 +00:00
}
/* Process registered CIDs */
static void
cub_add_cids_from_cces (struct cid_update_batch *cub, struct lsquic_conn *conn)
{
struct cce_cid_iter citer;
struct conn_cid_elem *cce;
void *peer_ctx;
peer_ctx = lsquic_conn_get_peer_ctx(conn, NULL);
for (cce = cce_iter_first(&citer, conn); cce; cce = cce_iter_next(&citer))
if (cce->cce_flags & CCE_REG)
cub_add(cub, &cce->cce_cid, peer_ctx);
}
static void
drop_all_mini_conns (lsquic_engine_t *engine)
2017-09-22 21:00:03 +00:00
{
struct lsquic_hash_elem *el;
lsquic_conn_t *conn;
struct cid_update_batch cub;
2017-09-22 21:00:03 +00:00
cub_init(&cub, engine->report_old_scids, engine->scids_ctx);
2017-09-22 21:00:03 +00:00
for (el = lsquic_hash_first(engine->conns_hash); el;
el = lsquic_hash_next(engine->conns_hash))
Latest changes - [OPTIMIZATION] Merge series of ACKs if possible Parsed single-range ACK frames (that is the majority of frames) are saved in the connection and their processing is deferred until the connection is ticked. If several ACKs come in a series between adjacent ticks, we check whether the latest ACK is a strict superset of the saved ACK. If it is, the older ACK is not processed. If ACK frames can be merged, they are merged and only one of them is either processed or saved. - [OPTIMIZATION] Speed up ACK verification by simplifying send history. Never generate a gap in the sent packet number sequence. This reduces the send history to a single number instead of potentially a series of packet ranges and thereby speeds up ACK verification. By default, detecting a gap in the send history is not fatal: only a single warning is generated per connection. The connection can continue to operate even if the ACK verification code is not able to detect some inconsistencies. - [OPTIMIZATION] Rearrange the lsquic_send_ctl struct The first part of struct lsquic_send_ctl now consists of members that are used in lsquic_send_ctl_got_ack() (in the absense of packet loss, which is the normal case). To speed up reads and writes, we no longer try to save space by using 8- and 16-bit integers. Use regular integer width for everything. - [OPTIMIZATION] Cache size of sent packet. - [OPTIMIZATION] Keep track of the largest ACKed in packet_out Instead of parsing our own ACK frames when packet has been acked, use the value saved in the packet_out structure when the ACK frame was generated. - [OPTIMIZATION] Take RTT sampling conditional out of ACK loop - [OPTIMIZATION] ACK processing: only call clock_gettime() if needed - [OPTIMIZATION] Several code-level optimizations to ACK processing. - Fix: http_client: fix -I flag; switch assert() to abort()
2018-03-09 19:17:39 +00:00
{
conn = lsquic_hashelem_getdata(el);
if (conn->cn_flags & LSCONN_MINI)
{
/* If promoted, why is it still in this hash? */
assert(!(conn->cn_flags & LSCONN_PROMOTED));
if (!(conn->cn_flags & LSCONN_PROMOTED))
cub_add_cids_from_cces(&cub, conn);
remove_conn_from_hash(engine, conn);
}
Latest changes - [OPTIMIZATION] Merge series of ACKs if possible Parsed single-range ACK frames (that is the majority of frames) are saved in the connection and their processing is deferred until the connection is ticked. If several ACKs come in a series between adjacent ticks, we check whether the latest ACK is a strict superset of the saved ACK. If it is, the older ACK is not processed. If ACK frames can be merged, they are merged and only one of them is either processed or saved. - [OPTIMIZATION] Speed up ACK verification by simplifying send history. Never generate a gap in the sent packet number sequence. This reduces the send history to a single number instead of potentially a series of packet ranges and thereby speeds up ACK verification. By default, detecting a gap in the send history is not fatal: only a single warning is generated per connection. The connection can continue to operate even if the ACK verification code is not able to detect some inconsistencies. - [OPTIMIZATION] Rearrange the lsquic_send_ctl struct The first part of struct lsquic_send_ctl now consists of members that are used in lsquic_send_ctl_got_ack() (in the absense of packet loss, which is the normal case). To speed up reads and writes, we no longer try to save space by using 8- and 16-bit integers. Use regular integer width for everything. - [OPTIMIZATION] Cache size of sent packet. - [OPTIMIZATION] Keep track of the largest ACKed in packet_out Instead of parsing our own ACK frames when packet has been acked, use the value saved in the packet_out structure when the ACK frame was generated. - [OPTIMIZATION] Take RTT sampling conditional out of ACK loop - [OPTIMIZATION] ACK processing: only call clock_gettime() if needed - [OPTIMIZATION] Several code-level optimizations to ACK processing. - Fix: http_client: fix -I flag; switch assert() to abort()
2018-03-09 19:17:39 +00:00
}
2017-09-22 21:00:03 +00:00
cub_flush(&cub);
}
void
lsquic_engine_process_conns (lsquic_engine_t *engine)
{
lsquic_conn_t *conn;
lsquic_time_t now;
#if LSQUIC_CONN_STATS
if (engine->busy.pin_conn)
{
update_busy_detector(engine, engine->busy.pin_conn, 1);
engine->busy.pin_conn = NULL;
}
#endif
ENGINE_IN(engine);
now = lsquic_time_now();
while ((conn = lsquic_attq_pop(engine->attq, now)))
2017-09-22 21:00:03 +00:00
{
conn = engine_decref_conn(engine, conn, LSCONN_ATTQ);
if (conn && !(conn->cn_flags & LSCONN_TICKABLE))
{
lsquic_mh_insert(&engine->conns_tickable, conn, conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
}
2017-09-22 21:00:03 +00:00
}
process_connections(engine, conn_iter_next_tickable, now);
ENGINE_OUT(engine);
2017-09-22 21:00:03 +00:00
}
static void
release_or_return_enc_data (struct lsquic_engine *engine,
void (*pmi_rel_or_ret) (void *, void *, void *, char),
struct lsquic_conn *conn, struct lsquic_packet_out *packet_out)
{
pmi_rel_or_ret(engine->pub.enp_pmi_ctx, packet_out->po_path->np_peer_ctx,
packet_out->po_enc_data, lsquic_packet_out_ipv6(packet_out));
packet_out->po_flags &= ~PO_ENCRYPTED;
packet_out->po_enc_data = NULL;
}
static void
release_enc_data (struct lsquic_engine *engine, struct lsquic_conn *conn,
struct lsquic_packet_out *packet_out)
{
release_or_return_enc_data(engine, engine->pub.enp_pmi->pmi_release,
conn, packet_out);
}
static void
return_enc_data (struct lsquic_engine *engine, struct lsquic_conn *conn,
struct lsquic_packet_out *packet_out)
{
release_or_return_enc_data(engine, engine->pub.enp_pmi->pmi_return,
conn, packet_out);
}
static int
copy_packet (struct lsquic_engine *engine, struct lsquic_conn *conn,
struct lsquic_packet_out *packet_out)
{
int ipv6;
ipv6 = NP_IS_IPv6(packet_out->po_path);
if (packet_out->po_flags & PO_ENCRYPTED)
{
if (ipv6 == lsquic_packet_out_ipv6(packet_out)
&& packet_out->po_data_sz == packet_out->po_enc_data_sz
&& 0 == memcmp(packet_out->po_data, packet_out->po_enc_data,
packet_out->po_data_sz))
return 0;
if (ipv6 == lsquic_packet_out_ipv6(packet_out)
&& packet_out->po_data_sz <= packet_out->po_enc_data_sz)
goto copy;
return_enc_data(engine, conn, packet_out);
}
packet_out->po_enc_data = engine->pub.enp_pmi->pmi_allocate(
engine->pub.enp_pmi_ctx, packet_out->po_path->np_peer_ctx,
conn->cn_conn_ctx, packet_out->po_data_sz, ipv6);
if (!packet_out->po_enc_data)
{
LSQ_DEBUG("could not allocate memory for outgoing unencrypted packet "
"of size %hu", packet_out->po_data_sz);
return -1;
}
copy:
memcpy(packet_out->po_enc_data, packet_out->po_data,
packet_out->po_data_sz);
packet_out->po_enc_data_sz = packet_out->po_data_sz;
packet_out->po_sent_sz = packet_out->po_data_sz;
packet_out->po_flags &= ~PO_IPv6;
packet_out->po_flags |= PO_ENCRYPTED|PO_SENT_SZ|(ipv6 << POIPv6_SHIFT);
return 0;
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
STAILQ_HEAD(conns_stailq, lsquic_conn);
TAILQ_HEAD(conns_tailq, lsquic_conn);
2017-09-22 21:00:03 +00:00
struct conns_out_iter
{
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
struct min_heap *coi_heap;
struct pr_queue *coi_prq;
2017-09-22 21:00:03 +00:00
TAILQ_HEAD(, lsquic_conn) coi_active_list,
coi_inactive_list;
lsquic_conn_t *coi_next;
#ifndef NDEBUG
lsquic_time_t coi_last_sent;
#endif
};
static void
coi_init (struct conns_out_iter *iter, struct lsquic_engine *engine)
{
iter->coi_heap = &engine->conns_out;
iter->coi_prq = engine->pr_queue;
2017-09-22 21:00:03 +00:00
iter->coi_next = NULL;
TAILQ_INIT(&iter->coi_active_list);
TAILQ_INIT(&iter->coi_inactive_list);
#ifndef NDEBUG
iter->coi_last_sent = 0;
#endif
}
static lsquic_conn_t *
coi_next (struct conns_out_iter *iter)
{
lsquic_conn_t *conn;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (lsquic_mh_count(iter->coi_heap) > 0)
2017-09-22 21:00:03 +00:00
{
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
conn = lsquic_mh_pop(iter->coi_heap);
2017-09-22 21:00:03 +00:00
TAILQ_INSERT_TAIL(&iter->coi_active_list, conn, cn_next_out);
conn->cn_flags |= LSCONN_COI_ACTIVE;
#ifndef NDEBUG
if (iter->coi_last_sent)
assert(iter->coi_last_sent <= conn->cn_last_sent);
iter->coi_last_sent = conn->cn_last_sent;
#endif
return conn;
}
else if (iter->coi_prq && (conn = lsquic_prq_next_conn(iter->coi_prq)))
{
return conn;
}
2017-09-22 21:00:03 +00:00
else if (!TAILQ_EMPTY(&iter->coi_active_list))
{
iter->coi_prq = NULL; /* Save function call in previous conditional */
2017-09-22 21:00:03 +00:00
conn = iter->coi_next;
if (!conn)
conn = TAILQ_FIRST(&iter->coi_active_list);
if (conn)
iter->coi_next = TAILQ_NEXT(conn, cn_next_out);
return conn;
}
else
return NULL;
}
static void
coi_deactivate (struct conns_out_iter *iter, lsquic_conn_t *conn)
{
if (!(conn->cn_flags & LSCONN_EVANESCENT))
{
assert(!TAILQ_EMPTY(&iter->coi_active_list));
TAILQ_REMOVE(&iter->coi_active_list, conn, cn_next_out);
conn->cn_flags &= ~LSCONN_COI_ACTIVE;
TAILQ_INSERT_TAIL(&iter->coi_inactive_list, conn, cn_next_out);
conn->cn_flags |= LSCONN_COI_INACTIVE;
}
}
static void
coi_reactivate (struct conns_out_iter *iter, lsquic_conn_t *conn)
{
assert(conn->cn_flags & LSCONN_COI_INACTIVE);
TAILQ_REMOVE(&iter->coi_inactive_list, conn, cn_next_out);
conn->cn_flags &= ~LSCONN_COI_INACTIVE;
TAILQ_INSERT_TAIL(&iter->coi_active_list, conn, cn_next_out);
conn->cn_flags |= LSCONN_COI_ACTIVE;
}
static void
coi_reheap (struct conns_out_iter *iter, lsquic_engine_t *engine)
{
lsquic_conn_t *conn;
while ((conn = TAILQ_FIRST(&iter->coi_active_list)))
{
TAILQ_REMOVE(&iter->coi_active_list, conn, cn_next_out);
conn->cn_flags &= ~LSCONN_COI_ACTIVE;
if ((conn->cn_flags & CONN_REF_FLAGS) != LSCONN_HAS_OUTGOING
&& !(conn->cn_flags & LSCONN_IMMED_CLOSE))
lsquic_mh_insert(iter->coi_heap, conn, conn->cn_last_sent);
else /* Closed connection gets one shot at sending packets */
(void) engine_decref_conn(engine, conn, LSCONN_HAS_OUTGOING);
2017-09-22 21:00:03 +00:00
}
while ((conn = TAILQ_FIRST(&iter->coi_inactive_list)))
{
TAILQ_REMOVE(&iter->coi_inactive_list, conn, cn_next_out);
conn->cn_flags &= ~LSCONN_COI_INACTIVE;
(void) engine_decref_conn(engine, conn, LSCONN_HAS_OUTGOING);
}
}
2020-06-03 04:13:30 +00:00
#if CAN_LOSE_PACKETS
static void
lose_matching_packets (const lsquic_engine_t *engine, struct out_batch *batch,
unsigned n)
{
const lsquic_cid_t *cid;
struct iovec *iov;
unsigned i;
char packno_str[22];
for (i = 0; i < n; ++i)
{
snprintf(packno_str, sizeof(packno_str), "%"PRIu64,
batch->packets[i]->po_packno);
if (0 == regexec(&engine->lose_packets_re, packno_str, 0, NULL, 0))
{
for (iov = batch->outs[i].iov; iov <
batch->outs[i].iov + batch->outs[i].iovlen; ++iov)
batch->outs[i].iov->iov_len -= 1;
cid = lsquic_conn_log_cid(batch->conns[i]);
LSQ_WARNC("losing packet %s for connection %"CID_FMT, packno_str,
CID_BITS(cid));
}
}
}
#endif
#ifdef NDEBUG
#define CONST_BATCH const
#else
#define CONST_BATCH
#endif
struct send_batch_ctx {
struct conns_stailq *closed_conns;
struct conns_tailq *ticked_conns;
struct conns_out_iter *conns_iter;
CONST_BATCH struct out_batch *batch;
};
static void
close_conn_immediately (struct lsquic_engine *engine,
const struct send_batch_ctx *sb_ctx, struct lsquic_conn *conn)
{
conn->cn_flags |= LSCONN_IMMED_CLOSE;
if (!(conn->cn_flags & LSCONN_CLOSING))
{
STAILQ_INSERT_TAIL(sb_ctx->closed_conns, conn, cn_next_closed_conn);
engine_incref_conn(conn, LSCONN_CLOSING);
if (conn->cn_flags & LSCONN_HASHED)
remove_conn_from_hash(engine, conn);
}
if (conn->cn_flags & LSCONN_TICKED)
{
TAILQ_REMOVE(sb_ctx->ticked_conns, conn, cn_next_ticked);
engine_decref_conn(engine, conn, LSCONN_TICKED);
}
}
static void
close_conn_on_send_error (struct lsquic_engine *engine,
const struct send_batch_ctx *sb_ctx, int n, int e_val)
{
const struct out_batch *batch = sb_ctx->batch;
struct lsquic_conn *const conn = batch->conns[n];
char buf[2][INET6_ADDRSTRLEN + sizeof(":65535")];
LSQ_WARNC("error sending packet for %s connection %"CID_FMT" - close it; "
"src: %s; dst: %s; errno: %d",
conn->cn_flags & LSCONN_EVANESCENT ? "evanecsent" :
conn->cn_flags & LSCONN_MINI ? "mini" : "regular",
CID_BITS(lsquic_conn_log_cid(conn)),
SA2STR(batch->outs[n].local_sa, buf[0]),
SA2STR(batch->outs[n].dest_sa, buf[1]),
e_val);
if (conn->cn_flags & LSCONN_EVANESCENT)
lsquic_prq_drop(conn);
else
close_conn_immediately(engine, sb_ctx, conn);
}
static void
apply_hp (struct conns_out_iter *iter)
{
struct lsquic_conn *conn;
TAILQ_FOREACH(conn, &iter->coi_active_list, cn_next_out)
if (conn->cn_esf_c->esf_flush_encryption && conn->cn_enc_session)
conn->cn_esf_c->esf_flush_encryption(conn->cn_enc_session);
TAILQ_FOREACH(conn, &iter->coi_inactive_list, cn_next_out)
if (conn->cn_esf_c->esf_flush_encryption && conn->cn_enc_session)
conn->cn_esf_c->esf_flush_encryption(conn->cn_enc_session);
}
#if LSQUIC_CONN_STATS
static void
update_batch_size_stats (struct lsquic_engine *engine, unsigned batch_size)
{
struct batch_size_stats *const stats = &engine->pub.enp_batch_size_stats;
++stats->count;
if (batch_size > stats->max)
stats->max = batch_size;
if (batch_size < stats->min || 0 == stats->min)
stats->min = batch_size;
if (stats->avg)
stats->avg = ((float) batch_size - stats->avg) * 0.4 + stats->avg;
else
stats->avg = (float) batch_size;
}
#endif
2017-09-22 21:00:03 +00:00
static unsigned
send_batch (lsquic_engine_t *engine, const struct send_batch_ctx *sb_ctx,
unsigned n_to_send)
2017-09-22 21:00:03 +00:00
{
int n_sent, i, e_val;
2017-09-22 21:00:03 +00:00
lsquic_time_t now;
unsigned off, skip;
size_t count;
CONST_BATCH struct out_batch *const batch = sb_ctx->batch;
struct lsquic_packet_out *CONST_BATCH *packet_out, *CONST_BATCH *end;
2017-09-22 21:00:03 +00:00
#if LSQUIC_CONN_STATS
update_batch_size_stats(engine, n_to_send);
#endif
apply_hp(sb_ctx->conns_iter);
2020-06-03 04:13:30 +00:00
#if CAN_LOSE_PACKETS
if (engine->flags & ENG_LOSE_PACKETS)
lose_matching_packets(engine, batch, n_to_send);
#endif
skip = 0;
restart_batch:
2017-09-22 21:00:03 +00:00
/* Set sent time before the write to avoid underestimating RTT */
now = lsquic_time_now();
for (i = skip; i < (int) (n_to_send - skip); ++i)
{
off = batch->pack_off[i];
count = batch->outs[i].iovlen;
assert(count > 0);
packet_out = &batch->packets[off];
end = packet_out + count;
do
(*packet_out)->po_sent = now;
while (++packet_out < end);
}
n_sent = engine->packets_out(engine->packets_out_ctx, batch->outs + skip,
n_to_send - skip);
e_val = errno;
if (n_sent < (int) (n_to_send - skip) && e_val != EMSGSIZE)
{
engine->pub.enp_flags &= ~ENPUB_CAN_SEND;
engine->resume_sending_at = now + 1000000;
LSQ_DEBUG("cannot send packets");
EV_LOG_GENERIC_EVENT("cannot send packets");
if (!(EAGAIN == e_val || EWOULDBLOCK == e_val))
close_conn_on_send_error(engine, sb_ctx,
n_sent < 0 ? 0 : n_sent, e_val);
}
2017-09-22 21:00:03 +00:00
if (n_sent >= 0)
LSQ_DEBUG("packets out returned %d (out of %u)", n_sent,
n_to_send - skip);
2017-09-22 21:00:03 +00:00
else
{
LSQ_DEBUG("packets out returned an error: %s", strerror(e_val));
2017-09-22 21:00:03 +00:00
n_sent = 0;
}
if (n_sent > 0)
engine->last_sent = now + n_sent;
for (i = skip; i < (int) (skip + n_sent); ++i)
2017-09-22 21:00:03 +00:00
{
eng_hist_inc(&engine->history, now, sl_packets_out);
/* `i' is added to maintain relative order */
batch->conns[i]->cn_last_sent = now + i;
off = batch->pack_off[i];
count = batch->outs[i].iovlen;
assert(count > 0);
packet_out = &batch->packets[off];
end = packet_out + count;
do
{
#if LOG_PACKET_CHECKSUM
log_packet_checksum(lsquic_conn_log_cid(batch->conns[i]), "out",
batch->outs[i].iov[packet_out - &batch->packets[off]].iov_base,
batch->outs[i].iov[packet_out - &batch->packets[off]].iov_len);
#endif
EV_LOG_PACKET_SENT(lsquic_conn_log_cid(batch->conns[i]),
*packet_out);
/* Release packet out buffer as soon as the packet is sent
* successfully. If not successfully sent, we hold on to
* this buffer until the packet sending is attempted again
* or until it times out and regenerated.
*/
if ((*packet_out)->po_flags & PO_ENCRYPTED)
release_enc_data(engine, batch->conns[i], *packet_out);
batch->conns[i]->cn_if->ci_packet_sent(batch->conns[i],
*packet_out);
}
while (++packet_out < end);
2017-09-22 21:00:03 +00:00
}
if (i < (int) n_to_send && e_val == EMSGSIZE)
{
LSQ_DEBUG("packet #%d could not be sent out for being too large", i);
if (batch->conns[i]->cn_if->ci_packet_too_large
&& batch->outs[i].iovlen == 1)
{
off = batch->pack_off[i];
packet_out = &batch->packets[off];
batch->conns[i]->cn_if->ci_packet_too_large(batch->conns[i],
*packet_out);
++i;
if (i < (int) n_to_send)
{
skip = i;
LSQ_DEBUG("restart batch starting at packet #%u", skip);
goto restart_batch;
}
n_sent = n_to_send;
}
else
close_conn_on_send_error(engine, sb_ctx, i, e_val);
}
2017-09-22 21:00:03 +00:00
if (LSQ_LOG_ENABLED_EXT(LSQ_LOG_DEBUG, LSQLM_EVENT))
for ( ; i < (int) n_to_send; ++i)
{
off = batch->pack_off[i];
count = batch->outs[i].iovlen;
assert(count > 0);
packet_out = &batch->packets[off];
end = packet_out + count;
do
EV_LOG_PACKET_NOT_SENT(lsquic_conn_log_cid(batch->conns[i]),
*packet_out);
while (++packet_out < end);
}
2017-09-22 21:00:03 +00:00
/* Return packets to the connection in reverse order so that the packet
* ordering is maintained.
*/
for (i = (int) n_to_send - 1; i >= (int) (skip + n_sent); --i)
2017-09-22 21:00:03 +00:00
{
off = batch->pack_off[i];
count = batch->outs[i].iovlen;
assert(count > 0);
packet_out = &batch->packets[off + count - 1];
end = &batch->packets[off - 1];
do
batch->conns[i]->cn_if->ci_packet_not_sent(batch->conns[i],
*packet_out);
while (--packet_out > end);
2017-09-22 21:00:03 +00:00
if (!(batch->conns[i]->cn_flags & (LSCONN_COI_ACTIVE|LSCONN_EVANESCENT)))
coi_reactivate(sb_ctx->conns_iter, batch->conns[i]);
2017-09-22 21:00:03 +00:00
}
return skip + n_sent;
2017-09-22 21:00:03 +00:00
}
/* Return 1 if went past deadline, 0 otherwise */
static int
check_deadline (lsquic_engine_t *engine)
{
if (engine->pub.enp_settings.es_proc_time_thresh &&
lsquic_time_now() > engine->deadline)
{
LSQ_INFO("went past threshold of %u usec, stop sending",
engine->pub.enp_settings.es_proc_time_thresh);
engine->flags |= ENG_PAST_DEADLINE;
return 1;
}
else
return 0;
}
static size_t
iov_size (const struct iovec *iov, const struct iovec *const end)
{
size_t size;
assert(iov < end);
size = 0;
do
size += iov->iov_len;
while (++iov < end);
return size;
}
2017-09-22 21:00:03 +00:00
static void
send_packets_out (struct lsquic_engine *engine,
struct conns_tailq *ticked_conns,
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
struct conns_stailq *closed_conns)
2017-09-22 21:00:03 +00:00
{
unsigned n, w, n_sent, n_batches_sent;
lsquic_packet_out_t *packet_out;
struct lsquic_packet_out **packet;
2017-09-22 21:00:03 +00:00
lsquic_conn_t *conn;
2018-04-02 19:16:01 +00:00
struct out_batch *const batch = &engine->out_batch;
struct iovec *iov, *packet_iov;
2017-09-22 21:00:03 +00:00
struct conns_out_iter conns_iter;
int shrink, deadline_exceeded;
const struct send_batch_ctx sb_ctx = {
closed_conns,
ticked_conns,
&conns_iter,
&engine->out_batch,
};
2017-09-22 21:00:03 +00:00
coi_init(&conns_iter, engine);
n_batches_sent = 0;
n_sent = 0, n = 0;
shrink = 0;
deadline_exceeded = 0;
iov = batch->iov;
packet = batch->packets;
2017-09-22 21:00:03 +00:00
while ((conn = coi_next(&conns_iter)))
{
packet_out = conn->cn_if->ci_next_packet_to_send(conn, 0);
2017-09-22 21:00:03 +00:00
if (!packet_out) {
/* Evanescent connection always has a packet to send: */
assert(!(conn->cn_flags & LSCONN_EVANESCENT));
LSQ_DEBUGC("batched all outgoing packets for %s conn %"CID_FMT,
(conn->cn_flags & LSCONN_MINI ? "mini" : "full"),
CID_BITS(lsquic_conn_log_cid(conn)));
2017-09-22 21:00:03 +00:00
coi_deactivate(&conns_iter, conn);
continue;
}
batch->outs[n].iov = packet_iov = iov;
next_coa:
2017-09-22 21:00:03 +00:00
if (!(packet_out->po_flags & (PO_ENCRYPTED|PO_NOENCRYPT)))
{
switch (conn->cn_esf_c->esf_encrypt_packet(conn->cn_enc_session,
&engine->pub, conn, packet_out))
2017-09-22 21:00:03 +00:00
{
case ENCPA_NOMEM:
/* Send what we have and wait for a more opportune moment */
conn->cn_if->ci_packet_not_sent(conn, packet_out);
goto end_for;
case ENCPA_BADCRYPT:
/* This is pretty bad: close connection immediately */
conn->cn_if->ci_packet_not_sent(conn, packet_out);
LSQ_INFOC("conn %"CID_FMT" has unsendable packets",
CID_BITS(lsquic_conn_log_cid(conn)));
2017-09-22 21:00:03 +00:00
if (!(conn->cn_flags & LSCONN_EVANESCENT))
{
close_conn_immediately(engine, &sb_ctx, conn);
coi_deactivate(&conns_iter, conn);
2017-09-22 21:00:03 +00:00
}
continue;
case ENCPA_OK:
break;
}
}
else if ((packet_out->po_flags & PO_NOENCRYPT)
&& engine->pub.enp_pmi != &stock_pmi)
{
if (0 != copy_packet(engine, conn, packet_out))
{
/* Copy can only fail if packet could not be allocated */
conn->cn_if->ci_packet_not_sent(conn, packet_out);
goto end_for;
}
}
LSQ_DEBUGC("batched packet %"PRIu64" for connection %"CID_FMT,
packet_out->po_packno, CID_BITS(lsquic_conn_log_cid(conn)));
2017-09-22 21:00:03 +00:00
if (packet_out->po_flags & PO_ENCRYPTED)
{
iov->iov_base = packet_out->po_enc_data;
iov->iov_len = packet_out->po_enc_data_sz;
2017-09-22 21:00:03 +00:00
}
else
{
iov->iov_base = packet_out->po_data;
iov->iov_len = packet_out->po_data_sz;
}
if (packet_iov == iov)
{
batch->pack_off[n] = packet - batch->packets;
batch->outs [n].ecn = lsquic_packet_out_ecn(packet_out);
batch->outs [n].peer_ctx = packet_out->po_path->np_peer_ctx;
batch->outs [n].local_sa = NP_LOCAL_SA(packet_out->po_path);
batch->outs [n].dest_sa = NP_PEER_SA(packet_out->po_path);
batch->outs [n].conn_ctx = conn->cn_conn_ctx;
batch->conns [n] = conn;
}
*packet = packet_out;
++packet;
++iov;
if ((conn->cn_flags & LSCONN_IETF)
&& ((1 << packet_out->po_header_type)
& ((1 << HETY_INITIAL)|(1 << HETY_HANDSHAKE)|(1 << HETY_0RTT)))
#ifndef NDEBUG
&& (engine->flags & ENG_COALESCE)
#endif
&& iov < batch->iov + sizeof(batch->iov) / sizeof(batch->iov[0]))
{
const struct to_coal to_coal = {
.prev_packet = packet_out,
.prev_sz_sum = iov_size(packet_iov, iov),
};
packet_out = conn->cn_if->ci_next_packet_to_send(conn, &to_coal);
if (packet_out)
goto next_coa;
2017-09-22 21:00:03 +00:00
}
batch->outs [n].iovlen = iov - packet_iov;
2017-09-22 21:00:03 +00:00
++n;
if (n == engine->batch_size
|| iov >= batch->iov + sizeof(batch->iov) / sizeof(batch->iov[0]))
2017-09-22 21:00:03 +00:00
{
w = send_batch(engine, &sb_ctx, n);
2017-09-22 21:00:03 +00:00
n = 0;
iov = batch->iov;
packet = batch->packets;
2017-09-22 21:00:03 +00:00
++n_batches_sent;
n_sent += w;
if (w < engine->batch_size)
{
shrink = 1;
break;
}
deadline_exceeded = check_deadline(engine);
if (deadline_exceeded)
break;
grow_batch_size(engine);
}
}
end_for:
if (n > 0) {
w = send_batch(engine, &sb_ctx, n);
2017-09-22 21:00:03 +00:00
n_sent += w;
shrink = w < n;
++n_batches_sent;
}
if (shrink)
shrink_batch_size(engine);
else if (n_batches_sent > 1)
{
deadline_exceeded = check_deadline(engine);
if (!deadline_exceeded)
grow_batch_size(engine);
}
2017-09-22 21:00:03 +00:00
coi_reheap(&conns_iter, engine);
LSQ_DEBUG("%s: sent %u packet%.*s", __func__, n_sent, n_sent != 1, "s");
}
int
lsquic_engine_has_unsent_packets (lsquic_engine_t *engine)
{
return lsquic_mh_count(&engine->conns_out) > 0
|| (engine->pr_queue && lsquic_prq_have_pending(engine->pr_queue))
2017-09-22 21:00:03 +00:00
;
}
static void
reset_deadline (lsquic_engine_t *engine, lsquic_time_t now)
{
engine->deadline = now + engine->pub.enp_settings.es_proc_time_thresh;
engine->flags &= ~ENG_PAST_DEADLINE;
}
static void
check_tickable_conns_again (struct lsquic_engine *engine)
{
struct lsquic_hash_elem *el;
struct lsquic_conn *conn;
unsigned count;
count = 0;
for (el = lsquic_hash_first(engine->conns_hash); el;
el = lsquic_hash_next(engine->conns_hash))
{
conn = lsquic_hashelem_getdata(el);
if (!(conn->cn_flags & LSCONN_TICKABLE)
&& conn->cn_if->ci_is_tickable(conn))
{
lsquic_mh_insert(&engine->conns_tickable, conn,
conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
++count;
}
}
LSQ_DEBUG("%u connection%s tickable again after sending has been "
"re-enabled", count, count == 1 ? " is" : "s are");
}
2017-09-22 21:00:03 +00:00
void
lsquic_engine_send_unsent_packets (lsquic_engine_t *engine)
{
lsquic_conn_t *conn;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
struct conns_stailq closed_conns;
struct conns_tailq ticked_conns = TAILQ_HEAD_INITIALIZER(ticked_conns);
struct cid_update_batch cub;
2017-09-22 21:00:03 +00:00
ENGINE_IN(engine);
cub_init(&cub, engine->report_old_scids, engine->scids_ctx);
2017-09-22 21:00:03 +00:00
STAILQ_INIT(&closed_conns);
reset_deadline(engine, lsquic_time_now());
if (!(engine->pub.enp_flags & ENPUB_CAN_SEND))
{
LSQ_DEBUG("can send again");
EV_LOG_GENERIC_EVENT("can send again");
engine->pub.enp_flags |= ENPUB_CAN_SEND;
check_tickable_conns_again(engine);
}
2017-09-22 21:00:03 +00:00
send_packets_out(engine, &ticked_conns, &closed_conns);
2017-09-22 21:00:03 +00:00
while ((conn = STAILQ_FIRST(&closed_conns))) {
STAILQ_REMOVE_HEAD(&closed_conns, cn_next_closed_conn);
if ((conn->cn_flags & (LSCONN_MINI|LSCONN_PROMOTED)) == LSCONN_MINI)
cub_add_cids_from_cces(&cub, conn);
2017-09-22 21:00:03 +00:00
(void) engine_decref_conn(engine, conn, LSCONN_CLOSING);
}
cub_flush(&cub);
ENGINE_OUT(engine);
}
static lsquic_conn_t *
next_new_full_conn (struct conns_stailq *new_full_conns)
{
lsquic_conn_t *conn;
conn = STAILQ_FIRST(new_full_conns);
if (conn)
STAILQ_REMOVE_HEAD(new_full_conns, cn_next_new_full);
return conn;
2017-09-22 21:00:03 +00:00
}
#if LSQUIC_CONN_STATS
static void
maybe_log_conn_stats (struct lsquic_engine *engine, struct lsquic_conn *conn,
lsquic_time_t now)
{
char cidstr[MAX_CID_LEN * 2 + 1];
if (!LSQ_LOG_ENABLED_EXT(LSQ_LOG_NOTICE, LSQLM_CONN_STATS))
return;
if (conn->cn_last_ticked + 1000000 >= now)
{
if (0 == engine->busy.last_log
|| engine->busy.last_log + 1000000 - 1000 < now)
{
engine->busy.last_log = now;
conn->cn_if->ci_log_stats(conn);
}
}
else
{
lsquic_logger_log1(LSQ_LOG_NOTICE, LSQLM_CONN_STATS,
"stop logging status for connection %s: no longer busy",
(lsquic_cid2str(lsquic_conn_log_cid(conn), cidstr), cidstr));
engine->busy.current = NULL;
engine->busy.last_log = 0;
}
}
#endif
2017-09-22 21:00:03 +00:00
static void
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
process_connections (lsquic_engine_t *engine, conn_iter_f next_conn,
lsquic_time_t now)
2017-09-22 21:00:03 +00:00
{
lsquic_conn_t *conn;
enum tick_st tick_st;
unsigned i, why;
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
lsquic_time_t next_tick_time;
struct conns_stailq closed_conns;
struct conns_tailq ticked_conns;
struct conns_stailq new_full_conns;
struct cid_update_batch cub_old, cub_live;
cub_init(&cub_old, engine->report_old_scids, engine->scids_ctx);
cub_init(&cub_live, engine->report_live_scids, engine->scids_ctx);
2017-09-22 21:00:03 +00:00
eng_hist_tick(&engine->history, now);
STAILQ_INIT(&closed_conns);
TAILQ_INIT(&ticked_conns);
2017-09-22 21:00:03 +00:00
reset_deadline(engine, now);
STAILQ_INIT(&new_full_conns);
2017-09-22 21:00:03 +00:00
if (!(engine->pub.enp_flags & ENPUB_CAN_SEND)
&& now > engine->resume_sending_at)
{
LSQ_NOTICE("failsafe activated: resume sending packets again after "
"timeout");
EV_LOG_GENERIC_EVENT("resume sending packets again after timeout");
engine->pub.enp_flags |= ENPUB_CAN_SEND;
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
i = 0;
while ((conn = next_conn(engine))
|| (conn = next_new_full_conn(&new_full_conns)))
2017-09-22 21:00:03 +00:00
{
tick_st = conn->cn_if->ci_tick(conn, now);
#if LSQUIC_CONN_STATS
if (conn == engine->busy.current)
maybe_log_conn_stats(engine, conn, now);
#endif
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
conn->cn_last_ticked = now + i /* Maintain relative order */ ++;
if (tick_st & TICK_PROMOTE)
{
lsquic_conn_t *new_conn;
EV_LOG_CONN_EVENT(lsquic_conn_log_cid(conn),
"scheduled for promotion");
assert(conn->cn_flags & LSCONN_MINI);
new_conn = new_full_conn_server(engine, conn, now);
if (new_conn)
{
STAILQ_INSERT_TAIL(&new_full_conns, new_conn, cn_next_new_full);
new_conn->cn_last_sent = engine->last_sent;
eng_hist_inc(&engine->history, now, sl_new_full_conns);
conn->cn_flags |= LSCONN_PROMOTED;
}
tick_st |= TICK_CLOSE; /* Destroy mini connection */
}
2017-09-22 21:00:03 +00:00
if (tick_st & TICK_SEND)
{
if (!(conn->cn_flags & LSCONN_HAS_OUTGOING))
{
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
lsquic_mh_insert(&engine->conns_out, conn, conn->cn_last_sent);
2017-09-22 21:00:03 +00:00
engine_incref_conn(conn, LSCONN_HAS_OUTGOING);
}
}
if (tick_st & TICK_CLOSE)
{
STAILQ_INSERT_TAIL(&closed_conns, conn, cn_next_closed_conn);
engine_incref_conn(conn, LSCONN_CLOSING);
if (conn->cn_flags & LSCONN_HASHED)
remove_conn_from_hash(engine, conn);
}
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
else
{
TAILQ_INSERT_TAIL(&ticked_conns, conn, cn_next_ticked);
engine_incref_conn(conn, LSCONN_TICKED);
if ((engine->flags & ENG_SERVER) && conn->cn_if->ci_report_live
&& conn->cn_if->ci_report_live(conn, now))
cub_add_cids_from_cces(&cub_live, conn);
}
2017-09-22 21:00:03 +00:00
}
cub_flush(&engine->new_scids);
if ((engine->pub.enp_flags & ENPUB_CAN_SEND)
&& lsquic_engine_has_unsent_packets(engine))
send_packets_out(engine, &ticked_conns, &closed_conns);
2017-09-22 21:00:03 +00:00
while ((conn = STAILQ_FIRST(&closed_conns))) {
STAILQ_REMOVE_HEAD(&closed_conns, cn_next_closed_conn);
if ((conn->cn_flags & (LSCONN_MINI|LSCONN_PROMOTED)) == LSCONN_MINI)
cub_add_cids_from_cces(&cub_old, conn);
2017-09-22 21:00:03 +00:00
(void) engine_decref_conn(engine, conn, LSCONN_CLOSING);
}
while ((conn = TAILQ_FIRST(&ticked_conns)))
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
{
TAILQ_REMOVE(&ticked_conns, conn, cn_next_ticked);
engine_decref_conn(engine, conn, LSCONN_TICKED);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (!(conn->cn_flags & LSCONN_TICKABLE)
&& conn->cn_if->ci_is_tickable(conn))
{
/* Floyd heapification is not faster, don't bother. */
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
lsquic_mh_insert(&engine->conns_tickable, conn, conn->cn_last_ticked);
engine_incref_conn(conn, LSCONN_TICKABLE);
}
else if (!(conn->cn_flags & LSCONN_ATTQ))
{
next_tick_time = conn->cn_if->ci_next_tick_time(conn, &why);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
if (next_tick_time)
{
if (0 == lsquic_attq_add(engine->attq, conn, next_tick_time,
why))
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
engine_incref_conn(conn, LSCONN_ATTQ);
}
else
/* In all other cases, the idle timeout would make the next
* tick time non-zero:
*/
assert((conn->cn_flags & LSCONN_IETF)
&& engine->pub.enp_settings.es_idle_timeout == 0);
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
}
}
cub_flush(&cub_live);
cub_flush(&cub_old);
2017-09-22 21:00:03 +00:00
}
static void
maybe_count_garbage (struct lsquic_engine *engine, size_t garbage_sz)
{
/* This is not very pretty (action at a distance via engine->curr_conn),
* but it's the cheapest I can come up with to handle the "count garbage
* toward amplification limit" requirement in
* [draft-ietf-quic-transport-28] Section 8.1.
*/
if (engine->curr_conn && engine->curr_conn->cn_if->ci_count_garbage)
engine->curr_conn->cn_if->ci_count_garbage(engine->curr_conn,
garbage_sz);
}
2017-09-22 21:00:03 +00:00
/* Return 0 if packet is being processed by a real connection, 1 if the
* packet was processed, but not by a connection, and -1 on error.
*/
int
lsquic_engine_packet_in (lsquic_engine_t *engine,
const unsigned char *packet_in_data, size_t packet_in_size,
const struct sockaddr *sa_local, const struct sockaddr *sa_peer,
void *peer_ctx, int ecn)
2017-09-22 21:00:03 +00:00
{
const unsigned char *const packet_begin = packet_in_data;
const unsigned char *const packet_end = packet_in_data + packet_in_size;
2017-09-22 21:00:03 +00:00
struct packin_parse_state ppstate;
lsquic_packet_in_t *packet_in;
2018-08-15 19:06:31 +00:00
int (*parse_packet_in_begin) (struct lsquic_packet_in *, size_t length,
int is_server, unsigned cid_len, struct packin_parse_state *);
unsigned n_zeroes;
int s, is_ietf;
lsquic_cid_t cid;
2017-09-22 21:00:03 +00:00
ENGINE_CALLS_INCR(engine);
2017-09-22 21:00:03 +00:00
if (engine->flags & ENG_SERVER)
parse_packet_in_begin = lsquic_parse_packet_in_server_begin;
else if (engine->flags & ENG_CONNS_BY_ADDR)
2018-08-15 19:06:31 +00:00
{
struct lsquic_hash_elem *el;
2018-08-15 19:06:31 +00:00
const struct lsquic_conn *conn;
el = find_conn_by_addr(engine->conns_hash, sa_local);
if (!el)
2018-08-15 19:06:31 +00:00
return -1;
conn = lsquic_hashelem_getdata(el);
2018-08-15 19:06:31 +00:00
if ((1 << conn->cn_version) & LSQUIC_GQUIC_HEADER_VERSIONS)
parse_packet_in_begin = lsquic_gquic_parse_packet_in_begin;
else if ((1 << conn->cn_version) & LSQUIC_IETF_VERSIONS)
parse_packet_in_begin = lsquic_ietf_v1_parse_packet_in_begin;
else if (conn->cn_version == LSQVER_050)
parse_packet_in_begin = lsquic_Q050_parse_packet_in_begin;
2018-08-15 19:06:31 +00:00
else
{
2020-06-03 04:13:30 +00:00
assert(conn->cn_version == LSQVER_046);
parse_packet_in_begin = lsquic_Q046_parse_packet_in_begin;
}
2018-08-15 19:06:31 +00:00
}
else
parse_packet_in_begin = lsquic_parse_packet_in_begin;
engine->curr_conn = NULL;
n_zeroes = 0;
is_ietf = 0;
2020-06-03 04:13:30 +00:00
#ifdef _MSC_VER
s = 0;
cid.len = 0;
cid.idbuf[0] = 0;
#endif
do
2017-09-22 21:00:03 +00:00
{
packet_in = lsquic_mm_get_packet_in(&engine->pub.enp_mm);
if (!packet_in)
return -1;
/* Library does not modify packet_in_data, it is not referenced after
* this function returns and subsequent release of pi_data is guarded
* by PI_OWN_DATA flag.
*/
packet_in->pi_data = (unsigned char *) packet_in_data;
if (0 != parse_packet_in_begin(packet_in, packet_end - packet_in_data,
engine->flags & ENG_SERVER,
engine->pub.enp_settings.es_scid_len, &ppstate))
{
LSQ_DEBUG("Cannot parse incoming packet's header");
maybe_count_garbage(engine, packet_end - packet_in_data);
lsquic_mm_put_packet_in(&engine->pub.enp_mm, packet_in);
s = 1;
break;
}
2017-09-22 21:00:03 +00:00
/* [draft-ietf-quic-transport-30] Section 12.2:
* " Receivers SHOULD ignore any subsequent packets with a different
* " Destination Connection ID than the first packet in the datagram.
*/
if (is_ietf && packet_in_data > packet_begin)
{
if (!((packet_in->pi_flags & (PI_GQUIC|PI_CONN_ID)) == PI_CONN_ID
&& LSQUIC_CIDS_EQ(&packet_in->pi_dcid, &cid)))
{
packet_in_data += packet_in->pi_data_sz;
maybe_count_garbage(engine, packet_in->pi_data_sz);
continue;
}
}
is_ietf = 0 == (packet_in->pi_flags & PI_GQUIC);
packet_in_data += packet_in->pi_data_sz;
if (is_ietf && packet_in_data < packet_end)
{
cid = packet_in->pi_dcid;
if (packet_begin == packet_in->pi_data) /* Only log once: */
LSQ_DEBUGC("received coalesced datagram of %zd bytes for "
"connection %"CID_FMT, packet_in_size, CID_BITS(&cid));
}
packet_in->pi_received = lsquic_time_now();
packet_in->pi_flags |= (3 & ecn) << PIBIT_ECN_SHIFT;
eng_hist_inc(&engine->history, packet_in->pi_received, sl_packets_in);
s = process_packet_in(engine, packet_in, &ppstate, sa_local, sa_peer,
peer_ctx, packet_in_size);
n_zeroes += s == 0;
}
while (0 == s && packet_in_data < packet_end);
return n_zeroes > 0 ? 0 : s;
2017-09-22 21:00:03 +00:00
}
#if __GNUC__ && !defined(NDEBUG)
__attribute__((weak))
#endif
unsigned
lsquic_engine_quic_versions (const lsquic_engine_t *engine)
{
return engine->pub.enp_settings.es_versions;
}
void
lsquic_engine_cooldown (lsquic_engine_t *engine)
{
struct lsquic_hash_elem *el;
lsquic_conn_t *conn;
if (engine->flags & ENG_COOLDOWN)
/* AFAICT, there is no harm in calling this function more than once,
* but log it just in case, as it may indicate an error in the caller.
*/
LSQ_INFO("cooldown called again");
engine->flags |= ENG_COOLDOWN;
LSQ_INFO("entering cooldown mode");
if (engine->flags & ENG_SERVER)
drop_all_mini_conns(engine);
for (el = lsquic_hash_first(engine->conns_hash); el;
el = lsquic_hash_next(engine->conns_hash))
{
conn = lsquic_hashelem_getdata(el);
lsquic_conn_going_away(conn);
}
}
#if LSQUIC_CONN_STATS
static void
update_busy_detector (struct lsquic_engine *engine, struct lsquic_conn *conn,
int immed)
{
char cidstr[MAX_CID_LEN * 2 + 1];
if (!(LSQ_LOG_ENABLED_EXT(LSQ_LOG_NOTICE, LSQLM_CONN_STATS)
&& conn->cn_if->ci_log_stats))
return;
if (conn == engine->busy.last_conn)
{
engine->busy.immed_ticks <<= 1u;
engine->busy.immed_ticks |= immed;
if (MAX_IMMED_TICKS == engine->busy.immed_ticks)
{
if (engine->busy.current != conn)
lsquic_logger_log1(LSQ_LOG_NOTICE, LSQLM_CONN_STATS,
"connection %s marked busy: it's had %u immediate ticks "
"in a row",
(lsquic_cid2str(lsquic_conn_log_cid(conn), cidstr), cidstr),
(unsigned) (sizeof(engine->busy.immed_ticks) * 8));
engine->busy.current = conn;
}
}
else
engine->busy.immed_ticks <<= 1;
engine->busy.last_conn = conn;
}
#endif
2017-09-22 21:00:03 +00:00
int
lsquic_engine_earliest_adv_tick (lsquic_engine_t *engine, int *diff)
{
const struct attq_elem *next_attq;
lsquic_time_t now, next_time;
#if LSQUIC_DEBUG_NEXT_ADV_TICK || LSQUIC_CONN_STATS
struct lsquic_conn *conn;
#endif
#if LSQUIC_DEBUG_NEXT_ADV_TICK
const enum lsq_log_level L = LSQ_LOG_DEBUG; /* Easy toggle */
#endif
2017-09-22 21:00:03 +00:00
ENGINE_CALLS_INCR(engine);
if ((engine->flags & ENG_PAST_DEADLINE)
&& lsquic_mh_count(&engine->conns_out))
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
{
#if LSQUIC_DEBUG_NEXT_ADV_TICK
conn = lsquic_mh_peek(&engine->conns_out);
engine->last_logged_conn = 0;
LSQ_LOGC(L, "next advisory tick is now: went past deadline last time "
"and have %u outgoing connection%.*s (%"CID_FMT" first)",
lsquic_mh_count(&engine->conns_out),
lsquic_mh_count(&engine->conns_out) != 1, "s",
CID_BITS(lsquic_conn_log_cid(conn)));
#endif
#if LSQUIC_CONN_STATS
conn = lsquic_mh_peek(&engine->conns_out);
update_busy_detector(engine, conn, 1);
#endif
[API Change, OPTIMIZATION] Only process conns that need to be processed The API is simplified: do not expose the user code to several queues. A "connection queue" is now an internal concept. The user processes connections using the single function lsquic_engine_process_conns(). When this function is called, only those connections are processed that need to be processed. A connection needs to be processed when: 1. New incoming packets have been fed to the connection. 2. User wants to read from a stream that is readable. 3. User wants to write to a stream that is writeable. 4. There are buffered packets that can be sent out. (This means that the user wrote to a stream outside of the lsquic library callback.) 5. A control frame (such as BLOCKED) needs to be sent out. 6. A stream needs to be serviced or delayed stream needs to be created. 7. An alarm rings. 8. Pacer timer expires. To achieve this, the library places the connections into two priority queues (min heaps): 1. Tickable Queue; and 2. Advisory Tick Time queue (ATTQ). Each time lsquic_engine_process_conns() is called, the Tickable Queue is emptied. After the connections have been ticked, they are queried again: if a connection is not being closed, it is placed either in the Tickable Queue if it is ready to be ticked again or it is placed in the Advisory Tick Time Queue. It is assumed that a connection always has at least one timer set (the idle alarm). The connections in the Tickable Queue are arranged in the least recently ticked order. This lets connections that have been quiet longer to get their packets scheduled first. This change means that the library no longer needs to be ticked periodically. The user code can query the library when is the next tick event and schedule it exactly. When connections are processed, only the tickable connections are processed, not *all* the connections. When there are no tick events, it means that no timer event is necessary -- only the file descriptor READ event is active. The following are improvements and simplifications that have been triggered: - Queue of connections with incoming packets is gone. - "Pending Read/Write Events" Queue is gone (along with its history and progress checks). This queue has become the Tickable Queue. - The connection hash no longer needs to track the connection insertion order.
2018-04-09 13:39:38 +00:00
*diff = 0;
return 1;
}
2021-04-16 18:54:30 +00:00
if ((engine->pub.enp_flags & ENPUB_CAN_SEND)
&& engine->pr_queue && lsquic_prq_have_pending(engine->pr_queue))
{
#if LSQUIC_DEBUG_NEXT_ADV_TICK
engine->last_logged_conn = 0;
LSQ_LOG(L, "next advisory tick is now: have pending PRQ elements");
#endif
*diff = 0;
return 1;
}
if (lsquic_mh_count(&engine->conns_tickable))
{
#if LSQUIC_DEBUG_NEXT_ADV_TICK
conn = lsquic_mh_peek(&engine->conns_tickable);
engine->last_logged_conn = 0;
LSQ_LOGC(L, "next advisory tick is now: have %u tickable "
"connection%.*s (%"CID_FMT" first)",
lsquic_mh_count(&engine->conns_tickable),
lsquic_mh_count(&engine->conns_tickable) != 1, "s",
CID_BITS(lsquic_conn_log_cid(conn)));
#endif
#if LSQUIC_CONN_STATS
conn = lsquic_mh_peek(&engine->conns_tickable);
update_busy_detector(engine, conn, 1);
#endif
*diff = 0;
return 1;
}
next_attq = lsquic_attq_next(engine->attq);
if (engine->pub.enp_flags & ENPUB_CAN_SEND)
{
if (next_attq)
next_time = next_attq->ae_adv_time;
else
return 0;
}
else
{
if (next_attq)
{
next_time = next_attq->ae_adv_time;
if (engine->resume_sending_at < next_time)
{
next_time = engine->resume_sending_at;
next_attq = NULL;
}
}
else
next_time = engine->resume_sending_at;
}
2017-09-22 21:00:03 +00:00
now = lsquic_time_now();
*diff = (int) ((int64_t) next_time - (int64_t) now);
#if LSQUIC_DEBUG_NEXT_ADV_TICK
if (next_attq)
{
/* Deduplicate consecutive log messages about the same reason for the
* same connection.
* If diff is always zero or diff reset to a higher value, event is
* still logged.
*/
if (!((unsigned) next_attq->ae_why == engine->last_logged_ae_why
&& (uintptr_t) next_attq->ae_conn
== engine->last_logged_conn
&& *diff < engine->last_tick_diff))
{
engine->last_logged_conn = (uintptr_t) next_attq->ae_conn;
engine->last_logged_ae_why = (unsigned) next_attq->ae_why;
engine->last_tick_diff = *diff;
LSQ_LOGC(L, "next advisory tick is %d usec away: conn %"CID_FMT
": %s", *diff, CID_BITS(lsquic_conn_log_cid(next_attq->ae_conn)),
lsquic_attq_why2str(next_attq->ae_why));
}
}
else
LSQ_LOG(L, "next advisory tick is %d usec away: resume sending", *diff);
#endif
#if LSQUIC_CONN_STATS
if (next_attq)
update_busy_detector(engine, next_attq->ae_conn,
/* Immediate if: a) time is now or in the past */
*diff <= 0
/* b) next event is pacer, which means that the
* connection wants to send, but is prevented
* by the pacer from doing so.
*/
|| next_attq->ae_why == AEW_PACER
/* c) next event is to retransmit data (meaning
* that there is data in flight) and the
* time is small, which implies a small RTT.
*/
|| (next_attq->ae_why == N_AEWS + AL_RETX_APP
&& *diff < 5000));
#endif
2017-09-22 21:00:03 +00:00
return 1;
}
unsigned
lsquic_engine_count_attq (lsquic_engine_t *engine, int from_now)
{
lsquic_time_t now;
ENGINE_CALLS_INCR(engine);
2017-09-22 21:00:03 +00:00
now = lsquic_time_now();
if (from_now < 0)
now -= from_now;
else
now += from_now;
return lsquic_attq_count_before(engine->attq, now);
2017-09-22 21:00:03 +00:00
}
int
lsquic_engine_add_cid (struct lsquic_engine_public *enpub,
struct lsquic_conn *conn, unsigned cce_idx)
{
struct lsquic_engine *const engine = (struct lsquic_engine *) enpub;
struct conn_cid_elem *const cce = &conn->cn_cces[cce_idx];
void *peer_ctx;
assert(cce_idx < conn->cn_n_cces);
assert(conn->cn_cces_mask & (1 << cce_idx));
assert(!(cce->cce_hash_el.qhe_flags & QHE_HASHED));
if (lsquic_hash_insert(engine->conns_hash, cce->cce_cid.idbuf,
cce->cce_cid.len, conn, &cce->cce_hash_el))
{
LSQ_DEBUGC("add %"CID_FMT" to the list of SCIDs",
CID_BITS(&cce->cce_cid));
peer_ctx = lsquic_conn_get_peer_ctx(conn, NULL);
cce->cce_flags |= CCE_REG;
cub_add(&engine->new_scids, &cce->cce_cid, peer_ctx);
return 0;
}
else
{
LSQ_WARNC("could not add new cid %"CID_FMT" to the SCID hash",
CID_BITS(&cce->cce_cid));
return -1;
}
}
void
lsquic_engine_retire_cid (struct lsquic_engine_public *enpub,
struct lsquic_conn *conn, unsigned cce_idx, lsquic_time_t now,
lsquic_time_t drain_time)
{
struct lsquic_engine *const engine = (struct lsquic_engine *) enpub;
struct conn_cid_elem *const cce = &conn->cn_cces[cce_idx];
struct purga_el *puel;
void *peer_ctx;
assert(cce_idx < conn->cn_n_cces);
if (cce->cce_hash_el.qhe_flags & QHE_HASHED)
lsquic_hash_erase(engine->conns_hash, &cce->cce_hash_el);
if (engine->purga)
{
peer_ctx = lsquic_conn_get_peer_ctx(conn, NULL);
puel = lsquic_purga_add(engine->purga, &cce->cce_cid, peer_ctx,
PUTY_CID_RETIRED, now);
if (puel)
puel->puel_time = now + drain_time;
}
conn->cn_cces_mask &= ~(1u << cce_idx);
LSQ_DEBUGC("retire CID %"CID_FMT, CID_BITS(&cce->cce_cid));
}