From 27b1017fe924a5308ddab3ae9ffc73ff0829028f Mon Sep 17 00:00:00 2001 From: Sebastian Sumpf Date: Wed, 20 Sep 2023 20:08:06 +0200 Subject: [PATCH] test/lxip_raw: test using Genode socket C-API directly The lxip_raw.run script will spawn a client/server scenario that tests Genode C-API calls. The scenario can be used standalone, meaning no actual network card is required. issue #5104 --- repos/dde_linux/run/lxip_raw.run | 84 +++++++ .../src/test/lxip_raw/client/main.cc | 197 +++++++++++++++ .../src/test/lxip_raw/client/target.mk | 7 + .../src/test/lxip_raw/include/data.h | 75 ++++++ .../src/test/lxip_raw/server/main.cc | 226 ++++++++++++++++++ .../src/test/lxip_raw/server/target.mk | 7 + 6 files changed, 596 insertions(+) create mode 100644 repos/dde_linux/run/lxip_raw.run create mode 100644 repos/dde_linux/src/test/lxip_raw/client/main.cc create mode 100644 repos/dde_linux/src/test/lxip_raw/client/target.mk create mode 100644 repos/dde_linux/src/test/lxip_raw/include/data.h create mode 100644 repos/dde_linux/src/test/lxip_raw/server/main.cc create mode 100644 repos/dde_linux/src/test/lxip_raw/server/target.mk diff --git a/repos/dde_linux/run/lxip_raw.run b/repos/dde_linux/run/lxip_raw.run new file mode 100644 index 0000000000..1eb887cf87 --- /dev/null +++ b/repos/dde_linux/run/lxip_raw.run @@ -0,0 +1,84 @@ + +set build_components { + lib/ld core init + timer + test/lxip_raw + server/nic_router +} + +build $build_components + +create_boot_directory + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +build_boot_image [build_artifacts] + +append qemu_args " -nographic " + +run_genode_until "test-lxip_server\] Success" 10 diff --git a/repos/dde_linux/src/test/lxip_raw/client/main.cc b/repos/dde_linux/src/test/lxip_raw/client/main.cc new file mode 100644 index 0000000000..c26d0b7252 --- /dev/null +++ b/repos/dde_linux/src/test/lxip_raw/client/main.cc @@ -0,0 +1,197 @@ +/* + * \brief Genode socket-interface test client part + * \author Sebastian Sumpf + * \date 2023-09-21 + */ + +/* + * Copyright (C) 2023-2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + + +#include +#include +#include +#include + +#include +#include + +#include + + +namespace Test { + struct Client; + using namespace Net; +} + +using namespace Genode; + +#define ASSERT(string, cond) { if (!(cond)) {\ + log("[", ++counter, "] ", string, " [failed]"); \ + error("assertion failed at line ", __LINE__, ": ", #cond); \ + throw -1;\ + } else { log("[", ++counter, "] ", string, " [ok]"); } } + +extern "C" void wait_for_continue(void); + + +struct Test::Client +{ + Env &env; + + using Ipv4_string = String<16>; + + Attached_rom_dataspace config { env, "config"}; + + uint16_t const port + { config.xml().attribute_value("server_port", (uint16_t)80) }; + + Ipv4_address ip_addr + { config.xml().attribute_value("server_ip", Ipv4_address()) }; + + unsigned long counter { 0 }; + + Data data { }; + char *recv_buf[Data::SIZE]; + + Client(Env &env) : env(env) + { + genode_socket_init(genode_env_ptr(env), nullptr); + + genode_socket_config address_config = { .dhcp = true }; + genode_socket_config_address(&address_config); + } + + /* connect blocking version */ + bool connect(genode_socket_handle *handle, genode_sockaddr *addr) + { + enum Errno err; + /* is non-blocking */ + err = genode_socket_connect(handle, addr); + if (err == GENODE_ENONE) return true; + if (err != GENODE_EINPROGRESS) return false; + + /* proceed with protocol described in manpage */ + bool success = false; + for (unsigned i = 0; i < 100; i++) { + if(genode_socket_poll(handle) & genode_socket_pollout_set()) { + success = true; break; + } + genode_socket_wait_for_progress(); + } + + if (!success) return false; + + enum Errno socket_err; + unsigned size = sizeof(enum Errno); + err = genode_socket_getsockopt(handle, GENODE_SOL_SOCKET, GENODE_SO_ERROR, + &socket_err, &size); + + if (err || socket_err) return false; + + return true; + } + + + unsigned long receive(genode_socket_handle *handle, char *buf, unsigned long length) + { + Msg_header msg_recv { buf, length }; + unsigned long bytes = 0; + Errno err; + while (true) { + err = genode_socket_recvmsg(handle, msg_recv.header(), &bytes, false); + if (err == GENODE_EAGAIN) + genode_socket_wait_for_progress(); + else break; + } + return bytes; + } + + void run_tcp() + { + enum Errno err; + genode_socket_handle *handle = nullptr; + ASSERT("create new socket (TCP)...", + (handle = genode_socket(AF_INET, SOCK_STREAM, 0, &err)) != nullptr); + + genode_sockaddr addr; + addr.family = AF_INET; + addr.in.port = host_to_big_endian(port); + addr.in.addr = ip_addr.to_uint32_big_endian(); + ASSERT("connect...", connect(handle, &addr) == true); + + genode_sockaddr name; + ASSERT("getsockname... ", genode_socket_getsockname(handle, &name) == GENODE_ENONE); + + char expected_name[] = { 10, 0, 2, 2 }; + ASSERT("check expected sockname IP...", + Ipv4_address::from_uint32_big_endian(name.in.addr) == Ipv4_address(expected_name)); + + ASSERT("getpeername... ", genode_socket_getpeername(handle, &name) == GENODE_ENONE); + + char expected_peer[] = { 10, 0, 2, 3 }; + ASSERT("check expected peername IP...", + Ipv4_address::from_uint32_big_endian(name.in.addr) == Ipv4_address(expected_peer)); + + /* send request */ + String<64> request { "GET / HTTP/1.0\r\nHost: localhost:80\r\n\r\n" }; + Msg_header msg { request.string(), request.length() }; + unsigned long bytes_send; + ASSERT("send GET request...", + genode_socket_sendmsg(handle, msg.header(), &bytes_send) == GENODE_ENONE + && bytes_send == request.length()); + + char buf[150]; + Http http {}; + + /* http header */ + ASSERT("receive HTTP header...", receive(handle, buf, 150) == http.header.length()); + ASSERT("check HTTP header...", !strcmp(http.header.string(), buf,http.header.length())); + + /* html */ + ASSERT("receive HTML...", receive(handle, buf, 150) == http.html.length()); + ASSERT("check HTML...", !strcmp(http.html.string(), buf, http.html.length() == 0)); + + ASSERT("shutdown...", genode_socket_shutdown(handle, SHUT_RDWR) == GENODE_ENONE); + ASSERT("release socket...", genode_socket_release(handle) == GENODE_ENONE); + } + + void run_udp() + { + enum Errno err; + genode_socket_handle *handle = nullptr; + ASSERT("create new socket (UDP)...", + (handle = genode_socket(AF_INET, SOCK_DGRAM, 0, &err)) != nullptr); + + genode_sockaddr addr; + addr.family = AF_INET; + addr.in.port = host_to_big_endian(port); + addr.in.addr = ip_addr.to_uint32_big_endian(); + + for (unsigned i = 0; i < data.size(); i += MAX_UDP_LOAD) { + Msg_header msg { addr, data.buffer() + i, MAX_UDP_LOAD}; + unsigned long bytes_send = 0; + ASSERT("send bytes...", + genode_socket_sendmsg(handle, msg.header(), &bytes_send) == GENODE_ENONE + && bytes_send == MAX_UDP_LOAD); + } + } +}; + + +void Component::construct(Genode::Env &env) +{ + static Test::Client client { env }; + + try { + client.run_tcp(); + client.run_udp(); + log("Success"); + } catch (...) { + log("Failure"); + } +} diff --git a/repos/dde_linux/src/test/lxip_raw/client/target.mk b/repos/dde_linux/src/test/lxip_raw/client/target.mk new file mode 100644 index 0000000000..4652499717 --- /dev/null +++ b/repos/dde_linux/src/test/lxip_raw/client/target.mk @@ -0,0 +1,7 @@ +TARGET = test-lxip_client +LIBS = lxip base net +SRC_CC = main.cc + +INC_DIR += $(PRG_DIR)/../include + +CC_CXX_WARN_STRICT = diff --git a/repos/dde_linux/src/test/lxip_raw/include/data.h b/repos/dde_linux/src/test/lxip_raw/include/data.h new file mode 100644 index 0000000000..d206930967 --- /dev/null +++ b/repos/dde_linux/src/test/lxip_raw/include/data.h @@ -0,0 +1,75 @@ +/** + * \brief Test data and helpers + * \author Sebastian Sumpf + * \date 2023-09-21 + * + */ + +/* + * Copyright (C) 2023-2024 Genode Labs GmbH + * + * This file is distributed under the terms of the GNU General Public License + * version 2. + */ + +#ifndef _INCLUDE__DATA_H_ +#define _INCLUDE__DATA_H_ + +namespace Test { + + enum { MAX_UDP_LOAD= 1472 }; + + struct Msg_header + { + genode_iovec iovec; + genode_msghdr msg { }; + + Msg_header(void const *data, unsigned long size) + : iovec { const_cast(data), size } + { + msg.iov = &iovec; + msg.iovlen = 1; + } + + Msg_header(genode_sockaddr &name, void const *data, unsigned long size) + : Msg_header(data, size) + { + msg.name = &name; + } + + genode_msghdr *header() { return &msg; } + }; + + struct Data + { + enum { SIZE = MAX_UDP_LOAD * 10 }; + + char buf[SIZE]; + + Data() + { + char j = 'A'; + unsigned step = MAX_UDP_LOAD / 2; + for (unsigned i = 0; i < size(); i += step, j += 1) { + Genode::memset(buf + i, j, step); + } + } + + char const *buffer() const { return buf; } + unsigned long size() const { return SIZE; } + }; + + struct Http + { + Genode::String<128> header + { "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n" }; /* HTTP response header */ + + Genode::String<150> html + {"Congrats!" + "

