mini/mini_client.c (575 lines of code) (raw):

#include "mini_client.h" #include <netdb.h> void xqc_mini_cli_init_engine_ssl_config(xqc_engine_ssl_config_t *ssl_cfg, xqc_mini_cli_args_t *args) { ssl_cfg->ciphers = args->quic_cfg.ciphers; ssl_cfg->groups = args->quic_cfg.groups; } void xqc_mini_cli_init_callback(xqc_engine_callback_t *cb, xqc_transport_callbacks_t *tcb, xqc_mini_cli_args_t *args) { static xqc_engine_callback_t callback = { .set_event_timer = xqc_mini_cli_set_event_timer, .log_callbacks = { .xqc_log_write_err = xqc_mini_cli_write_log_file, .xqc_log_write_stat = xqc_mini_cli_write_log_file, .xqc_qlog_event_write = xqc_mini_cli_write_qlog_file }, .keylog_cb = xqc_mini_cli_keylog_cb, }; static xqc_transport_callbacks_t transport_cbs = { .write_socket = xqc_mini_cli_write_socket, .write_socket_ex = xqc_mini_cli_write_socket_ex, .save_token = xqc_mini_cli_save_token, .save_session_cb = xqc_mini_cli_save_session_cb, .save_tp_cb = xqc_mini_cli_save_tp_cb, }; *cb = callback; *tcb = transport_cbs; } int xqc_mini_cli_init_xquic_engine(xqc_mini_cli_ctx_t *ctx, xqc_mini_cli_args_t *args) { int ret; xqc_config_t egn_cfg; xqc_engine_callback_t callback; xqc_engine_ssl_config_t ssl_cfg = {0}; xqc_transport_callbacks_t transport_cbs; /* get default parameters of xquic engine */ ret = xqc_engine_get_default_config(&egn_cfg, XQC_ENGINE_CLIENT); if (ret < 0) { return XQC_ERROR; } /* init ssl config */ xqc_mini_cli_init_engine_ssl_config(&ssl_cfg, args); /* init engine & transport callbacks */ xqc_mini_cli_init_callback(&callback, &transport_cbs, args); /* create client engine */ ctx->engine = xqc_engine_create(XQC_ENGINE_CLIENT, &egn_cfg, &ssl_cfg, &callback, &transport_cbs, ctx); if (ctx->engine == NULL) { printf("[error] xqc_engine_create error\n"); return XQC_ERROR; } ctx->ev_engine = event_new(ctx->eb, -1, 0, xqc_mini_cli_engine_cb, ctx); return XQC_OK; } void xqc_mini_cli_convert_text_to_sockaddr(int type, const char *addr_text, unsigned int port, struct sockaddr **saddr, socklen_t *saddr_len) { *saddr = calloc(1, sizeof(struct sockaddr_in)); struct sockaddr_in *addr_v4 = (struct sockaddr_in *)(*saddr); inet_pton(type, addr_text, &(addr_v4->sin_addr.s_addr)); addr_v4->sin_family = type; addr_v4->sin_port = htons(port); *saddr_len = sizeof(struct sockaddr_in); } void xqc_mini_cli_init_args(xqc_mini_cli_args_t *args) { /* init network args */ args->net_cfg.conn_timeout = 9; /** * init quic config * it's recommended to replace the constant value with option arguments according to actual needs */ strncpy(args->quic_cfg.ciphers, XQC_TLS_CIPHERS, CIPHER_SUIT_LEN - 1); strncpy(args->quic_cfg.groups, XQC_TLS_GROUPS, TLS_GROUPS_LEN - 1); args->quic_cfg.multipath = 0; /* init environmen args */ // args->env_cfg.log_level = XQC_LOG_DEBUG; strncpy(args->env_cfg.log_path, LOG_PATH, sizeof(args->env_cfg.log_path)); strncpy(args->env_cfg.out_file_dir, OUT_DIR, sizeof(args->env_cfg.out_file_dir)); strncpy(args->env_cfg.key_out_path, KEY_PATH, sizeof(args->env_cfg.key_out_path)); /* init request args */ args->req_cfg.method = REQUEST_METHOD_GET; // GET strncpy(args->req_cfg.scheme, "https", sizeof(args->req_cfg.scheme)); strncpy(args->req_cfg.url, "/", sizeof(args->req_cfg.url)); strncpy(args->req_cfg.host, DEFAULT_HOST, sizeof(args->req_cfg.host)); } int xqc_mini_cli_init_ctx(xqc_mini_cli_ctx_t *ctx, xqc_mini_cli_args_t *args) { memset(ctx, 0, sizeof(xqc_mini_cli_ctx_t)); /* init event base */ struct event_base *eb = event_base_new(); ctx->eb = eb; ctx->args = args; /* init log writer fd */ ctx->log_fd = xqc_mini_cli_open_log_file(ctx); if (ctx->log_fd < 0) { printf("[error] open log file failed\n"); return XQC_ERROR; } /* init keylog writer fd */ ctx->keylog_fd = xqc_mini_cli_open_keylog_file(ctx); if (ctx->keylog_fd < 0) { printf("[error] open keylog file failed\n"); return XQC_ERROR; } return 0; } int xqc_mini_cli_init_env(xqc_mini_cli_ctx_t *ctx, xqc_mini_cli_args_t *args) { int ret = XQC_OK; /* init client args */ xqc_mini_cli_init_args(args); /* init client ctx */ ret = xqc_mini_cli_init_ctx(ctx, args); return ret; } xqc_scheduler_callback_t xqc_mini_cli_get_sched_cb(xqc_mini_cli_args_t *args) { xqc_scheduler_callback_t sched = xqc_minrtt_scheduler_cb; if (strncmp(args->quic_cfg.mp_sched, "minrtt", strlen("minrtt")) == 0) { sched = xqc_minrtt_scheduler_cb; } if (strncmp(args->quic_cfg.mp_sched, "backup", strlen("backup")) == 0) { sched = xqc_backup_scheduler_cb; } return sched; } xqc_cong_ctrl_callback_t xqc_mini_cli_get_cc_cb(xqc_mini_cli_args_t *args) { xqc_cong_ctrl_callback_t ccc = xqc_bbr_cb; switch (args->quic_cfg.cc) { case CC_TYPE_BBR: ccc = xqc_bbr_cb; break; case CC_TYPE_CUBIC: ccc = xqc_cubic_cb; break; default: break; } return ccc; } void xqc_mini_cli_init_conn_settings(xqc_conn_settings_t *settings, xqc_mini_cli_args_t *args) { /* parse congestion control callback */ xqc_cong_ctrl_callback_t ccc = xqc_mini_cli_get_cc_cb(args); /* parse mp scheduler callback */ xqc_scheduler_callback_t sched = xqc_mini_cli_get_sched_cb(args); /* init connection settings */ memset(settings, 0, sizeof(xqc_conn_settings_t)); settings->cong_ctrl_callback = ccc; settings->cc_params.customize_on = 1; settings->cc_params.init_cwnd = 96; settings->so_sndbuf = 1024*1024; settings->proto_version = XQC_VERSION_V1; settings->spurious_loss_detect_on = 1; settings->scheduler_callback = sched; settings->reinj_ctl_callback = xqc_deadline_reinj_ctl_cb; settings->adaptive_ack_frequency = 1; } int xqc_mini_cli_init_alpn_ctx(xqc_mini_cli_ctx_t *ctx) { int ret = XQC_OK; /* init http3 callbacks */ xqc_h3_callbacks_t h3_cbs = { .h3c_cbs = { .h3_conn_create_notify = xqc_mini_cli_h3_conn_create_notify, .h3_conn_close_notify = xqc_mini_cli_h3_conn_close_notify, .h3_conn_handshake_finished = xqc_mini_cli_h3_conn_handshake_finished, }, .h3r_cbs = { .h3_request_create_notify = xqc_mini_cli_h3_request_create_notify, .h3_request_close_notify = xqc_mini_cli_h3_request_close_notify, .h3_request_read_notify = xqc_mini_cli_h3_request_read_notify, .h3_request_write_notify = xqc_mini_cli_h3_request_write_notify, } }; /* init http3 context */ ret = xqc_h3_ctx_init(ctx->engine, &h3_cbs); if (ret != XQC_OK) { printf("init h3 context error, ret: %d\n", ret); return ret; } return ret; } int xqc_mini_cli_init_engine_ctx(xqc_mini_cli_ctx_t *ctx) { int ret; /* init alpn ctx */ ret = xqc_mini_cli_init_alpn_ctx(ctx); return ret; } void xqc_mini_cli_free_ctx(xqc_mini_cli_ctx_t *ctx) { xqc_mini_cli_close_keylog_file(ctx); xqc_mini_cli_close_log_file(ctx); if (ctx->args) { free(ctx->args); ctx->args = NULL; } } void xqc_mini_cli_init_0rtt(xqc_mini_cli_args_t *args) { /* read session ticket */ int ret = xqc_mini_read_file_data(args->quic_cfg.session_ticket, SESSION_TICKET_BUF_MAX_SIZE, SESSION_TICKET_FILE); args->quic_cfg.session_ticket_len = ret > 0 ? ret : 0; /* read transport params */ ret = xqc_mini_read_file_data(args->quic_cfg.transport_parameter, TRANSPORT_PARAMS_MAX_SIZE, TRANSPORT_PARAMS_FILE); args->quic_cfg.transport_parameter_len = ret > 0 ? ret : 0; /* read token */ ret = xqc_mini_cli_read_token( args->quic_cfg.token, TOKEN_MAX_SIZE); args->quic_cfg.token_len = ret > 0 ? ret : 0; } void xqc_mini_cli_init_conn_ssl_config(xqc_conn_ssl_config_t *conn_ssl_config, xqc_mini_cli_args_t *args) { /* set session ticket and transport parameter args */ if (args->quic_cfg.session_ticket_len < 0 || args->quic_cfg.transport_parameter_len < 0) { conn_ssl_config->session_ticket_data = NULL; conn_ssl_config->transport_parameter_data = NULL; } else { conn_ssl_config->session_ticket_data = args->quic_cfg.session_ticket; conn_ssl_config->session_ticket_len = args->quic_cfg.session_ticket_len; conn_ssl_config->transport_parameter_data = args->quic_cfg.transport_parameter; conn_ssl_config->transport_parameter_data_len = args->quic_cfg.transport_parameter_len; } } int xqc_mini_cli_format_h3_req(xqc_http_header_t *headers, xqc_mini_cli_req_config_t* req_cfg) { /* response header buf list */ xqc_http_header_t req_hdr[] = { { .name = {.iov_base = ":method", .iov_len = 7}, .value = {.iov_base = method_s[req_cfg->method], .iov_len = strlen(method_s[req_cfg->method])}, .flags = 0, }, { .name = {.iov_base = ":scheme", .iov_len = 7}, .value = {.iov_base = req_cfg->scheme, .iov_len = strlen(req_cfg->scheme)}, .flags = 0, }, { .name = {.iov_base = "host", .iov_len = 4}, .value = {.iov_base = req_cfg->host, .iov_len = strlen(req_cfg->host)}, .flags = 0, }, { .name = {.iov_base = ":path", .iov_len = 5}, .value = {.iov_base = req_cfg->url, .iov_len = strlen(req_cfg->path)}, .flags = 0, }, { .name = {.iov_base = "content-type", .iov_len = 12}, .value = {.iov_base = "text/plain", .iov_len = 10}, .flags = 0, }, { .name = {.iov_base = "content-length", .iov_len = 14}, .value = {.iov_base = 0, .iov_len = 0}, .flags = 0, }, }; size_t req_sz = sizeof(req_hdr) / sizeof(req_hdr[0]); if (req_sz > H3_HDR_CNT) { printf("[error] header length is too large, request_size: %zd\n", req_sz); return XQC_ERROR; } for (size_t i = 0; i < req_sz; i++) { headers[i] = req_hdr[i]; } return req_sz; } int xqc_mini_cli_request_send(xqc_h3_request_t *h3_request, xqc_mini_cli_user_stream_t *user_stream) { int ret, fin; /* send packet header/body */ xqc_http_header_t header[H3_HDR_CNT]; xqc_mini_cli_req_config_t* req_cfg; req_cfg = &user_stream->user_conn->ctx->args->req_cfg; fin = 1; ret = xqc_mini_cli_format_h3_req(header, req_cfg); if (ret > 0) { user_stream->h3_hdrs.headers = header; user_stream->h3_hdrs.count = ret; if (user_stream->start_time == 0) { user_stream->start_time = xqc_now(); } /* send header */ ret = xqc_h3_request_send_headers(user_stream->h3_request, &user_stream->h3_hdrs, fin); if (ret < 0) { printf("[error] xqc_mini_cli_h3_request_send error %d\n", ret); } else { printf("[stats] xqc_mini_cli_h3_request_send success \n"); user_stream->hdr_sent = 1; } } if (req_cfg->method == REQUEST_METHOD_GET) { return XQC_OK; } return XQC_OK; } int xqc_mini_cli_send_h3_req(xqc_mini_cli_user_conn_t *user_conn, xqc_mini_cli_user_stream_t *user_stream) { user_stream->user_conn = user_conn; xqc_stream_settings_t settings = { .recv_rate_bytes_per_sec = 0 }; user_stream->h3_request = xqc_h3_request_create(user_conn->ctx->engine, &user_conn->cid, &settings, user_stream); if (user_stream->h3_request == NULL) { printf("[error] xqc_h3_request_create error\n"); return XQC_ERROR; } xqc_mini_cli_request_send(user_stream->h3_request, user_stream); /* generate engine main log to send packets */ xqc_engine_main_logic(user_conn->ctx->engine); return XQC_OK; } int xqc_mini_cli_init_socket(xqc_mini_cli_user_conn_t *user_conn) { int fd, size; xqc_mini_cli_ctx_t *ctx = user_conn->ctx; xqc_mini_cli_net_config_t* cfg = &ctx->args->net_cfg; struct sockaddr *addr = user_conn->local_addr; fd = socket(addr->sa_family, SOCK_DGRAM, 0); if (fd < 0) { printf("[error] create socket failed, errno: %d\n", get_sys_errno()); return XQC_ERROR; } #ifdef XQC_SYS_WINDOWS if (ioctlsocket(fd, FIONBIO, &flags) == SOCKET_ERROR) { goto err; } #else if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { printf("[error] set socket nonblock failed, errno: %d\n", get_sys_errno()); goto err; } #endif size = 1 * 1024 * 1024; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int)) < 0) { printf("[error] setsockopt failed, errno: %d\n", get_sys_errno()); goto err; } if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int)) < 0) { printf("[error] setsockopt failed, errno: %d\n", get_sys_errno()); goto err; } #if !defined(__APPLE__) int val = IP_PMTUDISC_DO; setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)); #endif #if !defined(__APPLE__) if (connect(fd, (struct sockaddr *)user_conn->peer_addr, user_conn->peer_addrlen) < 0) { printf("[error] connect socket failed, errno: %d\n", get_sys_errno()); goto err; } #endif ctx->args->net_cfg.last_socket_time = xqc_now(); printf("[stats] init socket succesfully \n"); user_conn->fd = fd; return XQC_OK; err: close(fd); return XQC_ERROR; } void xqc_mini_cli_socket_write_handler(xqc_mini_cli_user_conn_t *user_conn, int fd) { DEBUG printf("[stats] socket write handler\n"); } void xqc_mini_cli_socket_read_handler(xqc_mini_cli_user_conn_t *user_conn, int fd) { DEBUG ssize_t recv_size, recv_sum; uint64_t recv_time; xqc_int_t ret; unsigned char packet_buf[XQC_PACKET_BUF_LEN]; xqc_mini_cli_ctx_t *ctx; recv_size = recv_sum = 0; ctx = user_conn->ctx; do { /* recv quic packet from server */ recv_size = recvfrom(fd, packet_buf, sizeof(packet_buf), 0, user_conn->peer_addr, &user_conn->peer_addrlen); if (recv_size < 0 && get_sys_errno() == EAGAIN) { break; } if (recv_size < 0) { printf("recvfrom: recvmsg = %zd err=%s\n", recv_size, strerror(get_sys_errno())); break; } if (user_conn->get_local_addr == 0) { user_conn->get_local_addr = 1; user_conn->local_addrlen = sizeof(struct sockaddr_in6); ret = getsockname(user_conn->fd, (struct sockaddr*)user_conn->local_addr, &user_conn->local_addrlen); if (ret != 0) { printf("getsockname error, errno: %d\n", get_sys_errno()); user_conn->local_addrlen = 0; break; } } recv_sum += recv_size; recv_time = xqc_now(); ctx->args->net_cfg.last_socket_time = recv_time; /* process quic packet with xquic engine */ ret = xqc_engine_packet_process(ctx->engine, packet_buf, recv_size, user_conn->local_addr, user_conn->local_addrlen, user_conn->peer_addr, user_conn->peer_addrlen, (xqc_usec_t)recv_time, user_conn); if (ret != XQC_OK) { printf("[error] client_read_handler: packet process err, ret: %d\n", ret); return; } } while (recv_size > 0); finish_recv: // printf("[stats] xqc_mini_cli_socket_read_handler, recv size:%zu\n", recv_sum); xqc_engine_finish_recv(ctx->engine); } static void xqc_mini_cli_socket_event_callback(int fd, short what, void *arg) { //DEBUG; xqc_mini_cli_user_conn_t *user_conn = (xqc_mini_cli_user_conn_t *)arg; if (what & EV_WRITE) { xqc_mini_cli_socket_write_handler(user_conn, fd); } else if (what & EV_READ) { xqc_mini_cli_socket_read_handler(user_conn, fd); } else { printf("event callback: fd=%d, what=%d\n", fd, what); exit(1); } } int xqc_mini_cli_init_xquic_connection(xqc_mini_cli_user_conn_t *user_conn) { xqc_conn_ssl_config_t conn_ssl_config = {0}; xqc_conn_settings_t conn_settings = {0}; xqc_mini_cli_ctx_t *ctx; xqc_mini_cli_args_t *args; ctx = user_conn->ctx; args = ctx->args; /* load 0-rtt args */ xqc_mini_cli_init_0rtt(ctx->args); /* init connection settings */ xqc_mini_cli_init_conn_settings(&conn_settings, ctx->args); /* init connection ssl config */ xqc_mini_cli_init_conn_ssl_config(&conn_ssl_config, ctx->args); /* build connection */ const xqc_cid_t *cid = xqc_h3_connect(ctx->engine, &conn_settings, args->quic_cfg.token, args->quic_cfg.token_len, args->req_cfg.host, args->quic_cfg.no_encryption, &conn_ssl_config, user_conn->peer_addr, user_conn->peer_addrlen, user_conn); if (cid == NULL) { return XQC_ERROR; } memcpy(&user_conn->cid, cid, sizeof(xqc_cid_t)); printf("[stats] init xquic connection success \n"); return XQC_OK; } void xqc_mini_cli_on_socket_created(xqc_mini_cli_user_conn_t *user_conn) { xqc_mini_cli_ctx_t *ctx; ctx = user_conn->ctx; /* init callback function for READ/PERSIST EVENT */ user_conn->ev_socket = event_new(ctx->eb, user_conn->fd, EV_READ | EV_PERSIST, xqc_mini_cli_socket_event_callback, user_conn); event_add(user_conn->ev_socket, NULL); } int xqc_mini_cli_main_process(xqc_mini_cli_user_conn_t *user_conn, xqc_mini_cli_ctx_t *ctx) { int ret; xqc_mini_cli_args_t *args; user_conn->ctx = ctx; args = ctx->args; ret = xqc_mini_cli_init_xquic_connection(user_conn); if (ret < 0) { printf("[error] mini socket init xquic connection failed\n"); return XQC_ERROR; } xqc_mini_cli_user_stream_t *user_stream = calloc(1, sizeof(xqc_mini_cli_user_stream_t)); ret = xqc_mini_cli_send_h3_req(user_conn, user_stream); if (ret < 0) { return XQC_ERROR; } return XQC_OK; } void xqc_mini_cli_init_local_addr(struct sockaddr *local_addr) { char s_port[16] = "8443"; struct addrinfo hints = {0}; hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; /* resolve server's ip from hostname */ struct addrinfo *result = NULL; int rv = getaddrinfo(DEFAULT_IP, s_port, &hints, &result); if (rv != 0) { printf("get addr info from hostname: %s\n", gai_strerror(rv)); } memcpy(local_addr, result->ai_addr, result->ai_addrlen); } xqc_mini_cli_user_conn_t * xqc_mini_cli_user_conn_create(xqc_mini_cli_ctx_t *ctx) { int ret; xqc_mini_cli_user_conn_t *user_conn = calloc(1, sizeof(xqc_mini_cli_user_conn_t)); user_conn->ctx = ctx; /* set connection timeout */ struct timeval tv; tv.tv_sec = ctx->args->net_cfg.conn_timeout; tv.tv_usec = 0; user_conn->ev_timeout = event_new(ctx->eb, -1, 0, xqc_mini_cli_timeout_callback, user_conn); event_add(user_conn->ev_timeout, &tv); user_conn->local_addr = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in)); xqc_mini_cli_init_local_addr(user_conn->local_addr); user_conn->local_addrlen = sizeof(struct sockaddr_in); xqc_mini_cli_convert_text_to_sockaddr(AF_INET, DEFAULT_IP, DEFAULT_PORT, &(user_conn->peer_addr), &(user_conn->peer_addrlen)); /* init socket fd */ ret = xqc_mini_cli_init_socket(user_conn); if (ret < 0) { printf("[error] mini socket init socket failed\n"); return NULL; } user_conn->ev_socket = event_new(ctx->eb, user_conn->fd, EV_READ | EV_PERSIST, xqc_mini_cli_socket_event_callback, user_conn); event_add(user_conn->ev_socket, NULL); return user_conn; } void xqc_mini_cli_free_user_conn(xqc_mini_cli_user_conn_t *user_conn) { free(user_conn->peer_addr); free(user_conn->local_addr); free(user_conn); } void xqc_mini_cli_on_connection_finish(xqc_mini_cli_user_conn_t *user_conn) { if (user_conn->ev_timeout) { event_del(user_conn->ev_timeout); user_conn->ev_timeout = NULL; } if (user_conn->ev_socket) { event_del(user_conn->ev_socket); user_conn->ev_timeout = NULL; } close(user_conn->fd); } int main(int argc, char *argv[]) { int ret; xqc_mini_cli_ctx_t cli_ctx = {0}, *ctx = &cli_ctx; xqc_mini_cli_args_t *args = NULL; xqc_mini_cli_user_conn_t *user_conn = NULL; args = calloc(1, sizeof(xqc_mini_cli_args_t)); if (args == NULL) { printf("[error] calloc args failed\n"); goto exit; } /* init env (for windows) */ xqc_platform_init_env(); /* init client environment (ctx & args) */ ret = xqc_mini_cli_init_env(ctx, args); if (ret < 0) { goto exit; } /* init client engine */ ret = xqc_mini_cli_init_xquic_engine(ctx, args); if (ret < 0) { printf("[error] init xquic engine failed\n"); goto exit; } /* init engine ctx */ ret = xqc_mini_cli_init_engine_ctx(ctx); if (ret < 0) { printf("[error] init engine ctx failed\n"); goto exit; } user_conn = xqc_mini_cli_user_conn_create(ctx); if (user_conn == NULL) { printf("[error] init user_conn failed.\n"); goto exit; } /* cli main process: build connection, process request, etc. */ xqc_mini_cli_main_process(user_conn, ctx); /* start event loop */ event_base_dispatch(ctx->eb); exit: xqc_engine_destroy(ctx->engine); xqc_mini_cli_on_connection_finish(user_conn); xqc_mini_cli_free_ctx(ctx); xqc_mini_cli_free_user_conn(user_conn); return 0; }