L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
switch.cc
1/*
2 * Copyright (C) 2016-2018, 2020, 2023-2024 Kernkonzept GmbH.
3 * Author(s): Jean Wolter <jean.wolter@kernkonzept.com>
4 * Alexander Warg <warg@os.inf.tu-dresden.de>
5 *
6 * License: see LICENSE.spdx (in this directory or the directories above)
7 */
8#include "debug.h"
9#include "switch.h"
10#include "filter.h"
11
13: _max_ports(max_ports)
14{}
15
16bool
17Virtio_switch::add_port(Port_iface *port)
18{
19 if (_ports.size() == _max_ports)
20 {
21 Dbg(Dbg::Port, Dbg::Warn)
22 .printf("Port limit (%u) has been reached.\n", _max_ports);
23 return false;
24 }
25
26 if (!port->mac().is_unknown())
27 for (auto const *p: _ports)
28 if (p->mac() == port->mac())
29 {
30 Dbg(Dbg::Port, Dbg::Warn)
31 .printf("Rejecting port '%s'. MAC address already in use.\n",
32 port->get_name());
33 return false;
34 }
35
36 _ports.push_back(port);
37 return true;
38}
39
40bool
42{
43 if (!_monitor)
44 {
45 _monitor = port;
46 return true;
47 }
48
49 Dbg(Dbg::Port, Dbg::Warn).printf("'%s' already defined as monitor port,"
50 " rejecting monitor port '%s'\n",
51 _monitor->get_name(), port->get_name());
52 return false;
53}
54
55void
57{
58 for (std::vector<Port_iface *>::iterator it = _ports.begin();
59 it != _ports.end();)
60 {
61 auto *port = *it;
62 if (port->is_gone())
63 {
64 Dbg(Dbg::Port, Dbg::Info)
65 .printf("Client on port %p has gone. Deleting...\n", port);
66
67 _mac_table.flush(port);
68 it = _ports.erase(it);
69 delete port;
70 }
71 else
72 ++it;
73 }
74
75 if (_monitor && _monitor->is_gone())
76 {
77 delete(_monitor);
78 _monitor = nullptr;
79 }
80}
81
82template<typename REQ>
83void
84Virtio_switch::handle_tx_request(Port_iface *port, REQ const &request)
85{
86 // Trunk ports are required to have a VLAN tag and only accept packets that
87 // belong to a configured VLAN.
88 if (port->is_trunk() && !port->match_vlan(request.vlan_id()))
89 {
90 // Drop packet.
91 port->stat_inc_tx_dropped();
92 return;
93 }
94
95 // Access ports must not be VLAN tagged to prevent double tagging attacks.
96 if (port->is_access() && request.has_vlan())
97 {
98 // Drop packet.
99 port->stat_inc_tx_dropped();
100 return;
101 }
102
103 auto handle_request = [](Port_iface *dst_port, Port_iface *src_port,
104 REQ const &req)
105 {
106 auto transfer_src = req.transfer_src();
107 l4_uint64_t bytes;
108 auto res = dst_port->handle_request(src_port, transfer_src, &bytes);
109 switch (res)
110 {
111 case Port_iface::Result::Delivered:
112 dst_port->stat_inc_tx_num();
113 dst_port->stat_inc_tx_bytes(bytes);
114 src_port->stat_inc_rx_num();
115 src_port->stat_inc_rx_bytes(bytes);
116 break;
117 case Port_iface::Result::Dropped:
118 [[fallthrough]];
119 case Port_iface::Result::Exception:
120 [[fallthrough]];
121 default:
122 dst_port->stat_inc_tx_dropped();
123 break;
124 }
125 };
126
127 Mac_addr src = request.src_mac();
128
129 auto dst = request.dst_mac();
130 bool is_broadcast = dst.is_broadcast();
131 uint16_t vlan = request.has_vlan() ? request.vlan_id() : port->get_vlan();
132 _mac_table.learn(src, port, vlan);
133 if (L4_LIKELY(!is_broadcast))
134 {
135 auto *target = _mac_table.lookup(dst, vlan);
136 if (target)
137 {
138 // Do not send packets to the port they came in; they might
139 // be sent to us by another switch which does not know how
140 // to reach the target.
141 if (target != port)
142 {
143 handle_request(target, port, request);
144 if (_monitor && !filter_request(request))
145 handle_request(_monitor, port, request);
146 }
147 return;
148 }
149 }
150
151 // It is either a broadcast or an unknown destination - send to all
152 // known ports except the source port
153 for (auto *target: _ports)
154 {
155 if (target != port && target->match_vlan(vlan))
156 handle_request(target, port, request);
157 }
158
159 // Send a copy to the monitor port
160 if (_monitor && !filter_request(request))
161 handle_request(_monitor, port, request);
162}
163
164template<typename PORT>
165void
166Virtio_switch::handle_tx_requests(PORT *port, unsigned &num_reqs_handled)
167{
168 while (auto req = port->get_tx_request())
169 {
170 req->dump_request(port);
171 handle_tx_request(port, *req);
172
173 if (++num_reqs_handled >= Tx_burst)
174 // Port has hit its TX burst limit.
175 break;
176 }
177}
178
179bool
181{
182 /* handle IRQ on one port for the time being */
183 if (!port->tx_work_pending())
184 Dbg(Dbg::Port, Dbg::Debug)
185 .printf("%s: Irq without pending work\n", port->get_name());
186
187 unsigned num_reqs_handled = 0;
188 do
189 {
190 port->tx_q()->disable_notify();
191 port->rx_q()->disable_notify();
192
193 if (num_reqs_handled >= Tx_burst)
194 {
195 Dbg(Dbg::Port, Dbg::Debug)
196 .printf(
197 "%s: Tx burst limit hit, reschedule remaining Tx work.\n",
198 port->get_name());
199
200 // Port has hit its TX burst limit, so for fairness reasons, stop
201 // processing TX work from this port, and instead reschedule the
202 // pending work for later.
203 port->reschedule_pending_tx();
204 // NOTE: Notifications for this port remain disabled, until eventually
205 // the reschedule handler calls `handle_l4virtio_port_tx` again.
206 return false;
207 }
208
209 // Within the loop, to trigger before enabling notifications again.
210 all_rx_notify_disable_and_remember();
211
212 try
213 {
214 // throws Bad_descriptor exceptions raised on SRC port
215 handle_tx_requests(port, num_reqs_handled);
216 }
218 {
219 Dbg(Dbg::Port, Dbg::Warn, "REQ")
220 .printf("%s: caught bad descriptor exception: %s - %i"
221 " -- Signal device error on device %p.\n",
222 __PRETTY_FUNCTION__, e.message(), e.error, port);
223 port->device_error();
224 all_rx_notify_emit_and_enable();
225 return false;
226 }
227
228 all_rx_notify_emit_and_enable();
229
230 port->tx_q()->enable_notify();
231 port->rx_q()->enable_notify();
232
233 L4virtio::wmb();
234 L4virtio::rmb();
235 }
236 while (port->tx_work_pending());
237
238 return true;
239}
240
241#if CONFIG_VNS_IXL
242bool
243Virtio_switch::handle_ixl_port_tx(Ixl_port *port)
244{
245 unsigned num_reqs_handled = 0;
246
247 all_rx_notify_disable_and_remember();
248 handle_tx_requests(port, num_reqs_handled);
249 all_rx_notify_emit_and_enable();
250
251 if (num_reqs_handled >= Tx_burst && port->tx_work_pending())
252 {
253 Dbg(Dbg::Port, Dbg::Info)
254 .printf("%s: Tx burst limit hit, reschedule remaining Tx work.\n",
255 port->get_name());
256
257 // Port has hit its TX burst limit, so for fairness reasons, stop
258 // processing TX work from this port, and instead reschedule the
259 // pending work for later.
260 port->reschedule_pending_tx();
261 return false;
262 }
263
264 return true;
265}
266#endif
267
void device_error()
Transition device into DEVICE_NEEDS_RESET state.
Definition l4virtio:1024
void enable_notify()
Clear the 'no notify' flag for this queue.
Definition virtio:284
void disable_notify()
Set the 'no notify' flag for this queue.
Definition virtio:273
A Port on the Virtio Net Switch.
bool tx_work_pending() const
Check whether there is any work pending on the transmission queue.
bool is_unknown() const
Check if the MAC address is not yet known.
Definition mac_addr.h:66
Virtqueue * rx_q()
Getter for the receive queue.
Definition virtio_net.h:307
Virtqueue * tx_q()
Getter for the transmission queue.
Definition virtio_net.h:305
void check_ports()
Check validity of ports.
Definition switch.cc:56
Virtio_switch(unsigned max_ports)
Create a switch with n ports.
Definition switch.cc:12
bool add_monitor_port(Port_iface *port)
Add a monitor port to the switch.
Definition switch.cc:41
bool handle_l4virtio_port_tx(L4virtio_port *port)
Handle TX queue of the given port.
Definition switch.cc:180
bool add_port(Port_iface *port)
Add a port to the switch.
Definition switch.cc:17
unsigned long long l4_uint64_t
Unsigned 64bit value.
Definition l4int.h:31
#define L4_LIKELY(x)
Expression is likely to execute.
Definition compiler.h:283
Exception used by Queue to indicate descriptor errors.
Definition virtio:398
char const * message() const
Get a human readable description of the error code.
Definition virtio:430