genode/repos/os/src/server/nic_router/link.cc

363 lines
9.3 KiB
C++

/*
* \brief State tracking for UDP/TCP connections
* \author Martin Stein
* \date 2016-08-19
*/
/*
* Copyright (C) 2016-2017 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/tcp.h>
/* local includes */
#include "link.h"
#include "configuration.h"
using namespace Net;
using namespace Genode;
/******************
** Link_side_id **
******************/
constexpr size_t Link_side_id::data_size()
{
return sizeof(src_ip) + sizeof(src_port) +
sizeof(dst_ip) + sizeof(dst_port);
}
bool Link_side_id::operator == (Link_side_id const &id) const
{
return memcmp(id.data_base(), data_base(), data_size()) == 0;
}
bool Link_side_id::operator > (Link_side_id const &id) const
{
return memcmp(id.data_base(), data_base(), data_size()) > 0;
}
/***************
** Link_side **
***************/
Link_side::Link_side(Domain &domain,
Link_side_id const &id,
Link &link)
:
_domain(domain), _id(id), _link(link)
{
if (link.config().verbose()) {
log("[", domain, "] new ", l3_protocol_name(link.protocol()),
" link ", is_client() ? "client" : "server", ": ", *this);
}
}
Link_side const &Link_side::find_by_id(Link_side_id const &id) const
{
if (id == _id) {
return *this; }
bool const side = id > _id;
Link_side *const link_side = Avl_node<Link_side>::child(side);
if (!link_side) {
throw Link_side_tree::No_match(); }
return link_side->find_by_id(id);
}
void Link_side::print(Output &output) const
{
Genode::print(output, "src ", src_ip(), ":", src_port(),
" dst ", dst_ip(), ":", dst_port());
}
bool Link_side::is_client() const
{
return this == &_link.client();
}
/********************
** Link_side_tree **
********************/
Link_side const &Link_side_tree::find_by_id(Link_side_id const &id) const
{
Link_side *const link_side = first();
if (!link_side) {
throw No_match(); }
return link_side->find_by_id(id);
}
/**********
** Link **
**********/
void Link::print(Output &output) const
{
Genode::print(output, "CLN ", _client, " SRV ", _server);
}
Link::Link(Interface &cln_interface,
Link_side_id const &cln_id,
Pointer<Port_allocator_guard> srv_port_alloc,
Domain &srv_domain,
Link_side_id const &srv_id,
Timer::Connection &timer,
Configuration &config,
L3_protocol const protocol,
Microseconds const dissolve_timeout,
Interface_link_stats &stats)
:
_config(config),
_client_interface(cln_interface),
_server_port_alloc(srv_port_alloc),
_dissolve_timeout(timer, *this, &Link::_handle_dissolve_timeout),
_dissolve_timeout_us(dissolve_timeout),
_protocol(protocol),
_client(cln_interface.domain(), cln_id, *this),
_server(srv_domain, srv_id, *this),
_stats(stats),
_stats_curr(stats.opening)
{
_stats_curr()++;
_client_interface.links(_protocol).insert(this);
_client.domain().links(_protocol).insert(&_client);
_server.domain().links(_protocol).insert(&_server);
_dissolve_timeout.schedule(_dissolve_timeout_us);
}
Link::~Link() { _stats.destroyed++; }
void Link::_handle_dissolve_timeout(Duration)
{
dissolve(true);
_client_interface.links(_protocol).remove(this);
_client_interface.dissolved_links(_protocol).insert(this);
}
void Link::dissolve(bool timeout)
{
_stats_curr()--;
if (timeout) {
if (&_stats_curr() == &_stats.opening) { _stats_curr = _stats.dissolved_timeout_opening; }
if (&_stats_curr() == &_stats.open) { _stats_curr = _stats.dissolved_timeout_open; }
if (&_stats_curr() == &_stats.closing) { _stats_curr = _stats.dissolved_timeout_closing; }
if (&_stats_curr() == &_stats.closed) { _stats_curr = _stats.dissolved_timeout_closed; }
} else {
_stats_curr = _stats.dissolved_no_timeout;
}
_stats_curr()++;
_client.domain().links(_protocol).remove(&_client);
_server.domain().links(_protocol).remove(&_server);
if (_config().verbose()) {
log("Dissolve ", l3_protocol_name(_protocol), " link: ", *this); }
try {
if (_config().verbose()) {
log("Free ", l3_protocol_name(_protocol),
" port ", _server.dst_port(),
" at ", _server.domain(),
" that was used by ", _client.domain());
}
_server_port_alloc().free(_server.dst_port());
}
catch (Pointer<Port_allocator_guard>::Invalid) { }
}
void Link::handle_config(Domain &cln_domain,
Domain &srv_domain,
Pointer<Port_allocator_guard> srv_port_alloc,
Configuration &config)
{
Microseconds dissolve_timeout_us(0);
switch (_protocol) {
case L3_protocol::TCP: dissolve_timeout_us = config.tcp_idle_timeout(); break;
case L3_protocol::UDP: dissolve_timeout_us = config.udp_idle_timeout(); break;
case L3_protocol::ICMP: dissolve_timeout_us = config.icmp_idle_timeout(); break;
default: throw Interface::Bad_transport_protocol();
}
_dissolve_timeout_us = dissolve_timeout_us;
_dissolve_timeout.schedule(_dissolve_timeout_us);
_client.domain().links(_protocol).remove(&_client);
_server.domain().links(_protocol).remove(&_server);
_config = config;
_client._domain = cln_domain;
_server._domain = srv_domain;
_server_port_alloc = srv_port_alloc;
cln_domain.links(_protocol).insert(&_client);
srv_domain.links(_protocol).insert(&_server);
if (config.verbose()) {
log("[", cln_domain, "] update link client: ", _client);
log("[", srv_domain, "] update link server: ", _server);
}
}
/**************
** Tcp_link **
**************/
Tcp_link::Tcp_link(Interface &cln_interface,
Link_side_id const &cln_id,
Pointer<Port_allocator_guard> srv_port_alloc,
Domain &srv_domain,
Link_side_id const &srv_id,
Timer::Connection &timer,
Configuration &config,
L3_protocol const protocol,
Interface_link_stats &stats)
:
Link(cln_interface, cln_id, srv_port_alloc, srv_domain, srv_id, timer,
config, protocol, config.tcp_idle_timeout(), stats)
{ }
void Tcp_link::_closing()
{
_state = State::CLOSING;
_stats_curr()--;
_stats_curr = _stats.closing;
_stats_curr()++;
}
void Tcp_link::_closed()
{
_state = State::CLOSED;
_stats_curr()--;
_stats_curr = _stats.closed;
_stats_curr()++;
}
void Tcp_link::_tcp_packet(Tcp_packet &tcp,
Peer &sender,
Peer &receiver)
{
if (_state == State::CLOSED) {
return; }
if (tcp.rst()) {
_closed();
} else {
if (tcp.fin()) {
sender.fin = true;
_closing();
}
if (receiver.fin && tcp.ack()) {
receiver.fin_acked = true;
if (sender.fin_acked) {
_closed(); }
else {
_closing();
}
}
}
if (_state == State::OPEN) {
_packet();
} else {
_dissolve_timeout.schedule(
Microseconds(_config().tcp_max_segm_lifetime().value << 1));
}
}
void Tcp_link::server_packet(Tcp_packet &tcp)
{
if (_opening) {
_opening = false;
_stats_curr()--;
if (&_stats_curr() == &_stats.opening) { _stats_curr = _stats.open; }
_stats_curr()++;
}
_tcp_packet(tcp, _server, _client);
}
/**************
** Udp_link **
**************/
Udp_link::Udp_link(Interface &cln_interface,
Link_side_id const &cln_id,
Pointer<Port_allocator_guard> srv_port_alloc,
Domain &srv_domain,
Link_side_id const &srv_id,
Timer::Connection &timer,
Configuration &config,
L3_protocol const protocol,
Interface_link_stats &stats)
:
Link(cln_interface, cln_id, srv_port_alloc, srv_domain, srv_id, timer,
config, protocol, config.udp_idle_timeout(), stats)
{ }
void Udp_link::server_packet()
{
if (_opening) {
_opening = false;
_stats_curr()--;
if (&_stats_curr() == &_stats.opening) { _stats_curr = _stats.open; }
_stats_curr()++;
}
_packet();
}
/***************
** Icmp_link **
***************/
Icmp_link::Icmp_link(Interface &cln_interface,
Link_side_id const &cln_id,
Pointer<Port_allocator_guard> srv_port_alloc,
Domain &srv_domain,
Link_side_id const &srv_id,
Timer::Connection &timer,
Configuration &config,
L3_protocol const protocol,
Interface_link_stats &stats)
:
Link(cln_interface, cln_id, srv_port_alloc, srv_domain, srv_id, timer,
config, protocol, config.icmp_idle_timeout(), stats)
{ }
void Icmp_link::server_packet()
{
if (_opening) {
_opening = false;
_stats_curr()--;
if (&_stats_curr() == &_stats.opening) { _stats_curr = _stats.open; }
_stats_curr()++;
}
_packet();
}