vfs_tap: VFS plugin for Uplink/Nic session access

This plugin emulates a `/dev/tapX` device as found on FreeBSD. See
README for more information.

genodelabs/genode#4394
This commit is contained in:
Johannes Schlatow 2022-01-26 15:09:44 +01:00 committed by Norman Feske
parent 19958eafcf
commit 019cacf07e
18 changed files with 1397 additions and 2 deletions

View File

@ -1 +1 @@
392389f9e1323249c044ba6b7fea6ea3d73100c5
bfc0a252597735c423dff56b699b816d4fbda7e6

View File

@ -96,7 +96,7 @@ DIR_CONTENT(include/libc/vm) := \
DIRS += include/libc/net
DIR_CONTENT(include/libc/net) := \
$(addprefix $(D)/sys/net/, if.h if_dl.h if_tun.h if_types.h \
$(addprefix $(D)/sys/net/, if.h if_dl.h if_tun.h if_tap.h if_types.h \
radix.h route.h ethernet.h if_arp.h vnet.h)
DIRS += include/libc/netinet

View File

@ -1,4 +1,5 @@
base
net
os
so
timer_session

View File

@ -0,0 +1,143 @@
#
# Build
#
#
create_boot_directory
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/src/vfs_tap
set build_components {
core init timer
server/nic_router
test/libc_vfs_tap
}
build $build_components
#
# Generate config
#
append config {
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</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">
<resource name="RAM" quantum="10M"/>
<provides>
<service name="Nic"/>
<service name="Uplink"/>
</provides>
<config verbose="yes" verbose_packets="yes" verbose_domain_state="yes">
<policy label_prefix="tap_uplink_client -> " domain="tap_uplink"/>
<policy label_prefix="tap_nic_client -> " domain="tap_nic"/>
<domain name="tap_nic">
</domain>
<domain name="tap_uplink">
</domain>
</config>
</start>
<start name="tap_uplink_client">
<binary name="test-libc_vfs_tap"/>
<resource name="RAM" quantum="8M"/>
<config>
<libc stdin="/dev/log" stdout="/dev/log" stderr="/dev/log"/>
<vfs>
<dir name="dev">
<log/>
<tap name="tap0" mac="02:02:00:00:00:20" mode="uplink_client"/>
</dir>
</vfs>
</config>
</start>
<start name="tap_nic_client">
<binary name="test-libc_vfs_tap"/>
<resource name="RAM" quantum="8M"/>
<config>
<libc stdin="/dev/log" stdout="/dev/log" stderr="/dev/log"/>
<vfs>
<dir name="dev">
<log/>
<tap name="tap0"/>
</dir>
</vfs>
</config>
</start>
</config>
}
install_config $config
#
# Boot modules
#
set boot_modules {
core init timer test-libc_vfs_tap nic_router
libc.lib.so vfs.lib.so libm.lib.so posix.lib.so
}
build_boot_image $boot_modules
append qemu_args "-nographic "
run_genode_until "child \"tap_uplink_client\" exited with exit value 0" 40
set original_output $output
grep_output {\[init -> tap_uplink_client\].*}
compare_output_to {
[init -> tap_uplink_client] MAC address 02:02:00:00:00:20
[init -> tap_uplink_client] Successfully opened device tap0
[init -> tap_uplink_client] MAC address 02:02:00:00:00:21
[init -> tap_uplink_client] Warning: unsupported ioctl (request=0x4008745c)
[init -> tap_uplink_client] Warning: TAPGIFINFO failed
}
set output $original_output
grep_output {\[init -> tap_nic_client\].*}
compare_output_to {
[init -> tap_nic_client] Successfully opened device tap0
[init -> tap_nic_client] Warning: unsupported ioctl (request=0x4008745c)
[init -> tap_nic_client] Warning: TAPGIFINFO failed
}
# check that nic_router received packages from both clients
set output $original_output
grep_output {\[init -> nic_router\] \[tap.*}
set num_uplink_received [regexp -all {.*tap_uplink\] rcv} $output dummy]
if {$num_uplink_received < 1} {
puts "Error: No packet received from tap_uplink_client\n"
exit 1
}
set num_nic_received [regexp -all {.*tap_nic\] rcv} $output dummy]
if {$num_nic_received < 1} {
puts "Error: No packet received from tap_nic_client\n"
exit 1
}
# vi: set ft=tcl :

View File

@ -126,6 +126,11 @@ class Libc::Vfs_plugin final : public Plugin
*/
Ioctl_result _ioctl_sndctl(File_descriptor *, unsigned long, char *);
/**
* Tap related I/O controls
*/
Ioctl_result _ioctl_tapctl(File_descriptor *, unsigned long, char *);
/**
* Call functor 'fn' with ioctl info for the given file descriptor 'fd'
*

View File

@ -17,6 +17,7 @@
#include <base/env.h>
#include <base/log.h>
#include <vfs/dir_file_system.h>
#include <net/mac_address.h>
/* libc includes */
#include <errno.h>
@ -32,6 +33,8 @@
#include <sys/disk.h>
#include <sys/soundcard.h>
#include <dlfcn.h>
#include <net/if.h>
#include <net/if_tap.h>
/* libc plugin interface */
#include <libc-plugin/plugin.h>
@ -1770,6 +1773,85 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char
}
Libc::Vfs_plugin::Ioctl_result
Libc::Vfs_plugin::_ioctl_tapctl(File_descriptor *fd, unsigned long request, char *argp)
{
bool handled = false;
int result = 0;
if (request == TAPGIFNAME) { /* return device name */
if (!argp)
return { true, EINVAL };
ifreq *ifr = reinterpret_cast<ifreq*>(argp);
monitor().monitor([&] {
_with_info(*fd, [&] (Xml_node info) {
if (info.type() == "tap") {
String<IFNAMSIZ> name = info.attribute_value("name", String<IFNAMSIZ> { });
copy_cstring(ifr->ifr_name, name.string(), IFNAMSIZ);
handled = true;
}
});
return Fn::COMPLETE;
});
}
else if (request == SIOCGIFADDR) { /* get MAC address */
if (!argp)
return { true, EINVAL };
monitor().monitor([&] {
_with_info(*fd, [&] (Xml_node info) {
if (info.type() == "tap") {
Net::Mac_address mac = info.attribute_value("mac_addr", Net::Mac_address { });
mac.copy(argp);
handled = true;
}
});
return Fn::COMPLETE;
});
}
else if (request == SIOCSIFADDR) { /* set MAC address */
if (!argp)
return { true, EINVAL };
Net::Mac_address new_mac { argp };
String<18> mac_string { new_mac };
/* write string into file */
Absolute_path mac_addr_path = ioctl_dir(*fd);
mac_addr_path.append_element("mac_addr");
File_descriptor *mac_addr_fd = open(mac_addr_path.base(), O_RDWR);
if (!mac_addr_fd)
return { true, ENOTSUP };
write(mac_addr_fd, mac_string.string(), mac_string.length());
close(mac_addr_fd);
monitor().monitor([&] {
/* check whether mac address changed, return ENOTSUP if not */
_with_info(*fd, [&] (Xml_node info) {
if (info.type() == "tap") {
if (!info.has_attribute("mac_addr"))
result = ENOTSUP;
else {
Net::Mac_address cur_mac = info.attribute_value("mac_addr", Net::Mac_address { });
if (cur_mac != new_mac)
result = ENOTSUP;
}
handled = true;
}
});
return Fn::COMPLETE;
});
}
return { handled, result };
}
int Libc::Vfs_plugin::ioctl(File_descriptor *fd, unsigned long request, char *argp)
{
Ioctl_result result { false, 0 };
@ -1807,6 +1889,15 @@ int Libc::Vfs_plugin::ioctl(File_descriptor *fd, unsigned long request, char *ar
case SNDCTL_SYSINFO:
result = _ioctl_sndctl(fd, request, argp);
break;
case TAPSIFINFO:
case TAPGIFINFO:
case TAPSDEBUG:
case TAPGDEBUG:
case TAPGIFNAME:
case SIOCGIFADDR:
case SIOCSIFADDR:
result = _ioctl_tapctl(fd, request, argp);
break;
default:
break;
}

View File

@ -0,0 +1,93 @@
/*
* \brief tap device loopback test using FreeBSD API
* \author Johannes Schlatow
* \date 2022-01-26
*/
/*
* Copyright (C) 2022 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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <net/if.h>
#include <net/if_tap.h>
#include <sys/ioctl.h>
int main(int argc, char** argv)
{
enum { BUFFLEN = 1500 };
int fd = open("/dev/tap0", O_RDWR);
if (fd == -1) {
printf("Error: open(/dev/tap0) failed\n");
return 1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
if (ioctl(fd, TAPGIFNAME, (void *)&ifr) < 0) {
printf("Error: TAPGIFNAME failed\n");
return 2;
}
printf("Successfully opened device %s\n", ifr.ifr_name);
/* get mac address */
char mac[6];
memset(mac, 0, sizeof(mac));
if (ioctl(fd, SIOCGIFADDR, (void *)mac) < 0) {
printf("Error: SIOCGIFADDR failed\n");
return 3;
}
/**
* Set mac address if we are in uplink mode.
* In Uplink mode, the default mac address is 0x02 02 02 02 02 02.
* In Nic mode, the nic_router will assign 0x02 02 02 02 02 00 to the first
* client.
*/
if (mac[5] >= 0x02) {
mac[5]++;
if (ioctl(fd, SIOCSIFADDR, (void *)mac) < 0) {
printf("Error: SIOCSIFADDR failed\n");
return 4;
}
}
/* try other ioctls */
struct tapinfo info;
memset(&info, 0, sizeof(info));
if (ioctl(fd, TAPGIFINFO, (void *)&info) < 0)
printf("Warning: TAPGIFINFO failed\n");
char buffer[BUFFLEN];
unsigned frame_cnt = 0;
while (frame_cnt < 2) {
/* read a frame */
ssize_t received = read(fd, buffer, BUFFLEN);
if (received < 0)
return 1;
/* write a frame */
ssize_t written = write(fd, buffer, received);
if (written < received) {
printf("Unable to write frame %d\n", frame_cnt);
return 1;
}
frame_cnt++;
}
close(fd);
return 0;
}

View File

@ -0,0 +1,5 @@
TARGET = test-libc_vfs_tap
LIBS := posix
SRC_C := main.c

View File

@ -0,0 +1,5 @@
SRC_CC := vfs_tap.cc
vpath %.cc $(REP_DIR)/src/lib/vfs/tap
SHARED_LIB := yes

View File

@ -0,0 +1,9 @@
MIRROR_FROM_REP_DIR := src/lib/vfs/tap lib/mk/vfs_tap.mk
content: $(MIRROR_FROM_REP_DIR) LICENSE
$(MIRROR_FROM_REP_DIR):
$(mirror_from_rep_dir)
LICENSE:
cp $(GENODE_DIR)/LICENSE $@

View File

@ -0,0 +1 @@
2022-01-27 06d96f2618f11d62f9a959da196753337dbb4c4c

View File

@ -0,0 +1,5 @@
base
nic_session
os
uplink_session
vfs

View File

@ -0,0 +1,46 @@
The VFS TAP plugin offers access to Genode's Uplink or Nic session by providing
a special file system. It exposes a data file that reflects a _/dev/tap0_
file. The support of I/O control operations is provided in form of a structured
'info' file located in the directory named after the data file, e.g.
_/dev/.tap0/info_.
This file may by used to query the configured parameters and has the following
structure:
! <tap name="tap0" mac_addr="..."/>
Each parameter can also be accessed via its own file. The following list
presents all files:
* :mac_addr (rw): The MAC address of the device (immutable when in Nic mode).
* :name (ro): The name of the device.
When mounting the tap file system, the following optional attributes may
be provided:
* :mode: If set to "uplink_client", the plugin will open an Uplink session
instead of a Nic session.
* :label: Sets the session label of the Uplink/Nic session. If not provided,
an empty label is used.
* :mac: Sets the default mac address when mode="uplink_client".
The following config snippet illustrates its configuration:
! <vfs>
! <dir name="dev">
! <tap name="tap0"/>
! </dir>
! </vfs>
Note, that the plugin emulates the tap device and its I/O control operations
as expected by FreeBSD's libc. On Linux, the tap devices are created by
performing an I/O control operation on _/dev/net/tun_ after which the opened
file descriptor can be used for reading/writing. If only a single tap device is
needed, it is possible to use the tap plugin for _/dev/net/tun_ and just omit
the ioctl by creating an emulation header file _linux/if_tun.h_ with the
following content:
! #include <net/if_tap.h>
! #define TUNSETIFF TAPGIFNAME
! #define IFF_TAP 0
! #define IFF_NO_PI 0

View File