Welcome to our HTTP server!

" + "

This is a small test page."}; /* HTML page */ + }; + +} + +#endif /* _INCLUDE__DATA_H_ */ diff --git a/repos/dde_linux/src/test/lxip_raw/server/main.cc b/repos/dde_linux/src/test/lxip_raw/server/main.cc new file mode 100644 index 0000000000..f0a508a488 --- /dev/null +++ b/repos/dde_linux/src/test/lxip_raw/server/main.cc @@ -0,0 +1,226 @@ +/* + * \brief Genode socket-interface test server part + * \author Sebastian Sumpf + * \date 2023-09-21 + */ + +/* + * Copyright (C) 2023-2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#include +#include +#include +#include + +#include +#include + +#include + +namespace Test { + using namespace Net; + struct Server; +}; + +using namespace Genode; + + +#define ASSERT(string, cond) { if (!(cond)) {\ + log("[", ++counter, "] ", string, " [failed]"); \ + error("assertion failed at line ", __LINE__, ": ", #cond); \ + throw -1;\ + } else { log("[", ++counter, "] ", string, " [ok]"); } } + + +struct Test::Server +{ + Env &env; + + unsigned counter { 0 }; + + Attached_rom_dataspace config { env, "config" }; + + uint16_t const port { config.xml().attribute_value("port", (uint16_t)80) }; + + using Ipv4_string = String<16>; + + Ipv4_string const ip { + config.xml().attribute_value("ip_addr", Ipv4_string("0.0.0.0")) }; + Ipv4_string const netmask { + config.xml().attribute_value("netmask", Ipv4_string("0.0.0.0")) }; + Ipv4_string const gateway { + config.xml().attribute_value("gateway", Ipv4_string("0.0.0.0")) }; + Ipv4_string const nameserver { + config.xml().attribute_value("nameserver", Ipv4_string("0.0.0.0")) }; + + Ipv4_address const ip_addr { + config.xml().attribute_value("ip_addr", Ipv4_address()) }; + + Http http { }; + Data data { }; + + enum { SIZE = Data::SIZE }; + char buf[SIZE]; + + Server(Env &env) : env(env) + { + genode_socket_init(genode_env_ptr(env), nullptr); + + genode_socket_config address_config = { + .dhcp = false, + .ip_addr = ip.string(), + .netmask = netmask.string(), + .gateway = gateway.string(), + .nameserver = nameserver.string(), + }; + + genode_socket_config_address(&address_config); + } + + void serve(genode_socket_handle *handle) + { + Constructible msg; + msg.construct(buf, SIZE); + + /* Read the data from the port, blocking if nothing yet there. + We assume the request (the part we care about) is in one packet */ + unsigned long bytes = 0; + Errno err; + while (true) { + err = genode_socket_recvmsg(handle, msg->header(), &bytes, false); + if (err == GENODE_EAGAIN) + genode_socket_wait_for_progress(); + else break; + } + + ASSERT("recvmsg...", (bytes == 39 && err == GENODE_ENONE)); + + msg.destruct(); + + /* Is this an HTTP GET command? (only check the first 5 chars, since + there are other formats for GET, and we're keeping it very simple)*/ + ASSERT("message is GET command...", !strcmp(buf, "GET /", 5)); + + /* send http header */ + msg.construct(http.header.string(), http.header.length()); + ASSERT("send HTTP header...", + genode_socket_sendmsg(handle, msg->header(), &bytes) == GENODE_ENONE + && bytes == http.header.length()); + msg.destruct(); + + /* Send http header */ + msg.construct(http.html.string(), http.html.length()); + ASSERT("send HTML...", + genode_socket_sendmsg(handle, msg->header(), &bytes) == GENODE_ENONE + && bytes == http.html.length()); + } + + void run_tcp() + { + enum Errno err; + genode_socket_handle *handle = nullptr; + genode_socket_handle *handle_reuse = nullptr; + ASSERT("create new socket (TCP)...", + (handle = genode_socket(AF_INET, SOCK_STREAM, 0, &err)) != nullptr); + + ASSERT("create new socket (TCP re-use port)...", + (handle_reuse = genode_socket(AF_INET, SOCK_STREAM, 0, &err)) != nullptr); + + int opt = 1; + ASSERT("setsockopt REUSEPORT handle...", + genode_socket_setsockopt(handle, GENODE_SOL_SOCKET, GENODE_SO_REUSEPORT, + &opt, sizeof(opt)) == GENODE_ENONE); + ASSERT("setsockopt REUSEPORT handle re-use...", + genode_socket_setsockopt(handle_reuse, GENODE_SOL_SOCKET, GENODE_SO_REUSEPORT, + &opt, sizeof(opt)) == GENODE_ENONE); + + genode_sockaddr addr; + addr.family = AF_INET; + addr.in.port = host_to_big_endian(port); + addr.in.addr = INADDR_ANY; + ASSERT("bind socket...", genode_socket_bind(handle, &addr) == GENODE_ENONE); + ASSERT("bind socket re-use...", genode_socket_bind(handle_reuse, &addr) == GENODE_ENONE); + + ASSERT("listen...", genode_socket_listen(handle, 5) == GENODE_ENONE); + + genode_socket_handle *client = nullptr; + + err = GENODE_EAGAIN; + for (unsigned i = 0; i < 100 && err == GENODE_EAGAIN; i++) { + client = genode_socket_accept(handle, &addr, &err); + + if (err == GENODE_EAGAIN) + genode_socket_wait_for_progress(); + } + + ASSERT("accept...", err == GENODE_ENONE && client != nullptr); + + serve(client); + + ASSERT("release socket...", genode_socket_release(handle) == GENODE_ENONE); + } + + void run_udp() + { + enum Errno err; + genode_socket_handle *handle = nullptr; + ASSERT("create new socket (UDP)...", + (handle = genode_socket(AF_INET, SOCK_DGRAM, 0, &err)) != nullptr); + + genode_sockaddr addr; + addr.family = AF_INET; + addr.in.port = host_to_big_endian(port); + addr.in.addr = ip_addr.to_uint32_big_endian(); + ASSERT("bind socket...", genode_socket_bind(handle, &addr) == GENODE_ENONE); + err = genode_socket_bind(handle, &addr); + + unsigned long bytes_recv = 0; + bool once = true; + while (bytes_recv < SIZE) { + unsigned long bytes = 0; + genode_sockaddr recv_addr = { .family = AF_INET }; + Msg_header msg { recv_addr, buf + bytes_recv, SIZE - bytes_recv }; + + + err = genode_socket_recvmsg(handle, msg.header(), &bytes, false); + + bytes_recv += bytes; + + if (err == GENODE_EAGAIN) { + genode_socket_wait_for_progress(); + } else if (err == GENODE_ENONE) { + + if (once) { + Ipv4_address sender_ip = + Ipv4_address::from_uint32_big_endian(recv_addr.in.addr); + + char expected[] = { 10, 0, 2, 2 }; + Ipv4_address expected_ip { expected }; + + ASSERT("check expected sender IP address...", expected_ip == sender_ip); + once = false; + } + } else break; + } + ASSERT("receive bytes...", err == GENODE_ENONE); + ASSERT("check bytes...", strcmp(data.buffer(), buf, SIZE) == 0); + } +}; + + +void Component::construct(Genode::Env &env) +{ + static Test::Server server { env }; + + try { + server.run_tcp(); + server.run_udp(); + log("Success"); + } catch (...) { + log("Failure"); + } +} diff --git a/repos/dde_linux/src/test/lxip_raw/server/target.mk b/repos/dde_linux/src/test/lxip_raw/server/target.mk new file mode 100644 index 0000000000..f0ac07da05 --- /dev/null +++ b/repos/dde_linux/src/test/lxip_raw/server/target.mk @@ -0,0 +1,7 @@ +TARGET = test-lxip_server +LIBS = lxip base net +SRC_CC = main.cc + +INC_DIR += $(PRG_DIR)/../include + +CC_CXX_WARN_STRICT =