1 // Copyright (C) 2018-2019, Cloudflare, Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //
11 // * Redistributions in binary form must reproduce the above copyright
12 // notice, this list of conditions and the following disclaimer in the
13 // documentation and/or other materials provided with the distribution.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16 // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17 // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27 #include <inttypes.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <stdbool.h>
32 #include <unistd.h>
33
34 #include <fcntl.h>
35 #include <errno.h>
36
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <netdb.h>
40
41 #include <ev.h>
42
43 #include <quiche.h>
44
45 #define LOCAL_CONN_ID_LEN 16
46
47 #define MAX_DATAGRAM_SIZE 1350
48
49 struct conn_io {
50 ev_timer timer;
51
52 int sock;
53
54 quiche_conn *conn;
55 };
56
debug_log(const char * line,void * argp)57 static void debug_log(const char *line, void *argp) {
58 fprintf(stderr, "%s\n", line);
59 }
60
flush_egress(struct ev_loop * loop,struct conn_io * conn_io)61 static void flush_egress(struct ev_loop *loop, struct conn_io *conn_io) {
62 static uint8_t out[MAX_DATAGRAM_SIZE];
63
64 quiche_send_info send_info;
65
66 while (1) {
67 ssize_t written = quiche_conn_send(conn_io->conn, out, sizeof(out),
68 &send_info);
69
70 if (written == QUICHE_ERR_DONE) {
71 fprintf(stderr, "done writing\n");
72 break;
73 }
74
75 if (written < 0) {
76 fprintf(stderr, "failed to create packet: %zd\n", written);
77 return;
78 }
79
80 ssize_t sent = sendto(conn_io->sock, out, written, 0,
81 (struct sockaddr *) &send_info.to,
82 send_info.to_len);
83
84 if (sent != written) {
85 perror("failed to send");
86 return;
87 }
88
89 fprintf(stderr, "sent %zd bytes\n", sent);
90 }
91
92 double t = quiche_conn_timeout_as_nanos(conn_io->conn) / 1e9f;
93 conn_io->timer.repeat = t;
94 ev_timer_again(loop, &conn_io->timer);
95 }
96
recv_cb(EV_P_ ev_io * w,int revents)97 static void recv_cb(EV_P_ ev_io *w, int revents) {
98 static bool req_sent = false;
99
100 struct conn_io *conn_io = w->data;
101
102 static uint8_t buf[65535];
103
104 while (1) {
105 struct sockaddr_storage peer_addr;
106 socklen_t peer_addr_len = sizeof(peer_addr);
107 memset(&peer_addr, 0, peer_addr_len);
108
109 ssize_t read = recvfrom(conn_io->sock, buf, sizeof(buf), 0,
110 (struct sockaddr *) &peer_addr,
111 &peer_addr_len);
112
113 if (read < 0) {
114 if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
115 fprintf(stderr, "recv would block\n");
116 break;
117 }
118
119 perror("failed to read");
120 return;
121 }
122
123 quiche_recv_info recv_info = {
124 (struct sockaddr *) &peer_addr,
125
126 peer_addr_len,
127 };
128
129 ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info);
130
131 if (done < 0) {
132 fprintf(stderr, "failed to process packet\n");
133 continue;
134 }
135
136 fprintf(stderr, "recv %zd bytes\n", done);
137 }
138
139 fprintf(stderr, "done reading\n");
140
141 if (quiche_conn_is_closed(conn_io->conn)) {
142 fprintf(stderr, "connection closed\n");
143
144 ev_break(EV_A_ EVBREAK_ONE);
145 return;
146 }
147
148 if (quiche_conn_is_established(conn_io->conn) && !req_sent) {
149 const uint8_t *app_proto;
150 size_t app_proto_len;
151
152 quiche_conn_application_proto(conn_io->conn, &app_proto, &app_proto_len);
153
154 fprintf(stderr, "connection established: %.*s\n",
155 (int) app_proto_len, app_proto);
156
157 const static uint8_t r[] = "GET /index.html\r\n";
158 if (quiche_conn_stream_send(conn_io->conn, 4, r, sizeof(r), true) < 0) {
159 fprintf(stderr, "failed to send HTTP request\n");
160 return;
161 }
162
163 fprintf(stderr, "sent HTTP request\n");
164
165 req_sent = true;
166 }
167
168 if (quiche_conn_is_established(conn_io->conn)) {
169 uint64_t s = 0;
170
171 quiche_stream_iter *readable = quiche_conn_readable(conn_io->conn);
172
173 while (quiche_stream_iter_next(readable, &s)) {
174 fprintf(stderr, "stream %" PRIu64 " is readable\n", s);
175
176 bool fin = false;
177 ssize_t recv_len = quiche_conn_stream_recv(conn_io->conn, s,
178 buf, sizeof(buf),
179 &fin);
180 if (recv_len < 0) {
181 break;
182 }
183
184 printf("%.*s", (int) recv_len, buf);
185
186 if (fin) {
187 if (quiche_conn_close(conn_io->conn, true, 0, NULL, 0) < 0) {
188 fprintf(stderr, "failed to close connection\n");
189 }
190 }
191 }
192
193 quiche_stream_iter_free(readable);
194 }
195
196 flush_egress(loop, conn_io);
197 }
198
timeout_cb(EV_P_ ev_timer * w,int revents)199 static void timeout_cb(EV_P_ ev_timer *w, int revents) {
200 struct conn_io *conn_io = w->data;
201 quiche_conn_on_timeout(conn_io->conn);
202
203 fprintf(stderr, "timeout\n");
204
205 flush_egress(loop, conn_io);
206
207 if (quiche_conn_is_closed(conn_io->conn)) {
208 quiche_stats stats;
209
210 quiche_conn_stats(conn_io->conn, &stats);
211
212 fprintf(stderr, "connection closed, recv=%zu sent=%zu lost=%zu rtt=%" PRIu64 "ns\n",
213 stats.recv, stats.sent, stats.lost, stats.rtt);
214
215 ev_break(EV_A_ EVBREAK_ONE);
216 return;
217 }
218 }
219
main(int argc,char * argv[])220 int main(int argc, char *argv[]) {
221 const char *host = argv[1];
222 const char *port = argv[2];
223
224 const struct addrinfo hints = {
225 .ai_family = PF_UNSPEC,
226 .ai_socktype = SOCK_DGRAM,
227 .ai_protocol = IPPROTO_UDP
228 };
229
230 quiche_enable_debug_logging(debug_log, NULL);
231
232 struct addrinfo *peer;
233 if (getaddrinfo(host, port, &hints, &peer) != 0) {
234 perror("failed to resolve host");
235 return -1;
236 }
237
238 int sock = socket(peer->ai_family, SOCK_DGRAM, 0);
239 if (sock < 0) {
240 perror("failed to create socket");
241 return -1;
242 }
243
244 if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
245 perror("failed to make socket non-blocking");
246 return -1;
247 }
248
249 quiche_config *config = quiche_config_new(0xbabababa);
250 if (config == NULL) {
251 fprintf(stderr, "failed to create config\n");
252 return -1;
253 }
254
255 quiche_config_set_application_protos(config,
256 (uint8_t *) "\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 38);
257
258 quiche_config_set_max_idle_timeout(config, 5000);
259 quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);
260 quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);
261 quiche_config_set_initial_max_data(config, 10000000);
262 quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);
263 quiche_config_set_initial_max_stream_data_uni(config, 1000000);
264 quiche_config_set_initial_max_streams_bidi(config, 100);
265 quiche_config_set_initial_max_streams_uni(config, 100);
266 quiche_config_set_disable_active_migration(config, true);
267
268 if (getenv("SSLKEYLOGFILE")) {
269 quiche_config_log_keys(config);
270 }
271
272 uint8_t scid[LOCAL_CONN_ID_LEN];
273 int rng = open("/dev/urandom", O_RDONLY);
274 if (rng < 0) {
275 perror("failed to open /dev/urandom");
276 return -1;
277 }
278
279 ssize_t rand_len = read(rng, &scid, sizeof(scid));
280 if (rand_len < 0) {
281 perror("failed to create connection ID");
282 return -1;
283 }
284
285 quiche_conn *conn = quiche_connect(host, (const uint8_t*) scid, sizeof(scid),
286 peer->ai_addr, peer->ai_addrlen, config);
287
288 if (conn == NULL) {
289 fprintf(stderr, "failed to create connection\n");
290 return -1;
291 }
292
293 struct conn_io *conn_io = malloc(sizeof(*conn_io));
294 if (conn_io == NULL) {
295 fprintf(stderr, "failed to allocate connection IO\n");
296 return -1;
297 }
298
299 conn_io->sock = sock;
300 conn_io->conn = conn;
301
302 ev_io watcher;
303
304 struct ev_loop *loop = ev_default_loop(0);
305
306 ev_io_init(&watcher, recv_cb, conn_io->sock, EV_READ);
307 ev_io_start(loop, &watcher);
308 watcher.data = conn_io;
309
310 ev_init(&conn_io->timer, timeout_cb);
311 conn_io->timer.data = conn_io;
312
313 flush_egress(loop, conn_io);
314
315 ev_loop(loop, 0);
316
317 freeaddrinfo(peer);
318
319 quiche_conn_free(conn);
320
321 quiche_config_free(config);
322
323 return 0;
324 }
325