@ -0,0 +1,200 @@
/*
* \brief Vfs handle for a Nic client.
* \author Johannes Schlatow
* \date 2022-01-26
*/
/*
* Copyright (C) 2022 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.
*/
#ifndef _SRC__LIB__VFS__TAP__NIC_FILE_SYSTEM_H_
#define _SRC__LIB__VFS__TAP__NIC_FILE_SYSTEM_H_
#include <net/mac_address.h>
#include <nic/packet_allocator.h>
#include <nic_session/connection.h>
#include <vfs/single_file_system.h>
namespace Vfs {
using namespace Genode;
class Nic_file_system;
}
class Vfs::Nic_file_system : public Vfs::Single_file_system
{
public:
class Nic_vfs_handle;
using Vfs_handle = Nic_vfs_handle;
Nic_file_system(char const *name)
: Single_file_system(Node_type::TRANSACTIONAL_FILE, name,
Node_rwx::rw(), Genode::Xml_node("<data/>"))
{ }
};
class Vfs::Nic_file_system::Nic_vfs_handle : public Single_vfs_handle
{
public:
using Label = String<64>;
private:
using Read_result = File_io_service::Read_result;
using Write_result = File_io_service::Write_result;
enum { PKT_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE };
enum { BUF_SIZE = Uplink::Session::QUEUE_SIZE * PKT_SIZE };
Genode::Env &_env;
Nic::Packet_allocator _pkt_alloc;
Nic::Connection _nic;
bool _link_state;
bool _notifying = false;
bool _blocked = false;
Io_signal_handler<Nic_vfs_handle> _link_state_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_link_state};
Io_signal_handler<Nic_vfs_handle> _read_avail_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_read_avail };
Io_signal_handler<Nic_vfs_handle> _ack_avail_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_ack_avail };
void _handle_ack_avail()
{
while (_nic.tx()->ack_avail()) {
_nic.tx()->release_packet(_nic.tx()->get_acked_packet()); }
}
void _handle_read_avail()
{
if (!read_ready())
return;
if (_blocked) {
_blocked = false;
io_progress_response();
}
if (_notifying) {
_notifying = false;
read_ready_response();
}
}
void _handle_link_state()
{
_link_state = _nic.link_state();
_handle_read_avail();
}
public:
Nic_vfs_handle(Genode::Env &env,
Allocator &alloc,
Label const &label,
Net::Mac_address const &,
Directory_service &ds,
File_io_service &fs,
int flags)
: Single_vfs_handle { ds, fs, alloc, flags },
_env(env),
_pkt_alloc(&alloc),
_nic(_env, &_pkt_alloc, BUF_SIZE, BUF_SIZE, label.string()),
_link_state(_nic.link_state())
{
_nic.link_state_sigh(_link_state_handler);
_nic.tx_channel()->sigh_ack_avail (_ack_avail_handler);
_nic.rx_channel()->sigh_ready_to_ack (_read_avail_handler);
_nic.rx_channel()->sigh_packet_avail (_read_avail_handler);
}
bool notify_read_ready() override {
_notifying = true;
return true;
}
void mac_address(Net::Mac_address const &) { }
Net::Mac_address mac_address() {
return _nic.mac_address(); }
/************************
* Vfs_handle interface *
************************/
bool read_ready() override {
return _link_state && _nic.rx()->packet_avail() && _nic.rx()->ready_to_ack(); }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
if (!read_ready()) {
_blocked = true;
return Read_result::READ_QUEUED;
}
out_count = 0;
/* process a single packet from rx stream */
Packet_descriptor const rx_pkt { _nic.rx()->get_packet() };
if (rx_pkt.size() > 0 &&
_nic.rx()->packet_valid(rx_pkt)) {
const char *const rx_pkt_base {
_nic.rx()->packet_content(rx_pkt) };
out_count = static_cast<file_size>(min(rx_pkt.size(), static_cast<size_t>(count)));
memcpy(dst, rx_pkt_base, static_cast<size_t>(out_count));
_nic.rx()->acknowledge_packet(rx_pkt);
}
return Read_result::READ_OK;
}
Write_result write(char const *src, file_size count,
file_size &out_count) override
{
out_count = 0;
_handle_ack_avail();
if (!_nic.tx()->ready_to_submit()) {
return Write_result::WRITE_ERR_WOULD_BLOCK;
}
try {
size_t tx_pkt_size { static_cast<size_t>(count) };
Packet_descriptor tx_pkt {
_nic.tx()->alloc_packet(tx_pkt_size) };
void *tx_pkt_base {
_nic.tx()->packet_content(tx_pkt) };
memcpy(tx_pkt_base, src, tx_pkt_size);
_nic.tx()->submit_packet(tx_pkt);
out_count = tx_pkt_size;
return Write_result::WRITE_OK;
} catch (...) {
warning("exception while trying to forward packet from driver "
"to Nic connection TX");
return Write_result::WRITE_ERR_INVALID;
}
}
};
#endif /* _SRC__LIB__VFS__TAP__NIC_FILE_SYSTEM_H_ */

View File

@ -0,0 +1,2 @@
TARGET = dummy-vfs_tap
LIBS = vfs_tap

View File

@ -0,0 +1,232 @@
/*
* \brief Modified base class for the Uplink client role of NIC drivers
* \author Martin Stein
* \author Johannes Schlatow
* \date 2020-12-07
*/
/*
* Copyright (C) 2022 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.
*/
#ifndef _DRIVERS__NIC__UPLINK_CLIENT_BASE_H_
#define _DRIVERS__NIC__UPLINK_CLIENT_BASE_H_
/* Genode includes */
#include <net/mac_address.h>
#include <nic/packet_allocator.h>
#include <uplink_session/connection.h>
namespace Genode {
class Uplink_client_base;
}
class Genode::Uplink_client_base : Noncopyable
{
public:
using Label = String<64>;
protected:
enum class Transmit_result { ACCEPTED, REJECTED, RETRY };
enum class Write_result { WRITE_SUCCEEDED, WRITE_FAILED };
enum { PKT_SIZE = Nic::Packet_allocator::DEFAULT_PACKET_SIZE };
enum { BUF_SIZE = Uplink::Session::QUEUE_SIZE * PKT_SIZE };
Env &_env;
Allocator &_alloc;
Label const &_label;
Net::Mac_address _drv_mac_addr;
bool _drv_mac_addr_used { false };
bool _drv_link_state { false };
Constructible<Uplink::Connection> _conn { };
Nic::Packet_allocator _conn_pkt_alloc { &_alloc };
Io_signal_handler<Uplink_client_base> _conn_rx_ready_to_ack_handler { _env.ep(), *this, &Uplink_client_base::_conn_rx_handle_ready_to_ack };
Io_signal_handler<Uplink_client_base> _conn_rx_packet_avail_handler { _env.ep(), *this, &Uplink_client_base::_conn_rx_handle_packet_avail };
Io_signal_handler<Uplink_client_base> _conn_tx_ack_avail_handler { _env.ep(), *this, &Uplink_client_base::_conn_tx_handle_ack_avail };
/*****************************************
** Interface towards Uplink connection **
*****************************************/
void _conn_rx_handle_ready_to_ack()
{
if (!_conn.constructed())
return;
if (_custom_conn_rx_ready_to_ack_handler())
_custom_conn_rx_handle_ready_to_ack();
}
void _conn_tx_handle_ack_avail()
{
if (!_conn.constructed())
return;
while (_conn->tx()->ack_avail()) {
_conn->tx()->release_packet(_conn->tx()->get_acked_packet()); }
}
void _conn_rx_handle_packet_avail()
{
if (!_conn.constructed())
return;
if (_custom_conn_rx_packet_avail_handler())
_custom_conn_rx_handle_packet_avail();
}
/***************************************************
** Generic back end for interface towards driver **
***************************************************/
template <typename FUNC>
void _drv_rx_handle_pkt(size_t conn_tx_pkt_size,
FUNC && write_to_conn_tx_pkt)
{
if (!_conn.constructed()) {
return;
}
_conn_tx_handle_ack_avail();
if (!_conn->tx()->ready_to_submit()) {
return;
}
try {
Packet_descriptor conn_tx_pkt {
_conn->tx()->alloc_packet(conn_tx_pkt_size) };
void *conn_tx_pkt_base {
_conn->tx()->packet_content(conn_tx_pkt) };
size_t adjusted_conn_tx_pkt_size {
conn_tx_pkt_size };
Write_result write_result {
write_to_conn_tx_pkt(
conn_tx_pkt_base,
adjusted_conn_tx_pkt_size) };
switch (write_result) {
case Write_result::WRITE_SUCCEEDED:
if (adjusted_conn_tx_pkt_size == conn_tx_pkt_size) {
_conn->tx()->submit_packet(conn_tx_pkt);
} else if (adjusted_conn_tx_pkt_size < conn_tx_pkt_size) {
Packet_descriptor adjusted_conn_tx_pkt {
conn_tx_pkt.offset(), adjusted_conn_tx_pkt_size };
_conn->tx()->submit_packet(adjusted_conn_tx_pkt);
} else {
class Bad_size { };
throw Bad_size { };
}
break;
case Write_result::WRITE_FAILED:
_conn->tx()->release_packet(conn_tx_pkt);
}
} catch (...) {
warning("exception while trying to forward packet from driver "
"to Uplink connection TX");
return;
}
}
void _drv_handle_link_state(bool drv_link_state)
{
if (_drv_link_state == drv_link_state) {
return;
}
_drv_link_state = drv_link_state;
if (drv_link_state) {
/* create connection */
_drv_mac_addr_used = true;
_conn.construct(
_env, &_conn_pkt_alloc, BUF_SIZE, BUF_SIZE,
_drv_mac_addr, _label.string());
/* install signal handlers at connection */
_conn->rx_channel()->sigh_ready_to_ack(
_conn_rx_ready_to_ack_handler);
_conn->rx_channel()->sigh_packet_avail(
_conn_rx_packet_avail_handler);
_conn->tx_channel()->sigh_ack_avail(
_conn_tx_ack_avail_handler);
} else {
_conn.destruct();
_drv_mac_addr_used = false;
}
}
virtual Transmit_result
_drv_transmit_pkt(const char *conn_rx_pkt_base,
size_t conn_rx_pkt_size) = 0;
virtual void _custom_conn_rx_handle_packet_avail()
{
class Unexpected_call { };
throw Unexpected_call { };
}
virtual void _custom_conn_rx_handle_ready_to_ack()
{
class Unexpected_call { };
throw Unexpected_call { };
}
virtual bool _custom_conn_rx_packet_avail_handler() { return false; }
virtual bool _custom_conn_rx_ready_to_ack_handler() { return false; }
public:
Uplink_client_base(Env &env,
Allocator &alloc,
Net::Mac_address const &drv_mac_addr,
Label const &label)
:
_env { env },
_alloc { alloc },
_label { label },
_drv_mac_addr { drv_mac_addr }
{
log("MAC address ", _drv_mac_addr);
}
void mac_address(Net::Mac_address const &mac_address)
{
if (_drv_mac_addr_used) {
class Already_in_use { };
throw Already_in_use { };
}
_drv_mac_addr = mac_address;
log("MAC address ", _drv_mac_addr);
}
virtual ~Uplink_client_base() { }
};
#endif /* _DRIVERS__NIC__UPLINK_CLIENT_BASE_H_ */

