L4Re Operating System Framework
Interface and Usage Documentation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 _max_used{0}
15{
16 _ports = new Port_iface *[max_ports]();
17}
18
19int
20Virtio_switch::lookup_free_slot()
21{
22 for (unsigned idx = 0; idx < _max_ports; ++idx)
23 if (!_ports[idx])
24 return idx;
25
26 return -1;
27}
28
29bool
30Virtio_switch::add_port(Port_iface *port)
31{
32 if (!port->mac().is_unknown())
33 for (unsigned idx = 0; idx < _max_ports; ++idx)
34 if (_ports[idx] && _ports[idx]->mac() == port->mac())
35 {
36 Dbg(Dbg::Port, Dbg::Warn)
37 .printf("Rejecting port '%s'. MAC address already in use.\n",
38 port->get_name());
39 return false;
40 }
41
42 int idx = lookup_free_slot();
43 if (idx < 0)
44 return false;
45
46 unsigned uidx = static_cast<unsigned>(idx);
47 _ports[uidx] = port;
48 if (_max_used == uidx)
49 ++_max_used;
50
51 return true;
52}
53
54bool
56{
57 if (!_monitor)
58 {
59 _monitor = port;
60 return true;
61 }
62
63 Dbg(Dbg::Port, Dbg::Warn).printf("'%s' already defined as monitor port,"
64 " rejecting monitor port '%s'\n",
65 _monitor->get_name(), port->get_name());
66 return false;
67}
68
69void
71{
72 for (unsigned idx = 0; idx < _max_used; ++idx)
73 {
74 Port_iface *port = _ports[idx];
75 if (port && port->is_gone())
76 {
77 Dbg(Dbg::Port, Dbg::Info)
78 .printf("Client on port %p has gone. Deleting...\n", port);
79
80 _ports[idx] = nullptr;
81 if (idx == _max_used-1)
82 --_max_used;
83
84 _mac_table.flush(port);
85 delete(port);
86 }
87 }
88
89 if (_monitor && _monitor->is_gone())
90 {
91 delete(_monitor);
92 _monitor = nullptr;
93 }
94}
95
96template<typename REQ>
97void
98Virtio_switch::handle_tx_request(Port_iface *port, REQ const &request)
99{
100 // Trunk ports are required to have a VLAN tag and only accept packets that
101 // belong to a configured VLAN.
102 if (port->is_trunk() && !port->match_vlan(request.vlan_id()))
103 {
104 // Drop packet.
105 port->stat_inc_tx_dropped();
106 return;
107 }
108
109 // Access ports must not be VLAN tagged to prevent double tagging attacks.
110 if (port->is_access() && request.has_vlan())
111 {
112 // Drop packet.
113 port->stat_inc_tx_dropped();
114 return;
115 }
116
117 auto handle_request = [](Port_iface *dst_port, Port_iface *src_port,
118 REQ const &req)
119 {
120 auto transfer_src = req.transfer_src();
121 l4_uint64_t bytes;
122 auto res = dst_port->handle_request(src_port, transfer_src, &bytes);
123 switch (res)
124 {
125 case Port_iface::Result::Delivered:
126 dst_port->stat_inc_tx_num();
127 dst_port->stat_inc_tx_bytes(bytes);
128 src_port->stat_inc_rx_num();
129 src_port->stat_inc_rx_bytes(bytes);
130 break;
131 case Port_iface::Result::Dropped:
132 [[fallthrough]];
133 case Port_iface::Result::Exception:
134 [[fallthrough]];
135 default:
136 dst_port->stat_inc_tx_dropped();
137 break;
138 }
139 };
140
141 Mac_addr src = request.src_mac();
142
143 auto dst = request.dst_mac();
144 bool is_broadcast = dst.is_broadcast();
145 uint16_t vlan = request.has_vlan() ? request.vlan_id() : port->get_vlan();
146 _mac_table.learn(src, port, vlan);
147 if (L4_LIKELY(!is_broadcast))
148 {
149 auto *target = _mac_table.lookup(dst, vlan);
150 if (target)
151 {
152 // Do not send packets to the port they came in; they might
153 // be sent to us by another switch which does not know how
154 // to reach the target.
155 if (target != port)
156 {
157 handle_request(target, port, request);
158 if (_monitor && !filter_request(request))
159 handle_request(_monitor, port, request);
160 }
161 return;
162 }
163 }
164
165 // It is either a broadcast or an unknown destination - send to all
166 // known ports except the source port
167 for (unsigned idx = 0; idx < _max_used && _ports[idx]; ++idx)
168 {
169 auto *target = _ports[idx];
170 if (target != port && target->match_vlan(vlan))
171 handle_request(target, port, request);
172 }
173
174 // Send a copy to the monitor port
175 if (_monitor && !filter_request(request))
176 handle_request(_monitor, port, request);
177}
178
179template<typename PORT>
180void
181Virtio_switch::handle_tx_requests(PORT *port, unsigned &num_reqs_handled)
182{
183 while (auto req = port->get_tx_request())
184 {
185 req->dump_request(port);
186 handle_tx_request(port, *req);
187
188 if (++num_reqs_handled >= Tx_burst)
189 // Port has hit its TX burst limit.
190 break;
191 }
192}
193
194bool
196{
197 /* handle IRQ on one port for the time being */
198 if (!port->tx_work_pending())
199 Dbg(Dbg::Port, Dbg::Debug)
200 .printf("%s: Irq without pending work\n", port->get_name());
201
202 unsigned num_reqs_handled = 0;
203 do
204 {
205 port->tx_q()->disable_notify();
206 port->rx_q()->disable_notify();
207
208 if (num_reqs_handled >= Tx_burst)
209 {
210 Dbg(Dbg::Port, Dbg::Debug)
211 .printf(
212 "%s: Tx burst limit hit, reschedule remaining Tx work.\n",
213 port->get_name());
214
215 // Port has hit its TX burst limit, so for fairness reasons, stop
216 // processing TX work from this port, and instead reschedule the
217 // pending work for later.
218 port->reschedule_pending_tx();
219 // NOTE: Notifications for this port remain disabled, until eventually
220 // the reschedule handler calls `handle_l4virtio_port_tx` again.
221 return false;
222 }
223
224 // Within the loop, to trigger before enabling notifications again.
225 all_rx_notify_disable_and_remember();
226
227 try
228 {
229 // throws Bad_descriptor exceptions raised on SRC port
230 handle_tx_requests(port, num_reqs_handled);
231 }
233 {
234 Dbg(Dbg::Port, Dbg::Warn, "REQ")
235 .printf("%s: caught bad descriptor exception: %s - %i"
236 " -- Signal device error on device %p.\n",
237 __PRETTY_FUNCTION__, e.message(), e.error, port);
238 port->device_error();
239 all_rx_notify_emit_and_enable();
240 return false;
241 }
242
243 all_rx_notify_emit_and_enable();
244
245 port->tx_q()->enable_notify();
246 port->rx_q()->enable_notify();
247
248 L4virtio::wmb();
249 L4virtio::rmb();
250 }
251 while (port->tx_work_pending());
252
253 return true;
254}
255
256#if CONFIG_VNS_IXL
257bool
258Virtio_switch::handle_ixl_port_tx(Ixl_port *port)
259{
260 unsigned num_reqs_handled = 0;
261
262 all_rx_notify_disable_and_remember();
263 handle_tx_requests(port, num_reqs_handled);
264 all_rx_notify_emit_and_enable();
265
266 if (num_reqs_handled >= Tx_burst && port->tx_work_pending())
267 {
268 Dbg(Dbg::Port, Dbg::Info)
269 .printf("%s: Tx burst limit hit, reschedule remaining Tx work.\n",
270 port->get_name());
271
272 // Port has hit its TX burst limit, so for fairness reasons, stop
273 // processing TX work from this port, and instead reschedule the
274 // pending work for later.
275 port->reschedule_pending_tx();
276 return false;
277 }
278
279 return true;
280}
281#endif
282
void device_error()
Transition device into DEVICE_NEEDS_RESET state.
Definition l4virtio:1024
A Port on the Virtio Net Switch.
bool tx_work_pending() const
Check whether there is any work pending on the transmission queue.
A wrapper class around the value of a MAC address.
Definition mac_addr.h:20
bool is_broadcast() const
Check if MAC address is a broadcast or multicast address.
Definition mac_addr.h:46
Port_iface * lookup(Mac_addr dst, l4_uint16_t vlan_id) const
Find the destination port for a MAC address and VLAN id.
Definition mac_table.h:58
void learn(Mac_addr src, Port_iface *port, l4_uint16_t vlan_id)
Learn a MAC address (add it to the MAC table).
Definition mac_table.h:78
void flush(Port_iface *port)
Flush all associations with a given port.
Definition mac_table.h:129
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:70
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:55
bool handle_l4virtio_port_tx(L4virtio_port *port)
Handle TX queue of the given port.
Definition switch.cc:195
bool add_port(Port_iface *port)
Add a port to the switch.
Definition switch.cc:30
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:274
Debug C interface.
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