sandbox/init: route model

This commit optimizes the 'Child::resolve_session_request'
implementation by introducing an internal 'Route_model' for quickly
traversing routing rules instead of parsing XML on each session request.

Fixes #4068
This commit is contained in:
Norman Feske 2021-04-05 18:46:51 +02:00
parent b661459aca
commit f5f5b8c1f1
3 changed files with 373 additions and 97 deletions

View File

@ -68,8 +68,9 @@ Sandbox::Child::apply_config(Xml_node start_node)
*/
start_node.with_sub_node("route", [&] (Xml_node const &route) {
_start_node->xml().with_sub_node("route", [&] (Xml_node const &orig) {
if (route.differs_from(orig))
_uncertain_dependencies = true; }); });
if (route.differs_from(orig)) {
_construct_route_model_from_start_node(start_node);
_uncertain_dependencies = true; } }); });
/*
* Determine how the inline config is affected.
@ -505,9 +506,10 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name,
Session_label const &label,
Session::Diag const diag)
{
bool const rom_service = (service_name == Rom_session::service_name());
/* check for "config" ROM request */
if (service_name == Rom_session::service_name() &&
label.last_element() == "config") {
if (rom_service && label.last_element() == "config") {
if (_config_rom_service.constructed() &&
!_config_rom_service->abandoned())
@ -529,113 +531,89 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name,
* we resolve the session request with the binary name as label.
* Otherwise the regular routing is applied.
*/
if (service_name == Rom_session::service_name() &&
label == _unique_name && _unique_name != _binary_name)
if (rom_service && label == _unique_name && _unique_name != _binary_name)
return resolve_session_request(service_name, _binary_name, diag);
/* supply binary as dynamic linker if '<start ld="no">' */
if (!_use_ld && service_name == Rom_session::service_name() && label == "ld.lib.so")
if (rom_service && !_use_ld && label == "ld.lib.so")
return resolve_session_request(service_name, _binary_name, diag);
/* check for "session_requests" ROM request */
if (service_name == Rom_session::service_name()
&& label.last_element() == Session_requester::rom_name())
if (rom_service && label.last_element() == Session_requester::rom_name())
return Route { _session_requester.service(), Session::Label(), diag };
try {
Xml_node route_node = _default_route_accessor.default_route();
try {
route_node = _start_node->xml().sub_node("route"); }
catch (...) { }
Xml_node service_node = route_node.sub_node();
auto resolve_at_target = [&] (Xml_node const &target) -> Route
{
/*
* Determine session label to be provided to the server
*
* By default, the client's identity (accompanied with the a
* client-provided label) is presented as session label to the
* server. However, the target node can explicitly override the
* client's identity by a custom label via the 'label'
* attribute.
*/
typedef String<Session_label::capacity()> Label;
Label const target_label =
target.attribute_value("label", Label(label.string()));
for (; ; service_node = service_node.next()) {
Session::Diag const
target_diag { target.attribute_value("diag", diag.enabled) };
bool service_wildcard = service_node.has_type("any-service");
auto no_filter = [] (Service &) -> bool { return false; };
if (!service_node_matches(service_node, label, name(), service_name))
continue;
if (target.has_type("parent")) {
Xml_node target = service_node.sub_node();
for (; ; target = target.next()) {
/*
* Determine session label to be provided to the server
*
* By default, the client's identity (accompanied with the a
* client-provided label) is presented as session label to the
* server. However, the target node can explicitly override the
* client's identity by a custom label via the 'label'
* attribute.
*/
typedef String<Session_label::capacity()> Label;
Label const target_label =
target.attribute_value("label", Label(label.string()));
Session::Diag const
target_diag { target.attribute_value("diag", diag.enabled) };
auto no_filter = [] (Service &) -> bool { return false; };
if (target.has_type("parent")) {
try {
return Route { find_service(_parent_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("local")) {
try {
return Route { find_service(_local_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("child")) {
typedef Name_registry::Name Name;
Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name);
auto filter_server_name = [&] (Routed_service &s) -> bool {
return s.child_name() != server_name; };
try {
return Route { find_service(_child_services, service_name, filter_server_name),
target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("any-child")) {
if (is_ambiguous(_child_services, service_name)) {
error(name(), ": ambiguous routes to "
"service \"", service_name, "\"");
throw Service_denied();
}
try {
return Route { find_service(_child_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
if (!service_wildcard) {
warning(name(), ": lookup for service \"", service_name, "\" failed");
throw Service_denied();
}
if (target.last())
break;
}
try {
return Route { find_service(_parent_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
} catch (Xml_node::Nonexistent_sub_node) { }
warning(name(), ": no route to service \"", service_name, "\" (label=\"", label, "\")");
throw Service_denied();
if (target.has_type("local")) {
try {
return Route { find_service(_local_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("child")) {
typedef Name_registry::Name Name;
Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name);
auto filter_server_name = [&] (Routed_service &s) -> bool {
return s.child_name() != server_name; };
try {
return Route { find_service(_child_services, service_name,
filter_server_name), target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("any-child")) {
if (is_ambiguous(_child_services, service_name)) {
error(name(), ": ambiguous routes to "
"service \"", service_name, "\"");
throw Service_denied();
}
try {
return Route { find_service(_child_services, service_name,
no_filter), target_label, target_diag };
} catch (Service_denied) { }
}
throw Service_denied();
};
Route_model::Query const query(name(), service_name, label);
return _route_model->resolve(query, resolve_at_target);
}
@ -796,6 +774,8 @@ Sandbox::Child::Child(Env &env,
log(" priority: ", _resources.priority);
}
_construct_route_model_from_start_node(start_node);
/*
* Determine services provided by the child
*/

View File

@ -29,6 +29,7 @@
#include <name_registry.h>
#include <service.h>
#include <utils.h>
#include <route_model.h>
namespace Sandbox { class Child; }
@ -113,6 +114,21 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Reconstructible<Buffered_xml> _start_node;
Constructible<Route_model> _route_model { };
void _construct_route_model_from_start_node(Xml_node const &start)
{
_route_model.destruct();
start.with_sub_node("route", [&] (Xml_node const &route) {
_route_model.construct(_alloc, route); });
if (_route_model.constructed())
return;
_route_model.construct(_alloc, _default_route_accessor.default_route());
}
/*
* Version attribute of the start node, used to force child restarts.
*/

View File

@ -0,0 +1,280 @@
/*
* \brief Internal model of routing rules
* \author Norman Feske
* \date 2021-04-05
*/
/*
* Copyright (C) 2021 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 _ROUTE_MODEL_H_
#define _ROUTE_MODEL_H_
/* local includes */
#include <types.h>
namespace Sandbox {
struct Checksum;
class Route_model;
}
struct Sandbox::Checksum
{
unsigned long value = 0;
bool valid;
/**
* Constructor
*/
Checksum(char const *s) : valid(s != nullptr)
{
if (!s)
return;
while (uint8_t const byte = *s++) {
/* rotate value */
unsigned long const sign_bit = ((long)value < 0);
value = (value << 1) | sign_bit;
/* xor byte to lowest 8 bit */
value = value ^ (unsigned long)byte;
}
}
template <size_t N>
Checksum(String<N> const &s) : Checksum(s.string()) { }
bool operator != (Checksum const &other) const
{
return (other.value != value) || !valid;
}
};
class Sandbox::Route_model : Noncopyable
{
public:
struct Query : Noncopyable
{
Child_policy::Name const &child;
Service::Name const &service;
Session_label const &label;
Checksum const service_checksum { service };
Checksum const label_checksum { skip_label_prefix(child.string(),
label.string()) };
Query(Child_policy::Name const &child,
Service::Name const &service,
Session_label const &label)
:
child(child), service(service), label(label)
{ }
};
class Rule : Noncopyable, List<Rule>::Element
{
private:
friend class List<Rule>;
friend class Route_model;
friend void destroy<Rule>(Allocator &, Rule *);
Allocator &_alloc;
Xml_node const _node; /* points to 'Route_model::_route_node' */
struct Selector
{
typedef String<Session_label::capacity()> Label;
enum class Type
{
NO_LABEL, SPECIFIC_LABEL,
/*
* Presence of 'label_last', 'label_prefix',
* 'label_suffix', 'unscoped_label', or even
* a combination of attributes.
*/
COMPLICATED
} type = Type::NO_LABEL;
Checksum label_checksum { "" };
Selector(Xml_node const &node)
{
bool const complicated =
node.has_attribute("label_prefix") ||
node.has_attribute("label_suffix") ||
node.has_attribute("label_last") ||
node.has_attribute("unscoped_label");
if (complicated) {
type = Type::COMPLICATED;
return;
}
Label const label = node.attribute_value("label", Label());
if (label.valid()) {
type = Type::SPECIFIC_LABEL;
label_checksum = Checksum(label);
}
}
};
Selector const _selector;
Checksum const _service_checksum;
bool const _specific_service { _node.has_type("service") };
struct Target : Noncopyable, private List<Target>::Element
{
friend class List<Target>;
friend class Rule;
Xml_node const node; /* points to 'Route_model::_route_node' */
Target(Xml_node const &node) : node(node) { }
};
List<Target> _targets { };
/**
* Constructor is private to 'Route_model'
*/
Rule(Allocator &alloc, Xml_node const &node)
:
_alloc(alloc), _node(node), _selector(node),
_service_checksum(node.attribute_value("name", Service::Name()))
{
Target const *at_ptr = nullptr;
node.for_each_sub_node([&] (Xml_node sub_node) {
Target &target = *new (_alloc) Target(sub_node);
_targets.insert(&target, at_ptr);
at_ptr = &target;
});
}
~Rule()
{
while (Target *target_ptr = _targets.first()) {
_targets.remove(target_ptr);
destroy(_alloc, target_ptr);
}
}
/**
* Quick check for early detection of definite mismatches
*
* \return true if query definitely mismatches the rule,
* false if the undecided
*/
bool _mismatches(Query const &query) const
{
if (_specific_service
&& query.service_checksum != _service_checksum)
return true;
if (_selector.type == Selector::Type::SPECIFIC_LABEL
&& query.label_checksum != _selector.label_checksum)
return true;
return false;
}
public:
bool matches(Query const &query) const
{
/* handle common case */
if (_mismatches(query))
return false;
return service_node_matches(_node,
query.label,
query.child,
query.service);
}
template <typename FN>
Child_policy::Route resolve(FN const &fn) const
{
for (Target const *t = _targets.first(); t; t = t->next()) {
try { return fn(t->node); }
catch (Service_denied) { /* try next target */ }
}
/* query is not accepted by any of the targets */
throw Service_denied();
}
};
private:
Allocator &_alloc;
Buffered_xml const _route_node;
List<Rule> _rules { };
public:
Route_model(Allocator &alloc, Xml_node const &route)
:
_alloc(alloc), _route_node(_alloc, route)
{
Rule const *at_ptr = nullptr;
_route_node.xml().for_each_sub_node([&] (Xml_node const &node) {
Rule &rule = *new (_alloc) Rule(_alloc, node);
_rules.insert(&rule, at_ptr); /* append */
at_ptr = &rule;
});
}
~Route_model()
{
while (Rule *rule_ptr = _rules.first()) {
_rules.remove(rule_ptr);
destroy(_alloc, rule_ptr);
}
}
template <typename FN>
Child_policy::Route resolve(Query const &query, FN const &fn) const
{
for (Rule const *r = _rules.first(); r; r = r->next())
if (r->matches(query)) {
try {
return r->resolve(fn);
}
catch (Service_denied) {
if (r->_specific_service)
throw;
/*
* If none of the targets of a wildcard rule was
* satisfied with the query, continue with the next
* rule.
*/
}
}
warning(query.child, ": no route to "
"service \"", query.service, "\" "
"(label=\"", query.label, "\")");
throw Service_denied();
}
};
#endif /* _ROUTE_MODEL_H_ */