/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2018 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
/*
* This tests the external API entry points to configure the address/port
* spice-server is listening on
*/
#include
#include "basic-event-loop.h"
#include
#include
#include
#ifndef _WIN32
#include
#include
#include
#endif
#include
#include
#include "test-glib-compat.h"
/* Arbitrary base port, we want a port which is not in use by the system, and
* by another of our tests (in case of parallel runs)
*/
#define BASE_PORT 5728
#define PKI_DIR SPICE_TOP_SRCDIR "/server/tests/pki/"
typedef struct {
SpiceCoreInterface *core;
SpiceTimer *exit_mainloop_timer;
SpiceTimer *timeout_timer;
} TestEventLoop;
static void timeout_cb(SPICE_GNUC_UNUSED void *opaque)
{
g_assert_not_reached();
}
static void exit_mainloop_cb(SPICE_GNUC_UNUSED void *opaque)
{
basic_event_loop_quit();
}
static void test_event_loop_quit(TestEventLoop *event_loop)
{
event_loop->core->timer_start(event_loop->exit_mainloop_timer, 0);
}
static void test_event_loop_init(TestEventLoop *event_loop)
{
event_loop->core = basic_event_loop_init();
event_loop->timeout_timer = event_loop->core->timer_add(timeout_cb, NULL);
event_loop->exit_mainloop_timer = event_loop->core->timer_add(exit_mainloop_cb, NULL);
}
static void test_event_loop_destroy(TestEventLoop *event_loop)
{
if (event_loop->timeout_timer != NULL) {
event_loop->core->timer_remove(event_loop->timeout_timer);
event_loop->timeout_timer = NULL;
}
if (event_loop->exit_mainloop_timer != NULL) {
event_loop->core->timer_remove(event_loop->exit_mainloop_timer);
event_loop->exit_mainloop_timer = NULL;
}
basic_event_loop_destroy();
event_loop->core = NULL;
}
static void test_event_loop_run(TestEventLoop *event_loop)
{
event_loop->core->timer_start(event_loop->timeout_timer, 50000);
basic_event_loop_mainloop();
}
static BIO *fake_client_connect(const char *hostname, int port, bool use_tls)
{
if (port < 0) {
#ifndef _WIN32
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, hostname);
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sock);
return NULL;
}
return BIO_new_fd(sock, 0);
#else
g_assert_not_reached();
#endif
}
char con_buf[256];
g_snprintf(con_buf, sizeof(con_buf), "%s:%d", hostname, port);
SSL_CTX *ctx = NULL;
BIO *bio;
if (use_tls) {
ctx = SSL_CTX_new(TLS_client_method());
g_assert_nonnull(ctx);
bio = BIO_new_ssl_connect(ctx);
g_assert_nonnull(bio);
BIO_set_conn_hostname(bio, con_buf);
} else {
bio = BIO_new_connect(con_buf);
g_assert_nonnull(bio);
}
if (BIO_do_connect(bio) <= 0) {
BIO_free(bio);
bio = NULL;
}
SSL_CTX_free(ctx);
return bio;
}
static void check_magic(BIO *bio)
{
uint8_t buffer[4];
/* send dummy data to trigger a response from the server */
memset(buffer, 0xa5, G_N_ELEMENTS(buffer));
g_assert_cmpint(BIO_write(bio, buffer, G_N_ELEMENTS(buffer)), ==, G_N_ELEMENTS(buffer));
g_assert_cmpint(BIO_read(bio, buffer, G_N_ELEMENTS(buffer)), ==, G_N_ELEMENTS(buffer));
g_assert_cmpint(memcmp(buffer, "REDQ", 4), ==, 0);
}
typedef struct
{
const char *hostname;
int port;
bool use_tls;
TestEventLoop *event_loop;
} ThreadData;
static gpointer check_magic_thread(gpointer data)
{
ThreadData *thread_data = (ThreadData*) data;
BIO *bio;
bio = fake_client_connect(thread_data->hostname, thread_data->port, thread_data->use_tls);
g_assert_nonnull(bio);
check_magic(bio);
BIO_free_all(bio);
test_event_loop_quit(thread_data->event_loop);
g_free(thread_data);
return NULL;
}
static gpointer check_no_connect_thread(gpointer data)
{
ThreadData *thread_data = (ThreadData*) data;
BIO *bio = fake_client_connect(thread_data->hostname, thread_data->port, false);
g_assert_null(bio);
test_event_loop_quit(thread_data->event_loop);
g_free(thread_data);
return NULL;
}
static GThread *fake_client_new(GThreadFunc thread_func,
const char *hostname, int port,
bool use_tls,
TestEventLoop *event_loop)
{
ThreadData *thread_data = g_new0(ThreadData, 1);
if (port == -1) {
#ifdef _WIN32
g_assert_not_reached();
#endif
} else {
g_assert_cmpuint(port, >, 0);
g_assert_cmpuint(port, <, 65536);
}
thread_data->hostname = hostname;
thread_data->port = port;
thread_data->use_tls = use_tls;
thread_data->event_loop = event_loop;
/* check_magic_thread will assume ownership of 'connectable' */
return g_thread_new("fake-client-thread", thread_func, thread_data);
}
static void test_connect_plain(void)
{
GThread *thread;
int result;
TestEventLoop event_loop = { 0, };
test_event_loop_init(&event_loop);
/* server */
SpiceServer *server = spice_server_new();
spice_server_set_name(server, "SPICE listen test");
spice_server_set_noauth(server);
spice_server_set_port(server, BASE_PORT);
result = spice_server_init(server, event_loop.core);
g_assert_cmpint(result, ==, 0);
/* fake client */
thread = fake_client_new(check_magic_thread, "localhost", BASE_PORT, false, &event_loop);
test_event_loop_run(&event_loop);
g_assert_null(g_thread_join(thread));
test_event_loop_destroy(&event_loop);
spice_server_destroy(server);
}
static void test_connect_tls(void)
{
GThread *thread;
int result;
TestEventLoop event_loop = { 0, };
test_event_loop_init(&event_loop);
/* server */
SpiceServer *server = spice_server_new();
spice_server_set_name(server, "SPICE listen test");
spice_server_set_noauth(server);
result = spice_server_set_tls(server, BASE_PORT,
PKI_DIR "ca-cert.pem",
PKI_DIR "server-cert.pem",
PKI_DIR "server-key.pem",
NULL, NULL, NULL);
g_assert_cmpint(result, ==, 0);
result = spice_server_init(server, event_loop.core);
g_assert_cmpint(result, ==, 0);
/* fake client */
thread = fake_client_new(check_magic_thread, "localhost", BASE_PORT, true, &event_loop);
test_event_loop_run(&event_loop);
g_assert_null(g_thread_join(thread));
test_event_loop_destroy(&event_loop);
spice_server_destroy(server);
}
static void test_connect_plain_and_tls(void)
{
GThread *thread;
int result;
TestEventLoop event_loop = { 0, };
test_event_loop_init(&event_loop);
/* server */
SpiceServer *server = spice_server_new();
spice_server_set_name(server, "SPICE listen test");
spice_server_set_noauth(server);
spice_server_set_port(server, BASE_PORT);
result = spice_server_set_tls(server, BASE_PORT+1,
PKI_DIR "ca-cert.pem",
PKI_DIR "server-cert.pem",
PKI_DIR "server-key.pem",
NULL, NULL, NULL);
g_assert_cmpint(result, ==, 0);
result = spice_server_init(server, event_loop.core);
g_assert_cmpint(result, ==, 0);
/* fake client */
thread = fake_client_new(check_magic_thread, "localhost", BASE_PORT, false, &event_loop);
test_event_loop_run(&event_loop);
g_assert_null(g_thread_join(thread));
thread = fake_client_new(check_magic_thread, "localhost", BASE_PORT+1, true, &event_loop);
test_event_loop_run(&event_loop);
g_assert_null(g_thread_join(thread));
test_event_loop_destroy(&event_loop);
spice_server_destroy(server);
}
#ifndef _WIN32
static void test_connect_unix(void)
{
GThread *thread;
int result;
TestEventLoop event_loop = { 0, };
test_event_loop_init(&event_loop);
/* server */
SpiceServer *server = spice_server_new();
spice_server_set_name(server, "SPICE listen test");
spice_server_set_noauth(server);
spice_server_set_addr(server, "test-listen.unix", SPICE_ADDR_FLAG_UNIX_ONLY);
result = spice_server_init(server, event_loop.core);
g_assert_cmpint(result, ==, 0);
/* fake client */
thread = fake_client_new(check_magic_thread, "test-listen.unix", -1, false, &event_loop);
test_event_loop_run(&event_loop);
g_assert_null(g_thread_join(thread));
test_event_loop_destroy(&event_loop);
spice_server_destroy(server);
}
#endif
static void test_connect_ko(void)
{
GThread *thread;
TestEventLoop event_loop = { 0, };
test_event_loop_init(&event_loop);
/* fake client */
thread = fake_client_new(check_no_connect_thread, "localhost", BASE_PORT, false, &event_loop);
test_event_loop_run(&event_loop);
g_assert_null(g_thread_join(thread));
test_event_loop_destroy(&event_loop);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/server/listen/connect_plain", test_connect_plain);
g_test_add_func("/server/listen/connect_tls", test_connect_tls);
g_test_add_func("/server/listen/connect_both", test_connect_plain_and_tls);
#ifndef _WIN32
g_test_add_func("/server/listen/connect_unix", test_connect_unix);
#endif
g_test_add_func("/server/listen/connect_ko", test_connect_ko);
return g_test_run();
}