#include <string.h>
#ifdef _WIN32
#include <stdarg.h>
#include <winsock2.h>
#else
#include <err.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/quic.h>
#ifdef _WIN32
static const char *progname;
static void vwarnx(const char *fmt, va_list ap)
{
if (progname != NULL)
fprintf(stderr, "%s: ", progname);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
}
static void errx(int status, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
exit(status);
}
static void warnx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
#endif
static const unsigned char alpn_ossltest[] = {
8,
'h',
't',
't',
'p',
'/',
'1',
'.',
'0',
10,
'h',
'q',
'-',
'i',
'n',
't',
'e',
'r',
'o',
'p',
};
static int select_alpn(SSL *ssl, const unsigned char **out,
unsigned char *out_len, const unsigned char *in,
unsigned int in_len, void *arg)
{
if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest,
sizeof(alpn_ossltest), in,
in_len)
== OPENSSL_NPN_NEGOTIATED)
return SSL_TLSEXT_ERR_OK;
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
static SSL_CTX *create_ctx(const char *cert_path, const char *key_path)
{
SSL_CTX *ctx;
ctx = SSL_CTX_new(OSSL_QUIC_server_method());
if (ctx == NULL)
goto err;
if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) {
fprintf(stderr, "couldn't load certificate file: %s\n", cert_path);
goto err;
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) {
fprintf(stderr, "couldn't load key file: %s\n", key_path);
goto err;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL);
return ctx;
err:
SSL_CTX_free(ctx);
return NULL;
}
static int create_socket(uint16_t port)
{
int fd;
struct sockaddr_in sa = { 0 };
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
fprintf(stderr, "cannot create socket");
goto err;
}
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) {
fprintf(stderr, "cannot bind to %u\n", port);
BIO_closesocket(fd);
goto err;
}
return fd;
err:
BIO_closesocket(fd);
return -1;
}
static int run_quic_server(SSL_CTX *ctx, int fd)
{
int ok = 0;
SSL *listener, *conn;
unsigned char buf[8192];
size_t nread;
size_t nwritten;
if ((listener = SSL_new_listener(ctx, 0)) == NULL)
goto err;
if (!SSL_set_fd(listener, fd))
goto err;
if (!SSL_listen(listener))
goto err;
for (;;) {
ERR_clear_error();
printf("Waiting for connection\n");
conn = SSL_accept_connection(listener, 0);
if (conn == NULL) {
fprintf(stderr, "error while accepting connection\n");
goto err;
}
printf("Accepted new connection\n");
while (SSL_read_ex(conn, buf, sizeof(buf), &nread) > 0) {
if (SSL_write_ex(conn, buf, nread, &nwritten) > 0
&& nwritten == nread)
continue;
fprintf(stderr, "Error echoing client input");
break;
}
if (SSL_stream_conclude(conn, 0) != 1) {
fprintf(stderr, "Unable to conclude stream\n");
SSL_free(conn);
goto err;
}
while (SSL_shutdown(conn) != 1)
continue;
SSL_free(conn);
}
err:
SSL_free(listener);
return ok;
}
int main(int argc, char *argv[])
{
int res = EXIT_FAILURE;
SSL_CTX *ctx = NULL;
int fd;
unsigned long port;
#ifdef _WIN32
static const char *progname;
progname = argv[0];
#endif
if (argc != 4)
errx(res, "usage: %s <port> <server.crt> <server.key>", argv[0]);
if ((ctx = create_ctx(argv[2], argv[3])) == NULL) {
ERR_print_errors_fp(stderr);
errx(res, "Failed to create context");
}
port = strtoul(argv[1], NULL, 0);
if (port == 0 || port > UINT16_MAX) {
SSL_CTX_free(ctx);
errx(res, "Failed to parse port number");
}
if ((fd = create_socket((uint16_t)port)) < 0) {
SSL_CTX_free(ctx);
ERR_print_errors_fp(stderr);
errx(res, "Failed to create socket");
}
if (!run_quic_server(ctx, fd)) {
SSL_CTX_free(ctx);
BIO_closesocket(fd);
ERR_print_errors_fp(stderr);
errx(res, "Error in QUIC server loop");
}
SSL_CTX_free(ctx);
BIO_closesocket(fd);
res = EXIT_SUCCESS;
return res;
}