mirror of
https://github.com/genodelabs/genode.git
synced 2025-01-18 18:56:29 +00:00
nic_router: preserve session link state until read
See the NIC router README paragraph "Behavior regarding the NIC-session link state" for further information. Ref #3931
This commit is contained in:
parent
a7b878cbb5
commit
b0327d0544
@ -517,6 +517,46 @@ When set to zero, the limit is deactivated, meaning that the router always
|
||||
handles all available packets of a NIC session.
|
||||
|
||||
|
||||
Behavior regarding the NIC-session link state
|
||||
---------------------------------------------
|
||||
|
||||
At downlinks, the NIC router applies a feedback-driven state machine for the
|
||||
link state in order to ensure that clients recognize state transitions. This
|
||||
means that the NIC router guarantees that a new link state remains fixed until
|
||||
the client has read it using the corresponding RPC at the NIC-session
|
||||
interface. If, in the meantime, further "up" and "down" edges occur, they are
|
||||
memorized and executed as soon as the former state has been read by the
|
||||
client. Such postponed link state edges are merged in a way that they result in
|
||||
two contrary edges at a max. The following diagrams demonstrate this:
|
||||
|
||||
|
||||
! client reads: 0 1 0 1 0 1
|
||||
! _____ _______ _________________
|
||||
! client link state: ____| |______| |_________|
|
||||
! _ __________ _________________
|
||||
! real link state: ____| |_____| |___________|
|
||||
!
|
||||
! time --->
|
||||
|
||||
|
||||
! client reads: 0 1 0 1 0 1
|
||||
! ______ _____________ _____
|
||||
! client link state: ____| |______| |________| |_____
|
||||
! _ _______________________ _ _ _
|
||||
! real link state: ____| |_| |_| |_| |_| |________
|
||||
!
|
||||
! time --->
|
||||
|
||||
|
||||
! client reads: 0 1 0 1 0 1
|
||||
! ________ _________ __________
|
||||
! client link state: ____| |____________| |______|
|
||||
! _ __ _________ _ ___________
|
||||
! real link state: ____| |_| |______________| |_| |_|
|
||||
!
|
||||
! time --->
|
||||
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
|
@ -63,7 +63,9 @@ Interface_policy::Interface_policy(Genode::Session_label const &label,
|
||||
_label { label },
|
||||
_config { config },
|
||||
_session_env { session_env }
|
||||
{ }
|
||||
{
|
||||
_session_link_state_transition(UP);
|
||||
}
|
||||
|
||||
|
||||
Domain_name
|
||||
@ -88,6 +90,153 @@ Net::Session_component::Interface_policy::determine_domain_name() const
|
||||
}
|
||||
|
||||
|
||||
void Net::Session_component::
|
||||
Interface_policy::_session_link_state_transition(Transient_link_state tls)
|
||||
{
|
||||
_transient_link_state = tls;
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
}
|
||||
|
||||
|
||||
void Net::Session_component::Interface_policy::interface_unready()
|
||||
{
|
||||
switch (_transient_link_state) {
|
||||
case UP_ACKNOWLEDGED:
|
||||
|
||||
_session_link_state_transition(DOWN);
|
||||
break;
|
||||
|
||||
case UP:
|
||||
|
||||
_transient_link_state = UP_DOWN;
|
||||
break;
|
||||
|
||||
case DOWN_UP:
|
||||
|
||||
_transient_link_state = DOWN_UP_DOWN;
|
||||
break;
|
||||
|
||||
case UP_DOWN:
|
||||
|
||||
break;
|
||||
|
||||
case UP_DOWN_UP:
|
||||
|
||||
_transient_link_state = UP_DOWN;
|
||||
break;
|
||||
|
||||
case DOWN_ACKNOWLEDGED: break;
|
||||
case DOWN: break;
|
||||
case DOWN_UP_DOWN: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Net::Session_component::Interface_policy::interface_ready()
|
||||
{
|
||||
switch (_transient_link_state) {
|
||||
case DOWN_ACKNOWLEDGED:
|
||||
|
||||
_session_link_state_transition(UP);
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
|
||||
_transient_link_state = DOWN_UP;
|
||||
break;
|
||||
|
||||
case UP_DOWN:
|
||||
|
||||
_transient_link_state = UP_DOWN_UP;
|
||||
break;
|
||||
|
||||
case DOWN_UP:
|
||||
|
||||
break;
|
||||
|
||||
case DOWN_UP_DOWN:
|
||||
|
||||
_transient_link_state = DOWN_UP;
|
||||
break;
|
||||
|
||||
case UP_ACKNOWLEDGED: break;
|
||||
case UP: break;
|
||||
case UP_DOWN_UP: break;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Net::Session_component::Interface_policy::interface_link_state() const
|
||||
{
|
||||
switch (_transient_link_state) {
|
||||
case DOWN_ACKNOWLEDGED: return false;
|
||||
case DOWN: return false;
|
||||
case DOWN_UP: return true;
|
||||
case DOWN_UP_DOWN: return false;
|
||||
case UP_ACKNOWLEDGED: return true;
|
||||
case UP: return true;
|
||||
case UP_DOWN: return false;
|
||||
case UP_DOWN_UP: return true;
|
||||
}
|
||||
class Never_reached : Exception { };
|
||||
throw Never_reached { };
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Net::Session_component::Interface_policy::read_and_ack_session_link_state()
|
||||
{
|
||||
switch (_transient_link_state) {
|
||||
case DOWN_ACKNOWLEDGED:
|
||||
|
||||
return false;
|
||||
|
||||
case DOWN:
|
||||
|
||||
_transient_link_state = DOWN_ACKNOWLEDGED;
|
||||
return false;
|
||||
|
||||
case DOWN_UP:
|
||||
|
||||
_session_link_state_transition(UP);
|
||||
return false;
|
||||
|
||||
case DOWN_UP_DOWN:
|
||||
|
||||
_session_link_state_transition(UP_DOWN);
|
||||
return false;
|
||||
|
||||
case UP_ACKNOWLEDGED:
|
||||
|
||||
return true;
|
||||
|
||||
case UP:
|
||||
|
||||
_transient_link_state = UP_ACKNOWLEDGED;
|
||||
return true;
|
||||
|
||||
case UP_DOWN:
|
||||
|
||||
_session_link_state_transition(DOWN);
|
||||
return true;
|
||||
|
||||
case UP_DOWN_UP:
|
||||
|
||||
_session_link_state_transition(DOWN_UP);
|
||||
return true;
|
||||
}
|
||||
class Never_reached { };
|
||||
throw Never_reached { };
|
||||
}
|
||||
|
||||
|
||||
void Net::Session_component::Interface_policy::
|
||||
session_link_state_sigh(Signal_context_capability sigh)
|
||||
{
|
||||
_session_link_state_sigh = sigh;
|
||||
}
|
||||
|
||||
|
||||
/***********************
|
||||
** Session_component **
|
||||
***********************/
|
||||
@ -109,7 +258,7 @@ Net::Session_component::Session_component(Session_env &sessio
|
||||
_interface_policy { label, _session_env, config },
|
||||
_interface { _session_env.ep(), timer, router_mac, _alloc,
|
||||
mac, config, interfaces, *_tx.sink(),
|
||||
*_rx.source(), _link_state, _interface_policy },
|
||||
*_rx.source(), _interface_policy },
|
||||
_ram_ds { ram_ds }
|
||||
{
|
||||
_interface.attach_to_domain();
|
||||
@ -121,6 +270,19 @@ Net::Session_component::Session_component(Session_env &sessio
|
||||
}
|
||||
|
||||
|
||||
bool Net::Session_component::link_state()
|
||||
{
|
||||
return _interface_policy.read_and_ack_session_link_state();
|
||||
}
|
||||
|
||||
|
||||
void Net::
|
||||
Session_component::link_state_sigh(Signal_context_capability sigh)
|
||||
{
|
||||
_interface_policy.session_link_state_sigh(sigh);
|
||||
}
|
||||
|
||||
|
||||
/**********
|
||||
** Root **
|
||||
**********/
|
||||
|
@ -257,13 +257,45 @@ class Net::Session_component : private Session_component_base,
|
||||
{
|
||||
private:
|
||||
|
||||
struct Interface_policy : Net::Interface_policy
|
||||
class Interface_policy : public Net::Interface_policy
|
||||
{
|
||||
private:
|
||||
|
||||
using Signal_context_capability =
|
||||
Genode::Signal_context_capability;
|
||||
|
||||
/*
|
||||
* The transient link state is a combination of session and
|
||||
* interface link state. The first word in the value name
|
||||
* denotes the link state of the session. If the session
|
||||
* link state has already been read by the client and
|
||||
* can therefore be altered directly, it is marked as
|
||||
* 'ACKNOWLEDGED'. Otherwise, the denoted session state
|
||||
* has to stay fixed until the client has read it. In this
|
||||
* case, the session link state in the value name may be
|
||||
* followed by the pending link state edges. Consequently,
|
||||
* the last 'UP' or 'DOWN' in each value name denotes the
|
||||
* router-internal interface link-state.
|
||||
*/
|
||||
enum Transient_link_state
|
||||
{
|
||||
DOWN_ACKNOWLEDGED,
|
||||
DOWN,
|
||||
DOWN_UP,
|
||||
DOWN_UP_DOWN,
|
||||
UP_ACKNOWLEDGED,
|
||||
UP,
|
||||
UP_DOWN,
|
||||
UP_DOWN_UP
|
||||
};
|
||||
|
||||
Genode::Session_label const _label;
|
||||
Const_reference<Configuration> _config;
|
||||
Genode::Session_env const &_session_env;
|
||||
Transient_link_state _transient_link_state { DOWN_ACKNOWLEDGED };
|
||||
Signal_context_capability _session_link_state_sigh { };
|
||||
|
||||
void _session_link_state_transition(Transient_link_state tls);
|
||||
|
||||
public:
|
||||
|
||||
@ -271,6 +303,10 @@ class Net::Session_component : private Session_component_base,
|
||||
Genode::Session_env const &session_env,
|
||||
Configuration const &config);
|
||||
|
||||
bool read_and_ack_session_link_state();
|
||||
|
||||
void session_link_state_sigh(Genode::Signal_context_capability sigh);
|
||||
|
||||
|
||||
/***************************
|
||||
** Net::Interface_policy **
|
||||
@ -280,9 +316,11 @@ class Net::Session_component : private Session_component_base,
|
||||
void handle_config(Configuration const &config) override { _config = config; }
|
||||
Genode::Session_label const &label() const override { return _label; }
|
||||
void report(Genode::Xml_generator &xml) const override { _session_env.report(xml); };
|
||||
void interface_unready() override;
|
||||
void interface_ready() override;
|
||||
bool interface_link_state() const override;
|
||||
};
|
||||
|
||||
bool _link_state { true };
|
||||
Interface_policy _interface_policy;
|
||||
Interface _interface;
|
||||
Genode::Ram_dataspace_capability const _ram_ds;
|
||||
@ -306,9 +344,8 @@ class Net::Session_component : private Session_component_base,
|
||||
******************/
|
||||
|
||||
Mac_address mac_address() override { return _interface.mac(); }
|
||||
bool link_state() override { return _interface.link_state(); }
|
||||
void link_state_sigh(Genode::Signal_context_capability sigh) override {
|
||||
_interface.session_link_state_sigh(sigh); }
|
||||
bool link_state() override;
|
||||
void link_state_sigh(Genode::Signal_context_capability sigh) override;
|
||||
|
||||
|
||||
/***************
|
||||
|
@ -318,7 +318,7 @@ Interface::_transport_rules(Domain &local_domain, L3_protocol const prot) const
|
||||
void Interface::_attach_to_domain_raw(Domain &domain)
|
||||
{
|
||||
_domain = domain;
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
_policy.interface_ready();
|
||||
_interfaces.remove(this);
|
||||
domain.attach_interface(*this);
|
||||
}
|
||||
@ -330,7 +330,7 @@ void Interface::_detach_from_domain_raw()
|
||||
domain.detach_interface(*this);
|
||||
_interfaces.insert(this);
|
||||
_domain = Pointer<Domain>();
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
_policy.interface_unready();
|
||||
|
||||
domain.tcp_stats().dissolve_interface(_tcp_stats);
|
||||
domain.udp_stats().dissolve_interface(_udp_stats);
|
||||
@ -347,7 +347,7 @@ void Interface::_update_domain_object(Domain &new_domain) {
|
||||
old_domain.interface_updates_domain_object(*this);
|
||||
_interfaces.insert(this);
|
||||
_domain = Pointer<Domain>();
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
_policy.interface_unready();
|
||||
|
||||
old_domain.tcp_stats().dissolve_interface(_tcp_stats);
|
||||
old_domain.udp_stats().dissolve_interface(_udp_stats);
|
||||
@ -357,7 +357,7 @@ void Interface::_update_domain_object(Domain &new_domain) {
|
||||
|
||||
/* attach raw */
|
||||
_domain = new_domain;
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
_policy.interface_ready();
|
||||
_interfaces.remove(this);
|
||||
new_domain.attach_interface(*this);
|
||||
}
|
||||
@ -408,12 +408,6 @@ void Interface::attach_to_ip_config(Domain &domain,
|
||||
}
|
||||
|
||||
|
||||
void Interface::session_link_state_sigh(Signal_context_capability sigh)
|
||||
{
|
||||
_session_link_state_sigh = sigh;
|
||||
}
|
||||
|
||||
|
||||
void Interface::detach_from_ip_config()
|
||||
{
|
||||
/* destroy our own ARP waiters */
|
||||
@ -440,14 +434,15 @@ void Interface::detach_from_ip_config()
|
||||
void Interface::detach_from_remote_ip_config()
|
||||
{
|
||||
/* only the DNS server address of the local DHCP server can be remote */
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
_policy.interface_unready();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Interface::attach_to_remote_ip_config()
|
||||
{
|
||||
/* only the DNS server address of the local DHCP server can be remote */
|
||||
Signal_transmitter(_session_link_state_sigh).submit();
|
||||
_policy.interface_ready();
|
||||
}
|
||||
|
||||
|
||||
@ -876,11 +871,11 @@ void Interface::_send_icmp_dst_unreachable(Ipv4_address_prefix const &local_intf
|
||||
|
||||
bool Interface::link_state() const
|
||||
{
|
||||
return _domain.valid() && _session_link_state;
|
||||
return _policy.interface_link_state();
|
||||
}
|
||||
|
||||
|
||||
void Interface::handle_link_state()
|
||||
void Interface::handle_interface_link_state()
|
||||
{
|
||||
struct Keep_ip_config : Exception { };
|
||||
try {
|
||||
@ -900,7 +895,7 @@ void Interface::handle_link_state()
|
||||
catch (Keep_ip_config) { }
|
||||
|
||||
/* force report if configured */
|
||||
try { _config().report().handle_link_state(); }
|
||||
try { _config().report().handle_interface_link_state(); }
|
||||
catch (Pointer<Report>::Invalid) { }
|
||||
}
|
||||
|
||||
@ -1713,12 +1708,10 @@ Interface::Interface(Genode::Entrypoint &ep,
|
||||
Interface_list &interfaces,
|
||||
Packet_stream_sink &sink,
|
||||
Packet_stream_source &source,
|
||||
bool &session_link_state,
|
||||
Interface_policy &policy)
|
||||
:
|
||||
_sink { sink },
|
||||
_source { source },
|
||||
_session_link_state { session_link_state },
|
||||
_sink_ack { ep, *this, &Interface::_ack_avail },
|
||||
_sink_submit { ep, *this, &Interface::_ready_to_submit },
|
||||
_source_ack { ep, *this, &Interface::_ready_to_ack },
|
||||
|
@ -92,6 +92,12 @@ struct Net::Interface_policy
|
||||
|
||||
virtual Genode::Session_label const &label() const = 0;
|
||||
|
||||
virtual void interface_ready() = 0;
|
||||
|
||||
virtual void interface_unready() = 0;
|
||||
|
||||
virtual bool interface_link_state() const = 0;
|
||||
|
||||
virtual void report(Genode::Xml_generator &) const { throw Report::Empty(); }
|
||||
|
||||
virtual ~Interface_policy() { }
|
||||
@ -129,8 +135,6 @@ class Net::Interface : private Interface_list::Element
|
||||
|
||||
Packet_stream_sink &_sink;
|
||||
Packet_stream_source &_source;
|
||||
bool &_session_link_state;
|
||||
Signal_context_capability _session_link_state_sigh { };
|
||||
Signal_handler _sink_ack;
|
||||
Signal_handler _sink_submit;
|
||||
Signal_handler _source_ack;
|
||||
@ -351,6 +355,8 @@ class Net::Interface : private Interface_list::Element
|
||||
Ipv4_packet const &req_ip,
|
||||
Icmp_packet::Code const code);
|
||||
|
||||
bool link_state() const;
|
||||
|
||||
|
||||
/***********************************
|
||||
** Packet-stream signal handlers **
|
||||
@ -386,7 +392,6 @@ class Net::Interface : private Interface_list::Element
|
||||
Interface_list &interfaces,
|
||||
Packet_stream_sink &sink,
|
||||
Packet_stream_source &source,
|
||||
bool &session_link_state,
|
||||
Interface_policy &policy);
|
||||
|
||||
virtual ~Interface();
|
||||
@ -442,9 +447,7 @@ class Net::Interface : private Interface_list::Element
|
||||
|
||||
void attach_to_domain_finish();
|
||||
|
||||
bool link_state() const;
|
||||
|
||||
void handle_link_state();
|
||||
void handle_interface_link_state();
|
||||
|
||||
void report(Genode::Xml_generator &xml);
|
||||
|
||||
@ -467,8 +470,6 @@ class Net::Interface : private Interface_list::Element
|
||||
Interface_link_stats &icmp_stats() { return _icmp_stats; }
|
||||
Interface_object_stats &arp_stats() { return _arp_stats; }
|
||||
Interface_object_stats &dhcp_stats() { return _dhcp_stats; }
|
||||
|
||||
void session_link_state_sigh(Genode::Signal_context_capability sigh);
|
||||
};
|
||||
|
||||
#endif /* _INTERFACE_H_ */
|
||||
|
@ -90,7 +90,7 @@ void Net::Report::handle_config()
|
||||
_report();
|
||||
}
|
||||
|
||||
void Net::Report::handle_link_state()
|
||||
void Net::Report::handle_interface_link_state()
|
||||
{
|
||||
if (!_link_state_triggers) {
|
||||
return; }
|
||||
|
@ -75,7 +75,7 @@ class Net::Report
|
||||
|
||||
void handle_config();
|
||||
|
||||
void handle_link_state();
|
||||
void handle_interface_link_state();
|
||||
|
||||
|
||||
/***************
|
||||
|
@ -112,13 +112,33 @@ void Net::Uplink::print(Output &output) const
|
||||
***************************/
|
||||
|
||||
Net::Uplink_interface_base::Uplink_interface_base(Domain_name const &domain_name,
|
||||
Session_label const &label)
|
||||
Session_label const &label,
|
||||
bool const &session_link_state)
|
||||
:
|
||||
_domain_name { domain_name },
|
||||
_label { label }
|
||||
_domain_name { domain_name },
|
||||
_label { label },
|
||||
_session_link_state { session_link_state }
|
||||
{ }
|
||||
|
||||
|
||||
void Net::Uplink_interface_base::interface_unready()
|
||||
{
|
||||
_interface_ready = false;
|
||||
};
|
||||
|
||||
|
||||
void Net::Uplink_interface_base::interface_ready()
|
||||
{
|
||||
_interface_ready = true;
|
||||
};
|
||||
|
||||
|
||||
bool Net::Uplink_interface_base::interface_link_state() const
|
||||
{
|
||||
return _interface_ready && _session_link_state;
|
||||
}
|
||||
|
||||
|
||||
/**********************
|
||||
** Uplink_interface **
|
||||
**********************/
|
||||
@ -131,14 +151,14 @@ Net::Uplink_interface::Uplink_interface(Env &env,
|
||||
Domain_name const &domain_name,
|
||||
Session_label const &label)
|
||||
:
|
||||
Uplink_interface_base { domain_name, label },
|
||||
Nic::Packet_allocator { &alloc },
|
||||
Nic::Connection { env, this, BUF_SIZE, BUF_SIZE, label.string() },
|
||||
_link_state_handler { env.ep(), *this,
|
||||
&Uplink_interface::_handle_link_state },
|
||||
_interface { env.ep(), timer, mac_address(), alloc,
|
||||
Mac_address(), config, interfaces, *rx(), *tx(),
|
||||
_link_state, *this }
|
||||
Uplink_interface_base { domain_name, label, _session_link_state },
|
||||
Nic::Packet_allocator { &alloc },
|
||||
Nic::Connection { env, this, BUF_SIZE, BUF_SIZE, label.string() },
|
||||
_session_link_state_handler { env.ep(), *this,
|
||||
&Uplink_interface::_handle_session_link_state },
|
||||
_interface { env.ep(), timer, mac_address(), alloc,
|
||||
Mac_address(), config, interfaces, *rx(), *tx(),
|
||||
*this }
|
||||
{
|
||||
/* install packet stream signal handlers */
|
||||
rx_channel()->sigh_ready_to_ack (_interface.sink_ack());
|
||||
@ -147,13 +167,13 @@ Net::Uplink_interface::Uplink_interface(Env &env,
|
||||
tx_channel()->sigh_ready_to_submit(_interface.source_submit());
|
||||
|
||||
/* initialize link state handling */
|
||||
Nic::Connection::link_state_sigh(_link_state_handler);
|
||||
_link_state = link_state();
|
||||
Nic::Connection::link_state_sigh(_session_link_state_handler);
|
||||
_session_link_state = Nic::Connection::link_state();
|
||||
}
|
||||
|
||||
|
||||
void Net::Uplink_interface::_handle_link_state()
|
||||
void Net::Uplink_interface::_handle_session_link_state()
|
||||
{
|
||||
_link_state = link_state();
|
||||
_interface.handle_link_state();
|
||||
_session_link_state = Nic::Connection::link_state();
|
||||
_interface.handle_interface_link_state();
|
||||
}
|
||||
|
@ -104,8 +104,10 @@ class Net::Uplink_interface_base : public Interface_policy
|
||||
{
|
||||
private:
|
||||
|
||||
Const_reference<Domain_name> _domain_name;
|
||||
Genode::Session_label const _label;
|
||||
Const_reference<Domain_name> _domain_name;
|
||||
Genode::Session_label const _label;
|
||||
bool const &_session_link_state;
|
||||
bool _interface_ready { false };
|
||||
|
||||
|
||||
/***************************
|
||||
@ -115,11 +117,15 @@ class Net::Uplink_interface_base : public Interface_policy
|
||||
Domain_name determine_domain_name() const override { return _domain_name(); };
|
||||
void handle_config(Configuration const &) override { }
|
||||
Genode::Session_label const &label() const override { return _label; }
|
||||
void interface_unready() override;
|
||||
void interface_ready() override;
|
||||
bool interface_link_state() const override;
|
||||
|
||||
public:
|
||||
|
||||
Uplink_interface_base(Domain_name const &domain_name,
|
||||
Genode::Session_label const &label);
|
||||
Genode::Session_label const &label,
|
||||
bool const &session_link_state);
|
||||
|
||||
virtual ~Uplink_interface_base() { }
|
||||
|
||||
@ -143,13 +149,13 @@ class Net::Uplink_interface : public Uplink_interface_base,
|
||||
BUF_SIZE = Nic::Session::QUEUE_SIZE * PKT_SIZE,
|
||||
};
|
||||
|
||||
bool _link_state { false };
|
||||
Genode::Signal_handler<Uplink_interface> _link_state_handler;
|
||||
bool _session_link_state { false };
|
||||
Genode::Signal_handler<Uplink_interface> _session_link_state_handler;
|
||||
Net::Interface _interface;
|
||||
|
||||
Ipv4_address_prefix _read_interface();
|
||||
|
||||
void _handle_link_state();
|
||||
void _handle_session_link_state();
|
||||
|
||||
public:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user