summaryrefslogtreecommitdiffstats
path: root/src/output/dnssim/internal.h
blob: b9feddfe703d08b05d0d352f07ca4c041259c030 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
 * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
 * All rights reserved.
 *
 * This file is part of dnsjit.
 *
 * dnsjit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * dnsjit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with dnsjit.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __dnsjit_output_dnssim_internal_h
#define __dnsjit_output_dnssim_internal_h

#include <gnutls/gnutls.h>
#include <nghttp2/nghttp2.h>
#include <uv.h>
#include "core/object/dns.h"
#include "core/object/payload.h"

#define DNSSIM_MIN_GNUTLS_VERSION 0x030603
#define DNSSIM_MIN_GNUTLS_ERRORMSG "dnssim tls/https2 transport requires GnuTLS >= 3.6.3"

#define _self ((_output_dnssim_t*)self)
#define _ERR_MALFORMED -2
#define _ERR_MSGID -3
#define _ERR_TC -4
#define _ERR_QUESTION -5

#define _MAX_URI_LEN 65536
#define MAX_DNSMSG_SIZE 65535
#define WIRE_BUF_SIZE (MAX_DNSMSG_SIZE + 2 + 16384) /** max tcplen + 2b tcplen + 16kb tls record */

typedef struct _output_dnssim_request    _output_dnssim_request_t;
typedef struct _output_dnssim_connection _output_dnssim_connection_t;
typedef struct _output_dnssim_client     _output_dnssim_client_t;

/*
 * Query-related structures.
 */

typedef struct _output_dnssim_query _output_dnssim_query_t;
struct _output_dnssim_query {
    /*
     * Next query in the list.
     *
     * Currently, next is used for TCP clients/connection, which makes it
     * impossible to use for tracking multiple queries of a single request.
     *
     * TODO: refactor the linked lists to allow query to be part of multiple lists
     */
    _output_dnssim_query_t* next;

    output_dnssim_transport_t transport;
    _output_dnssim_request_t* req;

    /* Query state, currently used only for TCP. */
    enum {
        _OUTPUT_DNSSIM_QUERY_PENDING_WRITE,
        _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB,
        _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE,
        _OUTPUT_DNSSIM_QUERY_WRITE_FAILED,
        _OUTPUT_DNSSIM_QUERY_SENT,
        _OUTPUT_DNSSIM_QUERY_ORPHANED
    } state;
};

typedef struct _output_dnssim_query_udp _output_dnssim_query_udp_t;
struct _output_dnssim_query_udp {
    _output_dnssim_query_t qry;

    uv_udp_t* handle;
    uv_buf_t  buf;
};

typedef struct _output_dnssim_query_tcp _output_dnssim_query_tcp_t;
struct _output_dnssim_query_tcp {
    _output_dnssim_query_t qry;

    /* Connection this query is assigned to. */
    _output_dnssim_connection_t* conn;

    uv_write_t write_req;

    /* Send buffers for libuv; 0 is for dnslen, 1 is for dnsmsg. */
    uv_buf_t bufs[2];

    /* HTTP/2 stream id that was used to send this query. */
    int32_t stream_id;

    /* HTTP/2 expected content length. */
    int32_t content_len;

    /* Receive buffer (currently used only by HTTP/2). */
    uint8_t* recv_buf;
    ssize_t  recv_buf_len;
};

struct _output_dnssim_request {
    /* List of queries associated with this request. */
    _output_dnssim_query_t* qry;

    /* Client this request belongs to. */
    _output_dnssim_client_t* client;

    /* The DNS question to be resolved. */
    core_object_payload_t* payload;
    core_object_dns_t*     dns_q;
    const uint8_t*         question;
    ssize_t                question_len;

    /* Timestamps for latency calculation. */
    uint64_t created_at;
    uint64_t ended_at;

    /* Timer for tracking timeout of the request. */
    uv_timer_t* timer;

    /* The output component of this request. */
    output_dnssim_t* dnssim;

    /* State of the request. */
    enum {
        _OUTPUT_DNSSIM_REQ_ONGOING,
        _OUTPUT_DNSSIM_REQ_CLOSING
    } state;

    /* Statistics interval in which this request is tracked. */
    output_dnssim_stats_t* stats;
};

/*
 * Connection-related structures.
 */

/* Read-state of connection's data stream. */
typedef enum _output_dnssim_read_state {
    _OUTPUT_DNSSIM_READ_STATE_CLEAN,
    _OUTPUT_DNSSIM_READ_STATE_DNSLEN, /* Expecting bytes of dnslen. */
    _OUTPUT_DNSSIM_READ_STATE_DNSMSG, /* Expecting bytes of dnsmsg. */
    _OUTPUT_DNSSIM_READ_STATE_INVALID
} _output_dnssim_read_state_t;

/* TLS-related data for a single connection. */
typedef struct _output_dnssim_tls_ctx {
    gnutls_session_t session;
    uint8_t*         buf;
    ssize_t          buf_len;
    ssize_t          buf_pos;
    size_t           write_queue_size;
} _output_dnssim_tls_ctx_t;

/* HTTP2 context for a single connection. */
typedef struct _output_dnssim_http2_ctx {
    nghttp2_session* session;

    /* Query to which the dnsbuf currently being processed belongs to. */
    _output_dnssim_query_tcp_t* current_qry;

    /* Maximum number of concurrent and currently open streams. */
    uint32_t max_concurrent_streams;
    uint32_t open_streams;

    /* Flag indicating whether we received the peer's initial SETTINGS frame. */
    bool remote_settings_received;
} _output_dnssim_http2_ctx_t;

struct _output_dnssim_connection {
    _output_dnssim_connection_t* next;

    uv_tcp_t* handle;

    /* Timeout timer for establishing the connection. */
    uv_timer_t* handshake_timer;

    /* Idle timer for connection reuse. rfc7766#section-6.2.3 */
    uv_timer_t* idle_timer;
    bool        is_idle;

    /* List of queries that have been queued (pending write callback). */
    _output_dnssim_query_t* queued;

    /* List of queries that have been sent over this connection. */
    _output_dnssim_query_t* sent;

    /* Client this connection belongs to. */
    _output_dnssim_client_t* client;

    /* State of the connection.
     * Numeric ordering of constants is significant and follows the typical connection lifecycle.
     * Ensure new states are added to a proper place. */
    enum {
        _OUTPUT_DNSSIM_CONN_INITIALIZED     = 0,
        _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE   = 10,
        _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE   = 20,
        _OUTPUT_DNSSIM_CONN_ACTIVE          = 30,
        _OUTPUT_DNSSIM_CONN_CONGESTED       = 35,
        _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED = 38,
        _OUTPUT_DNSSIM_CONN_CLOSING         = 40,
        _OUTPUT_DNSSIM_CONN_CLOSED          = 50
    } state;

    /* State of the data stream read. */
    _output_dnssim_read_state_t read_state;

    /* Total length of the expected dns data (either 2 for dnslen, or dnslen itself). */
    size_t dnsbuf_len;

    /* Current position in the receive dns buffer. */
    size_t dnsbuf_pos;

    /* Receive buffer used for incomplete messages or dnslen. */
    char* dnsbuf_data;
    bool  dnsbuf_free_after_use;

    /* Statistics interval in which the handshake is tracked. */
    output_dnssim_stats_t* stats;

    /* TLS-related data. */
    _output_dnssim_tls_ctx_t* tls;

    /* HTTP/2-related data. */
    _output_dnssim_http2_ctx_t* http2;

    /* Prevents immediate closure of connection. Instead, connection is moved
     * to CLOSE_REQUESTED state and setter of this flag is responsible for
     * closing the connection when clearing this flag. */
    bool prevent_close;
};

/*
 * Client structure.
 */

struct _output_dnssim_client {
    /* Dnssim component this client belongs to. */
    output_dnssim_t* dnssim;

    /* List of connections.
     * Multiple connections may be used (e.g. some are already closed for writing).
     */
    _output_dnssim_connection_t* conn;

    /* List of queries that are pending to be sent over any available connection. */
    _output_dnssim_query_t* pending;

    /* TLS-ticket for session resumption. */
    gnutls_datum_t tls_ticket;
};

/*
 * DnsSim-related structures.
 */

