prog.c revision a74702c6
1/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc.  See LICENSE. */
2#include <assert.h>
3#ifndef WIN32
4#include <arpa/inet.h>
5#include <netinet/in.h>
6#include <signal.h>
7#endif
8#include <errno.h>
9#include <limits.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <sys/types.h>
14#include <sys/stat.h>
15#include <sys/queue.h>
16#ifndef WIN32
17#include <unistd.h>
18#else
19#include "vc_compat.h"
20#include "getopt.h"
21#pragma warning(disable:4028)
22#endif// WIN32
23
24#include <event2/event.h>
25
26#include <lsquic.h>
27
28#include <openssl/ssl.h>
29
30#include "../src/liblsquic/lsquic_hash.h"
31#include "../src/liblsquic/lsquic_int_types.h"
32#include "../src/liblsquic/lsquic_util.h"
33#include "../src/liblsquic/lsquic_logger.h"
34
35#include "test_config.h"
36#include "test_cert.h"
37#include "test_common.h"
38#include "prog.h"
39
40static int prog_stopped;
41static const char *s_keylog_dir;
42static const char *s_sess_resume_file;
43
44static SSL_CTX * get_ssl_ctx (void *, const struct sockaddr *);
45static void keylog_log_line (const SSL *, const char *);
46
47static const struct lsquic_packout_mem_if pmi = {
48    .pmi_allocate = pba_allocate,
49    .pmi_release  = pba_release,
50    .pmi_return   = pba_release,
51};
52
53
54int
55prog_init (struct prog *prog, unsigned flags,
56           struct sport_head *sports,
57           const struct lsquic_stream_if *stream_if, void *stream_if_ctx)
58{
59#ifdef WIN32
60    WSADATA wsd;
61    int s = WSAStartup(MAKEWORD(2, 2), &wsd);
62    if (s != 0)
63    {
64        LSQ_ERROR("WSAStartup failed: %d", s);
65        return -1;
66    }
67#endif
68    /* prog-specific initialization: */
69    memset(prog, 0, sizeof(*prog));
70    prog->prog_engine_flags = flags;
71    prog->prog_sports       = sports;
72    lsquic_engine_init_settings(&prog->prog_settings, flags);
73#if ECN_SUPPORTED
74    prog->prog_settings.es_ecn      = LSQUIC_DF_ECN;
75#else
76    prog->prog_settings.es_ecn      = 0;
77#endif
78
79    prog->prog_api.ea_settings      = &prog->prog_settings;
80    prog->prog_api.ea_stream_if     = stream_if;
81    prog->prog_api.ea_stream_if_ctx = stream_if_ctx;
82    prog->prog_api.ea_packets_out   = sport_packets_out;
83    prog->prog_api.ea_packets_out_ctx
84                                    = prog;
85    prog->prog_api.ea_pmi           = &pmi;
86    prog->prog_api.ea_pmi_ctx       = &prog->prog_pba;
87    prog->prog_api.ea_get_ssl_ctx   = get_ssl_ctx;
88#if LSQUIC_PREFERRED_ADDR
89    if (getenv("LSQUIC_PREFERRED_ADDR4") || getenv("LSQUIC_PREFERRED_ADDR6"))
90        prog->prog_flags |= PROG_SEARCH_ADDRS;
91#endif
92
93    /* Non prog-specific initialization: */
94    lsquic_global_init(flags & LSENG_SERVER ? LSQUIC_GLOBAL_SERVER :
95                                                    LSQUIC_GLOBAL_CLIENT);
96    lsquic_log_to_fstream(stderr, LLTS_HHMMSSMS);
97    lsquic_logger_lopt("=notice");
98    return 0;
99}
100
101
102static int
103prog_add_sport (struct prog *prog, const char *arg)
104{
105    struct service_port *sport;
106    sport = sport_new(arg, prog);
107    if (!sport)
108        return -1;
109    /* Default settings: */
110    sport->sp_flags = prog->prog_dummy_sport.sp_flags;
111    sport->sp_sndbuf = prog->prog_dummy_sport.sp_sndbuf;
112    sport->sp_rcvbuf = prog->prog_dummy_sport.sp_rcvbuf;
113    TAILQ_INSERT_TAIL(prog->prog_sports, sport, next_sport);
114    return 0;
115}
116
117
118void
119prog_print_common_options (const struct prog *prog, FILE *out)
120{
121    fprintf(out,
122"   -0 FILE     Provide session resumption file (reading or writing)\n"
123#if HAVE_REGEX
124"   -s SVCPORT  Service port.  Takes on the form of host:port, host,\n"
125"                 or port.  If host is not an IPv4 or IPv6 address, it is\n"
126"                 resolved.  If host is not set, the value of SNI is\n"
127"                 used (see the -H flag).  If port is not set, the default\n"
128"                 is 443.\n"
129#else
130"   -s SVCPORT  Service port.  Takes on the form of host:port or host.\n"
131"                 If host is not an IPv4 or IPv6 address, it is resolved.\n"
132"                 If port is not set, the default is 443.\n"
133#endif
134"                 Examples:\n"
135"                     127.0.0.1:12345\n"
136"                     ::1:443\n"
137"                     example.com\n"
138"                     example.com:8443\n"
139#if HAVE_REGEX
140"                     8443\n"
141#endif
142"                 If no -s option is given, 0.0.0.0:12345 address\n"
143"                 is used.\n"
144#if LSQUIC_DONTFRAG_SUPPORTED
145"   -D          Do not set `do not fragment' flag on outgoing UDP packets\n"
146#endif
147"   -z BYTES    Maximum size of outgoing UDP packets (client only).\n"
148"                 Overrides -o base_plpmtu.\n"
149"   -L LEVEL    Log level for all modules.  Possible values are `debug',\n"
150"                 `info', `notice', `warn', `error', `alert', `emerg',\n"
151"                 and `crit'.\n"
152"   -l LEVELS   Log levels for modules, e.g.\n"
153"                 -l event=info,engine=debug\n"
154"               Can be specified more than once.\n"
155"   -m MAX      Maximum number of outgoing packet buffers that can be\n"
156"                 assigned at any one time.  By default, there is no max.\n"
157"   -y style    Timestamp style used in log messages.  The following styles\n"
158"                 are supported:\n"
159"                   0   No timestamp\n"
160"                   1   Millisecond time (this is the default).\n"
161"                         Example: 11:04:05.196\n"
162"                   2   Full date and millisecond time.\n"
163"                         Example: 2017-03-21 13:43:46.671\n"
164"                   3   Chrome-like timestamp: date/time.microseconds.\n"
165"                         Example: 1223/104613.946956\n"
166"                   4   Microsecond time.\n"
167"                         Example: 11:04:05.196308\n"
168"                   5   Full date and microsecond time.\n"
169"                         Example: 2017-03-21 13:43:46.671345\n"
170"   -S opt=val  Socket options.  Supported options:\n"
171"                   sndbuf=12345    # Sets SO_SNDBUF\n"
172"                   rcvbuf=12345    # Sets SO_RCVBUF\n"
173"   -W          Use stock PMI (malloc & free)\n"
174    );
175
176#if HAVE_SENDMMSG
177    fprintf(out,
178"   -g          Use sendmmsg() to send packets.\n"
179    );
180#endif
181#if HAVE_RECVMMSG
182    fprintf(out,
183"   -j          Use recvmmsg() to receive packets.\n"
184    );
185#endif
186
187    if (prog->prog_engine_flags & LSENG_SERVER)
188        fprintf(out,
189"   -c CERTSPEC Service specification.  The specification is three values\n"
190"                 separated by commas.  The values are:\n"
191"                   * Domain name\n"
192"                   * File containing cert in PEM format\n"
193"                   * File containing private key in DER or PEM format\n"
194"                 Example:\n"
195"                   -c www.example.com,/tmp/cert.pem,/tmp/key.pkcs8\n"
196        );
197    else
198    {
199        if (prog->prog_engine_flags & LSENG_HTTP)
200            fprintf(out,
201"   -H host     Value of `host' HTTP header.  This is also used as SNI\n"
202"                 in Client Hello.  This option is used to override the\n"
203"                 `host' part of the address specified using -s flag.\n"
204            );
205        else
206            fprintf(out,
207"   -H host     Value of SNI in CHLO.\n"
208            );
209    }
210
211#ifndef WIN32
212    fprintf(out,
213"   -G dir      SSL keys will be logged to files in this directory.\n"
214    );
215#endif
216
217
218    fprintf(out,
219"   -k          Connect UDP socket.  Only meant to be used with clients\n"
220"                 to pick up ICMP errors.\n"
221"   -i USECS    Clock granularity in microseconds.  Defaults to %u.\n",
222        LSQUIC_DF_CLOCK_GRANULARITY
223    );
224    fprintf(out,
225"   -h          Print this help screen and exit\n"
226    );
227}
228
229
230int
231prog_set_opt (struct prog *prog, int opt, const char *arg)
232{
233#ifndef WIN32
234    struct stat st;
235    int s;
236#endif
237
238    switch (opt)
239    {
240#if LSQUIC_DONTFRAG_SUPPORTED
241    case 'D':
242        {
243            struct service_port *sport = TAILQ_LAST(prog->prog_sports, sport_head);
244            if (!sport)
245                sport = &prog->prog_dummy_sport;
246            sport->sp_flags |= SPORT_FRAGMENT_OK;
247        }
248        return 0;
249#endif
250#if HAVE_SENDMMSG
251    case 'g':
252        prog->prog_use_sendmmsg = 1;
253        return 0;
254#endif
255#if HAVE_RECVMMSG
256    case 'j':
257        prog->prog_use_recvmmsg = 1;
258        return 0;
259#endif
260    case 'm':
261        prog->prog_packout_max = atoi(arg);
262        return 0;
263    case 'z':
264        prog->prog_max_packet_size = atoi(arg);
265        return 0;
266    case 'W':
267        prog->prog_use_stock_pmi = 1;
268        return 0;
269    case 'c':
270        if (prog->prog_engine_flags & LSENG_SERVER)
271        {
272            if (!prog->prog_certs)
273                prog->prog_certs = lsquic_hash_create();
274            return load_cert(prog->prog_certs, arg);
275        }
276        else
277            return -1;
278    case 'H':
279        if (prog->prog_engine_flags & LSENG_SERVER)
280            return -1;
281        prog->prog_hostname = arg;
282        return 0;
283    case 'y':
284        lsquic_log_to_fstream(stderr, atoi(arg));
285        return 0;
286    case 'L':
287        return lsquic_set_log_level(arg);
288    case 'l':
289        return lsquic_logger_lopt(arg);
290    case 'o':
291        return set_engine_option(&prog->prog_settings,
292                                            &prog->prog_version_cleared, arg);
293    case 'i':
294        prog->prog_settings.es_clock_granularity = atoi(arg);
295        return 0;
296    case 's':
297        if (0 == (prog->prog_engine_flags & LSENG_SERVER) &&
298                                            !TAILQ_EMPTY(prog->prog_sports))
299            return -1;
300        return prog_add_sport(prog, arg);
301    case 'S':
302        {
303            struct service_port *sport = TAILQ_LAST(prog->prog_sports, sport_head);
304            if (!sport)
305                sport = &prog->prog_dummy_sport;
306            char *const name = strdup(optarg);
307            char *val = strchr(name, '=');
308            if (!val)
309            {
310                free(name);
311                return -1;
312            }
313            *val = '\0';
314            ++val;
315            if (0 == strcasecmp(name, "sndbuf"))
316            {
317                sport->sp_flags |= SPORT_SET_SNDBUF;
318                sport->sp_sndbuf = atoi(val);
319                free(name);
320                return 0;
321            }
322            else if (0 == strcasecmp(name, "rcvbuf"))
323            {
324                sport->sp_flags |= SPORT_SET_RCVBUF;
325                sport->sp_rcvbuf = atoi(val);
326                free(name);
327                return 0;
328            }
329            else
330            {
331                free(name);
332                return -1;
333            }
334        }
335    case 'k':
336        {
337            struct service_port *sport = TAILQ_LAST(prog->prog_sports, sport_head);
338            if (!sport)
339                sport = &prog->prog_dummy_sport;
340            sport->sp_flags |= SPORT_CONNECT;
341        }
342        return 0;
343    case '0':
344        s_sess_resume_file = optarg;
345        return 0;
346    case 'G':
347#ifndef WIN32
348        if (0 == stat(optarg, &st))
349        {
350            if (!S_ISDIR(st.st_mode))
351            {
352                LSQ_ERROR("%s is not a directory", optarg);
353                return -1;
354            }
355        }
356        else
357        {
358            s = mkdir(optarg, 0700);
359            if (s != 0)
360            {
361                LSQ_ERROR("cannot create directory %s: %s", optarg,
362                                                        strerror(errno));
363                return -1;
364            }
365        }
366        s_keylog_dir = optarg;
367        if (prog->prog_settings.es_ql_bits)
368        {
369            LSQ_NOTICE("QL loss bits turned off because of -G.  If you want "
370                "to turn it on, just override: -G dir -o ql_bits=2");
371            prog->prog_settings.es_ql_bits = 0;
372        }
373        return 0;
374#else
375        LSQ_ERROR("key logging is not supported on Windows");
376        return -1;
377#endif
378    default:
379        return 1;
380    }
381}
382
383
384struct event_base *
385prog_eb (struct prog *prog)
386{
387    return prog->prog_eb;
388}
389
390
391int
392prog_connect (struct prog *prog, unsigned char *sess_resume, size_t sess_resume_len)
393{
394    struct service_port *sport;
395
396    sport = TAILQ_FIRST(prog->prog_sports);
397    if (NULL == lsquic_engine_connect(prog->prog_engine, N_LSQVER,
398                    (struct sockaddr *) &sport->sp_local_addr,
399                    (struct sockaddr *) &sport->sas, sport, NULL,
400                    prog->prog_hostname ? prog->prog_hostname
401                    /* SNI is required for HTTP */
402                  : prog->prog_engine_flags & LSENG_HTTP ? sport->host
403                  : NULL,
404                    prog->prog_max_packet_size, sess_resume, sess_resume_len,
405                    sport->sp_token_buf, sport->sp_token_sz))
406        return -1;
407
408    prog_process_conns(prog);
409    return 0;
410}
411
412
413static int
414prog_init_client (struct prog *prog)
415{
416    struct service_port *sport;
417
418    sport = TAILQ_FIRST(prog->prog_sports);
419    if (0 != sport_init_client(sport, prog->prog_engine, prog->prog_eb))
420        return -1;
421
422    return 0;
423}
424
425
426static SSL_CTX *
427get_ssl_ctx (void *peer_ctx, const struct sockaddr *unused)
428{
429    const struct service_port *const sport = peer_ctx;
430    return sport->sp_prog->prog_ssl_ctx;
431}
432
433
434static int
435prog_new_session_cb (SSL *ssl, SSL_SESSION *session)
436{
437    unsigned char *buf;
438    size_t bufsz, nw;
439    FILE *file;
440
441    /* Our client is rather limited: only one file and only one ticket
442     * can be saved.  A more flexible client implementation would call
443     * lsquic_ssl_to_conn() and maybe save more tickets based on its
444     * own configuration.
445     */
446    if (!s_sess_resume_file)
447        return 0;
448
449    if (0 != lsquic_ssl_sess_to_resume_info(ssl, session, &buf, &bufsz))
450    {
451        LSQ_NOTICE("lsquic_ssl_sess_to_resume_info failed");
452        return 0;
453    }
454
455    file = fopen(s_sess_resume_file, "wb");
456    if (!file)
457    {
458        LSQ_WARN("cannot open %s for writing: %s",
459            s_sess_resume_file, strerror(errno));
460        free(buf);
461        return 0;
462    }
463
464    nw = fwrite(buf, 1, bufsz, file);
465    if (nw == bufsz)
466    {
467        LSQ_INFO("wrote %zd bytes of session resumption information to %s",
468            nw, s_sess_resume_file);
469        s_sess_resume_file = NULL;  /* Save just one ticket */
470    }
471    else
472        LSQ_WARN("error: fwrite(%s) returns %zd instead of %zd: %s",
473            s_sess_resume_file, nw, bufsz, strerror(errno));
474
475    fclose(file);
476    free(buf);
477    return 0;
478}
479
480
481static int
482prog_init_ssl_ctx (struct prog *prog)
483{
484    unsigned char ticket_keys[48];
485
486    prog->prog_ssl_ctx = SSL_CTX_new(TLS_method());
487    if (!prog->prog_ssl_ctx)
488    {
489        LSQ_ERROR("cannot allocate SSL context");
490        return -1;
491    }
492
493    SSL_CTX_set_min_proto_version(prog->prog_ssl_ctx, TLS1_3_VERSION);
494    SSL_CTX_set_max_proto_version(prog->prog_ssl_ctx, TLS1_3_VERSION);
495    SSL_CTX_set_default_verify_paths(prog->prog_ssl_ctx);
496
497    /* This is obviously test code: the key is just an array of NUL bytes */
498    memset(ticket_keys, 0, sizeof(ticket_keys));
499    if (1 != SSL_CTX_set_tlsext_ticket_keys(prog->prog_ssl_ctx,
500                                        ticket_keys, sizeof(ticket_keys)))
501    {
502        LSQ_ERROR("SSL_CTX_set_tlsext_ticket_keys failed");
503        return -1;
504    }
505
506    if (s_keylog_dir)
507        SSL_CTX_set_keylog_callback(prog->prog_ssl_ctx, keylog_log_line);
508
509    if (s_sess_resume_file)
510    {
511        SSL_CTX_set_session_cache_mode(prog->prog_ssl_ctx,
512                                                    SSL_SESS_CACHE_CLIENT);
513        SSL_CTX_set_early_data_enabled(prog->prog_ssl_ctx, 1);
514        SSL_CTX_sess_set_new_cb(prog->prog_ssl_ctx, prog_new_session_cb);
515    }
516
517    return 0;
518}
519
520
521static int
522prog_init_server (struct prog *prog)
523{
524    struct service_port *sport;
525
526    TAILQ_FOREACH(sport, prog->prog_sports, next_sport)
527        if (0 != sport_init_server(sport, prog->prog_engine, prog->prog_eb))
528            return -1;
529
530    return 0;
531}
532
533
534void
535prog_process_conns (struct prog *prog)
536{
537    int diff;
538    struct timeval timeout;
539
540    lsquic_engine_process_conns(prog->prog_engine);
541
542    if (lsquic_engine_earliest_adv_tick(prog->prog_engine, &diff))
543    {
544        if (diff < 0
545                || (unsigned) diff < prog->prog_settings.es_clock_granularity)
546        {
547            timeout.tv_sec  = 0;
548            timeout.tv_usec = prog->prog_settings.es_clock_granularity;
549        }
550        else
551        {
552            timeout.tv_sec = (unsigned) diff / 1000000;
553            timeout.tv_usec = (unsigned) diff % 1000000;
554        }
555
556        if (!prog_is_stopped())
557            event_add(prog->prog_timer, &timeout);
558    }
559}
560
561
562static void
563prog_timer_handler (int fd, short what, void *arg)
564{
565    struct prog *const prog = arg;
566    if (!prog_is_stopped())
567        prog_process_conns(prog);
568}
569
570
571static void
572prog_usr1_handler (int fd, short what, void *arg)
573{
574    LSQ_NOTICE("Got SIGUSR1, stopping engine");
575    prog_stop(arg);
576}
577
578
579static void
580prog_usr2_handler (int fd, short what, void *arg)
581{
582    struct prog *const prog = arg;
583
584    LSQ_NOTICE("Got SIGUSR2, cool down engine");
585    prog->prog_flags |= PROG_FLAG_COOLDOWN;
586    lsquic_engine_cooldown(prog->prog_engine);
587    prog_process_conns(prog);
588}
589
590
591int
592prog_run (struct prog *prog)
593{
594#ifndef WIN32
595    prog->prog_usr1 = evsignal_new(prog->prog_eb, SIGUSR1,
596                                                    prog_usr1_handler, prog);
597    evsignal_add(prog->prog_usr1, NULL);
598    prog->prog_usr2 = evsignal_new(prog->prog_eb, SIGUSR2,
599                                                    prog_usr2_handler, prog);
600    evsignal_add(prog->prog_usr2, NULL);
601#endif
602
603    event_base_loop(prog->prog_eb, 0);
604
605    return 0;
606}
607
608
609void
610prog_cleanup (struct prog *prog)
611{
612    lsquic_engine_destroy(prog->prog_engine);
613    event_base_free(prog->prog_eb);
614    if (!prog->prog_use_stock_pmi)
615        pba_cleanup(&prog->prog_pba);
616    if (prog->prog_ssl_ctx)
617        SSL_CTX_free(prog->prog_ssl_ctx);
618    if (prog->prog_certs)
619        delete_certs(prog->prog_certs);
620    lsquic_global_cleanup();
621}
622
623
624void
625prog_stop (struct prog *prog)
626{
627    struct service_port *sport;
628
629    prog_stopped = 1;
630
631    while ((sport = TAILQ_FIRST(prog->prog_sports)))
632    {
633        TAILQ_REMOVE(prog->prog_sports, sport, next_sport);
634        sport_destroy(sport);
635    }
636
637    if (prog->prog_timer)
638    {
639        event_del(prog->prog_timer);
640        event_free(prog->prog_timer);
641        prog->prog_timer = NULL;
642    }
643    if (prog->prog_usr1)
644    {
645        event_del(prog->prog_usr1);
646        event_free(prog->prog_usr1);
647        prog->prog_usr1 = NULL;
648    }
649    if (prog->prog_usr2)
650    {
651        event_del(prog->prog_usr2);
652        event_free(prog->prog_usr2);
653        prog->prog_usr2 = NULL;
654    }
655}
656
657
658static void *
659keylog_open_file (const SSL *ssl)
660{
661    const lsquic_conn_t *conn;
662    const lsquic_cid_t *cid;
663    FILE *fh;
664    int sz;
665    char id_str[MAX_CID_LEN * 2 + 1];
666    char path[PATH_MAX];
667
668    conn = lsquic_ssl_to_conn(ssl);
669    cid = lsquic_conn_id(conn);
670    lsquic_hexstr(cid->idbuf, cid->len, id_str, sizeof(id_str));
671    sz = snprintf(path, sizeof(path), "%s/%s.keys", s_keylog_dir, id_str);
672    if ((size_t) sz >= sizeof(path))
673    {
674        LSQ_WARN("%s: file too long", __func__);
675        return NULL;
676    }
677    fh = fopen(path, "ab");
678    if (!fh)
679        LSQ_WARN("could not open %s for appending: %s", path, strerror(errno));
680    return fh;
681}
682
683
684static void
685keylog_log_line (const SSL *ssl, const char *line)
686{
687    FILE *file;
688
689    file = keylog_open_file(ssl);
690    if (file)
691    {
692        fputs(line, file);
693        fputs("\n", file);
694        fclose(file);
695    }
696}
697
698
699static struct ssl_ctx_st *
700no_cert (void *cert_lu_ctx, const struct sockaddr *sa_UNUSED, const char *sni)
701{
702    return NULL;
703}
704
705
706int
707prog_prep (struct prog *prog)
708{
709    int s;
710    char err_buf[100];
711
712    if (s_keylog_dir && prog->prog_certs)
713    {
714        struct lsquic_hash_elem *el;
715        struct server_cert *cert;
716
717        for (el = lsquic_hash_first(prog->prog_certs); el;
718                                el = lsquic_hash_next(prog->prog_certs))
719        {
720            cert = lsquic_hashelem_getdata(el);
721            SSL_CTX_set_keylog_callback(cert->ce_ssl_ctx, keylog_log_line);
722        }
723    }
724
725    if (0 != lsquic_engine_check_settings(prog->prog_api.ea_settings,
726                        prog->prog_engine_flags, err_buf, sizeof(err_buf)))
727    {
728        LSQ_ERROR("Error in settings: %s", err_buf);
729        return -1;
730    }
731
732    if (!prog->prog_use_stock_pmi)
733        pba_init(&prog->prog_pba, prog->prog_packout_max);
734    else
735    {
736        prog->prog_api.ea_pmi = NULL;
737        prog->prog_api.ea_pmi_ctx = NULL;
738    }
739
740    if (TAILQ_EMPTY(prog->prog_sports))
741    {
742        if (prog->prog_hostname)
743            s = prog_add_sport(prog, prog->prog_hostname);
744        else
745            s = prog_add_sport(prog, "0.0.0.0:12345");
746        if (0 != s)
747            return -1;
748    }
749
750    if (prog->prog_certs)
751    {
752    prog->prog_api.ea_lookup_cert = lookup_cert;
753    prog->prog_api.ea_cert_lu_ctx = prog->prog_certs;
754    }
755    else
756    {
757        if (prog->prog_engine_flags & LSENG_SERVER)
758            LSQ_WARN("Not a single service specified.  Use -c option.");
759        prog->prog_api.ea_lookup_cert = no_cert;
760    }
761
762    prog->prog_eb = event_base_new();
763    prog->prog_engine = lsquic_engine_new(prog->prog_engine_flags,
764                                                            &prog->prog_api);
765    if (!prog->prog_engine)
766        return -1;
767
768    prog->prog_timer = event_new(prog->prog_eb, -1, 0,
769                                        prog_timer_handler, prog);
770
771    if (0 != prog_init_ssl_ctx(prog))
772        return -1;
773
774    if (prog->prog_engine_flags & LSENG_SERVER)
775        s = prog_init_server(prog);
776    else
777        s = prog_init_client(prog);
778
779    if (s != 0)
780        return -1;
781
782    return 0;
783}
784
785
786int
787prog_is_stopped (void)
788{
789    return prog_stopped != 0;
790}
791
792
793static void
794send_unsent (evutil_socket_t fd, short what, void *arg)
795{
796    struct prog *const prog = arg;
797    assert(prog->prog_send);
798    event_del(prog->prog_send);
799    event_free(prog->prog_send);
800    prog->prog_send = NULL;
801    LSQ_DEBUG("on_write event fires");
802    lsquic_engine_send_unsent_packets(prog->prog_engine);
803}
804
805
806void
807prog_sport_cant_send (struct prog *prog, int fd)
808{
809    assert(!prog->prog_send);
810    LSQ_DEBUG("cannot send: register on_write event");
811    prog->prog_send = event_new(prog->prog_eb, fd, EV_WRITE, send_unsent, prog);
812    event_add(prog->prog_send, NULL);
813}
814