A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
openflow-interface.cc
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: GPL-2.0-only
3 *
4 * Author: Blake Hurd <naimorai@gmail.com>
5 */
6
8
10
11namespace ns3
12{
13
14NS_LOG_COMPONENT_DEFINE("OpenFlowInterface");
15
16namespace ofi
17{
18
19Stats::Stats(ofp_stats_types _type, size_t body_len)
20{
21 type = _type;
22 size_t min_body = 0;
23 size_t max_body = 0;
24
25 switch (type)
26 {
27 case OFPST_DESC:
28 break;
29 case OFPST_FLOW:
30 min_body = max_body = sizeof(ofp_flow_stats_request);
31 break;
32 case OFPST_AGGREGATE:
33 min_body = max_body = sizeof(ofp_aggregate_stats_request);
34 break;
35 case OFPST_TABLE:
36 break;
37 case OFPST_PORT:
38 min_body = 0;
39 max_body =
40 std::numeric_limits<size_t>::max(); // Not sure about this one. This would guarantee
41 // that the body_len is always acceptable.
42 break;
43 case OFPST_PORT_TABLE:
44 break;
45 default:
46 NS_LOG_ERROR("received stats request of unknown type " << type);
47 return; // -EINVAL;
48 }
49
50 if ((min_body != 0 || max_body != 0) && (body_len < min_body || body_len > max_body))
51 {
52 NS_LOG_ERROR("stats request type " << type << " with bad body length " << body_len);
53 return; // -EINVAL;
54 }
55}
56
57int
58Stats::DoInit(const void* body, int body_len, void** state)
59{
60 switch (type)
61 {
62 case OFPST_DESC:
63 return 0;
64 case OFPST_FLOW:
65 return FlowStatsInit(body, body_len, state);
66 case OFPST_AGGREGATE:
67 return AggregateStatsInit(body, body_len, state);
68 case OFPST_TABLE:
69 return 0;
70 case OFPST_PORT:
71 return PortStatsInit(body, body_len, state);
72 case OFPST_PORT_TABLE:
73 return 0;
74 case OFPST_VENDOR:
75 return 0;
76 }
77
78 return 0;
79}
80
81int
82Stats::DoDump(Ptr<OpenFlowSwitchNetDevice> swtch, void* state, ofpbuf* buffer)
83{
84 switch (type)
85 {
86 case OFPST_DESC:
87 return DescStatsDump(state, buffer);
88 case OFPST_FLOW:
89 return FlowStatsDump(swtch, (FlowStatsState*)state, buffer);
90 case OFPST_AGGREGATE:
91 return AggregateStatsDump(swtch, (ofp_aggregate_stats_request*)state, buffer);
92 case OFPST_TABLE:
93 return TableStatsDump(swtch, state, buffer);
94 case OFPST_PORT:
95 return PortStatsDump(swtch, (PortStatsState*)state, buffer);
96 case OFPST_PORT_TABLE:
97 return PortTableStatsDump(swtch, state, buffer);
98 case OFPST_VENDOR:
99 return 0;
100 }
101
102 return 0;
103}
104
105void
107{
108 switch (type)
109 {
110 case OFPST_DESC:
111 break;
112 case OFPST_FLOW:
113 free((FlowStatsState*)state);
114 break;
115 case OFPST_AGGREGATE:
116 free((ofp_aggregate_stats_request*)state);
117 break;
118 case OFPST_TABLE:
119 break;
120 case OFPST_PORT:
121 free(((PortStatsState*)state)->ports);
122 free((PortStatsState*)state);
123 break;
124 case OFPST_PORT_TABLE:
125 break;
126 case OFPST_VENDOR:
127 break;
128 }
129}
130
131int
132Stats::DescStatsDump(void* state, ofpbuf* buffer)
133{
134 ofp_desc_stats* ods = (ofp_desc_stats*)ofpbuf_put_zeros(buffer, sizeof *ods);
135 strncpy(ods->mfr_desc,
137 sizeof ods->mfr_desc);
138 strncpy(ods->hw_desc, OpenFlowSwitchNetDevice::GetHardwareDescription(), sizeof ods->hw_desc);
139 strncpy(ods->sw_desc, OpenFlowSwitchNetDevice::GetSoftwareDescription(), sizeof ods->sw_desc);
140 strncpy(ods->serial_num, OpenFlowSwitchNetDevice::GetSerialNumber(), sizeof ods->serial_num);
141 return 0;
142}
143
144#define MAX_FLOW_STATS_BYTES 4096
145
146int
147Stats::FlowStatsInit(const void* body, int body_len, void** state)
148{
149 const ofp_flow_stats_request* fsr = (ofp_flow_stats_request*)body;
150 auto s = (FlowStatsState*)xmalloc(sizeof(FlowStatsState));
151
152 s->table_idx = fsr->table_id == 0xff ? 0 : fsr->table_id;
153 memset(&s->position, 0, sizeof s->position);
154 s->rq = *fsr;
155 *state = s;
156 return 0;
157}
158
159int
160Stats_FlowDumpCallback(sw_flow* flow, void* state)
161{
162 auto s = (Stats::FlowStatsState*)state;
163
164 // Fill Flow Stats
165 ofp_flow_stats* ofs;
166 int length = sizeof *ofs + flow->sf_acts->actions_len;
167 ofs = (ofp_flow_stats*)ofpbuf_put_zeros(s->buffer, length);
168 ofs->length = htons(length);
169 ofs->table_id = s->table_idx;
170 ofs->match.wildcards = htonl(flow->key.wildcards);
171 ofs->match.in_port = flow->key.flow.in_port;
172 memcpy(ofs->match.dl_src, flow->key.flow.dl_src, ETH_ADDR_LEN);
173 memcpy(ofs->match.dl_dst, flow->key.flow.dl_dst, ETH_ADDR_LEN);
174 ofs->match.dl_vlan = flow->key.flow.dl_vlan;
175 ofs->match.dl_type = flow->key.flow.dl_type;
176 ofs->match.nw_src = flow->key.flow.nw_src;
177 ofs->match.nw_dst = flow->key.flow.nw_dst;
178 ofs->match.nw_proto = flow->key.flow.nw_proto;
179 ofs->match.tp_src = flow->key.flow.tp_src;
180 ofs->match.tp_dst = flow->key.flow.tp_dst;
181 ofs->duration = htonl(s->now - flow->created);
182 ofs->priority = htons(flow->priority);
183 ofs->idle_timeout = htons(flow->idle_timeout);
184 ofs->hard_timeout = htons(flow->hard_timeout);
185 ofs->packet_count = htonll(flow->packet_count);
186 ofs->byte_count = htonll(flow->byte_count);
187 memcpy(ofs->actions, flow->sf_acts->actions, flow->sf_acts->actions_len);
188
189 return s->buffer->size >= MAX_FLOW_STATS_BYTES;
190}
191
192int
194{
195 sw_flow_key match_key;
196
197 flow_extract_match(&match_key, &s->rq.match);
198
199 s->buffer = buffer;
200 s->now = time_now();
201 while (s->table_idx < swtch->GetChain()->n_tables &&
202 (s->rq.table_id == 0xff || s->rq.table_id == s->table_idx))
203 {
204 sw_table* table = swtch->GetChain()->tables[s->table_idx];
205
206 if (table->iterate(table,
207 &match_key,
208 s->rq.out_port,
209 &s->position,
211 s))
212 {
213 break;
214 }
215
216 s->table_idx++;
217 memset(&s->position, 0, sizeof s->position);
218 }
219 return s->buffer->size >= MAX_FLOW_STATS_BYTES;
220}
221
222int
223Stats::AggregateStatsInit(const void* body, int body_len, void** state)
224{
225 // ofp_aggregate_stats_request *s = (ofp_aggregate_stats_request*)body;
226 *state = (ofp_aggregate_stats_request*)body;
227 return 0;
228}
229
230int
231Stats_AggregateDumpCallback(sw_flow* flow, void* state)
232{
233 ofp_aggregate_stats_reply* s = (ofp_aggregate_stats_reply*)state;
234 s->packet_count += flow->packet_count;
235 s->byte_count += flow->byte_count;
236 s->flow_count++;
237 return 0;
238}
239
240int
242 ofp_aggregate_stats_request* s,
243 ofpbuf* buffer)
244{
245 ofp_aggregate_stats_request* rq = s;
246 ofp_aggregate_stats_reply* rpy =
247 (ofp_aggregate_stats_reply*)ofpbuf_put_zeros(buffer, sizeof *rpy);
248 sw_flow_key match_key;
249 flow_extract_match(&match_key, &rq->match);
250 int table_idx = rq->table_id == 0xff ? 0 : rq->table_id;
251
252 sw_table_position position;
253 memset(&position, 0, sizeof position);
254
255 while (table_idx < swtch->GetChain()->n_tables &&
256 (rq->table_id == 0xff || rq->table_id == table_idx))
257 {
258 sw_table* table = swtch->GetChain()->tables[table_idx];
259 int error = table->iterate(table,
260 &match_key,
261 rq->out_port,
262 &position,
264 rpy);
265 if (error)
266 {
267 return error;
268 }
269
270 table_idx++;
271 memset(&position, 0, sizeof position);
272 }
273
274 rpy->packet_count = htonll(rpy->packet_count);
275 rpy->byte_count = htonll(rpy->byte_count);
276 rpy->flow_count = htonl(rpy->flow_count);
277 return 0;
278}
279
280int
281Stats::TableStatsDump(Ptr<OpenFlowSwitchNetDevice> swtch, void* state, ofpbuf* buffer)
282{
283 sw_chain* ft = swtch->GetChain();
284 for (int i = 0; i < ft->n_tables; i++)
285 {
286 ofp_table_stats* ots = (ofp_table_stats*)ofpbuf_put_zeros(buffer, sizeof *ots);
287 sw_table_stats stats;
288 ft->tables[i]->stats(ft->tables[i], &stats);
289 strncpy(ots->name, stats.name, sizeof ots->name);
290 ots->table_id = i;
291 ots->wildcards = htonl(stats.wildcards);
292 ots->max_entries = htonl(stats.max_flows);
293 ots->active_count = htonl(stats.n_flows);
294 ots->lookup_count = htonll(stats.n_lookup);
295 ots->matched_count = htonll(stats.n_matched);
296 }
297 return 0;
298}
299
300// stats for the port table which is similar to stats for the flow tables
301int
303{
304 ofp_vport_table_stats* opts = (ofp_vport_table_stats*)ofpbuf_put_zeros(buffer, sizeof *opts);
305 opts->max_vports = htonl(swtch->GetVPortTable().max_vports);
306 opts->active_vports = htonl(swtch->GetVPortTable().active_vports);
307 opts->lookup_count = htonll(swtch->GetVPortTable().lookup_count);
308 opts->port_match_count = htonll(swtch->GetVPortTable().port_match_count);
309 opts->chain_match_count = htonll(swtch->GetVPortTable().chain_match_count);
310
311 return 0;
312}
313
314int
315Stats::PortStatsInit(const void* body, int body_len, void** state)
316{
317 auto s = (PortStatsState*)xmalloc(sizeof(PortStatsState));
318
319 // the body contains a list of port numbers
320 s->ports = (uint32_t*)xmalloc(body_len);
321 memcpy(s->ports, body, body_len);
322 s->num_ports = body_len / sizeof(uint32_t);
323
324 *state = s;
325 return 0;
326}
327
328int
330{
331 ofp_port_stats* ops;
333
334 // port stats are different depending on whether port is physical or virtual
335 for (size_t i = 0; i < s->num_ports; i++)
336 {
337 port = ntohl(s->ports[i]);
338 // physical port?
339 if (port <= OFPP_MAX)
340 {
341 Port p = swtch->GetSwitchPort(port);
342
343 if (!p.netdev)
344 {
345 continue;
346 }
347
348 ops = (ofp_port_stats*)ofpbuf_put_zeros(buffer, sizeof *ops);
349 ops->port_no = htonl(swtch->GetSwitchPortIndex(p));
350 ops->rx_packets = htonll(p.rx_packets);
351 ops->tx_packets = htonll(p.tx_packets);
352 ops->rx_bytes = htonll(p.rx_bytes);
353 ops->tx_bytes = htonll(p.tx_bytes);
354 ops->rx_dropped = htonll(-1);
355 ops->tx_dropped = htonll(p.tx_dropped);
356 ops->rx_errors = htonll(-1);
357 ops->tx_errors = htonll(-1);
358 ops->rx_frame_err = htonll(-1);
359 ops->rx_over_err = htonll(-1);
360 ops->rx_crc_err = htonll(-1);
361 ops->collisions = htonll(-1);
362 ops->mpls_ttl0_dropped = htonll(p.mpls_ttl0_dropped);
363 ops++;
364 }
365 else if (port >= OFPP_VP_START && port <= OFPP_VP_END) // virtual port?
366 {
367 // lookup the virtual port
368 vport_table_t vt = swtch->GetVPortTable();
369 vport_table_entry* vpe = vport_table_lookup(&vt, port);
370 if (!vpe)
371 {
372 NS_LOG_ERROR("vport entry not found!");
373 continue;
374 }
375 // only tx_packets and tx_bytes are really relevant for virtual ports
376 ops = (ofp_port_stats*)ofpbuf_put_zeros(buffer, sizeof *ops);
377 ops->port_no = htonl(vpe->vport);
378 ops->rx_packets = htonll(-1);
379 ops->tx_packets = htonll(vpe->packet_count);
380 ops->rx_bytes = htonll(-1);
381 ops->tx_bytes = htonll(vpe->byte_count);
382 ops->rx_dropped = htonll(-1);
383 ops->tx_dropped = htonll(-1);
384 ops->rx_errors = htonll(-1);
385 ops->tx_errors = htonll(-1);
386 ops->rx_frame_err = htonll(-1);
387 ops->rx_over_err = htonll(-1);
388 ops->rx_crc_err = htonll(-1);
389 ops->collisions = htonll(-1);
390 ops->mpls_ttl0_dropped = htonll(-1);
391 ops++;
392 }
393 }
394 return 0;
395}
396
397bool
398Action::IsValidType(ofp_action_type type)
399{
400 switch (type)
401 {
402 case OFPAT_OUTPUT:
403 case OFPAT_SET_VLAN_VID:
404 case OFPAT_SET_VLAN_PCP:
405 case OFPAT_STRIP_VLAN:
406 case OFPAT_SET_DL_SRC:
407 case OFPAT_SET_DL_DST:
408 case OFPAT_SET_NW_SRC:
409 case OFPAT_SET_NW_DST:
410 case OFPAT_SET_TP_SRC:
411 case OFPAT_SET_TP_DST:
412 case OFPAT_SET_MPLS_LABEL:
413 case OFPAT_SET_MPLS_EXP:
414 return true;
415 default:
416 return false;
417 }
418}
419
420uint16_t
421Action::Validate(ofp_action_type type,
422 size_t len,
423 const sw_flow_key* key,
424 const ofp_action_header* ah)
425{
426 size_t size = 0;
427
428 switch (type)
429 {
430 case OFPAT_OUTPUT: {
431 if (len != sizeof(ofp_action_output))
432 {
433 return OFPBAC_BAD_LEN;
434 }
435
436 ofp_action_output* oa = (ofp_action_output*)ah;
437
438 // To prevent loops, make sure there's no action to send to the OFP_TABLE virtual port.
439
440 // port is now 32-bit
441 if (oa->port == OFPP_NONE || oa->port == key->flow.in_port) // htonl(OFPP_NONE);
442 { // if (oa->port == htons(OFPP_NONE) || oa->port == key->flow.in_port)
443 return OFPBAC_BAD_OUT_PORT;
444 }
445
446 return ACT_VALIDATION_OK;
447 }
448 case OFPAT_SET_VLAN_VID:
449 size = sizeof(ofp_action_vlan_vid);
450 break;
451 case OFPAT_SET_VLAN_PCP:
452 size = sizeof(ofp_action_vlan_pcp);
453 break;
454 case OFPAT_STRIP_VLAN:
455 size = sizeof(ofp_action_header);
456 break;
457 case OFPAT_SET_DL_SRC:
458 case OFPAT_SET_DL_DST:
459 size = sizeof(ofp_action_dl_addr);
460 break;
461 case OFPAT_SET_NW_SRC:
462 case OFPAT_SET_NW_DST:
463 size = sizeof(ofp_action_nw_addr);
464 break;
465 case OFPAT_SET_TP_SRC:
466 case OFPAT_SET_TP_DST:
467 size = sizeof(ofp_action_tp_port);
468 break;
469 case OFPAT_SET_MPLS_LABEL:
470 size = sizeof(ofp_action_mpls_label);
471 break;
472 case OFPAT_SET_MPLS_EXP:
473 size = sizeof(ofp_action_mpls_exp);
474 break;
475 default:
476 break;
477 }
478
479 if (len != size)
480 {
481 return OFPBAC_BAD_LEN;
482 }
483 return ACT_VALIDATION_OK;
484}
485
486void
487Action::Execute(ofp_action_type type, ofpbuf* buffer, sw_flow_key* key, const ofp_action_header* ah)
488{
489 switch (type)
490 {
491 case OFPAT_OUTPUT:
492 break;
493 case OFPAT_SET_VLAN_VID:
494 set_vlan_vid(buffer, key, ah);
495 break;
496 case OFPAT_SET_VLAN_PCP:
497 set_vlan_pcp(buffer, key, ah);
498 break;
499 case OFPAT_STRIP_VLAN:
500 strip_vlan(buffer, key, ah);
501 break;
502 case OFPAT_SET_DL_SRC:
503 case OFPAT_SET_DL_DST:
504 set_dl_addr(buffer, key, ah);
505 break;
506 case OFPAT_SET_NW_SRC:
507 case OFPAT_SET_NW_DST:
508 set_nw_addr(buffer, key, ah);
509 break;
510 case OFPAT_SET_TP_SRC:
511 case OFPAT_SET_TP_DST:
512 set_tp_port(buffer, key, ah);
513 break;
514 case OFPAT_SET_MPLS_LABEL:
515 set_mpls_label(buffer, key, ah);
516 break;
517 case OFPAT_SET_MPLS_EXP:
518 set_mpls_exp(buffer, key, ah);
519 break;
520 default:
521 break;
522 }
523}
524
525bool
526VPortAction::IsValidType(ofp_vport_action_type type)
527{
528 switch (type)
529 {
530 case OFPPAT_POP_MPLS:
531 case OFPPAT_PUSH_MPLS:
532 case OFPPAT_SET_MPLS_LABEL:
533 case OFPPAT_SET_MPLS_EXP:
534 return true;
535 default:
536 return false;
537 }
538}
539
540uint16_t
541VPortAction::Validate(ofp_vport_action_type type, size_t len, const ofp_action_header* ah)
542{
543 size_t size = 0;
544
545 switch (type)
546 {
547 case OFPPAT_POP_MPLS:
548 size = sizeof(ofp_vport_action_pop_mpls);
549 break;
550 case OFPPAT_PUSH_MPLS:
551 size = sizeof(ofp_vport_action_push_mpls);
552 break;
553 case OFPPAT_SET_MPLS_LABEL:
554 size = sizeof(ofp_vport_action_set_mpls_label);
555 break;
556 case OFPPAT_SET_MPLS_EXP:
557 size = sizeof(ofp_vport_action_set_mpls_exp);
558 break;
559 default:
560 break;
561 }
562
563 if (len != size)
564 {
565 return OFPBAC_BAD_LEN;
566 }
567 return ACT_VALIDATION_OK;
568}
569
570void
571VPortAction::Execute(ofp_vport_action_type type,
572 ofpbuf* buffer,
573 const sw_flow_key* key,
574 const ofp_action_header* ah)
575{
576 switch (type)
577 {
578 case OFPPAT_POP_MPLS: {
579 ofp_vport_action_pop_mpls* opapm = (ofp_vport_action_pop_mpls*)ah;
580 pop_mpls_act(nullptr, buffer, key, &opapm->apm);
581 break;
582 }
583 case OFPPAT_PUSH_MPLS: {
584 ofp_vport_action_push_mpls* opapm = (ofp_vport_action_push_mpls*)ah;
585 push_mpls_act(nullptr, buffer, key, &opapm->apm);
586 break;
587 }
588 case OFPPAT_SET_MPLS_LABEL: {
589 ofp_vport_action_set_mpls_label* oparml = (ofp_vport_action_set_mpls_label*)ah;
590 set_mpls_label_act(buffer, key, oparml->label_out);
591 break;
592 }
593 case OFPPAT_SET_MPLS_EXP: {
594 ofp_vport_action_set_mpls_exp* oparme = (ofp_vport_action_set_mpls_exp*)ah;
595 set_mpls_exp_act(buffer, key, oparme->exp);
596 break;
597 }
598 default:
599 break;
600 }
601}
602
603bool
604EricssonAction::IsValidType(er_action_type type)
605{
606 switch (type)
607 {
608 case ERXT_POP_MPLS:
609 case ERXT_PUSH_MPLS:
610 return true;
611 default:
612 return false;
613 }
614}
615
616uint16_t
617EricssonAction::Validate(er_action_type type, size_t len)
618{
619 size_t size = 0;
620
621 switch (type)
622 {
623 case ERXT_POP_MPLS:
624 size = sizeof(er_action_pop_mpls);
625 break;
626 case ERXT_PUSH_MPLS:
627 size = sizeof(er_action_push_mpls);
628 break;
629 default:
630 break;
631 }
632
633 if (len != size)
634 {
635 return OFPBAC_BAD_LEN;
636 }
637 return ACT_VALIDATION_OK;
638}
639
640void
641EricssonAction::Execute(er_action_type type,
642 ofpbuf* buffer,
643 const sw_flow_key* key,
644 const er_action_header* ah)
645{
646 switch (type)
647 {
648 case ERXT_POP_MPLS: {
649 er_action_pop_mpls* erapm = (er_action_pop_mpls*)ah;
650 pop_mpls_act(nullptr, buffer, key, &erapm->apm);
651 break;
652 }
653 case ERXT_PUSH_MPLS: {
654 er_action_push_mpls* erapm = (er_action_push_mpls*)ah;
655 push_mpls_act(nullptr, buffer, key, &erapm->apm);
656 break;
657 }
658 default:
659 break;
660 }
661}
662
663/* static */
664TypeId
666{
667 static TypeId tid = TypeId("ns3::ofi::Controller")
668 .SetParent<Object>()
669 .SetGroupName("OpenFlow")
670 .AddConstructor<Controller>();
671 return tid;
672}
673
675{
676 m_switches.clear();
677}
678
679void
681{
682 if (m_switches.find(swtch) != m_switches.end())
683 {
684 NS_LOG_INFO("This Controller has already registered this switch!");
685 }
686 else
687 {
688 m_switches.insert(swtch);
689 }
690}
691
692void
694{
695 if (m_switches.find(swtch) == m_switches.end())
696 {
697 NS_LOG_ERROR("Can't send to this switch, not registered to the Controller.");
698 return;
699 }
700
701 swtch->ForwardControlInput(msg, length);
702}
703
704ofp_flow_mod*
705Controller::BuildFlow(sw_flow_key key,
706 uint32_t buffer_id,
707 uint16_t command,
708 void* acts,
709 size_t actions_len,
710 int idle_timeout,
711 int hard_timeout)
712{
713 ofp_flow_mod* ofm = (ofp_flow_mod*)malloc(sizeof(ofp_flow_mod) + actions_len);
714 ofm->header.version = OFP_VERSION;
715 ofm->header.type = OFPT_FLOW_MOD;
716 ofm->header.length = htons(sizeof(ofp_flow_mod) + actions_len);
717 ofm->command = htons(command);
718 ofm->idle_timeout = htons(idle_timeout);
719 ofm->hard_timeout = htons(hard_timeout);
720 ofm->buffer_id = htonl(buffer_id);
721 ofm->priority = OFP_DEFAULT_PRIORITY;
722 memcpy(ofm->actions, acts, actions_len);
723
724 ofm->match.wildcards = key.wildcards; // Wildcard fields
725 ofm->match.in_port = key.flow.in_port; // Input switch port
726 memcpy(ofm->match.dl_src,
727 key.flow.dl_src,
728 sizeof ofm->match.dl_src); // Ethernet source address.
729 memcpy(ofm->match.dl_dst,
730 key.flow.dl_dst,
731 sizeof ofm->match.dl_dst); // Ethernet destination address.
732 ofm->match.dl_vlan = key.flow.dl_vlan; // Input VLAN OFP_VLAN_NONE;
733 ofm->match.dl_type = key.flow.dl_type; // Ethernet frame type ETH_TYPE_IP;
734 ofm->match.nw_proto = key.flow.nw_proto; // IP Protocol
735 ofm->match.nw_src = key.flow.nw_src; // IP source address
736 ofm->match.nw_dst = key.flow.nw_dst; // IP destination address
737 ofm->match.tp_src = key.flow.tp_src; // TCP/UDP source port
738 ofm->match.tp_dst = key.flow.tp_dst; // TCP/UDP destination port
739 ofm->match.mpls_label1 = key.flow.mpls_label1; // Top of label stack htonl(MPLS_INVALID_LABEL);
740 ofm->match.mpls_label2 =
741 key.flow.mpls_label1; // Second label (if available) htonl(MPLS_INVALID_LABEL);
742
743 return ofm;
744}
745
746uint8_t
748{
749 ofp_header* hdr = (ofp_header*)ofpbuf_try_pull(buffer, sizeof(ofp_header));
750 uint8_t type = hdr->type;
751 ofpbuf_push_uninit(buffer, sizeof(ofp_header));
752 return type;
753}
754
755void
757{
758 if (cb)
759 {
760 int error = 1;
761 while (error > 0) // Switch's StatsDump returns 1 if the reply isn't complete.
762 {
763 error = cb->swtch->StatsDump(cb);
764 }
765
766 if (error != 0) // When the reply is complete, error will equal zero if there's no errors.
767 {
768 NS_LOG_WARN("Dump Callback Error: " << strerror(-error));
769 }
770
771 // Clean up
772 cb->swtch->StatsDone(cb);
773 }
774}
775
776/* static */
777TypeId
779{
780 static TypeId tid = TypeId("ns3::ofi::DropController")
782 .SetGroupName("OpenFlow")
783 .AddConstructor<DropController>();
784 return tid;
785}
786
787void
789{
790 if (m_switches.find(swtch) == m_switches.end())
791 {
792 NS_LOG_ERROR("Can't receive from this switch, not registered to the Controller.");
793 return;
794 }
795
796 // We have received any packet at this point, so we pull the header to figure out what type of
797 // packet we're handling.
798 uint8_t type = GetPacketType(buffer);
799
800 if (type == OFPT_PACKET_IN) // The switch didn't understand the packet it received, so it
801 // forwarded it to the controller.
802 {
803 ofp_packet_in* opi = (ofp_packet_in*)ofpbuf_try_pull(buffer, offsetof(ofp_packet_in, data));
804 int port = ntohs(opi->in_port);
805
806 // Create matching key.
807 sw_flow_key key;
808 key.wildcards = 0;
809 flow_extract(buffer, port != -1 ? port : OFPP_NONE, &key.flow);
810
811 ofp_flow_mod* ofm = BuildFlow(key,
812 opi->buffer_id,
813 OFPFC_ADD,
814 nullptr,
815 0,
816 OFP_FLOW_PERMANENT,
817 OFP_FLOW_PERMANENT);
818 SendToSwitch(swtch, ofm, ofm->header.length);
819 }
820}
821
822TypeId
824{
825 static TypeId tid =
826 TypeId("ns3::ofi::LearningController")
828 .SetGroupName("Openflow")
829 .AddConstructor<LearningController>()
830 .AddAttribute("ExpirationTime",
831 "Time it takes for learned MAC state entry/created flow to expire.",
832 TimeValue(Seconds(0)),
835 return tid;
836}
837
838void
840{
841 if (m_switches.find(swtch) == m_switches.end())
842 {
843 NS_LOG_ERROR("Can't receive from this switch, not registered to the Controller.");
844 return;
845 }
846
847 // We have received any packet at this point, so we pull the header to figure out what type of
848 // packet we're handling.
849 uint8_t type = GetPacketType(buffer);
850
851 if (type == OFPT_PACKET_IN) // The switch didn't understand the packet it received, so it
852 // forwarded it to the controller.
853 {
854 ofp_packet_in* opi = (ofp_packet_in*)ofpbuf_try_pull(buffer, offsetof(ofp_packet_in, data));
855 int port = ntohs(opi->in_port);
856
857 // Create matching key.
858 sw_flow_key key;
859 key.wildcards = 0;
860 flow_extract(buffer, port != -1 ? port : OFPP_NONE, &key.flow);
861
862 uint16_t out_port = OFPP_FLOOD;
863 uint16_t in_port = ntohs(key.flow.in_port);
864
865 // If the destination address is learned to a specific port, find it.
866 Mac48Address dst_addr;
867 dst_addr.CopyFrom(key.flow.dl_dst);
868 if (!dst_addr.IsBroadcast())
869 {
870 auto st = m_learnState.find(dst_addr);
871 if (st != m_learnState.end())
872 {
873 out_port = st->second.port;
874 }
875 else
876 {
877 NS_LOG_INFO("Setting to flood; don't know yet what port " << dst_addr
878 << " is connected to");
879 }
880 }
881 else
882 {
883 NS_LOG_INFO("Setting to flood; this packet is a broadcast");
884 }
885
886 // Create output-to-port action
887 ofp_action_output x[1];
888 x[0].type = htons(OFPAT_OUTPUT);
889 x[0].len = htons(sizeof(ofp_action_output));
890 x[0].port = out_port;
891
892 // Create a new flow that outputs matched packets to a learned port, OFPP_FLOOD if there's
893 // no learned port.
894 ofp_flow_mod* ofm = BuildFlow(key,
895 opi->buffer_id,
896 OFPFC_ADD,
897 x,
898 sizeof(x),
899 OFP_FLOW_PERMANENT,
900 m_expirationTime.IsZero() ? OFP_FLOW_PERMANENT
902 SendToSwitch(swtch, ofm, ofm->header.length);
903
904 // We can learn a specific port for the source address for future use.
905 Mac48Address src_addr;
906 src_addr.CopyFrom(key.flow.dl_src);
907 auto st = m_learnState.find(src_addr);
908 if (st == m_learnState.end()) // We haven't learned our source MAC yet.
909 {
910 LearnedState ls;
911 ls.port = in_port;
912 m_learnState.insert(std::make_pair(src_addr, ls));
913 NS_LOG_INFO("Learned that " << src_addr << " can be found over port " << in_port);
914
915 // Learn src_addr goes to a certain port.
916 ofp_action_output x2[1];
917 x2[0].type = htons(OFPAT_OUTPUT);
918 x2[0].len = htons(sizeof(ofp_action_output));
919 x2[0].port = in_port;
920
921 // Switch MAC Addresses and ports to the flow we're modifying
922 src_addr.CopyTo(key.flow.dl_dst);
923 dst_addr.CopyTo(key.flow.dl_src);
924 key.flow.in_port = out_port;
925 ofp_flow_mod* ofm2 = BuildFlow(
926 key,
927 -1,
928 OFPFC_MODIFY,
929 x2,
930 sizeof(x2),
931 OFP_FLOW_PERMANENT,
932 m_expirationTime.IsZero() ? OFP_FLOW_PERMANENT : m_expirationTime.GetSeconds());
933 SendToSwitch(swtch, ofm2, ofm2->header.length);
934 }
935 }
936}
937
938void
940 uint64_t packet_uid,
941 ofpbuf* buffer,
942 sw_flow_key* key,
943 const ofp_action_header* actions,
944 size_t actions_len,
945 int ignore_no_fwd)
946{
948 /* Every output action needs a separate clone of 'buffer', but the common
949 * case is just a single output action, so that doing a clone and then
950 * freeing the original buffer is wasteful. So the following code is
951 * slightly obscure just to avoid that. */
952 int prev_port;
953 size_t max_len = 0; // Initialize to make compiler happy
954 uint16_t in_port = key->flow.in_port; // ntohs(key->flow.in_port);
955 auto p = (uint8_t*)actions;
956
957 prev_port = -1;
958
959 if (actions_len == 0)
960 {
961 NS_LOG_INFO("No actions set to this flow. Dropping packet.");
962 return;
963 }
964
965 /* The action list was already validated, so we can be a bit looser
966 * in our sanity-checking. */
967 while (actions_len > 0)
968 {
969 ofp_action_header* ah = (ofp_action_header*)p;
970 size_t len = htons(ah->len);
971
972 if (prev_port != -1)
973 {
974 swtch->DoOutput(packet_uid, in_port, max_len, prev_port, ignore_no_fwd);
975 prev_port = -1;
976 }
977
978 if (ah->type == htons(OFPAT_OUTPUT))
979 {
980 ofp_action_output* oa = (ofp_action_output*)p;
981
982 // port is now 32-bits
983 prev_port = oa->port; // ntohl(oa->port);
984 // prev_port = ntohs(oa->port);
985 max_len = ntohs(oa->max_len);
986 }
987 else
988 {
989 uint16_t type = ntohs(ah->type);
991 (ofp_action_type)type)) // Execute a built-in OpenFlow action against 'buffer'.
992 {
993 Action::Execute((ofp_action_type)type, buffer, key, ah);
994 }
995 else if (type == OFPAT_VENDOR)
996 {
997 ExecuteVendor(buffer, key, ah);
998 }
999 }
1000
1001 p += len;
1002 actions_len -= len;
1003 }
1004
1005 if (prev_port != -1)
1006 {
1007 swtch->DoOutput(packet_uid, in_port, max_len, prev_port, ignore_no_fwd);
1008 }
1009}
1010
1011uint16_t
1012ValidateActions(const sw_flow_key* key, const ofp_action_header* actions, size_t actions_len)
1013{
1014 auto p = (uint8_t*)actions;
1015 int err;
1016
1017 while (actions_len >= sizeof(ofp_action_header))
1018 {
1019 ofp_action_header* ah = (ofp_action_header*)p;
1020 size_t len = ntohs(ah->len);
1021 uint16_t type;
1022
1023 /* Make there's enough remaining data for the specified length
1024 * and that the action length is a multiple of 64 bits. */
1025 if ((actions_len < len) || (len % 8) != 0)
1026 {
1027 return OFPBAC_BAD_LEN;
1028 }
1029
1030 type = ntohs(ah->type);
1031 if (Action::IsValidType((ofp_action_type)type)) // Validate built-in OpenFlow actions.
1032 {
1033 err = Action::Validate((ofp_action_type)type, len, key, ah);
1034 if (err != ACT_VALIDATION_OK)
1035 {
1036 return err;
1037 }
1038 }
1039 else if (type == OFPAT_VENDOR)
1040 {
1041 err = ValidateVendor(key, ah, len);
1042 if (err != ACT_VALIDATION_OK)
1043 {
1044 return err;
1045 }
1046 }
1047 else
1048 {
1049 return OFPBAC_BAD_TYPE;
1050 }
1051
1052 p += len;
1053 actions_len -= len;
1054 }
1055
1056 // Check if there's any trailing garbage.
1057 if (actions_len != 0)
1058 {
1059 return OFPBAC_BAD_LEN;
1060 }
1061
1062 return ACT_VALIDATION_OK;
1063}
1064
1065void
1067 uint64_t packet_uid,
1068 ofpbuf* buffer,
1069 sw_flow_key* key,
1070 const ofp_action_header* actions,
1071 size_t actions_len)
1072{
1073 /* Every output action needs a separate clone of 'buffer', but the common
1074 * case is just a single output action, so that doing a clone and then
1075 * freeing the original buffer is wasteful. So the following code is
1076 * slightly obscure just to avoid that. */
1077 int prev_port;
1078 size_t max_len = 0; // Initialize to make compiler happy
1079 uint16_t in_port = ntohs(key->flow.in_port);
1080 auto p = (uint8_t*)actions;
1081 uint16_t type;
1082 ofp_action_output* oa;
1083
1084 prev_port = -1;
1085 /* The action list was already validated, so we can be a bit looser
1086 * in our sanity-checking. */
1087 while (actions_len > 0)
1088 {
1089 ofp_action_header* ah = (ofp_action_header*)p;
1090 size_t len = htons(ah->len);
1091 if (prev_port != -1)
1092 {
1093 swtch->DoOutput(packet_uid, in_port, max_len, prev_port, false);
1094 prev_port = -1;
1095 }
1096
1097 if (ah->type == htons(OFPAT_OUTPUT))
1098 {
1099 oa = (ofp_action_output*)p;
1100 prev_port = ntohl(oa->port);
1101 max_len = ntohs(oa->max_len);
1102 }
1103 else
1104 {
1105 type = ah->type; // ntohs(ah->type);
1106 VPortAction::Execute((ofp_vport_action_type)type, buffer, key, ah);
1107 }
1108
1109 p += len;
1110 actions_len -= len;
1111 }
1112
1113 if (prev_port != -1)
1114 {
1115 swtch->DoOutput(packet_uid, in_port, max_len, prev_port, false);
1116 }
1117}
1118
1119uint16_t
1120ValidateVPortActions(const ofp_action_header* actions, size_t actions_len)
1121{
1122 auto p = (uint8_t*)actions;
1123 int err;
1124
1125 while (actions_len >= sizeof(ofp_action_header))
1126 {
1127 ofp_action_header* ah = (ofp_action_header*)p;
1128 size_t len = ntohs(ah->len);
1129 uint16_t type;
1130
1131 /* Make there's enough remaining data for the specified length
1132 * and that the action length is a multiple of 64 bits. */
1133 if ((actions_len < len) || (len % 8) != 0)
1134 {
1135 return OFPBAC_BAD_LEN;
1136 }
1137
1138 type = ntohs(ah->type);
1140 (ofp_vport_action_type)type)) // Validate "built-in" OpenFlow port table actions.
1141 {
1142 err = VPortAction::Validate((ofp_vport_action_type)type, len, ah);
1143 if (err != ACT_VALIDATION_OK)
1144 {
1145 return err;
1146 }
1147 }
1148 else
1149 {
1150 return OFPBAC_BAD_TYPE;
1151 }
1152
1153 p += len;
1154 actions_len -= len;
1155 }
1156
1157 // Check if there's any trailing garbage.
1158 if (actions_len != 0)
1159 {
1160 return OFPBAC_BAD_LEN;
1161 }
1162
1163 return ACT_VALIDATION_OK;
1164}
1165
1166void
1167ExecuteVendor(ofpbuf* buffer, const sw_flow_key* key, const ofp_action_header* ah)
1168{
1169 ofp_action_vendor_header* avh = (ofp_action_vendor_header*)ah;
1170
1171 switch (ntohl(avh->vendor))
1172 {
1173 case NX_VENDOR_ID:
1174 // Nothing to execute yet.
1175 break;
1176 case ER_VENDOR_ID: {
1177 const er_action_header* erah = (const er_action_header*)avh;
1178 EricssonAction::Execute((er_action_type)ntohs(erah->subtype), buffer, key, erah);
1179 break;
1180 }
1181 default:
1182 // This should not be possible due to prior validation.
1183 NS_LOG_INFO("attempt to execute action with unknown vendor: " << ntohl(avh->vendor));
1184 break;
1185 }
1186}
1187
1188uint16_t
1189ValidateVendor(const sw_flow_key* key, const ofp_action_header* ah, uint16_t len)
1190{
1191 ofp_action_vendor_header* avh;
1192 int ret = ACT_VALIDATION_OK;
1193
1194 if (len < sizeof(ofp_action_vendor_header))
1195 {
1196 return OFPBAC_BAD_LEN;
1197 }
1198
1199 avh = (ofp_action_vendor_header*)ah;
1200
1201 switch (ntohl(avh->vendor))
1202 {
1203 case NX_VENDOR_ID: // Validate Nicara OpenFlow actions.
1204 ret = OFPBAC_BAD_VENDOR_TYPE; // Nothing to validate yet.
1205 break;
1206 case ER_VENDOR_ID: // Validate Ericsson OpenFlow actions.
1207 {
1208 const er_action_header* erah = (const er_action_header*)avh;
1209 ret = EricssonAction::Validate((er_action_type)ntohs(erah->subtype), len);
1210 break;
1211 }
1212 default:
1213 return OFPBAC_BAD_VENDOR;
1214 }
1215
1216 return ret;
1217}
1218
1219} // namespace ofi
1220
1221} // namespace ns3
an EUI-48 address
void CopyFrom(const uint8_t buffer[6])
void CopyTo(uint8_t buffer[6]) const
bool IsBroadcast() const
A base class which provides memory management and object aggregation.
Definition object.h:78
static const char * GetManufacturerDescription()
void StatsDone(ofi::StatsDumpCallback *cb_)
Stats callback is done.
int StatsDump(ofi::StatsDumpCallback *cb_)
Stats callback is ready for a dump.
Smart pointer class similar to boost::intrusive_ptr.
double GetSeconds() const
Get an approximation of the time stored in this instance in the indicated unit.
Definition nstime.h:392
bool IsZero() const
Exactly equivalent to t == 0.
Definition nstime.h:304
AttributeValue implementation for Time.
Definition nstime.h:1431
a unique identifier for an interface.
Definition type-id.h:48
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
An interface for a Controller of OpenFlowSwitchNetDevices.
virtual void SendToSwitch(Ptr< OpenFlowSwitchNetDevice > swtch, void *msg, size_t length)
However the controller is implemented, this method is to be used to pass a message on to a switch.
uint8_t GetPacketType(ofpbuf *buffer)
Get the packet type on the buffer, which can then be used to determine how to handle the buffer.
Switches_t m_switches
The collection of switches registered to this controller.
~Controller() override
Destructor.
static TypeId GetTypeId()
Register this type.
ofp_flow_mod * BuildFlow(sw_flow_key key, uint32_t buffer_id, uint16_t command, void *acts, size_t actions_len, int idle_timeout, int hard_timeout)
Construct flow data from a matching key to build a flow entry for adding, modifying,...
void StartDump(StatsDumpCallback *cb)
Starts a callback-based, reliable, possibly multi-message reply to a request made by the controller.
virtual void AddSwitch(Ptr< OpenFlowSwitchNetDevice > swtch)
Adds a switch to the controller.
Demonstration of a Drop controller.
void ReceiveFromSwitch(Ptr< OpenFlowSwitchNetDevice > swtch, ofpbuf *buffer) override
A switch calls this method to pass a message on to the Controller.
static TypeId GetTypeId()
Register this type.
Demonstration of a Learning controller.
static TypeId GetTypeId()
Register this type.
void ReceiveFromSwitch(Ptr< OpenFlowSwitchNetDevice > swtch, ofpbuf *buffer) override
A switch calls this method to pass a message on to the Controller.
Time m_expirationTime
Time it takes for learned MAC state entry/created flow to expire.
LearnState_t m_learnState
Learned state data.
int PortStatsInit(const void *body, int body_len, void **state)
Initialize the stats.
int PortTableStatsDump(Ptr< OpenFlowSwitchNetDevice > dp, void *state, ofpbuf *buffer)
Dump the stats.
int FlowStatsDump(Ptr< OpenFlowSwitchNetDevice > dp, FlowStatsState *state, ofpbuf *buffer)
Dump the stats.
int(* AggregateDumpCallback)(sw_flow *flow, void *state)
Aggregate dump callback functor.
Stats(ofp_stats_types _type, size_t body_len)
Constructor.
void DoCleanup(void *state)
Cleans any state created by the init or dump functions.
int AggregateStatsInit(const void *body, int body_len, void **state)
Initialize the stats.
int DoDump(Ptr< OpenFlowSwitchNetDevice > swtch, void *state, ofpbuf *buffer)
Appends statistics for OpenFlowSwitchNetDevice to 'buffer'.
int DoInit(const void *body, int body_len, void **state)
Prepares to dump some kind of statistics on the connected OpenFlowSwitchNetDevice.
int AggregateStatsDump(Ptr< OpenFlowSwitchNetDevice > dp, ofp_aggregate_stats_request *state, ofpbuf *buffer)
Dump the stats.
int TableStatsDump(Ptr< OpenFlowSwitchNetDevice > dp, void *state, ofpbuf *buffer)
Dump the stats.
int PortStatsDump(Ptr< OpenFlowSwitchNetDevice > dp, PortStatsState *state, ofpbuf *buffer)
Dump the stats.
ofp_stats_types type
Status type.
int FlowStatsInit(const void *body, int body_len, void **state)
Initialize the stats.
int DescStatsDump(void *state, ofpbuf *buffer)
Dumps the stats description.
int(* FlowDumpCallback)(sw_flow *flow, void *state)
Flow dump callback functor.
uint16_t port
Definition dsdv-manet.cc:33
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition nstime.h:1432
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1452
#define NS_LOG_ERROR(msg)
Use NS_LOG to output a message of level LOG_ERROR.
Definition log.h:243
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_FUNCTION_NOARGS()
Output the name of the function.
#define NS_LOG_WARN(msg)
Use NS_LOG to output a message of level LOG_WARN.
Definition log.h:250
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:264
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1344
void ExecuteVPortActions(Ptr< OpenFlowSwitchNetDevice > swtch, uint64_t packet_uid, ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *actions, size_t actions_len)
Executes a list of virtual port table entry actions.
void ExecuteActions(Ptr< OpenFlowSwitchNetDevice > swtch, uint64_t packet_uid, ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *actions, size_t actions_len, int ignore_no_fwd)
Executes a list of flow table actions.
uint16_t ValidateVendor(const sw_flow_key *key, const ofp_action_header *ah, uint16_t len)
Validates a vendor-defined action.
void ExecuteVendor(ofpbuf *buffer, const sw_flow_key *key, const ofp_action_header *ah)
Executes a vendor-defined action.
uint16_t ValidateActions(const sw_flow_key *key, const ofp_action_header *actions, size_t actions_len)
Validates a list of flow table actions.
int Stats_AggregateDumpCallback(sw_flow *flow, void *state)
int Stats_FlowDumpCallback(sw_flow *flow, void *state)
uint16_t ValidateVPortActions(const ofp_action_header *actions, size_t actions_len)
Validates a list of virtual port table entry actions.
Every class exported by the ns3 library is enclosed in the ns3 namespace.
#define MAX_FLOW_STATS_BYTES
void strip_vlan(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_mpls_label(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_dl_addr(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_nw_addr(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_vlan_pcp(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_tp_port(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_mpls_exp(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
void set_vlan_vid(ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
uint8_t data[writeSize]
static bool IsValidType(ofp_action_type type)
static void Execute(ofp_action_type type, ofpbuf *buffer, sw_flow_key *key, const ofp_action_header *ah)
Executes the action.
static uint16_t Validate(ofp_action_type type, size_t len, const sw_flow_key *key, const ofp_action_header *ah)
Validates the action on whether its data is valid or not.
static bool IsValidType(er_action_type type)
static void Execute(er_action_type type, ofpbuf *buffer, const sw_flow_key *key, const er_action_header *ah)
Executes the action.
static uint16_t Validate(er_action_type type, size_t len)
Validates the action on whether its data is valid or not.
Port and its metadata.
Ptr< NetDevice > netdev
NetDevice pointer.
unsigned long long int mpls_ttl0_dropped
Counter of packets dropped due to MPLS TTL.
unsigned long long int tx_packets
Counter of Tx packets.
unsigned long long int tx_bytes
Counter of Tx bytes.
unsigned long long int rx_packets
Counter of Rx packets.
unsigned long long int rx_bytes
Counter of Rx bytes.
unsigned long long int tx_dropped
Counter of Tx dropped packets.
State of the FlowStats request/reply.
ofp_flow_stats_request rq
Stats requests.
sw_table_position position
Table position.
State of the PortStats request/reply.
uint32_t * ports
Array of ports in network byte order.
uint32_t num_ports
Number of ports in host byte order.
Callback for a stats dump request.
Ptr< OpenFlowSwitchNetDevice > swtch
The switch that we're requesting data from.
static uint16_t Validate(ofp_vport_action_type type, size_t len, const ofp_action_header *ah)
Validates the action on whether its data is valid or not.
static void Execute(ofp_vport_action_type type, ofpbuf *buffer, const sw_flow_key *key, const ofp_action_header *ah)
Executes the action.
static bool IsValidType(ofp_vport_action_type type)