Skip to content
Snippets Groups Projects
Commit ecf9156e authored by Daniel Salzman's avatar Daniel Salzman
Browse files

net/requestor: add support for TFO

parent c0556dcb
No related branches found
No related tags found
No related merge requests found
......@@ -17,6 +17,8 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h> // OpenBSD
#include <netinet/tcp.h> // TCP_FASTOPEN
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
......@@ -218,8 +220,30 @@ int net_bound_socket(int type, const struct sockaddr_storage *addr, net_bind_fla
return sock;
}
static int tfo_connect(int sock, const struct sockaddr_storage *addr)
{
#if defined(__linux__)
/* connect() will be called implicitly with sendmsg(). */
return KNOT_EOK;
#elif defined(__FreeBSD__)
return sockopt_enable(sock, IPPROTO_TCP, TCP_FASTOPEN);
#elif defined(__APPLE__)
/* Connection is performed lazily when first data is sent. */
sa_endpoints_t ep = {
.sae_dstaddr = (const struct sockaddr *)addr,
.sae_dstaddrlen = sockaddr_len(addr)
};
int flags = CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE;
int ret = connectx(sock, &ep, SAE_ASSOCID_ANY, flags, NULL, 0, NULL, NULL);
return (ret == 0 ? KNOT_EOK : knot_map_errno());
#else
return KNOT_ENOTSUP;
#endif
}
int net_connected_socket(int type, const struct sockaddr_storage *dst_addr,
const struct sockaddr_storage *src_addr)
const struct sockaddr_storage *src_addr, bool tfo)
{
if (dst_addr == NULL) {
return KNOT_EINVAL;
......@@ -242,16 +266,42 @@ int net_connected_socket(int type, const struct sockaddr_storage *dst_addr,
}
/* Connect to destination. */
int ret = connect(sock, (const struct sockaddr *)dst_addr, sockaddr_len(dst_addr));
if (ret != 0 && errno != EINPROGRESS) {
ret = knot_map_errno();
close(sock);
return ret;
if (tfo && net_is_stream(sock)) {
int ret = tfo_connect(sock, dst_addr);
if (ret != KNOT_EOK) {
close(sock);
return ret;
}
} else {
int ret = connect(sock, (const struct sockaddr *)dst_addr,
sockaddr_len(dst_addr));
if (ret != 0 && errno != EINPROGRESS) {
ret = knot_map_errno();
close(sock);
return ret;
}
}
return sock;
}
int net_bound_tfo(int sock, int backlog)
{
#if defined(TCP_FASTOPEN)
#if defined(__APPLE__)
if (backlog > 0) {
backlog = 1; // just on-off switch on macOS
}
#endif
if (setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog)) != 0) {
return knot_map_errno();
}
return KNOT_EOK;
#endif
return KNOT_ENOTSUP;
}
bool net_is_connected(int sock)
{
struct sockaddr_storage addr;
......@@ -355,7 +405,7 @@ static bool wait_should_retry(int error)
* \brief I/O operation callbacks.
*/
struct io {
ssize_t (*process)(int sockfd, struct msghdr *msg);
ssize_t (*process)(int sockfd, struct msghdr *msg, int timeout_ms);
int (*wait)(int sockfd, int timeout_ms);
};
......@@ -422,7 +472,7 @@ static ssize_t io_exec(const struct io *io, int fd, struct msghdr *msg,
for (;;) {
/* Perform I/O. */
ssize_t ret = io->process(fd, msg);
ssize_t ret = io->process(fd, msg, *timeout_ptr);
if (ret == -1 && errno == EINTR) {
continue;
}
......@@ -471,7 +521,7 @@ static ssize_t io_exec(const struct io *io, int fd, struct msghdr *msg,
return done;
}
static ssize_t recv_process(int fd, struct msghdr *msg)
static ssize_t recv_process(int fd, struct msghdr *msg, int timeout_ms)
{
return recvmsg(fd, msg, MSG_DONTWAIT | MSG_NOSIGNAL);
}
......@@ -491,7 +541,24 @@ static ssize_t recv_data(int sock, struct msghdr *msg, bool oneshot, int *timeou
return io_exec(&RECV_IO, sock, msg, oneshot, timeout_ptr);
}
static ssize_t send_process(int fd, struct msghdr *msg)
static ssize_t send_process_tfo(int fd, struct msghdr *msg, int timeout_ms)
{
#if defined(__linux__)
int ret = sendmsg(fd, msg, MSG_FASTOPEN);
if (ret != 0 && errno == EINPROGRESS) {
if (poll_one(fd, POLLOUT, timeout_ms) != 1) {
errno = ETIMEDOUT;
return -1;
}
ret = sendmsg(fd, msg, MSG_NOSIGNAL);
}
return ret;
#else
return sendmsg(fd, msg, MSG_NOSIGNAL);
#endif
}
static ssize_t send_process(int fd, struct msghdr *msg, int timeout_ms)
{
return sendmsg(fd, msg, MSG_NOSIGNAL);
}
......@@ -501,14 +568,18 @@ static int send_wait(int fd, int timeout_ms)
return poll_one(fd, POLLOUT, timeout_ms);
}
static ssize_t send_data(int sock, struct msghdr *msg, int *timeout_ptr)
static ssize_t send_data(int sock, struct msghdr *msg, int *timeout_ptr, bool tfo)
{
static const struct io SEND_IO = {
.process = send_process,
.wait = send_wait
};
static const struct io SEND_IO_TFO = {
.process = send_process_tfo,
.wait = send_wait
};
return io_exec(&SEND_IO, sock, msg, false, timeout_ptr);
return io_exec(tfo ? &SEND_IO_TFO : &SEND_IO, sock, msg, false, timeout_ptr);
}
/* -- generic stream and datagram I/O -------------------------------------- */
......@@ -530,7 +601,7 @@ ssize_t net_base_send(int sock, const uint8_t *buffer, size_t size,
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
int ret = send_data(sock, &msg, &timeout_ms);
int ret = send_data(sock, &msg, &timeout_ms, false);
if (ret < 0) {
return ret;
} else if (ret != size) {
......@@ -583,7 +654,8 @@ ssize_t net_stream_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms)
/* -- DNS specific I/O ----------------------------------------------------- */
ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms)
ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms,
struct sockaddr_storage *tfo_addr)
{
if (sock < 0 || buffer == NULL || size > UINT16_MAX) {
return KNOT_EINVAL;
......@@ -599,8 +671,10 @@ ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeo
struct msghdr msg = { 0 };
msg.msg_iov = iov;
msg.msg_iovlen = 2;
msg.msg_name = (void *)tfo_addr;
msg.msg_namelen = tfo_addr ? sizeof(*tfo_addr) : 0;
ssize_t ret = send_data(sock, &msg, &timeout_ms);
ssize_t ret = send_data(sock, &msg, &timeout_ms, tfo_addr != NULL);
if (ret < 0) {
return ret;
}
......
......@@ -61,11 +61,21 @@ int net_bound_socket(int type, const struct sockaddr_storage *addr, net_bind_fla
* \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM).
* \param dst_addr Destination address.
* \param src_addr Source address (can be NULL).
* \param tfo Enable TCP Fast Open.
*
* \return socket or error code
*/
int net_connected_socket(int type, const struct sockaddr_storage *dst_addr,
const struct sockaddr_storage *src_addr);
const struct sockaddr_storage *src_addr, bool tfo);
/*!
* \brief Enables TCP Fast Open on a bound socket.
*
* \param sock Socket.
*
* \return KNOT_EOK or error code
*/
int net_bound_tfo(int sock, int backlog);
/*!
* \brief Return true if the socket is fully connected.
......@@ -169,9 +179,12 @@ ssize_t net_stream_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms);
* message size according to the specification. These two bytes are not
* reflected in the return value.
*
* \param[in] tfo_addr If not NULL, send using TCP Fast Open to this address.
*
* \see net_base_send
*/
ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms);
ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms,
struct sockaddr_storage *tfo_addr);
/*!
* \brief Receive a DNS message from a TCP socket.
......
/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -323,7 +323,7 @@ static void send_update_response(conf_t *conf, const zone_t *zone, knot_request_
if (net_is_stream(req->fd)) {
net_dns_tcp_send(req->fd, req->resp->wire, req->resp->size,
conf->cache.srv_tcp_remote_io_timeout);
conf->cache.srv_tcp_remote_io_timeout, NULL);
} else {
net_dgram_send(req->fd, req->resp->wire, req->resp->size,
&req->remote);
......
/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -43,7 +43,8 @@ static int request_ensure_connected(knot_request_t *request)
int sock_type = use_tcp(request) ? SOCK_STREAM : SOCK_DGRAM;
request->fd = net_connected_socket(sock_type,
&request->remote,
&request->source);
&request->source,
request->flags & KNOT_REQUEST_TFO);
if (request->fd < 0) {
return request->fd;
}
......@@ -63,10 +64,13 @@ static int request_send(knot_request_t *request, int timeout_ms)
knot_pkt_t *query = request->query;
uint8_t *wire = query->wire;
size_t wire_len = query->size;
struct sockaddr_storage *tfo_addr = (request->flags & KNOT_REQUEST_TFO) ?
&request->remote : NULL;
/* Send query. */
if (use_tcp(request)) {
ret = net_dns_tcp_send(request->fd, wire, wire_len, timeout_ms);
ret = net_dns_tcp_send(request->fd, wire, wire_len, timeout_ms,
tfo_addr);
} else {
ret = net_dgram_send(request->fd, wire, wire_len, NULL);
}
......
/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -25,7 +25,9 @@
#include "libknot/rrtype/tsig.h"
typedef enum {
KNOT_REQUEST_UDP = 1 << 0 /*!< Use UDP for requests. */
KNOT_REQUEST_NONE = 0, /*!< Empty flag. */
KNOT_REQUEST_UDP = 1 << 0, /*!< Use UDP for requests. */
KNOT_REQUEST_TFO = 1 << 1, /*!< Enable TCP Fast Open for requests. */
} knot_request_flag_t;
typedef enum {
......
......@@ -219,24 +219,6 @@ static int disable_pmtudisc(int sock, int family)
return KNOT_EOK;
}
/*!
* \brief Enable TCP Fast Open.
*/
static int enable_fastopen(int sock, int backlog)
{
#if defined(TCP_FASTOPEN)
#if __APPLE__
if (backlog > 0) {
backlog = 1; // just on-off switch on macOS
}
#endif
if (setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog)) != 0) {
return knot_map_errno();
}
#endif
return KNOT_EOK;
}
static iface_t *server_init_xdp_iface(struct sockaddr_storage *addr, unsigned *thread_id_start)
{
#ifndef ENABLE_XDP
......@@ -458,9 +440,9 @@ static iface_t *server_init_iface(struct sockaddr_storage *addr,
}
}
/* TCP Fast Open. */
ret = enable_fastopen(sock, TCP_BACKLOG_SIZE);
if (ret < 0 && warn_flag_misc) {
/* Try to enable TCP Fast Open. */
ret = net_bound_tfo(sock, TCP_BACKLOG_SIZE);
if (ret != KNOT_EOK && ret != KNOT_ENOTSUP && warn_flag_misc) {
log_warning("failed to enable TCP Fast Open on %s (%s)",
addr_str, knot_strerror(ret));
warn_flag_misc = false;
......
/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -195,7 +195,7 @@ static int tcp_handle(tcp_context_t *tcp, int fd, struct iovec *rx, struct iovec
/* Send, if response generation passed and wasn't ignored. */
if (ans->size > 0 && tcp_send_state(tcp->layer.state)) {
int sent = net_dns_tcp_send(fd, ans->wire, ans->size,
tcp->io_timeout);
tcp->io_timeout, NULL);
if (sent != ans->size) {
tcp_log_error(&ss, "send", sent);
ret = KNOT_EOF;
......
/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -283,7 +283,7 @@ int knot_ctl_connect(knot_ctl_t *ctx, const char *path)
}
// Connect to socket.
ctx->sock = net_connected_socket(SOCK_STREAM, &addr, NULL);
ctx->sock = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
if (ctx->sock < 0) {
return ctx->sock;
}
......
/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -212,7 +212,7 @@ static void test_connected_one(const struct sockaddr_storage *server_addr,
{
int r;
int client = net_connected_socket(type, server_addr, NULL);
int client = net_connected_socket(type, server_addr, source_addr, false);
ok(client >= 0, "%s, %s: client, create connected socket", name, addr_name);
const uint8_t out[] = "test message";
......@@ -365,7 +365,7 @@ static void test_refused(void)
r = listen(server, LISTEN_BACKLOG);
ok(r == 0, "server, start listening");
client = net_connected_socket(SOCK_STREAM, &addr, NULL);
client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
ok(client >= 0, "client, connect");
r = net_stream_send(client, (uint8_t *)"", 1, TIMEOUT);
......@@ -378,7 +378,7 @@ static void test_refused(void)
// listening, closed immediately
client = net_connected_socket(SOCK_STREAM, &addr, NULL);
client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
ok(client >= 0, "client, connect");
r = close(server);
......@@ -442,7 +442,7 @@ static void handler_dns(int sock, void *_ctx)
static void dns_send_hello(int sock)
{
net_dns_tcp_send(sock, (uint8_t *)"wimbgunts", 9, TIMEOUT);
net_dns_tcp_send(sock, (uint8_t *)"wimbgunts", 9, TIMEOUT, NULL);
}
static void dns_send_fragmented(int sock)
......@@ -510,7 +510,7 @@ static void test_dns_tcp(void)
ok(r, "%s, server, start handler", t->name);
addr = addr_from_socket(server);
int client = net_connected_socket(SOCK_STREAM, &addr, NULL);
int client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
ok(client >= 0, "%s, client, create connected socket", t->name);
sync_wait(client);
......@@ -549,7 +549,7 @@ static void test_nonblocking_mode(int type)
}
struct sockaddr_storage server_addr = addr_from_socket(server);
client = net_connected_socket(type, &server_addr, NULL);
client = net_connected_socket(type, &server_addr, NULL, false);
ok(client >= 0, "%s: connected, create", name);
ok(!socket_is_blocking(client), "%s: connected, nonblocking mode", name);
......@@ -575,7 +575,7 @@ static void test_nonblocking_accept(void)
// create client
int client = net_connected_socket(SOCK_STREAM, &addr_server, NULL);
int client = net_connected_socket(SOCK_STREAM, &addr_server, NULL, false);
ok(client >= 0, "client, create connected socket");
struct sockaddr_storage addr_client = addr_from_socket(client);
......@@ -599,7 +599,7 @@ static void test_nonblocking_accept(void)
// client reconnect
close(client);
client = net_connected_socket(SOCK_STREAM, &addr_server, NULL);
client = net_connected_socket(SOCK_STREAM, &addr_server, NULL, false);
ok(client >= 0, "client, reconnect");
r = poll_read(server);
......
/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -98,7 +98,7 @@ int main(int argc, char *argv[])
// create TCP client
int client = net_connected_socket(SOCK_STREAM, &addr, NULL);
int client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
ok(client >= 0, "client: connect to server");
int optval = 8192;
......@@ -125,7 +125,7 @@ int main(int argc, char *argv[])
for (size_t i = 0; i < sizeof(sndbuf); i++) {
sndbuf[i] = i;
}
r = net_dns_tcp_send(client, sndbuf, sizeof(sndbuf), TIMEOUT);
r = net_dns_tcp_send(client, sndbuf, sizeof(sndbuf), TIMEOUT, NULL);
ok(r == sizeof(sndbuf), "client: net_dns_tcp_send() with short-write");
// receive message
......
/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -17,6 +17,7 @@
#include <assert.h>
#include <tap/basic.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
......@@ -30,6 +31,8 @@
#include "contrib/sockaddr.h"
#include "contrib/ucw/mempool.h"
bool TFO = false;
/* @note Purpose of this test is not to verify process_answer functionality,
* but simply if the requesting/receiving works, so mirror is okay. */
static int reset(knot_layer_t *ctx) { return KNOT_STATE_PRODUCE; }
......@@ -69,7 +72,7 @@ static void *responder_thread(void *arg)
break;
}
knot_wire_set_qr(buf);
net_dns_tcp_send(client, buf, len, -1);
net_dns_tcp_send(client, buf, len, -1, NULL);
close(client);
}
......@@ -87,7 +90,9 @@ static knot_request_t *make_query(knot_requestor_t *requestor,
static const knot_dname_t *root = (uint8_t *)"";
knot_pkt_put_question(pkt, root, KNOT_CLASS_IN, KNOT_RRTYPE_SOA);
return knot_request_make(requestor->mm, dst, src, pkt, NULL, 0);
knot_request_flag_t flags = TFO ? KNOT_REQUEST_TFO: KNOT_REQUEST_NONE;
return knot_request_make(requestor->mm, dst, src, pkt, NULL, flags);
}
static void test_disconnected(knot_requestor_t *requestor,
......@@ -116,6 +121,18 @@ static void test_connected(knot_requestor_t *requestor,
int main(int argc, char *argv[])
{
#if defined(__linux__)
FILE *fd = fopen("/proc/sys/net/ipv4/tcp_fastopen", "r");
if (fd != NULL) {
char val = fgetc(fd);
fclose(fd);
// 0 - disabled, 1 - server TFO (client fallbacks),
// 2 - client TFO, 3 - both
if (val == '1' || val == '3') {
TFO = true;
}
}
#endif
plan_lazy();
knot_mm_t mm;
......@@ -145,6 +162,11 @@ int main(int argc, char *argv[])
ret = listen(responder_fd, 10);
ok(ret == 0, "check listen return");
if (TFO) {
ret = net_bound_tfo(responder_fd, 10);
ok(ret == KNOT_EOK, "check bound TFO return");
}
pthread_t thread;
pthread_create(&thread, 0, responder_thread, &responder_fd);
......@@ -152,9 +174,9 @@ int main(int argc, char *argv[])
test_connected(&requestor, &server, &client);
/* Terminate responder. */
int conn = net_connected_socket(SOCK_STREAM, &server, NULL);
int conn = net_connected_socket(SOCK_STREAM, &server, NULL, false);
assert(conn > 0);
conn = net_dns_tcp_send(conn, (uint8_t *)"", 1, TIMEOUT);
conn = net_dns_tcp_send(conn, (uint8_t *)"", 1, TIMEOUT, NULL);
assert(conn > 0);
pthread_join(thread, NULL);
close(responder_fd);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment