Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/openssl/doc/designs/ddd/ddd-04-fd-nonblocking.c
34889 views
1
#include <sys/poll.h>
2
#include <openssl/ssl.h>
3
4
/*
5
* Demo 4: Client — Client Creates FD — Nonblocking
6
* ================================================
7
*
8
* This is an example of (part of) an application which uses libssl in an
9
* asynchronous, nonblocking fashion. The client is responsible for creating the
10
* socket and passing it to libssl. The functions show all interactions with
11
* libssl the application makes, and would hypothetically be linked into a
12
* larger application.
13
*/
14
typedef struct app_conn_st {
15
SSL *ssl;
16
int fd;
17
int rx_need_tx, tx_need_rx;
18
} APP_CONN;
19
20
/*
21
* The application is initializing and wants an SSL_CTX which it will use for
22
* some number of outgoing connections, which it creates in subsequent calls to
23
* new_conn. The application may also call this function multiple times to
24
* create multiple SSL_CTX.
25
*/
26
SSL_CTX *create_ssl_ctx(void)
27
{
28
SSL_CTX *ctx;
29
30
#ifdef USE_QUIC
31
ctx = SSL_CTX_new(OSSL_QUIC_client_method());
32
#else
33
ctx = SSL_CTX_new(TLS_client_method());
34
#endif
35
if (ctx == NULL)
36
return NULL;
37
38
/* Enable trust chain verification. */
39
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
40
41
/* Load default root CA store. */
42
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
43
SSL_CTX_free(ctx);
44
return NULL;
45
}
46
47
return ctx;
48
}
49
50
/*
51
* The application wants to create a new outgoing connection using a given
52
* SSL_CTX.
53
*
54
* hostname is a string like "openssl.org" used for certificate validation.
55
*/
56
APP_CONN *new_conn(SSL_CTX *ctx, int fd, const char *bare_hostname)
57
{
58
APP_CONN *conn;
59
SSL *ssl;
60
#ifdef USE_QUIC
61
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
62
#endif
63
64
conn = calloc(1, sizeof(APP_CONN));
65
if (conn == NULL)
66
return NULL;
67
68
ssl = conn->ssl = SSL_new(ctx);
69
if (ssl == NULL) {
70
free(conn);
71
return NULL;
72
}
73
74
SSL_set_connect_state(ssl); /* cannot fail */
75
76
if (SSL_set_fd(ssl, fd) <= 0) {
77
SSL_free(ssl);
78
free(conn);
79
return NULL;
80
}
81
82
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
83
SSL_free(ssl);
84
free(conn);
85
return NULL;
86
}
87
88
if (SSL_set_tlsext_host_name(ssl, bare_hostname) <= 0) {
89
SSL_free(ssl);
90
free(conn);
91
return NULL;
92
}
93
94
#ifdef USE_QUIC
95
/* Configure ALPN, which is required for QUIC. */
96
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
97
/* Note: SSL_set_alpn_protos returns 1 for failure. */
98
SSL_free(ssl);
99
free(conn);
100
return NULL;
101
}
102
#endif
103
104
conn->fd = fd;
105
return conn;
106
}
107
108
/*
109
* Non-blocking transmission.
110
*
111
* Returns -1 on error. Returns -2 if the function would block (corresponds to
112
* EWOULDBLOCK).
113
*/
114
int tx(APP_CONN *conn, const void *buf, int buf_len)
115
{
116
int rc, l;
117
118
conn->tx_need_rx = 0;
119
120
l = SSL_write(conn->ssl, buf, buf_len);
121
if (l <= 0) {
122
rc = SSL_get_error(conn->ssl, l);
123
switch (rc) {
124
case SSL_ERROR_WANT_READ:
125
conn->tx_need_rx = 1;
126
case SSL_ERROR_WANT_CONNECT:
127
case SSL_ERROR_WANT_WRITE:
128
return -2;
129
default:
130
return -1;
131
}
132
}
133
134
return l;
135
}
136
137
/*
138
* Non-blocking reception.
139
*
140
* Returns -1 on error. Returns -2 if the function would block (corresponds to
141
* EWOULDBLOCK).
142
*/
143
int rx(APP_CONN *conn, void *buf, int buf_len)
144
{
145
int rc, l;
146
147
conn->rx_need_tx = 0;
148
149
l = SSL_read(conn->ssl, buf, buf_len);
150
if (l <= 0) {
151
rc = SSL_get_error(conn->ssl, l);
152
switch (rc) {
153
case SSL_ERROR_WANT_WRITE:
154
conn->rx_need_tx = 1;
155
case SSL_ERROR_WANT_READ:
156
return -2;
157
default:
158
return -1;
159
}
160
}
161
162
return l;
163
}
164
165
/*
166
* The application wants to know a fd it can poll on to determine when the
167
* SSL state machine needs to be pumped.
168
*
169
* If the fd returned has:
170
*
171
* POLLIN: SSL_read *may* return data;
172
* if application does not want to read yet, it should call pump().
173
*
174
* POLLOUT: SSL_write *may* accept data
175
*
176
* POLLERR: An application should call pump() if it is not likely to call
177
* SSL_read or SSL_write soon.
178
*
179
*/
180
int get_conn_fd(APP_CONN *conn)
181
{
182
return conn->fd;
183
}
184
185
/*
186
* These functions returns zero or more of:
187
*
188
* POLLIN: The SSL state machine is interested in socket readability events.
189
*
190
* POLLOUT: The SSL state machine is interested in socket writeability events.
191
*
192
* POLLERR: The SSL state machine is interested in socket error events.
193
*
194
* get_conn_pending_tx returns events which may cause SSL_write to make
195
* progress and get_conn_pending_rx returns events which may cause SSL_read
196
* to make progress.
197
*/
198
int get_conn_pending_tx(APP_CONN *conn)
199
{
200
#ifdef USE_QUIC
201
return (SSL_net_read_desired(conn->ssl) ? POLLIN : 0)
202
| (SSL_net_write_desired(conn->ssl) ? POLLOUT : 0)
203
| POLLERR;
204
#else
205
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
206
#endif
207
}
208
209
int get_conn_pending_rx(APP_CONN *conn)
210
{
211
return get_conn_pending_tx(conn);
212
}
213
214
#ifdef USE_QUIC
215
/*
216
* Returns the number of milliseconds after which some call to libssl must be
217
* made. Any call (SSL_read/SSL_write/SSL_pump) will do. Returns -1 if there is
218
* no need for such a call. This may change after the next call
219
* to libssl.
220
*/
221
static inline int timeval_to_ms(const struct timeval *t);
222
223
int get_conn_pump_timeout(APP_CONN *conn)
224
{
225
struct timeval tv;
226
int is_infinite;
227
228
if (!SSL_get_event_timeout(conn->ssl, &tv, &is_infinite))
229
return -1;
230
231
return is_infinite ? -1 : timeval_to_ms(&tv);
232
}
233
234
/*
235
* Called to advance internals of libssl state machines without having to
236
* perform an application-level read/write.
237
*/
238
void pump(APP_CONN *conn)
239
{
240
SSL_handle_events(conn->ssl);
241
}
242
#endif
243
244
/*
245
* The application wants to close the connection and free bookkeeping
246
* structures.
247
*/
248
void teardown(APP_CONN *conn)
249
{
250
SSL_shutdown(conn->ssl);
251
SSL_free(conn->ssl);
252
free(conn);
253
}
254
255
/*
256
* The application is shutting down and wants to free a previously
257
* created SSL_CTX.
258
*/
259
void teardown_ctx(SSL_CTX *ctx)
260
{
261
SSL_CTX_free(ctx);
262
}
263
264
/*
265
* ============================================================================
266
* Example driver for the above code. This is just to demonstrate that the code
267
* works and is not intended to be representative of a real application.
268
*/
269
#include <sys/types.h>
270
#include <sys/socket.h>
271
#include <sys/signal.h>
272
#ifdef USE_QUIC
273
# include <sys/time.h>
274
#endif
275
#include <netdb.h>
276
#include <unistd.h>
277
#include <fcntl.h>
278
279
#ifdef USE_QUIC
280
281
static inline void ms_to_timeval(struct timeval *t, int ms)
282
{
283
t->tv_sec = ms < 0 ? -1 : ms/1000;
284
t->tv_usec = ms < 0 ? 0 : (ms%1000)*1000;
285
}
286
287
static inline int timeval_to_ms(const struct timeval *t)
288
{
289
return t->tv_sec*1000 + t->tv_usec/1000;
290
}
291
292
#endif
293
294
int main(int argc, char **argv)
295
{
296
int rc, fd = -1, res = 1;
297
static char tx_msg[300];
298
const char *tx_p = tx_msg;
299
char rx_buf[2048];
300
int l, tx_len;
301
#ifdef USE_QUIC
302
struct timeval timeout;
303
#else
304
int timeout = 2000 /* ms */;
305
#endif
306
APP_CONN *conn = NULL;
307
struct addrinfo hints = {0}, *result = NULL;
308
SSL_CTX *ctx = NULL;
309
310
#ifdef USE_QUIC
311
ms_to_timeval(&timeout, 2000);
312
#endif
313
314
if (argc < 3) {
315
fprintf(stderr, "usage: %s host port\n", argv[0]);
316
goto fail;
317
}
318
319
tx_len = snprintf(tx_msg, sizeof(tx_msg),
320
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
321
322
ctx = create_ssl_ctx();
323
if (ctx == NULL) {
324
fprintf(stderr, "cannot create SSL context\n");
325
goto fail;
326
}
327
328
hints.ai_family = AF_INET;
329
hints.ai_socktype = SOCK_STREAM;
330
hints.ai_flags = AI_PASSIVE;
331
rc = getaddrinfo(argv[1], argv[2], &hints, &result);
332
if (rc < 0) {
333
fprintf(stderr, "cannot resolve\n");
334
goto fail;
335
}
336
337
signal(SIGPIPE, SIG_IGN);
338
339
#ifdef USE_QUIC
340
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
341
#else
342
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
343
#endif
344
if (fd < 0) {
345
fprintf(stderr, "cannot create socket\n");
346
goto fail;
347
}
348
349
rc = connect(fd, result->ai_addr, result->ai_addrlen);
350
if (rc < 0) {
351
fprintf(stderr, "cannot connect\n");
352
goto fail;
353
}
354
355
rc = fcntl(fd, F_SETFL, O_NONBLOCK);
356
if (rc < 0) {
357
fprintf(stderr, "cannot make socket nonblocking\n");
358
goto fail;
359
}
360
361
conn = new_conn(ctx, fd, argv[1]);
362
if (conn == NULL) {
363
fprintf(stderr, "cannot establish connection\n");
364
goto fail;
365
}
366
367
/* TX */
368
while (tx_len != 0) {
369
l = tx(conn, tx_p, tx_len);
370
if (l > 0) {
371
tx_p += l;
372
tx_len -= l;
373
} else if (l == -1) {
374
fprintf(stderr, "tx error\n");
375
goto fail;
376
} else if (l == -2) {
377
#ifdef USE_QUIC
378
struct timeval start, now, deadline, t;
379
#endif
380
struct pollfd pfd = {0};
381
382
#ifdef USE_QUIC
383
ms_to_timeval(&t, get_conn_pump_timeout(conn));
384
if (t.tv_sec < 0 || timercmp(&t, &timeout, >))
385
t = timeout;
386
387
gettimeofday(&start, NULL);
388
timeradd(&start, &timeout, &deadline);
389
#endif
390
391
pfd.fd = get_conn_fd(conn);
392
pfd.events = get_conn_pending_tx(conn);
393
#ifdef USE_QUIC
394
if (poll(&pfd, 1, timeval_to_ms(&t)) == 0)
395
#else
396
if (poll(&pfd, 1, timeout) == 0)
397
#endif
398
{
399
#ifdef USE_QUIC
400
pump(conn);
401
402
gettimeofday(&now, NULL);
403
if (timercmp(&now, &deadline, >=))
404
#endif
405
{
406
fprintf(stderr, "tx timeout\n");
407
goto fail;
408
}
409
}
410
}
411
}
412
413
/* RX */
414
for (;;) {
415
l = rx(conn, rx_buf, sizeof(rx_buf));
416
if (l > 0) {
417
fwrite(rx_buf, 1, l, stdout);
418
} else if (l == -1) {
419
break;
420
} else if (l == -2) {
421
#ifdef USE_QUIC
422
struct timeval start, now, deadline, t;
423
#endif
424
struct pollfd pfd = {0};
425
426
#ifdef USE_QUIC
427
ms_to_timeval(&t, get_conn_pump_timeout(conn));
428
if (t.tv_sec < 0 || timercmp(&t, &timeout, >))
429
t = timeout;
430
431
gettimeofday(&start, NULL);
432
timeradd(&start, &timeout, &deadline);
433
#endif
434
435
pfd.fd = get_conn_fd(conn);
436
pfd.events = get_conn_pending_rx(conn);
437
#ifdef USE_QUIC
438
if (poll(&pfd, 1, timeval_to_ms(&t)) == 0)
439
#else
440
if (poll(&pfd, 1, timeout) == 0)
441
#endif
442
{
443
#ifdef USE_QUIC
444
pump(conn);
445
gettimeofday(&now, NULL);
446
if (timercmp(&now, &deadline, >=))
447
#endif
448
{
449
fprintf(stderr, "rx timeout\n");
450
goto fail;
451
}
452
}
453
}
454
}
455
456
res = 0;
457
fail:
458
if (conn != NULL)
459
teardown(conn);
460
if (ctx != NULL)
461
teardown_ctx(ctx);
462
if (result != NULL)
463
freeaddrinfo(result);
464
return res;
465
}
466
467