View File

@ -0,0 +1,178 @@
/*
* \brief Vfs handle for an uplink client.
* \author Johannes Schlatow
* \date 2022-01-24
*/
/*
* Copyright (C) 2022 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.
*/
#ifndef _SRC__LIB__VFS__TAP__UPLINK_FILE_SYSTEM_H_
#define _SRC__LIB__VFS__TAP__UPLINK_FILE_SYSTEM_H_
#include <vfs/single_file_system.h>
/* local Uplink_client_base with added custom handler */
#include "uplink_client_base.h"
namespace Vfs {
using namespace Genode;
class Uplink_file_system;
}
class Vfs::Uplink_file_system : public Vfs::Single_file_system
{
public:
class Uplink_vfs_handle;
using Vfs_handle = Uplink_vfs_handle;
Uplink_file_system(char const *name)
: Single_file_system(Node_type::TRANSACTIONAL_FILE, name,
Node_rwx::rw(), Genode::Xml_node("<data/>"))
{ }
};
class Vfs::Uplink_file_system::Uplink_vfs_handle : public Single_vfs_handle,
public Genode::Uplink_client_base
{
private:
using Read_result = File_io_service::Read_result;
using Write_result = File_io_service::Write_result;
bool _notifying = false;
bool _blocked = false;
void _handle_read_avail()
{
if (!read_ready())
return;
if (_blocked) {
_blocked = false;
io_progress_response();
}
if (_notifying) {
_notifying = false;
read_ready_response();
}
}
/************************
** Uplink_client_base **
************************/
bool _custom_conn_rx_ready_to_ack_handler() override { return true; }
bool _custom_conn_rx_packet_avail_handler() override { return true; }
void _custom_conn_rx_handle_packet_avail() override { _handle_read_avail(); }
void _custom_conn_rx_handle_ready_to_ack() override { _handle_read_avail(); }
Transmit_result _drv_transmit_pkt(const char *, size_t) override
{
class Unexpected_call { };
throw Unexpected_call { };
}
public:
Uplink_vfs_handle(Genode::Env &env,
Allocator &alloc,
Label const &label,
Net::Mac_address const &mac,
Directory_service &ds,
File_io_service &fs,
int flags)
: Single_vfs_handle { ds, fs, alloc, flags },
Uplink_client_base { env, alloc, mac, label }
{ _drv_handle_link_state(true); }
bool notify_read_ready() override
{
_notifying = true;
return true;
}
void mac_address(Net::Mac_address const & mac)
{
if (_drv_mac_addr_used) {
_drv_handle_link_state(false);
Uplink_client_base::mac_address(mac);
_drv_handle_link_state(true);
}
else
Uplink_client_base::mac_address(mac);
}
Net::Mac_address mac_address() const {
return _drv_mac_addr; }
/************************
* Vfs_handle interface *
************************/
bool read_ready() override {
return _drv_link_state && _conn->rx()->packet_avail() && _conn->rx()->ready_to_ack(); }
Read_result read(char *dst, file_size count,
file_size &out_count) override
{
if (!_conn.constructed())
return Read_result::READ_ERR_INVALID;
if (!read_ready()) {
_blocked = true;
return Read_result::READ_QUEUED;
}
out_count = 0;
/* process a single packet from rx stream */
Packet_descriptor const conn_rx_pkt { _conn->rx()->get_packet() };
if (conn_rx_pkt.size() > 0 &&
_conn->rx()->packet_valid(conn_rx_pkt)) {
const char *const conn_rx_pkt_base {
_conn->rx()->packet_content(conn_rx_pkt) };
out_count = static_cast<file_size>(min(conn_rx_pkt.size(), static_cast<size_t>(count)));
memcpy(dst, conn_rx_pkt_base, static_cast<size_t>(out_count));
_conn->rx()->acknowledge_packet(conn_rx_pkt);
}
return Read_result::READ_OK;
}
Write_result write(char const *src, file_size count,
file_size &out_count) override
{
if (!_conn.constructed())
return Write_result::WRITE_ERR_INVALID;
out_count = 0;
_drv_rx_handle_pkt(static_cast<size_t>(count), [&] (void * dst, size_t dst_size) {
out_count = dst_size;
memcpy(dst, src, dst_size);
return Uplink_client_base::Write_result::WRITE_SUCCEEDED;
});
if (out_count == count)
return Write_result::WRITE_OK;
else
return Write_result::WRITE_ERR_WOULD_BLOCK;
}
};
#endif /* _SRC__LIB__VFS__TAP__UPLINK_FILE_SYSTEM_H_ */

