Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/openssl/doc/designs/ddd/ddd-02-conn-nonblocking-threads.c
34889 views
1
#include <sys/poll.h>
2
#include <openssl/ssl.h>
3
4
/*
5
* Demo 2: Client — Managed Connection — Nonblocking
6
* ==============================================================
7
*
8
* This is an example of (part of) an application which uses libssl in an
9
* asynchronous, nonblocking fashion. The functions show all interactions with
10
* libssl the application makes, and would hypothetically be linked into a
11
* larger application.
12
*
13
* In this example, libssl still makes syscalls directly using an fd, which is
14
* configured in nonblocking mode. As such, the application can still be
15
* abstracted from the details of what that fd is (is it a TCP socket? is it a
16
* UDP socket?); this code passes the application an fd and the application
17
* simply calls back into this code when poll()/etc. indicates it is ready.
18
*/
19
typedef struct app_conn_st {
20
SSL *ssl;
21
BIO *ssl_bio;
22
int rx_need_tx, tx_need_rx;
23
} APP_CONN;
24
25
/*
26
* The application is initializing and wants an SSL_CTX which it will use for
27
* some number of outgoing connections, which it creates in subsequent calls to
28
* new_conn. The application may also call this function multiple times to
29
* create multiple SSL_CTX.
30
*/
31
SSL_CTX *create_ssl_ctx(void)
32
{
33
SSL_CTX *ctx;
34
35
#ifdef USE_QUIC
36
ctx = SSL_CTX_new(OSSL_QUIC_client_thread_method());
37
#else
38
ctx = SSL_CTX_new(TLS_client_method());
39
#endif
40
if (ctx == NULL)
41
return NULL;
42
43
/* Enable trust chain verification. */
44
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
45
46
/* Load default root CA store. */
47
if (SSL_CTX_set_default_verify_paths(ctx) == 0) {
48
SSL_CTX_free(ctx);
49
return NULL;
50
}
51
52
return ctx;
53
}
54
55
/*
56
* The application wants to create a new outgoing connection using a given
57
* SSL_CTX.
58
*
59
* hostname is a string like "openssl.org:443" or "[::1]:443".
60
*/
61
APP_CONN *new_conn(SSL_CTX *ctx, const char *hostname)
62
{
63
APP_CONN *conn;
64
BIO *out, *buf;
65
SSL *ssl = NULL;
66
const char *bare_hostname;
67
#ifdef USE_QUIC
68
static const unsigned char alpn[] = {5, 'd', 'u', 'm', 'm', 'y'};
69
#endif
70
71
conn = calloc(1, sizeof(APP_CONN));
72
if (conn == NULL)
73
return NULL;
74
75
out = BIO_new_ssl_connect(ctx);
76
if (out == NULL) {
77
free(conn);
78
return NULL;
79
}
80
81
if (BIO_get_ssl(out, &ssl) == 0) {
82
BIO_free_all(out);
83
free(conn);
84
return NULL;
85
}
86
87
buf = BIO_new(BIO_f_buffer());
88
if (buf == NULL) {
89
BIO_free_all(out);
90
free(conn);
91
return NULL;
92
}
93
94
BIO_push(out, buf);
95
96
if (BIO_set_conn_hostname(out, hostname) == 0) {
97
BIO_free_all(out);
98
free(conn);
99
return NULL;
100
}
101
102
/* Returns the parsed hostname extracted from the hostname:port string. */
103
bare_hostname = BIO_get_conn_hostname(out);
104
if (bare_hostname == NULL) {
105
BIO_free_all(out);
106
free(conn);
107
return NULL;
108
}
109
110
/* Tell the SSL object the hostname to check certificates against. */
111
if (SSL_set1_host(ssl, bare_hostname) <= 0) {
112
BIO_free_all(out);
113
free(conn);
114
return NULL;
115
}
116
117
#ifdef USE_QUIC
118
/* Configure ALPN, which is required for QUIC. */
119
if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn))) {
120
/* Note: SSL_set_alpn_protos returns 1 for failure. */
121
BIO_free_all(out);
122
free(conn);
123
return NULL;
124
}
125
#endif
126
127
/* Make the BIO nonblocking. */
128
BIO_set_nbio(out, 1);
129
130
conn->ssl_bio = out;
131
return conn;
132
}
133
134
/*
135
* Non-blocking transmission.
136
*
137
* Returns -1 on error. Returns -2 if the function would block (corresponds to
138
* EWOULDBLOCK).
139
*/
140
int tx(APP_CONN *conn, const void *buf, int buf_len)
141
{
142
int l;
143
144
conn->tx_need_rx = 0;
145
146
l = BIO_write(conn->ssl_bio, buf, buf_len);
147
if (l <= 0) {
148
if (BIO_should_retry(conn->ssl_bio)) {
149
conn->tx_need_rx = BIO_should_read(conn->ssl_bio);
150
return -2;
151
} else {
152
return -1;
153
}
154
}
155
156
return l;
157
}
158
159
/*
160
* Non-blocking reception.
161
*
162
* Returns -1 on error. Returns -2 if the function would block (corresponds to
163
* EWOULDBLOCK).
164
*/
165
int rx(APP_CONN *conn, void *buf, int buf_len)
166
{
167
int l;
168
169
conn->rx_need_tx = 0;
170
171
l = BIO_read(conn->ssl_bio, buf, buf_len);
172
if (l <= 0) {
173
if (BIO_should_retry(conn->ssl_bio)) {
174
conn->rx_need_tx = BIO_should_write(conn->ssl_bio);
175
return -2;
176
} else {
177
return -1;
178
}
179
}
180
181
return l;
182
}
183
184
/*
185
* The application wants to know a fd it can poll on to determine when the
186
* SSL state machine needs to be pumped.
187
*/
188
int get_conn_fd(APP_CONN *conn)
189
{
190
#ifdef USE_QUIC
191
BIO_POLL_DESCRIPTOR d;
192
193
if (!BIO_get_rpoll_descriptor(conn->ssl_bio, &d))
194
return -1;
195
196
return d.value.fd;
197
#else
198
return BIO_get_fd(conn->ssl_bio, NULL);
199
#endif
200
}
201
202
/*
203
* These functions returns zero or more of:
204
*
205
* POLLIN: The SSL state machine is interested in socket readability events.
206
*
207
* POLLOUT: The SSL state machine is interested in socket writeability events.
208
*
209
* POLLERR: The SSL state machine is interested in socket error events.
210
*
211
* get_conn_pending_tx returns events which may cause SSL_write to make
212
* progress and get_conn_pending_rx returns events which may cause SSL_read
213
* to make progress.
214
*/
215
int get_conn_pending_tx(APP_CONN *conn)
216
{
217
#ifdef USE_QUIC
218
return (SSL_net_read_desired(conn->ssl) ? POLLIN : 0)
219
| (SSL_net_write_desired(conn->ssl) ? POLLOUT : 0)
220
| POLLERR;
221
#else
222
return (conn->tx_need_rx ? POLLIN : 0) | POLLOUT | POLLERR;
223
#endif
224
}
225
226
int get_conn_pending_rx(APP_CONN *conn)
227
{
228
#ifdef USE_QUIC
229
return get_conn_pending_tx(conn);
230
#else
231
return (conn->rx_need_tx ? POLLOUT : 0) | POLLIN | POLLERR;
232
#endif
233
}
234
235
/*
236
* The application wants to close the connection and free bookkeeping
237
* structures.
238
*/
239
void teardown(APP_CONN *conn)
240
{
241
BIO_free_all(conn->ssl_bio);
242
free(conn);
243
}
244
245
/*
246
* The application is shutting down and wants to free a previously
247
* created SSL_CTX.
248
*/
249
void teardown_ctx(SSL_CTX *ctx)
250
{
251
SSL_CTX_free(ctx);
252
}
253
254
/*
255
* ============================================================================
256
* Example driver for the above code. This is just to demonstrate that the code
257
* works and is not intended to be representative of a real application.
258
*/
259
int main(int argc, char **argv)
260
{
261
static char tx_msg[384], host_port[300];
262
const char *tx_p = tx_msg;
263
char rx_buf[2048];
264
int res = 1, l, tx_len;
265
int timeout = 2000 /* ms */;
266
APP_CONN *conn = NULL;
267
SSL_CTX *ctx = NULL;
268
269
if (argc < 3) {
270
fprintf(stderr, "usage: %s host port\n", argv[0]);
271
goto fail;
272
}
273
274
snprintf(host_port, sizeof(host_port), "%s:%s", argv[1], argv[2]);
275
tx_len = snprintf(tx_msg, sizeof(tx_msg),
276
"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[1]);
277
278
ctx = create_ssl_ctx();
279
if (ctx == NULL) {
280
fprintf(stderr, "cannot create SSL context\n");
281
goto fail;
282
}
283
284
conn = new_conn(ctx, host_port);
285
if (conn == NULL) {
286
fprintf(stderr, "cannot establish connection\n");
287
goto fail;
288
}
289
290
/* TX */
291
while (tx_len != 0) {
292
l = tx(conn, tx_p, tx_len);
293
if (l > 0) {
294
tx_p += l;
295
tx_len -= l;
296
} else if (l == -1) {
297
fprintf(stderr, "tx error\n");
298
} else if (l == -2) {
299
struct pollfd pfd = {0};
300
pfd.fd = get_conn_fd(conn);
301
pfd.events = get_conn_pending_tx(conn);
302
if (poll(&pfd, 1, timeout) == 0) {
303
fprintf(stderr, "tx timeout\n");
304
goto fail;
305
}
306
}
307
}
308
309
/* RX */
310
for (;;) {
311
l = rx(conn, rx_buf, sizeof(rx_buf));
312
if (l > 0) {
313
fwrite(rx_buf, 1, l, stdout);
314
} else if (l == -1) {
315
break;
316
} else if (l == -2) {
317
struct pollfd pfd = {0};
318
pfd.fd = get_conn_fd(conn);
319
pfd.events = get_conn_pending_rx(conn);
320
if (poll(&pfd, 1, timeout) == 0) {
321
fprintf(stderr, "rx timeout\n");
322
goto fail;
323
}
324
}
325
}
326
327
res = 0;
328
fail:
329
if (conn != NULL)
330
teardown(conn);
331
if (ctx != NULL)
332
teardown_ctx(ctx);
333
return res;
334
}
335
336