bmv2
Designing your own switch target with bmv2
Loading...
Searching...
No Matches
match_tables.h
1/* Copyright 2013-present Barefoot Networks, Inc.
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16/*
17 * Antonin Bas
18 *
19 */
20
21#ifndef BM_BM_SIM_MATCH_TABLES_H_
22#define BM_BM_SIM_MATCH_TABLES_H_
23
24#include <cstddef> // for ptrdiff_t
25#include <iosfwd>
26#include <iterator>
27#include <memory>
28#include <string>
29#include <type_traits>
30#include <unordered_map>
31#include <vector>
32
33// shared_mutex will only be available in C++-14, so for now I'm using boost
34#include <boost/thread/shared_mutex.hpp>
35
36#include "match_units.h"
37#include "actions.h"
38#include "control_flow.h"
39#include "lookup_structures.h"
40#include "action_entry.h"
41#include "action_profile.h"
42
43namespace bm {
44
45enum class MatchTableType {
46 NONE = 0,
47 SIMPLE,
48 INDIRECT,
49 INDIRECT_WS
50};
51
52class MatchTableAbstract : public NamedP4Object {
53 public:
54 friend class handle_iterator;
55
56 using counter_value_t = Counter::counter_value_t;
57
58 struct EntryCommon {
59 entry_handle_t handle;
60 std::vector<MatchKeyParam> match_key;
61 uint32_t timeout_ms{0};
62 uint32_t time_since_hit_ms{0};
63 int priority;
64 };
65
66 class handle_iterator {
67 public:
68 using iterator_category = std::forward_iterator_tag;
69 using value_type = handle_t;
70 using difference_type = std::ptrdiff_t; // default for std::iterator
71 using pointer = handle_t*;
72 using reference = handle_t&;
73
74 handle_iterator(const MatchTableAbstract *mt,
75 const MatchUnitAbstract_::handle_iterator &it)
76 : mt(mt), it(it) { }
77
78 const entry_handle_t &operator*() const {
79 ReadLock lock = mt->lock_read();
80 return *it;
81 }
82
83 const entry_handle_t *operator->() const {
84 ReadLock lock = mt->lock_read();
85 return it.operator->();
86 }
87
88 bool operator==(const handle_iterator &other) const {
89 ReadLock lock = mt->lock_read();
90 return (it == other.it);
91 }
92
93 bool operator!=(const handle_iterator &other) const {
94 ReadLock lock = mt->lock_read();
95 return !(*this == other);
96 }
97
98 handle_iterator &operator++() {
99 ReadLock lock = mt->lock_read();
100 it++;
101 return *this;
102 }
103
104 const handle_iterator operator++(int) {
105 // Use operator++()
106 const handle_iterator old(*this);
107 ++(*this);
108 return old;
109 }
110
111 private:
112 const MatchTableAbstract *mt;
113 MatchUnitAbstract_::handle_iterator it;
114 };
115
116 public:
117 MatchTableAbstract(const std::string &name, p4object_id_t id,
118 bool with_counters, bool with_ageing,
119 MatchUnitAbstract_ *mu);
120
121 virtual ~MatchTableAbstract() { }
122
123 const ControlFlowNode *apply_action(Packet *pkt);
124
125 virtual MatchTableType get_table_type() const = 0;
126
127 virtual const ActionEntry &lookup(const Packet &pkt, bool *hit,
128 entry_handle_t *handle,
129 const ControlFlowNode **next_node) = 0;
130
131 virtual size_t get_num_entries() const = 0;
132
133 virtual bool is_valid_handle(entry_handle_t handle) const = 0;
134
135 MatchErrorCode dump_entry(std::ostream *out,
136 entry_handle_t handle) const {
137 auto lock = lock_read();
138 // TODO(antonin): this seems too conservative: we know that indirect indices
139 // have to remain valid while a match entry is pointing to it.
140 auto lock_impl = lock_impl_read();
141 return dump_entry_(out, handle);
142 }
143
144 std::string dump_entry_string(entry_handle_t handle) const {
145 auto lock = lock_read();
146 auto lock_impl = lock_impl_read();
147 return dump_entry_string_(handle);
148 }
149
150 void reset_state(bool reset_default_entry = true);
151
152 void serialize(std::ostream *out) const;
153 void deserialize(std::istream *in, const P4Objects &objs);
154
155 void set_next_node(p4object_id_t action_id, const ControlFlowNode *next_node);
156 void set_next_node_hit(const ControlFlowNode *next_node);
157 // one of set_next_node_miss / set_next_node_miss_default has to be called
158 // set_next_node_miss: if the P4 program has a table-action switch statement
159 // with a 'miss' case
160 // set_next_node_miss_default: in the general case
161 // it is ok to call both, in which case set_next_node_miss will take priority
162 void set_next_node_miss(const ControlFlowNode *next_node);
163 void set_next_node_miss_default(const ControlFlowNode *next_node);
164
165 void set_direct_meters(MeterArray *meter_array,
166 header_id_t target_header,
167 int target_offset);
168
169 MatchErrorCode query_counters(entry_handle_t handle,
170 counter_value_t *bytes,
171 counter_value_t *packets) const;
172 MatchErrorCode reset_counters();
173 MatchErrorCode write_counters(entry_handle_t handle,
174 counter_value_t bytes,
175 counter_value_t packets);
176
177 MatchErrorCode set_meter_rates(
178 entry_handle_t handle, const std::vector<Meter::rate_config_t> &configs);
179
180 MatchErrorCode get_meter_rates(
181 entry_handle_t handle, std::vector<Meter::rate_config_t> *configs) const;
182
183 MatchErrorCode reset_meter_rates(entry_handle_t handle);
184
185 MatchErrorCode set_entry_ttl(entry_handle_t handle, unsigned int ttl_ms);
186
187 void sweep_entries(std::vector<entry_handle_t> *entries) const;
188
189 handle_iterator handles_begin() const;
190 handle_iterator handles_end() const;
191
192 // meant to be called by P4Objects when loading the JSON
193 // set_default_entry sets a default entry obtained from the JSON. You can make
194 // sure that it cannot be changed by the control plane by using the is_const
195 // parameter.
196 void set_default_default_entry(const ActionFn *action_fn,
197 ActionData action_data,
198 bool is_const);
199
200 MatchTableAbstract(const MatchTableAbstract &other) = delete;
201 MatchTableAbstract &operator=(const MatchTableAbstract &other) = delete;
202
203 MatchTableAbstract(MatchTableAbstract &&other) = delete;
204 MatchTableAbstract &operator=(MatchTableAbstract &&other) = delete;
205
206 protected:
207 using ReadLock = boost::shared_lock<boost::shared_mutex>;
208 using WriteLock = boost::unique_lock<boost::shared_mutex>;
209
210 protected:
211 const ControlFlowNode *get_next_node(p4object_id_t action_id) const;
212 const ControlFlowNode *get_next_node_default(p4object_id_t action_id) const;
213
214 // assumes that entry->handle has been set
215 void set_entry_common_info(EntryCommon *entry) const;
216
217 ReadLock lock_read() const { return ReadLock(t_mutex); }
218 WriteLock lock_write() const { return WriteLock(t_mutex); }
219
220 protected:
221 // Not sure these guys need to be atomic with the current code
222 // TODO(antonin): check
223 std::atomic_bool with_counters{false};
224 std::atomic_bool with_meters{false};
225 std::atomic_bool with_ageing{false};
226
227 std::unordered_map<p4object_id_t, const ControlFlowNode *> next_nodes{};
228 const ControlFlowNode *next_node_hit{nullptr};
229 // next node if table is a miss
230 const ControlFlowNode *next_node_miss{nullptr};
231 // true if the P4 program explictly specified a table switch statement with a
232 // "miss" case
233 bool has_next_node_hit{false};
234 bool has_next_node_miss{false};
235 // default default entry is used when no default entry has been programmed by
236 // the control-plane. Its next_node member is set according to the following
237 // rules (in increasing order of priority):
238 // - if the P4 program specififed a table switch statement with a "miss" case,
239 // it is the first node of the "miss" branch
240 // - if the P4 table definition specified a default action, it is the next
241 // node correspondign to this action
242 // - otherwise it will be the default unconditional next node as per the JSON
243 // and the P4 program, or NULL
244 // Regarding the action_fn member, it is empty unless a default entry was
245 // provided in the P4 / JSON.
246 ActionEntry default_default_entry{};
247 bool const_default_entry{false};
248
249 header_id_t meter_target_header{};
250 int meter_target_offset{};
251
252 private:
253 virtual void reset_state_(bool reset_default_entry) = 0;
254
255 virtual void serialize_(std::ostream *out) const = 0;
256 virtual void deserialize_(std::istream *in, const P4Objects &objs) = 0;
257
258 virtual MatchErrorCode dump_entry_(std::ostream *out,
259 entry_handle_t handle) const = 0;
260
261 // Used to propagate changes to the default default entry to the table
262 // implementation. For a direct match table, the default default entry is
263 // copied to the default entry.
264 virtual void set_default_default_entry_() = 0;
265
266 // TODO(antonin): I realized that since the action profile refactor from 2017
267 // - to enable action profile sharing -, there were race conditions all over
268 // the place as the action profile itself was no longer protected by the mutex
269 // of its parent table (there is no single parent table any more). The most
270 // obvious issue was when calling apply_action(). Because of the complexity of
271 // the ActionEntry structure, lookup() returns it by reference. However, the
272 // action profile mutex was not held during the duration of apply_action. This
273 // means that modifications to the action profile ActionEntry vector (such as
274 // adding new members, potentially leading to memory reallocation) could read
275 // to the reference being "invalidated". This was actually very easy to
276 // trigger in a unit test with 2 competing threads, one calling apply_action
277 // repeatedly on a packet and one creating a multitude of members.
278 // As a temporary solution I have added these virtual methods to acquire the
279 // "implementation" mutex (in this case of an indirect table, the action
280 // profile's lock). I could think of several alternatives but nothing I
281 // liked.
282 virtual ReadLock lock_impl_read() const { return ReadLock(); }
283 virtual WriteLock lock_impl_write() const { return WriteLock(); }
284
285 // the internal version does not acquire the lock
286 std::string dump_entry_string_(entry_handle_t handle) const;
287
288 private:
289 mutable boost::shared_mutex t_mutex{};
290 MatchUnitAbstract_ *match_unit_{nullptr};
291};
292
293// MatchTable is exposed to the runtime for configuration
294
295class MatchTable : public MatchTableAbstract {
296 public:
297 struct Entry : public EntryCommon {
298 const ActionFn *action_fn;
299 ActionData action_data;
300 };
301
302 public:
303 MatchTable(const std::string &name, p4object_id_t id,
304 std::unique_ptr<MatchUnitAbstract<ActionEntry> > match_unit,
305 bool with_counters = false, bool with_ageing = false);
306
307 MatchErrorCode add_entry(const std::vector<MatchKeyParam> &match_key,
308 const ActionFn *action_fn,
309 ActionData action_data, // move it
310 entry_handle_t *handle,
311 int priority = -1);
312
313 MatchErrorCode delete_entry(entry_handle_t handle);
314
315 MatchErrorCode modify_entry(entry_handle_t handle,
316 const ActionFn *action_fn,
317 ActionData action_data);
318
319 MatchErrorCode set_default_action(const ActionFn *action_fn,
320 ActionData action_data);
321
322 MatchErrorCode reset_default_entry();
323
324 MatchErrorCode get_entry(entry_handle_t handle, Entry *entry) const;
325
326 MatchErrorCode get_entry_from_key(const std::vector<MatchKeyParam> &match_key,
327 Entry *entry, int priority = 1) const;
328
329 std::vector<Entry> get_entries() const;
330
331 MatchErrorCode get_default_entry(Entry *entry) const;
332
333 MatchTableType get_table_type() const override {
334 return MatchTableType::SIMPLE;
335 }
336
337 const ActionEntry &lookup(const Packet &pkt, bool *hit,
338 entry_handle_t *handle,
339 const ControlFlowNode **next_node) override;
340
341 size_t get_num_entries() const override {
342 return match_unit->get_num_entries();
343 }
344
345 bool is_valid_handle(entry_handle_t handle) const override {
346 return match_unit->valid_handle(handle);
347 }
348
349 // meant to be called by P4Objects when loading the JSON
350 // set_const_default_action_fn makes sure that the control plane cannot change
351 // the default action; note that the action data can still be changed. It only
352 // makes sense for regular match-tables.
353 void set_const_default_action_fn(const ActionFn *const_default_action_fn);
354
355 void set_immutable_entries();
356
357 public:
358 static std::unique_ptr<MatchTable> create(
359 const std::string &match_type,
360 const std::string &name,
361 p4object_id_t id,
362 size_t size, const MatchKeyBuilder &match_key_builder,
363 LookupStructureFactory *lookup_factory,
364 bool with_counters, bool with_ageing);
365
366 private:
367 void reset_state_(bool reset_default_entry) override;
368
369 void serialize_(std::ostream *out) const override;
370 void deserialize_(std::istream *in, const P4Objects &objs) override;
371
372 MatchErrorCode dump_entry_(std::ostream *out,
373 entry_handle_t handle) const override;
374
375 void set_default_default_entry_() override;
376
377 MatchErrorCode get_entry_(entry_handle_t handle, Entry *entry) const;
378
379 private:
380 ActionEntry default_entry{};
381 std::unique_ptr<MatchUnitAbstract<ActionEntry> > match_unit;
382 const ActionFn *const_default_action{nullptr};
383 bool immutable_entries{false};
384};
385
386class MatchTableIndirect : public MatchTableAbstract {
387 public:
388 using mbr_hdl_t = ActionProfile::mbr_hdl_t;
389
390 using IndirectIndex = ActionProfile::IndirectIndex;
391
392 struct Entry : public EntryCommon {
393 mbr_hdl_t mbr;
394 };
395
396 public:
397 MatchTableIndirect(
398 const std::string &name, p4object_id_t id,
399 std::unique_ptr<MatchUnitAbstract<IndirectIndex> > match_unit,
400 bool with_counters = false, bool with_ageing = false);
401
402 void set_action_profile(ActionProfile *action_profile);
403
404 ActionProfile *get_action_profile() const;
405
406 MatchErrorCode add_entry(const std::vector<MatchKeyParam> &match_key,
407 mbr_hdl_t mbr,
408 entry_handle_t *handle,
409 int priority = -1);
410
411 MatchErrorCode delete_entry(entry_handle_t handle);
412
413 MatchErrorCode modify_entry(entry_handle_t handle, mbr_hdl_t mbr);
414
415 MatchErrorCode set_default_member(mbr_hdl_t mbr);
416
417 MatchErrorCode reset_default_entry();
418
419 MatchErrorCode get_entry(entry_handle_t handle, Entry *entry) const;
420
421 MatchErrorCode get_entry_from_key(const std::vector<MatchKeyParam> &match_key,
422 Entry *entry, int priority = 1) const;
423
424 std::vector<Entry> get_entries() const;
425
426 MatchErrorCode get_default_entry(Entry *entry) const;
427
428 MatchTableType get_table_type() const override {
429 return MatchTableType::INDIRECT;
430 }
431
432 const ActionEntry &lookup(const Packet &pkt, bool *hit,
433 entry_handle_t *handle,
434 const ControlFlowNode **next_node) override;
435
436 size_t get_num_entries() const override {
437 return match_unit->get_num_entries();
438 }
439
440 bool is_valid_handle(entry_handle_t handle) const override {
441 return match_unit->valid_handle(handle);
442 }
443
444 public:
445 static std::unique_ptr<MatchTableIndirect> create(
446 const std::string &match_type,
447 const std::string &name, p4object_id_t id,
448 size_t size, const MatchKeyBuilder &match_key_builder,
449 LookupStructureFactory *lookup_factory,
450 bool with_counters, bool with_ageing);
451
452 protected:
453 void reset_state_(bool reset_default_entry) override;
454
455 void serialize_(std::ostream *out) const override;
456 void deserialize_(std::istream *in, const P4Objects &objs) override;
457
458 void dump_(std::ostream *stream) const;
459
460 MatchErrorCode dump_entry_(std::ostream *out,
461 entry_handle_t handle) const override;
462
463 void set_default_default_entry_() override;
464
465 MatchErrorCode get_entry_(entry_handle_t handle, Entry *entry) const;
466
467 ReadLock lock_impl_read() const override;
468 WriteLock lock_impl_write() const override;
469
470 protected:
471 IndirectIndex default_index{};
472 std::unique_ptr<MatchUnitAbstract<IndirectIndex> > match_unit;
473 ActionProfile *action_profile{nullptr};
474 bool default_set{false};
475 ActionEntry empty_action{}; // for lookups yielding empty groups
476};
477
478class MatchTableIndirectWS : public MatchTableIndirect {
479 public:
480 using grp_hdl_t = ActionProfile::grp_hdl_t;
481
482 // If the entry points to a member, grp will be set to its maximum possible
483 // value, i.e. std::numeric_limits<grp_hdl_t>::max(). If the entry points to a
484 // group, it will be mbr that will be set to its max possible value.
485 struct Entry : public EntryCommon {
486 mbr_hdl_t mbr;
487 grp_hdl_t grp;
488 };
489
490 public:
491 MatchTableIndirectWS(
492 const std::string &name, p4object_id_t id,
493 std::unique_ptr<MatchUnitAbstract<IndirectIndex> > match_unit,
494 bool with_counters = false, bool with_ageing = false);
495
496 MatchErrorCode add_entry_ws(const std::vector<MatchKeyParam> &match_key,
497 grp_hdl_t grp,
498 entry_handle_t *handle,
499 int priority = -1);
500
501 MatchErrorCode modify_entry_ws(entry_handle_t handle, grp_hdl_t grp);
502
503 MatchErrorCode set_default_group(grp_hdl_t grp);
504
505 MatchErrorCode get_entry(entry_handle_t handle, Entry *entry) const;
506
507 MatchErrorCode get_entry_from_key(const std::vector<MatchKeyParam> &match_key,
508 Entry *entry, int priority = 1) const;
509
510 std::vector<Entry> get_entries() const;
511
512 MatchErrorCode get_default_entry(Entry *entry) const;
513
514 MatchTableType get_table_type() const override {
515 return MatchTableType::INDIRECT_WS;
516 }
517
518 const ActionEntry &lookup(const Packet &pkt, bool *hit,
519 entry_handle_t *handle,
520 const ControlFlowNode **next_node) override;
521
522 public:
523 static std::unique_ptr<MatchTableIndirectWS> create(
524 const std::string &match_type,
525 const std::string &name, p4object_id_t id,
526 size_t size, const MatchKeyBuilder &match_key_builder,
527 LookupStructureFactory *lookup_factory,
528 bool with_counters, bool with_ageing);
529
530 private:
531 void serialize_(std::ostream *out) const override;
532 void deserialize_(std::istream *in, const P4Objects &objs) override;
533
534 MatchErrorCode dump_entry_(std::ostream *out,
535 entry_handle_t handle) const override;
536
537 MatchErrorCode get_entry_(entry_handle_t handle, Entry *entry) const;
538};
539
540} // namespace bm
541
542#endif // BM_BM_SIM_MATCH_TABLES_H_
uint64_t counter_value_t
A counter value (measuring bytes or packets) is a uint64_t.
Definition counters.h:56
NamedP4Object & operator=(const NamedP4Object &other)=delete
Deleted copy assignment operator.