1 | /*
|
---|
2 | * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License 2.0 (the "License"). You may not use
|
---|
5 | * this file except in compliance with the License. You can obtain a copy
|
---|
6 | * in the file LICENSE in the source distribution or at
|
---|
7 | * https://www.openssl.org/source/license.html
|
---|
8 | */
|
---|
9 |
|
---|
10 | /*
|
---|
11 | * This is a temporary test server for QUIC. It will eventually be replaced
|
---|
12 | * by s_server and removed once we have full QUIC server support.
|
---|
13 | */
|
---|
14 |
|
---|
15 | #include <stdio.h>
|
---|
16 | #include <openssl/bio.h>
|
---|
17 | #include <openssl/ssl.h>
|
---|
18 | #include <openssl/err.h>
|
---|
19 | #include "internal/e_os.h"
|
---|
20 | #include "internal/sockets.h"
|
---|
21 | #include "internal/quic_tserver.h"
|
---|
22 | #include "internal/quic_stream_map.h"
|
---|
23 | #include "internal/time.h"
|
---|
24 |
|
---|
25 | static BIO *bio_err = NULL;
|
---|
26 |
|
---|
27 | static void wait_for_activity(QUIC_TSERVER *qtserv)
|
---|
28 | {
|
---|
29 | fd_set readfds, writefds;
|
---|
30 | fd_set *readfdsp = NULL, *writefdsp = NULL;
|
---|
31 | struct timeval timeout, *timeoutp = NULL;
|
---|
32 | int width;
|
---|
33 | int sock;
|
---|
34 | BIO *bio = ossl_quic_tserver_get0_rbio(qtserv);
|
---|
35 | OSSL_TIME deadline;
|
---|
36 |
|
---|
37 | BIO_get_fd(bio, &sock);
|
---|
38 |
|
---|
39 | if (ossl_quic_tserver_get_net_read_desired(qtserv)) {
|
---|
40 | readfdsp = &readfds;
|
---|
41 | FD_ZERO(readfdsp);
|
---|
42 | openssl_fdset(sock, readfdsp);
|
---|
43 | }
|
---|
44 |
|
---|
45 | if (ossl_quic_tserver_get_net_write_desired(qtserv)) {
|
---|
46 | writefdsp = &writefds;
|
---|
47 | FD_ZERO(writefdsp);
|
---|
48 | openssl_fdset(sock, writefdsp);
|
---|
49 | }
|
---|
50 |
|
---|
51 | deadline = ossl_quic_tserver_get_deadline(qtserv);
|
---|
52 |
|
---|
53 | if (!ossl_time_is_infinite(deadline)) {
|
---|
54 | timeout = ossl_time_to_timeval(ossl_time_subtract(deadline,
|
---|
55 | ossl_time_now()));
|
---|
56 | timeoutp = &timeout;
|
---|
57 | }
|
---|
58 |
|
---|
59 | width = sock + 1;
|
---|
60 |
|
---|
61 | if (readfdsp == NULL && writefdsp == NULL && timeoutp == NULL)
|
---|
62 | return;
|
---|
63 |
|
---|
64 | select(width, readfdsp, writefdsp, NULL, timeoutp);
|
---|
65 | }
|
---|
66 |
|
---|
67 | /* Helper function to create a BIO connected to the server */
|
---|
68 | static BIO *create_dgram_bio(int family, const char *hostname, const char *port)
|
---|
69 | {
|
---|
70 | int sock = -1;
|
---|
71 | BIO_ADDRINFO *res;
|
---|
72 | const BIO_ADDRINFO *ai = NULL;
|
---|
73 | BIO *bio;
|
---|
74 |
|
---|
75 | if (BIO_sock_init() != 1)
|
---|
76 | return NULL;
|
---|
77 |
|
---|
78 | /*
|
---|
79 | * Lookup IP address info for the server.
|
---|
80 | */
|
---|
81 | if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_SERVER, family, SOCK_DGRAM,
|
---|
82 | 0, &res))
|
---|
83 | return NULL;
|
---|
84 |
|
---|
85 | /*
|
---|
86 | * Loop through all the possible addresses for the server and find one
|
---|
87 | * we can create and start listening on
|
---|
88 | */
|
---|
89 | for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) {
|
---|
90 | /* Create the UDP socket */
|
---|
91 | sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0);
|
---|
92 | if (sock == -1)
|
---|
93 | continue;
|
---|
94 |
|
---|
95 | /* Start listening on the socket */
|
---|
96 | if (!BIO_listen(sock, BIO_ADDRINFO_address(ai), 0)) {
|
---|
97 | BIO_closesocket(sock);
|
---|
98 | continue;
|
---|
99 | }
|
---|
100 |
|
---|
101 | /* Set to non-blocking mode */
|
---|
102 | if (!BIO_socket_nbio(sock, 1)) {
|
---|
103 | BIO_closesocket(sock);
|
---|
104 | continue;
|
---|
105 | }
|
---|
106 |
|
---|
107 | break; /* stop searching if we found an addr */
|
---|
108 | }
|
---|
109 |
|
---|
110 | /* Free the address information resources we allocated earlier */
|
---|
111 | BIO_ADDRINFO_free(res);
|
---|
112 |
|
---|
113 | /* If we didn't bind any sockets, fail */
|
---|
114 | if (ai == NULL)
|
---|
115 | return NULL;
|
---|
116 |
|
---|
117 | /* Create a BIO to wrap the socket */
|
---|
118 | bio = BIO_new(BIO_s_datagram());
|
---|
119 | if (bio == NULL) {
|
---|
120 | BIO_closesocket(sock);
|
---|
121 | return NULL;
|
---|
122 | }
|
---|
123 |
|
---|
124 | /*
|
---|
125 | * Associate the newly created BIO with the underlying socket. By
|
---|
126 | * passing BIO_CLOSE here the socket will be automatically closed when
|
---|
127 | * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which
|
---|
128 | * case you must close the socket explicitly when it is no longer
|
---|
129 | * needed.
|
---|
130 | */
|
---|
131 | BIO_set_fd(bio, sock, BIO_CLOSE);
|
---|
132 |
|
---|
133 | return bio;
|
---|
134 | }
|
---|
135 |
|
---|
136 | static void usage(void)
|
---|
137 | {
|
---|
138 | BIO_printf(bio_err, "quicserver [-6][-trace] hostname port certfile keyfile\n");
|
---|
139 | }
|
---|
140 |
|
---|
141 | int main(int argc, char *argv[])
|
---|
142 | {
|
---|
143 | QUIC_TSERVER_ARGS tserver_args = {0};
|
---|
144 | QUIC_TSERVER *qtserv = NULL;
|
---|
145 | int ipv6 = 0, trace = 0;
|
---|
146 | int argnext = 1;
|
---|
147 | BIO *bio = NULL;
|
---|
148 | char *hostname, *port, *certfile, *keyfile;
|
---|
149 | int ret = EXIT_FAILURE;
|
---|
150 | unsigned char reqbuf[1024];
|
---|
151 | size_t numbytes, reqbytes = 0;
|
---|
152 | const char reqterm[] = {
|
---|
153 | '\r', '\n', '\r', '\n'
|
---|
154 | };
|
---|
155 | const char *response[] = {
|
---|
156 | "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>Hello world</body>\n</html>\n",
|
---|
157 | "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>Hello again</body>\n</html>\n",
|
---|
158 | "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>Another response</body>\n</html>\n",
|
---|
159 | "HTTP/1.0 200 ok\r\nContent-type: text/html\r\n\r\n<!DOCTYPE html>\n<html>\n<body>A message</body>\n</html>\n",
|
---|
160 | };
|
---|
161 | unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' };
|
---|
162 | int first = 1;
|
---|
163 | uint64_t streamid;
|
---|
164 | size_t respnum = 0;
|
---|
165 |
|
---|
166 | bio_err = BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT);
|
---|
167 | if (argc == 0 || bio_err == NULL)
|
---|
168 | goto end2;
|
---|
169 |
|
---|
170 | while (argnext < argc) {
|
---|
171 | if (argv[argnext][0] != '-')
|
---|
172 | break;
|
---|
173 | if (strcmp(argv[argnext], "-6") == 0) {
|
---|
174 | ipv6 = 1;
|
---|
175 | } else if(strcmp(argv[argnext], "-trace") == 0) {
|
---|
176 | trace = 1;
|
---|
177 | } else {
|
---|
178 | BIO_printf(bio_err, "Unrecognised argument %s\n", argv[argnext]);
|
---|
179 | usage();
|
---|
180 | goto end2;
|
---|
181 | }
|
---|
182 | argnext++;
|
---|
183 | }
|
---|
184 |
|
---|
185 | if (argc - argnext != 4) {
|
---|
186 | usage();
|
---|
187 | goto end2;
|
---|
188 | }
|
---|
189 | hostname = argv[argnext++];
|
---|
190 | port = argv[argnext++];
|
---|
191 | certfile = argv[argnext++];
|
---|
192 | keyfile = argv[argnext++];
|
---|
193 |
|
---|
194 | bio = create_dgram_bio(ipv6 ? AF_INET6 : AF_INET, hostname, port);
|
---|
195 | if (bio == NULL || !BIO_up_ref(bio)) {
|
---|
196 | BIO_printf(bio_err, "Unable to create server socket\n");
|
---|
197 | goto end2;
|
---|
198 | }
|
---|
199 |
|
---|
200 | tserver_args.libctx = NULL;
|
---|
201 | tserver_args.net_rbio = bio;
|
---|
202 | tserver_args.net_wbio = bio;
|
---|
203 | tserver_args.alpn = alpn;
|
---|
204 | tserver_args.alpnlen = sizeof(alpn);
|
---|
205 | tserver_args.ctx = NULL;
|
---|
206 |
|
---|
207 | qtserv = ossl_quic_tserver_new(&tserver_args, certfile, keyfile);
|
---|
208 | if (qtserv == NULL) {
|
---|
209 | BIO_printf(bio_err, "Failed to create the QUIC_TSERVER\n");
|
---|
210 | goto end;
|
---|
211 | }
|
---|
212 |
|
---|
213 | BIO_printf(bio_err, "Starting quicserver\n");
|
---|
214 | BIO_printf(bio_err,
|
---|
215 | "Note that this utility will be removed in a future OpenSSL version.\n");
|
---|
216 | BIO_printf(bio_err,
|
---|
217 | "For test purposes only. Not for use in a production environment.\n");
|
---|
218 |
|
---|
219 | /* Ownership of the BIO is passed to qtserv */
|
---|
220 | bio = NULL;
|
---|
221 |
|
---|
222 | if (trace)
|
---|
223 | #ifndef OPENSSL_NO_SSL_TRACE
|
---|
224 | ossl_quic_tserver_set_msg_callback(qtserv, SSL_trace, bio_err);
|
---|
225 | #else
|
---|
226 | BIO_printf(bio_err,
|
---|
227 | "Warning: -trace specified but no SSL tracing support present\n");
|
---|
228 | #endif
|
---|
229 |
|
---|
230 | /* Wait for handshake to complete */
|
---|
231 | ossl_quic_tserver_tick(qtserv);
|
---|
232 | while(!ossl_quic_tserver_is_handshake_confirmed(qtserv)) {
|
---|
233 | wait_for_activity(qtserv);
|
---|
234 | ossl_quic_tserver_tick(qtserv);
|
---|
235 | if (ossl_quic_tserver_is_terminated(qtserv)) {
|
---|
236 | BIO_printf(bio_err, "Failed waiting for handshake completion\n");
|
---|
237 | ret = EXIT_FAILURE;
|
---|
238 | goto end;
|
---|
239 | }
|
---|
240 | }
|
---|
241 |
|
---|
242 | for (;; respnum++) {
|
---|
243 | if (respnum >= OSSL_NELEM(response))
|
---|
244 | goto end;
|
---|
245 | /* Wait for an incoming stream */
|
---|
246 | do {
|
---|
247 | streamid = ossl_quic_tserver_pop_incoming_stream(qtserv);
|
---|
248 | if (streamid == UINT64_MAX)
|
---|
249 | wait_for_activity(qtserv);
|
---|
250 | ossl_quic_tserver_tick(qtserv);
|
---|
251 | if (ossl_quic_tserver_is_terminated(qtserv)) {
|
---|
252 | /* Assume we finished everything the clients wants from us */
|
---|
253 | ret = EXIT_SUCCESS;
|
---|
254 | goto end;
|
---|
255 | }
|
---|
256 | } while(streamid == UINT64_MAX);
|
---|
257 |
|
---|
258 | /* Read the request */
|
---|
259 | do {
|
---|
260 | if (first)
|
---|
261 | first = 0;
|
---|
262 | else
|
---|
263 | wait_for_activity(qtserv);
|
---|
264 |
|
---|
265 | ossl_quic_tserver_tick(qtserv);
|
---|
266 | if (ossl_quic_tserver_is_terminated(qtserv)) {
|
---|
267 | BIO_printf(bio_err, "Failed reading request\n");
|
---|
268 | ret = EXIT_FAILURE;
|
---|
269 | goto end;
|
---|
270 | }
|
---|
271 |
|
---|
272 | if (ossl_quic_tserver_read(qtserv, streamid, reqbuf + reqbytes,
|
---|
273 | sizeof(reqbuf) - reqbytes,
|
---|
274 | &numbytes)) {
|
---|
275 | if (numbytes > 0)
|
---|
276 | fwrite(reqbuf + reqbytes, 1, numbytes, stdout);
|
---|
277 | reqbytes += numbytes;
|
---|
278 | }
|
---|
279 | } while (reqbytes < sizeof(reqterm)
|
---|
280 | || memcmp(reqbuf + reqbytes - sizeof(reqterm), reqterm,
|
---|
281 | sizeof(reqterm)) != 0);
|
---|
282 |
|
---|
283 | if ((streamid & QUIC_STREAM_DIR_UNI) != 0) {
|
---|
284 | /*
|
---|
285 | * Incoming stream was uni-directional. Create a server initiated
|
---|
286 | * uni-directional stream for the response.
|
---|
287 | */
|
---|
288 | if (!ossl_quic_tserver_stream_new(qtserv, 1, &streamid)) {
|
---|
289 | BIO_printf(bio_err, "Failed creating response stream\n");
|
---|
290 | goto end;
|
---|
291 | }
|
---|
292 | }
|
---|
293 |
|
---|
294 | /* Send the response */
|
---|
295 |
|
---|
296 | ossl_quic_tserver_tick(qtserv);
|
---|
297 | if (!ossl_quic_tserver_write(qtserv, streamid,
|
---|
298 | (unsigned char *)response[respnum],
|
---|
299 | strlen(response[respnum]), &numbytes))
|
---|
300 | goto end;
|
---|
301 |
|
---|
302 | if (!ossl_quic_tserver_conclude(qtserv, streamid))
|
---|
303 | goto end;
|
---|
304 | }
|
---|
305 |
|
---|
306 | end:
|
---|
307 | /* Free twice because we did an up-ref */
|
---|
308 | BIO_free(bio);
|
---|
309 | end2:
|
---|
310 | BIO_free(bio);
|
---|
311 | ossl_quic_tserver_free(qtserv);
|
---|
312 | BIO_free(bio_err);
|
---|
313 | return ret;
|
---|
314 | }
|
---|