bmv2
Designing your own switch target with bmv2
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 
43 namespace bm {
44 
45 enum class MatchTableType {
46  NONE = 0,
47  SIMPLE,
48  INDIRECT,
49  INDIRECT_WS
50 };
51 
52 class 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 
295 class 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 
386 class 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 
478 class 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_
lookup_structures.h
bm::Counter::counter_value_t
uint64_t counter_value_t
A counter value (measuring bytes or packets) is a uint64_t.
Definition: counters.h:56
actions.h
bm::NamedP4Object::operator=
NamedP4Object & operator=(const NamedP4Object &other)=delete
Deleted copy assignment operator.