View File

@ -0,0 +1,379 @@
/*
* \brief Tap device emulation
* \author Johannes Schlatow
* \date 2022-01-21
*/
/*
* Copyright (C) 2022 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.
*/
/* Genode includes */
#include <net/mac_address.h>
#include <os/vfs.h>
#include <vfs/single_file_system.h>
#include <vfs/dir_file_system.h>
#include <vfs/readonly_value_file_system.h>
#include <vfs/value_file_system.h>
#include <util/xml_generator.h>
/* local includes */
#include "uplink_file_system.h"
#include "nic_file_system.h"
namespace Vfs {
enum Uplink_mode {
NIC_CLIENT,
UPLINK_CLIENT
};
static inline size_t ascii_to(char const *, Uplink_mode &);
/* overload Value_file_system to work with Net::Mac_address */
class Mac_file_system;
/* main file system */
class Tap_file_system;
}
class Vfs::Mac_file_system : public Value_file_system<Net::Mac_address>
{
public:
Mac_file_system(Name const & name, Net::Mac_address const & mac)
: Value_file_system(name, mac)
{ }
using Value_file_system<Net::Mac_address>::value;
Net::Mac_address value()
{
Net::Mac_address val { };
Net::ascii_to(buffer().string(), val);
return val;
}
Net::Mac_address value() const
{
Net::Mac_address val { };
Net::ascii_to(buffer().string(), val);
return val;
}
};
struct Vfs::Tap_file_system
{
using Name = String<64>;
template <typename>
struct Local_factory;
template <typename>
struct Data_file_system;
template <typename>
struct Compound_file_system;
struct Device_update_handler;
};
Genode::size_t Vfs::ascii_to(char const *s, Uplink_mode &mode)
{
if (!strcmp(s, "uplink", 6)) { mode = Uplink_mode::UPLINK_CLIENT; return 6; }
if (!strcmp(s, "uplink_client", 13)) { mode = Uplink_mode::UPLINK_CLIENT; return 13; }
mode = Uplink_mode::NIC_CLIENT;
return strlen(s);
}
/**
* Interface for upwards reporting if the tap device state changed.
* Currently, it is only used for triggering the info fs to read the
* mac address from the device.
*/
struct Vfs::Tap_file_system::Device_update_handler : Interface
{
virtual void device_state_changed() = 0;
};
/**
* File system node for processing the packet data read/write
*/
template <typename FS>
class Vfs::Tap_file_system::Data_file_system : public FS
{
private:
using Local_vfs_handle = typename FS::Vfs_handle;
using Label = typename FS::Vfs_handle::Label;
using Registered_handle = Genode::Registered<Local_vfs_handle>;
using Handle_registry = Genode::Registry<Registered_handle>;
using Open_result = Directory_service::Open_result;
Name const &_name;
Label const &_label;
Net::Mac_address const &_default_mac;
Genode::Env &_env;
Device_update_handler &_device_update_handler;
Handle_registry _handle_registry { };
public:
Data_file_system(Genode::Env & env,
Name const & name,
Label const & label,
Net::Mac_address const & mac,
Device_update_handler & handler)
:
FS(name.string()),
_name(name), _label(label), _default_mac(mac), _env(env),
_device_update_handler(handler)
{ }
/* must only be called if handle has been opened */
Local_vfs_handle &device()
{
Local_vfs_handle *dev = nullptr;
_handle_registry.for_each([&] (Local_vfs_handle &handle) {
dev = &handle;
});
struct Device_unavailable { };
if (!dev)
throw Device_unavailable();
return *dev;
}
static const char *name() { return "data"; }
char const *type() override { return "data"; }
/*********************************
** Directory service interface **
*********************************/
Open_result open(char const *path, unsigned flags,
Vfs_handle **out_handle,
Allocator &alloc) override
{
if (!FS::_single_file(path))
return Open_result::OPEN_ERR_UNACCESSIBLE;
/* A tap device is exclusive open, thus return error if already opened. */
unsigned handles = 0;
_handle_registry.for_each([&handles] (Local_vfs_handle const &) {
handles++;
});
if (handles) return Open_result::OPEN_ERR_EXISTS;
try {
*out_handle = new (alloc)
Registered_handle(_handle_registry, _env, alloc, _label.string(),
_default_mac, *this, *this, flags);
_device_update_handler.device_state_changed();
return Open_result::OPEN_OK;
}
catch (Genode::Out_of_ram) { return Open_result::OPEN_ERR_OUT_OF_RAM; }
catch (Genode::Out_of_caps) { return Open_result::OPEN_ERR_OUT_OF_CAPS; }
}
};
template <typename FS>
struct Vfs::Tap_file_system::Local_factory : File_system_factory,
Device_update_handler
{
using Label = typename FS::Vfs_handle::Label;
using Name_fs = Readonly_value_file_system<Name>;
using Mac_addr_fs = Mac_file_system;
Name const _name;
Label const _label;
Uplink_mode const _mode;
Net::Mac_address const _default_mac;
Vfs::Env &_env;
Data_file_system<FS> _data_fs { _env.env(), _name, _label, _default_mac, *this };
struct Info
{
Name const &_name;
Mac_addr_fs const &_mac_addr_fs;
Info(Name const & name,
Mac_addr_fs const & mac_addr_fs)
: _name(name),
_mac_addr_fs(mac_addr_fs)
{ }
void print(Genode::Output &out) const
{
char buf[128] { };
Genode::Xml_generator xml(buf, sizeof(buf), "tap", [&] () {
xml.attribute("mac_addr", String<20>(_mac_addr_fs.value()));
xml.attribute("name", _name);
});
Genode::print(out, Genode::Cstring(buf));
}
};
Mac_addr_fs _mac_addr_fs { "mac_addr", _default_mac };
Name_fs _name_fs { "name", _name };
Info _info { _name, _mac_addr_fs };
Readonly_value_file_system<Info> _info_fs { "info", _info };
/********************
** Watch handlers **
********************/
Genode::Watch_handler<Vfs::Tap_file_system::Local_factory<FS>> _mac_addr_changed_handler {
_mac_addr_fs, "/mac_addr",
_env.alloc(),
*this,
&Vfs::Tap_file_system::Local_factory<FS>::_mac_addr_changed };
void _mac_addr_changed()
{
Net::Mac_address new_mac = _mac_addr_fs.value();
/* update of mac address only if changed */
if (new_mac != _data_fs.device().mac_address())
_data_fs.device().mac_address(new_mac);
/* read back mac from device */
_mac_addr_fs.value(_data_fs.device().mac_address());
/* propagate changes to info_fs */
_info_fs.value(_info);
}
/*****************************
** Device update interface **
*****************************/
void device_state_changed() override
{
/* update mac address */
_mac_addr_fs.value(_data_fs.device().mac_address());
/* propagate changes to info_fs */
_info_fs.value(_info);
}
/***********************
** Factory interface **
***********************/
Vfs::File_system *create(Vfs::Env&, Xml_node node) override
{
if (node.has_type("data")) return &_data_fs;
if (node.has_type("info")) return &_info_fs;
if (node.has_type("mac_addr")) return &_mac_addr_fs;
if (node.has_type("name")) return &_name_fs;
return nullptr;
}
/***********************
** Constructor, etc. **
***********************/
static Name name(Xml_node config)
{
return config.attribute_value("name", Name("tap"));
}
Local_factory(Vfs::Env &env, Xml_node config)
:
_name (name(config)),
_label (config.attribute_value("label", Label(""))),
_mode (config.attribute_value("mode", Uplink_mode::NIC_CLIENT)),
_default_mac(config.attribute_value("mac", Net::Mac_address { 0x02 })),
_env(env)
{ }
};
template <typename FS>
class Vfs::Tap_file_system::Compound_file_system : private Local_factory<FS>,
public Vfs::Dir_file_system
{
private:
typedef Tap_file_system::Name Name;
typedef String<200> Config;
static Config _config(Name const &name)
{
char buf[Config::capacity()] { };
/*
* By not using the node type "dir", we operate the
* 'Dir_file_system' in root mode, allowing multiple sibling nodes
* to be present at the mount point.
*/
Genode::Xml_generator xml(buf, sizeof(buf), "compound", [&] () {
xml.node("data", [&] () {
xml.attribute("name", name); });
xml.node("dir", [&] () {
xml.attribute("name", Name(".", name));
xml.node("info", [&] () {});
xml.node("mac_addr", [&] () {});
xml.node("name", [&] () {});
});
});
return Config(Genode::Cstring(buf));
}
public:
Compound_file_system(Vfs::Env &vfs_env, Genode::Xml_node node)
:
Local_factory<FS>(vfs_env, node),
Vfs::Dir_file_system(vfs_env,
Xml_node(_config(Local_factory<FS>::name(node)).string()),
*this)
{ }
static const char *name() { return "tap"; }
char const *type() override { return name(); }
};
extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
{
struct Factory : Vfs::File_system_factory
{
Vfs::File_system *create(Vfs::Env &env, Genode::Xml_node config) override
{
if (config.attribute_value("mode", Vfs::Uplink_mode::NIC_CLIENT) == Vfs::Uplink_mode::NIC_CLIENT)
return new (env.alloc())
Vfs::Tap_file_system::Compound_file_system<Vfs::Nic_file_system>(env, config);
else
return new (env.alloc())
Vfs::Tap_file_system::Compound_file_system<Vfs::Uplink_file_system>(env, config);
}
};
static Factory f;
return &f;
}