From 019cacf07e75385c56210a6e2ce5ecce55a811b9 Mon Sep 17 00:00:00 2001 From: Johannes Schlatow Date: Wed, 26 Jan 2022 15:09:44 +0100 Subject: [PATCH] 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 --- repos/libports/ports/libc.hash | 2 +- repos/libports/ports/libc.port | 2 +- repos/libports/recipes/src/libc/used_apis | 1 + repos/libports/run/libc_vfs_tap.run | 143 +++++++ .../src/lib/libc/internal/vfs_plugin.h | 5 + repos/libports/src/lib/libc/vfs_plugin.cc | 91 +++++ repos/libports/src/test/libc_vfs_tap/main.c | 93 +++++ .../libports/src/test/libc_vfs_tap/target.mk | 5 + repos/os/lib/mk/vfs_tap.mk | 5 + repos/os/recipes/src/vfs_tap/content.mk | 9 + repos/os/recipes/src/vfs_tap/hash | 1 + repos/os/recipes/src/vfs_tap/used_apis | 5 + repos/os/src/lib/vfs/tap/README | 46 +++ repos/os/src/lib/vfs/tap/nic_file_system.h | 200 +++++++++ repos/os/src/lib/vfs/tap/target.mk | 2 + repos/os/src/lib/vfs/tap/uplink_client_base.h | 232 +++++++++++ repos/os/src/lib/vfs/tap/uplink_file_system.h | 178 ++++++++ repos/os/src/lib/vfs/tap/vfs_tap.cc | 379 ++++++++++++++++++ 18 files changed, 1397 insertions(+), 2 deletions(-) create mode 100644 repos/libports/run/libc_vfs_tap.run create mode 100644 repos/libports/src/test/libc_vfs_tap/main.c create mode 100644 repos/libports/src/test/libc_vfs_tap/target.mk create mode 100644 repos/os/lib/mk/vfs_tap.mk create mode 100644 repos/os/recipes/src/vfs_tap/content.mk create mode 100644 repos/os/recipes/src/vfs_tap/hash create mode 100644 repos/os/recipes/src/vfs_tap/used_apis create mode 100644 repos/os/src/lib/vfs/tap/README create mode 100644 repos/os/src/lib/vfs/tap/nic_file_system.h create mode 100644 repos/os/src/lib/vfs/tap/target.mk create mode 100644 repos/os/src/lib/vfs/tap/uplink_client_base.h create mode 100644 repos/os/src/lib/vfs/tap/uplink_file_system.h create mode 100644 repos/os/src/lib/vfs/tap/vfs_tap.cc diff --git a/repos/libports/ports/libc.hash b/repos/libports/ports/libc.hash index d3b2511e5a..529d9e4509 100644 --- a/repos/libports/ports/libc.hash +++ b/repos/libports/ports/libc.hash @@ -1 +1 @@ -392389f9e1323249c044ba6b7fea6ea3d73100c5 +bfc0a252597735c423dff56b699b816d4fbda7e6 diff --git a/repos/libports/ports/libc.port b/repos/libports/ports/libc.port index cc20759725..927cf923a6 100644 --- a/repos/libports/ports/libc.port +++ b/repos/libports/ports/libc.port @@ -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 diff --git a/repos/libports/recipes/src/libc/used_apis b/repos/libports/recipes/src/libc/used_apis index 890a12fd73..1273c4ce8b 100644 --- a/repos/libports/recipes/src/libc/used_apis +++ b/repos/libports/recipes/src/libc/used_apis @@ -1,4 +1,5 @@ base +net os so timer_session diff --git a/repos/libports/run/libc_vfs_tap.run b/repos/libports/run/libc_vfs_tap.run new file mode 100644 index 0000000000..54a8134694 --- /dev/null +++ b/repos/libports/run/libc_vfs_tap.run @@ -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 { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +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 : diff --git a/repos/libports/src/lib/libc/internal/vfs_plugin.h b/repos/libports/src/lib/libc/internal/vfs_plugin.h index 7d79ef758c..d88a8bae0c 100644 --- a/repos/libports/src/lib/libc/internal/vfs_plugin.h +++ b/repos/libports/src/lib/libc/internal/vfs_plugin.h @@ -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' * diff --git a/repos/libports/src/lib/libc/vfs_plugin.cc b/repos/libports/src/lib/libc/vfs_plugin.cc index 6dc8204d96..56abd0d337 100644 --- a/repos/libports/src/lib/libc/vfs_plugin.cc +++ b/repos/libports/src/lib/libc/vfs_plugin.cc @@ -17,6 +17,7 @@ #include #include #include +#include /* libc includes */ #include @@ -32,6 +33,8 @@ #include #include #include +#include +#include /* libc plugin interface */ #include @@ -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(argp); + + monitor().monitor([&] { + _with_info(*fd, [&] (Xml_node info) { + if (info.type() == "tap") { + String name = info.attribute_value("name", String { }); + 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; } diff --git a/repos/libports/src/test/libc_vfs_tap/main.c b/repos/libports/src/test/libc_vfs_tap/main.c new file mode 100644 index 0000000000..dd73d0c133 --- /dev/null +++ b/repos/libports/src/test/libc_vfs_tap/main.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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; +} diff --git a/repos/libports/src/test/libc_vfs_tap/target.mk b/repos/libports/src/test/libc_vfs_tap/target.mk new file mode 100644 index 0000000000..c528de78f3 --- /dev/null +++ b/repos/libports/src/test/libc_vfs_tap/target.mk @@ -0,0 +1,5 @@ +TARGET = test-libc_vfs_tap + +LIBS := posix + +SRC_C := main.c diff --git a/repos/os/lib/mk/vfs_tap.mk b/repos/os/lib/mk/vfs_tap.mk new file mode 100644 index 0000000000..9d0ff217f8 --- /dev/null +++ b/repos/os/lib/mk/vfs_tap.mk @@ -0,0 +1,5 @@ +SRC_CC := vfs_tap.cc + +vpath %.cc $(REP_DIR)/src/lib/vfs/tap + +SHARED_LIB := yes diff --git a/repos/os/recipes/src/vfs_tap/content.mk b/repos/os/recipes/src/vfs_tap/content.mk new file mode 100644 index 0000000000..b579058870 --- /dev/null +++ b/repos/os/recipes/src/vfs_tap/content.mk @@ -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 $@ diff --git a/repos/os/recipes/src/vfs_tap/hash b/repos/os/recipes/src/vfs_tap/hash new file mode 100644 index 0000000000..68ceabce15 --- /dev/null +++ b/repos/os/recipes/src/vfs_tap/hash @@ -0,0 +1 @@ +2022-01-27 06d96f2618f11d62f9a959da196753337dbb4c4c diff --git a/repos/os/recipes/src/vfs_tap/used_apis b/repos/os/recipes/src/vfs_tap/used_apis new file mode 100644 index 0000000000..002ceb0342 --- /dev/null +++ b/repos/os/recipes/src/vfs_tap/used_apis @@ -0,0 +1,5 @@ +base +nic_session +os +uplink_session +vfs diff --git a/repos/os/src/lib/vfs/tap/README b/repos/os/src/lib/vfs/tap/README new file mode 100644 index 0000000000..a712247c60 --- /dev/null +++ b/repos/os/src/lib/vfs/tap/README @@ -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: + +! + +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: + +! +! +! +! +! + +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 +! #define TUNSETIFF TAPGIFNAME +! #define IFF_TAP 0 +! #define IFF_NO_PI 0 diff --git a/repos/os/src/lib/vfs/tap/nic_file_system.h b/repos/os/src/lib/vfs/tap/nic_file_system.h new file mode 100644 index 0000000000..77d5841c69 --- /dev/null +++ b/repos/os/src/lib/vfs/tap/nic_file_system.h @@ -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 +#include +#include +#include + + +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("")) + { } +}; + + +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 _link_state_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_link_state}; + Io_signal_handler _read_avail_handler { _env.ep(), *this, &Nic_vfs_handle::_handle_read_avail }; + Io_signal_handler _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(min(rx_pkt.size(), static_cast(count))); + memcpy(dst, rx_pkt_base, static_cast(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(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_ */ diff --git a/repos/os/src/lib/vfs/tap/target.mk b/repos/os/src/lib/vfs/tap/target.mk new file mode 100644 index 0000000000..e86c3b4b18 --- /dev/null +++ b/repos/os/src/lib/vfs/tap/target.mk @@ -0,0 +1,2 @@ +TARGET = dummy-vfs_tap +LIBS = vfs_tap diff --git a/repos/os/src/lib/vfs/tap/uplink_client_base.h b/repos/os/src/lib/vfs/tap/uplink_client_base.h new file mode 100644 index 0000000000..bcc3809f19 --- /dev/null +++ b/repos/os/src/lib/vfs/tap/uplink_client_base.h @@ -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 +#include +#include + +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 _conn { }; + Nic::Packet_allocator _conn_pkt_alloc { &_alloc }; + Io_signal_handler _conn_rx_ready_to_ack_handler { _env.ep(), *this, &Uplink_client_base::_conn_rx_handle_ready_to_ack }; + Io_signal_handler _conn_rx_packet_avail_handler { _env.ep(), *this, &Uplink_client_base::_conn_rx_handle_packet_avail }; + Io_signal_handler _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 + 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_ */ diff --git a/repos/os/src/lib/vfs/tap/uplink_file_system.h b/repos/os/src/lib/vfs/tap/uplink_file_system.h new file mode 100644 index 0000000000..1974d3e884 --- /dev/null +++ b/repos/os/src/lib/vfs/tap/uplink_file_system.h @@ -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 + +/* 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("")) + { } +}; + + +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(min(conn_rx_pkt.size(), static_cast(count))); + memcpy(dst, conn_rx_pkt_base, static_cast(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(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_ */ diff --git a/repos/os/src/lib/vfs/tap/vfs_tap.cc b/repos/os/src/lib/vfs/tap/vfs_tap.cc new file mode 100644 index 0000000000..36f5e73831 --- /dev/null +++ b/repos/os/src/lib/vfs/tap/vfs_tap.cc @@ -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 +#include +#include +#include +#include +#include +#include + +/* 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 +{ + public: + + Mac_file_system(Name const & name, Net::Mac_address const & mac) + : Value_file_system(name, mac) + { } + + using Value_file_system::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 + struct Local_factory; + + template + struct Data_file_system; + + template + 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 +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; + using Handle_registry = Genode::Registry; + 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 +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; + 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 _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_fs { "info", _info }; + + /******************** + ** Watch handlers ** + ********************/ + + Genode::Watch_handler> _mac_addr_changed_handler { + _mac_addr_fs, "/mac_addr", + _env.alloc(), + *this, + &Vfs::Tap_file_system::Local_factory::_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 +class Vfs::Tap_file_system::Compound_file_system : private Local_factory, + 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(vfs_env, node), + Vfs::Dir_file_system(vfs_env, + Xml_node(_config(Local_factory::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(env, config); + else + return new (env.alloc()) + Vfs::Tap_file_system::Compound_file_system(env, config); + } + }; + + static Factory f; + return &f; +}