mirror of
https://github.com/curl/curl.git
synced 2024-12-27 06:59:43 +08:00
3af0e76d1e
USe configure --with-ngtcp2 or --with-quiche Using either option will enable a HTTP3 build. Co-authored-by: Alessandro Ghedini <alessandro@ghedini.me> Closes #3500
230 lines
6.2 KiB
C
230 lines
6.2 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.haxx.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "curl_setup.h"
|
|
|
|
#ifdef USE_QUICHE
|
|
#include <quiche.h>
|
|
#include <openssl/err.h>
|
|
#include "urldata.h"
|
|
#include "sendf.h"
|
|
#include "strdup.h"
|
|
#include "rand.h"
|
|
#include "quic.h"
|
|
|
|
/* The last 3 #include files should be in this order */
|
|
#include "curl_printf.h"
|
|
#include "curl_memory.h"
|
|
#include "memdebug.h"
|
|
|
|
#define QUIC_MAX_STREAMS (256*1024)
|
|
#define QUIC_MAX_DATA (1*1024*1024)
|
|
#define QUIC_IDLE_TIMEOUT 60 * 1000 /* milliseconds */
|
|
|
|
static CURLcode process_ingress(struct connectdata *conn,
|
|
curl_socket_t sockfd);
|
|
|
|
static CURLcode flush_egress(struct connectdata *conn, curl_socket_t sockfd);
|
|
|
|
static Curl_recv quic_stream_recv;
|
|
static Curl_send quic_stream_send;
|
|
|
|
|
|
CURLcode Curl_quic_connect(struct connectdata *conn, curl_socket_t sockfd,
|
|
const struct sockaddr *addr, socklen_t addrlen)
|
|
{
|
|
CURLcode result;
|
|
struct quicsocket *qs = &conn->quic;
|
|
(void)addr;
|
|
(void)addrlen;
|
|
|
|
infof(conn->data, "Connecting socket %d over QUIC\n", sockfd);
|
|
|
|
qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
|
|
if(!qs->cfg)
|
|
return CURLE_FAILED_INIT; /* TODO: better return code */
|
|
|
|
quiche_config_set_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
|
|
quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
|
|
quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
|
|
quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg, QUIC_MAX_DATA);
|
|
quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
|
|
quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
|
|
quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
|
|
quiche_config_set_application_protos(qs->cfg, (uint8_t *) "\x05hq-20", 6);
|
|
|
|
result = Curl_rand(conn->data, qs->scid, sizeof(qs->scid));
|
|
if(result)
|
|
return result;
|
|
|
|
qs->conn = quiche_connect(conn->host.name, (const uint8_t *) qs->scid,
|
|
sizeof(qs->scid), qs->cfg);
|
|
if(!qs->conn)
|
|
return CURLE_FAILED_INIT; /* TODO: better return code */
|
|
|
|
result = flush_egress(conn, sockfd);
|
|
if(result)
|
|
return CURLE_FAILED_INIT; /* TODO: better return code */
|
|
|
|
infof(conn->data, "Sent QUIC client Initial\n");
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex,
|
|
bool *done)
|
|
{
|
|
CURLcode result;
|
|
struct quicsocket *qs = &conn->quic;
|
|
curl_socket_t sockfd = conn->sock[sockindex];
|
|
|
|
result = process_ingress(conn, sockfd);
|
|
if(result)
|
|
return result;
|
|
|
|
result = flush_egress(conn, sockfd);
|
|
if(result)
|
|
return result;
|
|
|
|
if(quiche_conn_is_established(qs->conn)) {
|
|
conn->recv[sockindex] = quic_stream_recv;
|
|
conn->send[sockindex] = quic_stream_send;
|
|
*done = TRUE;
|
|
}
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode process_ingress(struct connectdata *conn, int sockfd)
|
|
{
|
|
ssize_t recvd;
|
|
struct quicsocket *qs = &conn->quic;
|
|
static uint8_t buf[65535];
|
|
|
|
do {
|
|
recvd = recv(sockfd, buf, sizeof(buf), 0);
|
|
if((recvd < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
|
|
break;
|
|
|
|
if(recvd < 0)
|
|
return CURLE_RECV_ERROR;
|
|
|
|
recvd = quiche_conn_recv(qs->conn, buf, recvd);
|
|
if(recvd == QUICHE_ERR_DONE)
|
|
break;
|
|
|
|
if(recvd < 0)
|
|
return CURLE_RECV_ERROR;
|
|
} while(1);
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode flush_egress(struct connectdata *conn, int sockfd)
|
|
{
|
|
ssize_t sent;
|
|
struct quicsocket *qs = &conn->quic;
|
|
static uint8_t out[1200];
|
|
|
|
do {
|
|
sent = quiche_conn_send(qs->conn, out, sizeof(out));
|
|
if(sent == QUICHE_ERR_DONE)
|
|
break;
|
|
|
|
if(sent < 0)
|
|
return CURLE_SEND_ERROR;
|
|
|
|
sent = send(sockfd, out, sent, 0);
|
|
if(sent < 0)
|
|
return CURLE_SEND_ERROR;
|
|
} while(1);
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static ssize_t quic_stream_recv(struct connectdata *conn,
|
|
int sockindex,
|
|
char *buf,
|
|
size_t buffersize,
|
|
CURLcode *curlcode)
|
|
{
|
|
bool fin;
|
|
ssize_t recvd;
|
|
struct quicsocket *qs = &conn->quic;
|
|
curl_socket_t sockfd = conn->sock[sockindex];
|
|
|
|
if(process_ingress(conn, sockfd)) {
|
|
*curlcode = CURLE_RECV_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
recvd = quiche_conn_stream_recv(qs->conn, 0, (uint8_t *) buf, buffersize, &fin);
|
|
if(recvd == QUICHE_ERR_DONE) {
|
|
*curlcode = CURLE_AGAIN;
|
|
return -1;
|
|
}
|
|
|
|
if(recvd < 0) {
|
|
*curlcode = CURLE_RECV_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
*curlcode = CURLE_OK;
|
|
return recvd;
|
|
}
|
|
|
|
static ssize_t quic_stream_send(struct connectdata *conn,
|
|
int sockindex,
|
|
const void *mem,
|
|
size_t len,
|
|
CURLcode *curlcode)
|
|
{
|
|
ssize_t sent;
|
|
struct quicsocket *qs = &conn->quic;
|
|
curl_socket_t sockfd = conn->sock[sockindex];
|
|
|
|
sent = quiche_conn_stream_send(qs->conn, 0, mem, len, true);
|
|
if(sent < 0) {
|
|
*curlcode = CURLE_SEND_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
if(flush_egress(conn, sockfd)) {
|
|
*curlcode = CURLE_SEND_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
*curlcode = CURLE_OK;
|
|
return sent;
|
|
}
|
|
|
|
/*
|
|
* Store quiche version info in this buffer, Prefix with a space. Return total
|
|
* length written.
|
|
*/
|
|
int Curl_quic_ver(char *p, size_t len)
|
|
{
|
|
return msnprintf(p, len, " quiche");
|
|
}
|
|
|
|
#endif
|