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
This commit is contained in:
Sebastian Sumpf 2023-09-20 20:08:06 +02:00 committed by Christian Helmuth
parent 42d9640443
commit 27b1017fe9
6 changed files with 596 additions and 0 deletions

View File

@ -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 {
<config verbose="yes">
<parent-provides>
<service name="CPU"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="IRQ"/>
<service name="LOG"/>
<service name="PD"/>
<service name="RM"/>
<service name="ROM"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="nic_router" caps="200">
<resource name="RAM" quantum="10M"/>
<provides>
<service name="Nic"/>
<service name="Uplink"/>
</provides>
<config verbose_domain_state="yes" verbose_packets="no" verbose_packet_drop="yes">
<default-policy domain="default"/>
<domain name="default" interface="10.0.2.1/24">
<dhcp-server ip_first="10.0.2.2" ip_last="10.0.2.2"/>
<udp dst="0.0.0.0/0">
<permit-any domain="default"/>
</udp>
<tcp dst="0.0.0.0/0">
<permit-any domain="default"/>
</tcp>
</domain>
</config>
</start>
<start name="test-lxip_server" caps="200">
<resource name="RAM" quantum="10M"/>
<config ld_verbose="yes" ip_addr="10.0.2.3" netmask="255.255.255.0" gateway="10.0.2.1" nameserver="8.8.8.8"/>
<route>
<service name="Nic"> <child name="nic_router"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="test-lxip_client" caps="200">
<resource name="RAM" quantum="10M"/>
<config ld_verbose="yes" server_ip="10.0.2.3" server_port="80"/>
<route>
<service name="Nic"> <child name="nic_router"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>
}
install_config $config
build_boot_image [build_artifacts]
append qemu_args " -nographic "
run_genode_until "test-lxip_server\] Success" 10

View File

@ -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 <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <net/ipv4.h>
#include <util/endian.h>
#include <genode_c_api/socket_types.h>
#include <genode_c_api/socket.h>
#include <data.h>
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<genode_uint16_t>(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");
}
}

View File

@ -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 =

View File

@ -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<void *>(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
{"<html><head><title>Congrats!</title></head><body>"
"<h1>Welcome to our HTTP server!</h1>"
"<p>This is a small test page.</body></html>"}; /* HTML page */
};
}
#endif /* _INCLUDE__DATA_H_ */

View File

@ -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 <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <net/ipv4.h>
#include <util/endian.h>
#include <genode_c_api/socket_types.h>
#include <genode_c_api/socket.h>
#include <data.h>
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_header> 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<genode_uint16_t>(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");
}
}

View File

@ -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 =