From aadf20d39aa80ddba44be3277be87c672b7e5d7d Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Wed, 24 Feb 2016 19:13:18 +0100 Subject: [PATCH] server/tftp_rom Serves ROM from TFTP, RCF1350. Fixes #9 --- run/tftp_rom.run | 128 +++++++++ src/server/tftp_rom/README | 18 ++ src/server/tftp_rom/main.cc | 525 ++++++++++++++++++++++++++++++++++ src/server/tftp_rom/target.mk | 3 + 4 files changed, 674 insertions(+) create mode 100644 run/tftp_rom.run create mode 100644 src/server/tftp_rom/README create mode 100644 src/server/tftp_rom/main.cc create mode 100644 src/server/tftp_rom/target.mk diff --git a/run/tftp_rom.run b/run/tftp_rom.run new file mode 100644 index 0000000..fab5852 --- /dev/null +++ b/run/tftp_rom.run @@ -0,0 +1,128 @@ +# +# \brief Test of 'tftp_rom' server +# \author Emery Hemingway +# \date 2016-02-24 +# + +if {![have_include "load/tftp"]} { + puts "please enable TFTP booting first" + exit 1 +} + +set build_components { + core init + drivers/nic + drivers/timer + server/tftp_rom + test/libc +} + +source ${genode_dir}/repos/base/run/platform_drv.inc +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +append config { + + + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# generic modules +set boot_modules { + core init ld.lib.so + libc.lib.so + libc.lib.so + libm.lib.so + lwip.lib.so + nic_drv + test-libc + tftp_rom + timer +} + +# platform-specific modules +append_platform_drv_boot_modules + +build_boot_image $boot_modules + +append qemu_args " -nographic -net nic,model=e1000 -net user" + +# append qemu_args " -net dump,file=/tmp/tftp_rom.pcap" + +run_genode_until {child "test-libc" exited with exit value 0} 60 diff --git a/src/server/tftp_rom/README b/src/server/tftp_rom/README new file mode 100644 index 0000000..99f8094 --- /dev/null +++ b/src/server/tftp_rom/README @@ -0,0 +1,18 @@ +This component serves ROM requests, loading each from TFTP. +Only a subset of RFC1350 is implemented, so the maximum transfer size is 32MB. + +The IP stack configuration is handled by DHCP by default, +see the libc_lwip_nic_dhcp library for details. + +The rest of the configuration is parsed from policies, the options are: + + ip - server IP address, required + port - sever port + dir - root requests into this server-side directory + timeout - session will timeout if forward progress is not made for this period of time + +Example: + + +WARNING: The TFTP protocol has no security assurance whatsoever, +use an authenticated tunnel whenever possible! diff --git a/src/server/tftp_rom/main.cc b/src/server/tftp_rom/main.cc new file mode 100644 index 0000000..ef3311b --- /dev/null +++ b/src/server/tftp_rom/main.cc @@ -0,0 +1,525 @@ +/* + * \brief TFTP client, ROM server + * \author Emery Hemingway + * \date 2016-02-24 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* lwIP raw API */ +#include +#include +#include +#include +#include + + +namespace Tftp_rom { + + using namespace Genode; + + class Timeout_dispatcher; + class Session_component; + class Root; + struct Main; + + typedef List Session_list; + +} + + +extern "C" void rrq_cb(void *arg, udp_pcb *upcb, pbuf *pbuf, + ip_addr_t *addr, Genode::uint16_t port); + +extern "C" void data_cb(void *arg, udp_pcb *upcb, pbuf *pbuf, + ip_addr_t *addr, Genode::uint16_t port); + + +class Tftp_rom::Session_component : + public Genode::Rpc_object, + public Session_list::Element, + Genode::Lock +{ + private: + + typedef Genode::String<128> Filename; + Filename const _filename; + + Rom_dataspace_capability _dataspace; + Signal_context_capability _sigh; + + udp_pcb *_pcb; /* lwIP UDP context */ + pbuf *_chain_head = NULL; /* lwIP buffer chain head */ + pbuf *_chain_tail = NULL; /* lwIP buffer chain tail */ + + unsigned long const _start; /* start of session */ + + unsigned _ack_timeout = 1 << 11; + unsigned const _client_timeout; + + uint16_t _block_num; /* TFTP block number */ + + ip_addr_t _addr; + uint16_t const _port; + + inline void finalize() + { + unlock(); + _ack_timeout = 0; + pbuf_free(_chain_head); + } + + inline void timeout() + { + PERR("%s timed out", _filename.string()); + finalize(); + } + + public: + + void initial_request() + { + udp_bind(_pcb, IP_ADDR_ANY, 0); + + Genode::size_t filename_len = Genode::strlen(_filename.string()); + + pbuf *req = pbuf_alloc(PBUF_TRANSPORT, filename_len+9, PBUF_RAM); + + uint8_t *buf = (uint8_t*)req->payload; + + buf[0] = 0x00; + buf[1] = 0x01; + + Genode::strncpy((char*)buf+2, _filename.string(), filename_len+1); + Genode::strncpy((char*)buf+3+filename_len, "octet", 6); + + udp_sendto(_pcb, req, &_addr, _port); + } + + Session_component(char const *namestr, + ip_addr &ipaddr, + uint16_t port, + unsigned long now, + unsigned timeout) + : + Lock(LOCKED), + _filename(namestr), + _pcb(udp_new()), + _start(now), + _client_timeout(timeout), + _addr(ipaddr), _port(port) + { + if (_pcb == NULL) { + PERR("failed to create UDP context"); + throw Genode::Root::Unavailable(); + } + + /* set callback */ + udp_recv(_pcb, rrq_cb, this); + + initial_request(); + } + + ~Session_component() { if (_pcb != NULL) udp_remove(_pcb); } + + /************************************** + ** Members available to lwIP thread ** + **************************************/ + + ip_addr_t *addr() { return &_addr; } + + void send_ack() + { + pbuf *ack = pbuf_alloc(PBUF_TRANSPORT, 4, PBUF_RAM); + uint8_t *buf = (uint8_t*)ack->payload; + + buf[0] = 0x00; + buf[1] = 0x04; + + buf[2] = _block_num >> 8; + buf[3] = _block_num; + + udp_send(_pcb, ack); + } + + void first_response(pbuf *data, ip_addr_t *addr, uint16_t port) + { + /* + * we now know the port the server will use, + * lwIP will now drop all other packets + */ + udp_connect(_pcb, addr, port); + + /* swap out the callback */ + udp_recv(_pcb, data_cb, this); + } + + /** + * Returns false if data was not in + * response to the last request + */ + bool add_block(pbuf *data) + { + uint8_t *buf = (uint8_t*)data->payload; + + /* TFTP packets always start with zero */ + if (buf[0]) + return false; + + if (buf[1] == 0x05) { + buf[data->len-1] = '\0'; + PERR("%s: %s", _filename.string(), (char*)buf+4); + _ack_timeout = 0; + /* permanent error, inform the client */ + finalize(); + pbuf_free(data); + return true; + } + + if ((buf[1] != 0x03) + || (host_to_big_endian(*((uint16_t*)buf+1)) != (_block_num+1))) + return false; + + ++_block_num; + send_ack(); + + bool done = data->len < 516; + + /* hit the hard 32MB limit */ + if (!done && _block_num == 0xffff) { + PERR("%s: maximum file size exceded!", _filename.string()); + finalize(); + } + + /* data pointer is invalid after pbuf_cat */ + if (_chain_head == NULL) _chain_head = _chain_tail = data; + else pbuf_chain(_chain_tail, data); + + if (done) /* construct the dataspace */ { + + size_t rom_len = 0; + + /* + * pbuf.tot_len is only a 16 bit number so + * a recount is probably required + */ + for (pbuf *link = _chain_head; link != NULL; link = link->next) + rom_len += link->len-4; + + Genode::Dataspace_capability ds = + Genode::env()->ram_session()->alloc(rom_len); + uint8_t *rom_addr = Genode::env()->rm_session()->attach(ds); + uint8_t *p = rom_addr; + + for (pbuf *link = _chain_head; link != NULL; link = link->next) { + size_t len = link->len - 4; + Genode::memcpy(p, ((uint8_t*)link->payload)+4, len); + p += len; + } + + _dataspace = Genode::static_cap_cast(ds); + Genode::env()->rm_session()->detach(rom_addr); + PLOG("%s retrieved", _filename.string()); + finalize(); + } + + return true; + } + + /************************************* + ** Tiggered by timer on RPC thread ** + *************************************/ + + bool done() const { return _ack_timeout == 0; } + + void check_time(unsigned long now) + { + /* XXX: timer rollover? */ + if (!_block_num) { + if (_client_timeout & (_client_timeout < now - _start)) + timeout(); + else + initial_request(); + return; + } + + unsigned period = (now - _start) / _block_num; + + if (_client_timeout && (_client_timeout < period)) { + timeout(); + return; + } + + if (_ack_timeout < period) + send_ack(); + + _ack_timeout = period+(period/2); + } + + /*************************** + ** ROM session interface ** + ***************************/ + + Rom_dataspace_capability dataspace() override + { + if (!done()) lock(); + return _dataspace; + }; + + void sigh(Signal_context_capability sigh) override { _sigh = sigh; } + +}; + + +/******************** + ** lwIP callbacks ** + ********************/ + + +extern "C" void rrq_cb(void *arg, udp_pcb *upcb, pbuf *data, + ip_addr_t *addr, Genode::uint16_t port) +{ + Tftp_rom::Session_component *session = (Tftp_rom::Session_component*)arg; + + if (!ip_addr_cmp(addr, session->addr())) { + PERR("dropping rogue packet"); + pbuf_free(data); + return; + } + + if (session->add_block(data)) { + session->first_response(data, addr, port); + return; + } + + pbuf_free(data); + session->initial_request(); +} + + +extern "C" void data_cb(void *arg, udp_pcb *upcb, pbuf *data, + ip_addr_t *addr, Genode::uint16_t port) +{ + Tftp_rom::Session_component *session = (Tftp_rom::Session_component*)arg; + if (session->add_block(data)) return; + + /* bad packet */ + pbuf_free(data); + session->send_ack(); +} + + +class Tftp_rom::Root : public Genode::Root_component +{ + private: + + typedef Genode::Thread<8192> Timeout_base; + + class Timeout_dispatcher : Timeout_base, Genode::Lock + { + private: + + enum { TIMER_PERIOD_US = 1 << 20 }; + + Timer::Connection _timer; + Signal_receiver _sig_rec; + Signal_context _sig_ctx; + Signal_context_capability _sig_cap; + Session_list _sessions; + + protected: + + void entry() override + { + for (Genode::Signal_context *ctx = _sig_rec.wait_for_signal().context(); + ctx == &_sig_ctx; + ctx = _sig_rec.wait_for_signal().context()) + { + Lock::Guard(*this); + + Session_component *session = _sessions.first(); + if (!session) { + _timer.sigh(Signal_context_capability()); + continue; + } + + unsigned long now = _timer.elapsed_ms(); + + do { + if (session->done()) { + Session_component *old = session; + session = old->next(); + _sessions.remove(old); + } else { + session->check_time(now); + session = session->next(); + } + } while (session); + } + } + + public: + + Timeout_dispatcher() + : + Timeout_base(Cpu_session::DEFAULT_WEIGHT-1, "timeout_ep"), + _sig_cap(_sig_rec.manage(&_sig_ctx)) + { + _timer.trigger_periodic(TIMER_PERIOD_US); + start(); + } + + /* A destructor for style */ + ~Timeout_dispatcher() + { + /* break entry loop */ + Genode::Signal_context tmp_ctx; + Genode::Signal_transmitter(_sig_rec.manage(&tmp_ctx)).submit(); + join(); + _sig_rec.dissolve(&tmp_ctx); + _sig_rec.dissolve(&_sig_ctx); + } + + unsigned long elapsed_ms() { return _timer.elapsed_ms(); } + + void insert(Session_component *session) + { + Lock::Guard(*this); + + if (!_sessions.first()) + _timer.sigh(_sig_cap); + + _sessions.insert(session); + } + + void remove(Session_component *session) + { + Lock::Guard(*this); + + _sessions.remove(session); + /* timer will be stopped at the next signal */ + } + + } _timeout_dispatcher; + + protected: + + Session_component *_create_session(const char *args) override + { + Session_component *session; + + config()->reload(); + + ip_addr ipaddr; + unsigned port = 69; + unsigned timeout = 0; + + enum { FILENAME_MAX_LEN = 128 }; + char filename[FILENAME_MAX_LEN]; + Genode::Arg_string::find_arg(args, "filename") + .string(filename, sizeof(filename), ""); + + Session_label const label(args); + + try { + + Session_policy policy(label); + + try { + char addr_str[53]; + policy.attribute("ip").value(addr_str, sizeof(addr_str)); + ipaddr_aton(addr_str, &ipaddr); + } catch (...) { + PERR("%s: 'ip' not specified in policy", label.string()); + throw Root::Unavailable(); + } + + try { policy.attribute("port").value(&port); } + catch (...) { } + + try { policy.attribute("timeout").value(&timeout); } + catch (...) { } + + try { + Path<1024> path; + + policy.attribute("dir").value(path.base(), path.capacity()); + path.append("/"); + path.append(filename); + + session = new (md_alloc()) + Session_component(path.base(), ipaddr, port, + _timeout_dispatcher.elapsed_ms(), timeout*1000); + PLOG("%s:%s requested", path.base(), label.string()); + } catch (...) { + session = new (md_alloc()) + Session_component(filename, ipaddr, port, + _timeout_dispatcher.elapsed_ms(), timeout*1000); + PLOG("%s requested", label.string()); + } + + } catch (Session_policy::No_policy_defined) { + PERR("no policy for defined for %s", label.string()); + throw Root::Unavailable(); + } + + _timeout_dispatcher.insert(session); + return session; + } + + void _destroy_session(Session_component *session) override + { + _timeout_dispatcher.remove(session); + destroy(md_alloc(), session); + } + + public: + + Root(Server::Entrypoint &ep, Genode::Allocator &md_alloc) + : Genode::Root_component(&ep.rpc_ep(), &md_alloc) { } +}; + + +struct Tftp_rom::Main +{ + Server::Entrypoint &_ep; + + Sliced_heap _sliced_heap = { env()->ram_session(), env()->rm_session() }; + + Root _root = { _ep, _sliced_heap }; + + Main(Server::Entrypoint &ep) : _ep(ep) + { + /* DHCP hasn't settled, but timeouts take care of that */ + env()->parent()->announce(_ep.manage(_root)); + } +}; + + +namespace Server { + + char const *name() { return "tftp_rom_ep"; } + + size_t stack_size() { return 4*1024*sizeof(long); } + + void construct(Entrypoint &ep) { static Tftp_rom::Main main(ep); } +} diff --git a/src/server/tftp_rom/target.mk b/src/server/tftp_rom/target.mk new file mode 100644 index 0000000..1ff12f8 --- /dev/null +++ b/src/server/tftp_rom/target.mk @@ -0,0 +1,3 @@ +TARGET = tftp_rom +SRC_CC = main.cc +LIBS = config server lwip libc libc_lwip libc_lwip_nic_dhcp