Skip to content

Build a test server

XQUIC serve as a middleware of transport layer, which induces QUIC facilities to socket application with optional HTTP(or HTTP/3) functionality.

To help you quickly familiarize yourself with how to utilize XQUIC in an socket application, we provide a minimum client and server with XQUIC interface and callback operations.

Please be aware that mini_client and mini_server only demonstrate part of the XQUIC interface, and are only suitable for learning and testing purposes.

mini_server basically includes the following modules:

  • Engine: Engine is the core module of XQUIC main process, which must be initialized in the first stage.

  • Connection: Connection module is a submodule of Engine, which is responsible for establishing and managing connections.

  • Request: Request module is a submodule of Connection, which is responsible for data transmission.

Engine

Engine manage the core logic of XQUIC, including connection management, common callback management, incoming and outgoing packet processing, etc. To make sure the correctness of packet processing, both c/s should be initialized with engine.

Here follows the basic steps to build a xquic engine in server:

Create an engine

Before building a xquic engine, make sure to initialize with several configurations as follows:

  • xqc_engine_type_t engine_type: type of engine, 0: server, 1: client

  • xqc_config_t * engine_config: configuration of engine

engine default config can be obtained using xqc_engine_get_default_config:

c
/* Psudo */ 
xqc_engine_get_default_config(&engine_config, engine_type);
  • xqc_engine_ssl_config_t * ssl_config: configuration of ssl

mini_server init ssl config using xqc_mini_svr_init_engine_ssl_config, which defines the following parameters of ssl config:

c
/* Psudo */
ssl_cfg->private_key_file = args->env_cfg.private_key_file; //"server.key"
ssl_cfg->cert_file = args->env_cfg.cert_file;   // "server.crt"
ssl_config->ciphers = args->quic_cfg.ciphers;   // "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"
ssl_config->groups = args->quic_cfg.groups;     // "P-256:X25519:P-384:P-521"
  • xqc_engine_callback_t * engine_callbacks and xqc_transport_callbacks_t * transport_callbacks: engine basic callbacks and transport callbacks.

mini_server using xqc_mini_svr_init_callback to initialize the engine callbacks and transport callbacks with the following self-defined callback functions. It's recommended to refers to callbacks API for more details if you'd like to customize the callbacks:

c
/* Psudo */ 
static xqc_engine_callback_t callback = {
    .set_event_timer = xqc_mini_svr_set_event_timer,
    .log_callbacks = {
        .xqc_log_write_err = xqc_mini_svr_write_log_file,
        .xqc_log_write_stat = xqc_mini_svr_write_log_file,
        .xqc_qlog_event_write = xqc_mini_svr_write_qlog_file
    },
    .keylog_cb = xqc_mini_svr_keylog_cb,
};

static xqc_transport_callbacks_t transport_cbs = {
    .server_accept = xqc_mini_svr_accept,
    .write_socket = xqc_mini_svr_write_socket,
    .write_socket_ex = xqc_mini_svr_write_socket_ex,
    .conn_update_cid_notify = xqc_mini_svr_conn_update_cid_notify,
};
  • void * user_data: application context, will be passed to callback functions for convenient uses.

After that engine can be created using xqc_engine_create:

c
/* Psudo */ 
xqc_engine_t *engine = xqc_engine_create(0, &engine_config, &ssl_config,
                                         &callback, &transport_cbs, user_data);

Init engine context

After initialization of engine, there's still some context to be completed:

Init connection settings

The server typically accepts requests from the client, and passively establish connections with the client. Therefore, connection settings should be set up before accepting requests from the client:

mini_server uses xqc_mini_svr_init_conn_settings to initialize the connection settings with the following parameters:

c
/* Psudo */
xqc_conn_settings_t conn_settings = {
    .cong_ctrl_callback = ccc,
    .cc_params = {
        .customize_on = 1,
        .init_cwnd = 32,
        .bbr_enable_lt_bw = 1,
    },
    .spurious_loss_detect_on = 1,
    .init_idle_time_out = 60000,
    .enable_multipath = args->quic_cfg.multipath,
    .scheduler_callback = sched,
    .standby_path_probe_timeout = 1000,
    .adaptive_ack_frequency = 1,
    .anti_amplification_limit = 4,
};

/* set customized connection settings to engine ctx */
xqc_server_set_conn_settings(engine, &conn_settings);

Register application callbacks

Refers to mini_client section for details.

Connection

XQUIC can support reliable transmission upon the UDP transport layer based on the Connection module.

Unlike TCP, QUIC connection is more like an abstraction of socket state management, and is negotiate and maintained based on socket transmission.

Initialize a socket and socket callback

The server typically accepts requests from the client, and passively establish connections with the client.

To enable server receive and process packets from the client, it's necessary of server to create and initialize socket. All the socket logic are included in xqc_mini_svr_create_user_conn.

mini_server uses xqc_mini_svr_create_socket to create socket with specific address, port and several parameters and uses xqc_mini_svr_socket_event_callback to register the callback function for socket events.

c
/* Psudo */
xqc_mini_svr_create_socket(user_conn, &args->net_cfg);

/* bind socket event callback to fd event */
user_conn->ev_socket = event_new(ctx->eb, user_conn->fd, EV_READ | EV_PERSIST,
                                 xqc_mini_svr_socket_event_callback, user_conn);
event_add(user_conn->ev_socket, NULL);

Create a connection (passively)

The functionality of XQUIC is primarily introduced in the handling of READ EVENTS, which allows the server to process raw data packets from the client using the XQUIC interface, thereby enabling features such as parsing QUIC requests and establishing QUIC connections.

c
/* Psudo: socket read handler, receive raw data and process it in xquic engine */
ssize_t recv_size = recvfrom(fd, packet_buf, sizeof(packet_buf), 0,
                             (struct sockaddr *) &peer_addr, &peer_addrlen);

/* process quic packet with xquic engine */
xqc_engine_packet_process(engine, packet_buf, recv_size,
                          (struct sockaddr *)(user_conn->local_addr), user_conn->local_addrlen,
                          (struct sockaddr *)(user_conn->peer_addr), user_conn->peer_addrlen,
                          (xqc_usec_t)recv_time, user_conn);

xqc_engine_finish_recv(engine);

Request

Server process and respond request using application callback function, which was registered in regitster application callbacks.

mini_server enables requests sending & receiving via the following steps:

Receive request

mini_server uses the xqc_mini_svr_h3_request_read_notify interface to customize the processing for client request data that has been parsed by the xquic main logic.

c
/* Psudo */
// Step 1: send headers
xqc_http_headers_t *headers = xqc_h3_request_recv_headers(h3_request, &fin);

// Step 2: send body
ssize_t read = xqc_h3_request_recv_body(h3_request, recv_buff, recv_buff_size, &fin);

Send request

When the server triggers write notify, mini_server uses xqc_mini_svr_h3_request_write_notify to process sending request.

After receiving a request from the client, mini_server uses xqc_mini_cli_handle_h3_request to send response to the client.

For example:

c
/* Psudo */
// Step 1: send headers
xqc_h3_request_send_headers(h3_request, &headers, 0);

// Step 2: send body
xqc_h3_request_send_body(user_stream->h3_request, send_buf, send_buf_size, fin);