typedef struct _output_dnssim_source _output_dnssim_source_t;
struct _output_dnssim_source {
    _output_dnssim_source_t* next;
    struct sockaddr_storage  addr;
};

typedef struct _output_dnssim _output_dnssim_t;
struct _output_dnssim {
    output_dnssim_t pub;

    uv_loop_t  loop;
    uv_timer_t stats_timer;

    struct sockaddr_storage   target;
    _output_dnssim_source_t*  source;
    output_dnssim_transport_t transport;

    char                      h2_uri_authority[_MAX_URI_LEN];
    char                      h2_uri_path[_MAX_URI_LEN];
    bool                      h2_zero_out_msgid;
    output_dnssim_h2_method_t h2_method;

    /* Array of clients, mapped by client ID (ranges from 0 to max_clients). */
    _output_dnssim_client_t* client_arr;

    gnutls_priority_t*               tls_priority;
    gnutls_certificate_credentials_t tls_cred;
    char                             wire_buf[WIRE_BUF_SIZE]; /* thread-local buffer for processing tls input */
};

/* Provides data for HTTP/2 data frames. */
typedef struct {
    const uint8_t* buf;
    size_t         len;
} _output_dnssim_https2_data_provider_t;

/*
 * Forward function declarations.
 */

int  _output_dnssim_bind_before_connect(output_dnssim_t* self, uv_handle_t* handle);
int  _output_dnssim_create_query_udp(output_dnssim_t* self, _output_dnssim_request_t* req);
int  _output_dnssim_create_query_tcp(output_dnssim_t* self, _output_dnssim_request_t* req);
void _output_dnssim_close_query_udp(_output_dnssim_query_udp_t* qry);
void _output_dnssim_close_query_tcp(_output_dnssim_query_tcp_t* qry);
int  _output_dnssim_answers_request(_output_dnssim_request_t* req, core_object_dns_t* response);
void _output_dnssim_request_answered(_output_dnssim_request_t* req, core_object_dns_t* msg);
void _output_dnssim_maybe_free_request(_output_dnssim_request_t* req);
void _output_dnssim_on_uv_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf);
void _output_dnssim_create_request(output_dnssim_t* self, _output_dnssim_client_t* client, core_object_payload_t* payload);
int  _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client);
int  _output_dnssim_tcp_connect(output_dnssim_t* self, _output_dnssim_connection_t* conn);
void _output_dnssim_tcp_close(_output_dnssim_connection_t* conn);
void _output_dnssim_tcp_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry);
void _output_dnssim_conn_close(_output_dnssim_connection_t* conn);
void _output_dnssim_conn_idle(_output_dnssim_connection_t* conn);
int  _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client);
void _output_dnssim_conn_activate(_output_dnssim_connection_t* conn);
void _output_dnssim_conn_maybe_free(_output_dnssim_connection_t* conn);
void _output_dnssim_read_dns_stream(_output_dnssim_connection_t* conn, size_t len, const char* data);
void _output_dnssim_read_dnsmsg(_output_dnssim_connection_t* conn, size_t len, const char* data);

#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
int  _output_dnssim_create_query_tls(output_dnssim_t* self, _output_dnssim_request_t* req);
void _output_dnssim_close_query_tls(_output_dnssim_query_tcp_t* qry);
int  _output_dnssim_tls_init(_output_dnssim_connection_t* conn);
void _output_dnssim_tls_process_input_data(_output_dnssim_connection_t* conn);
void _output_dnssim_tls_close(_output_dnssim_connection_t* conn);
void _output_dnssim_tls_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry);

int  _output_dnssim_create_query_https2(output_dnssim_t* self, _output_dnssim_request_t* req);
void _output_dnssim_close_query_https2(_output_dnssim_query_tcp_t* qry);
int  _output_dnssim_https2_init(_output_dnssim_connection_t* conn);
int  _output_dnssim_https2_setup(_output_dnssim_connection_t* conn);
void _output_dnssim_https2_process_input_data(_output_dnssim_connection_t* conn, size_t len, const char* data);
void _output_dnssim_https2_close(_output_dnssim_connection_t* conn);
void _output_dnssim_https2_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry);
#endif

#endif