diff --git a/NEWS b/NEWS index 696168b633c..9ab6cae279d 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,8 @@ Post-v2.5.0 - OpenFlow: * OpenFlow 1.1+ OFPT_QUEUE_GET_CONFIG_REQUEST now supports OFPP_ANY. * OpenFlow 1.4+ OFPMP_QUEUE_DESC is now supported. - * New property-based packet-in message format NXT_PACKET_IN2. + * New property-based packet-in message format NXT_PACKET_IN2 with support + for arbitrary user-provided data. - ovs-ofctl: * queue-get-config command now allows a queue ID to be specified. - DPDK: diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index 4ddf05bff0e..0c13bff7417 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -254,6 +254,7 @@ enum nx_packet_in2_prop_type { /* Other. */ NXPINT_REASON, /* uint8_t, one of OFPR_*. */ NXPINT_METADATA, /* NXM or OXM for metadata fields. */ + NXPINT_USERDATA, /* From NXAST_CONTROLLER2 userdata. */ }; /* Configures the "role" of the sending controller. The default role is: diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 7dc852e6a5f..e95071fcd02 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -30,6 +30,7 @@ #include "nx-match.h" #include "odp-netlink.h" #include "ofp-parse.h" +#include "ofp-prop.h" #include "ofp-util.h" #include "ofpbuf.h" #include "unaligned.h" @@ -273,6 +274,8 @@ enum ofp_raw_action_type { /* NX1.0+(20): struct nx_action_controller. */ NXAST_RAW_CONTROLLER, + /* NX1.0+(37): struct nx_action_controller2, ... */ + NXAST_RAW_CONTROLLER2, /* NX1.0+(22): struct nx_action_write_metadata. */ NXAST_RAW_WRITE_METADATA, @@ -625,6 +628,27 @@ struct nx_action_controller { }; OFP_ASSERT(sizeof(struct nx_action_controller) == 16); +/* Properties for NXAST_CONTROLLER2. */ +enum nx_action_controller2_prop_type { + NXAC2PT_MAX_LEN, /* ovs_be16 max bytes to send (default all). */ + NXAC2PT_CONTROLLER_ID, /* ovs_be16 dest controller ID (default 0). */ + NXAC2PT_REASON, /* uint8_t reason (OFPR_*), default 0. */ + NXAC2PT_USERDATA, /* Data to copy into NXPINT_USERDATA. */ +}; + +/* Action structure for NXAST_CONTROLLER2. + * + * This replacement for NXAST_CONTROLLER makes it extensible via properties. */ +struct nx_action_controller2 { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* Length is 16 or more. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_CONTROLLER2. */ + uint8_t zeros[6]; /* Must be zero. */ + /* Followed by NXAC2PT_* properties. */ +}; +OFP_ASSERT(sizeof(struct nx_action_controller2) == 16); + static enum ofperr decode_NXAST_RAW_CONTROLLER(const struct nx_action_controller *nac, enum ofp_version ofp_version OVS_UNUSED, @@ -633,9 +657,77 @@ decode_NXAST_RAW_CONTROLLER(const struct nx_action_controller *nac, struct ofpact_controller *oc; oc = ofpact_put_CONTROLLER(out); + oc->ofpact.raw = NXAST_RAW_CONTROLLER; oc->max_len = ntohs(nac->max_len); oc->controller_id = ntohs(nac->controller_id); oc->reason = nac->reason; + ofpact_finish(out, &oc->ofpact); + + return 0; +} + +static enum ofperr +decode_NXAST_RAW_CONTROLLER2(const struct nx_action_controller2 *nac2, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + if (!is_all_zeros(nac2->zeros, sizeof nac2->zeros)) { + return OFPERR_NXBRC_MUST_BE_ZERO; + } + + size_t start_ofs = out->size; + struct ofpact_controller *oc = ofpact_put_CONTROLLER(out); + oc->ofpact.raw = NXAST_RAW_CONTROLLER2; + oc->max_len = UINT16_MAX; + oc->reason = OFPR_ACTION; + + struct ofpbuf properties; + ofpbuf_use_const(&properties, nac2, ntohs(nac2->len)); + ofpbuf_pull(&properties, sizeof *nac2); + + while (properties.size > 0) { + struct ofpbuf payload; + uint64_t type; + + enum ofperr error = ofpprop_pull(&properties, &payload, &type); + if (error) { + return error; + } + + switch (type) { + case NXAC2PT_MAX_LEN: + error = ofpprop_parse_u16(&payload, &oc->max_len); + break; + + case NXAC2PT_CONTROLLER_ID: + error = ofpprop_parse_u16(&payload, &oc->controller_id); + break; + + case NXAC2PT_REASON: { + uint8_t u8; + error = ofpprop_parse_u8(&payload, &u8); + oc->reason = u8; + break; + } + + case NXAC2PT_USERDATA: + out->size = start_ofs + OFPACT_CONTROLLER_SIZE; + ofpbuf_put(out, payload.msg, ofpbuf_msgsize(&payload)); + oc = ofpbuf_at_assert(out, start_ofs, sizeof *oc); + oc->userdata_len = ofpbuf_msgsize(&payload); + break; + + default: + error = OFPPROP_UNKNOWN(false, "NXAST_RAW_CONTROLLER2", type); + break; + } + if (error) { + return error; + } + } + + ofpact_finish(out, &oc->ofpact); + return 0; } @@ -644,12 +736,33 @@ encode_CONTROLLER(const struct ofpact_controller *controller, enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out) { - struct nx_action_controller *nac; + if (controller->userdata_len + || controller->ofpact.raw == NXAST_RAW_CONTROLLER2) { + size_t start_ofs = out->size; + put_NXAST_CONTROLLER2(out); + if (controller->max_len != UINT16_MAX) { + ofpprop_put_u16(out, NXAC2PT_MAX_LEN, controller->max_len); + } + if (controller->controller_id != 0) { + ofpprop_put_u16(out, NXAC2PT_CONTROLLER_ID, + controller->controller_id); + } + if (controller->reason != OFPR_ACTION) { + ofpprop_put_u8(out, NXAC2PT_REASON, controller->reason); + } + if (controller->userdata_len != 0) { + ofpprop_put(out, NXAC2PT_USERDATA, controller->userdata, + controller->userdata_len); + } + pad_ofpat(out, start_ofs); + } else { + struct nx_action_controller *nac; - nac = put_NXAST_CONTROLLER(out); - nac->max_len = htons(controller->max_len); - nac->controller_id = htons(controller->controller_id); - nac->reason = controller->reason; + nac = put_NXAST_CONTROLLER(out); + nac->max_len = htons(controller->max_len); + nac->controller_id = htons(controller->controller_id); + nac->reason = controller->reason; + } } static char * OVS_WARN_UNUSED_RESULT @@ -659,6 +772,7 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts, enum ofp_packet_in_reason reason = OFPR_ACTION; uint16_t controller_id = 0; uint16_t max_len = UINT16_MAX; + const char *userdata = NULL; if (!arg[0]) { /* Use defaults. */ @@ -685,6 +799,8 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts, if (error) { return error; } + } else if (!strcmp(name, "userdata")) { + userdata = value; } else { return xasprintf("unknown key \"%s\" parsing controller " "action", name); @@ -692,7 +808,7 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts, } } - if (reason == OFPR_ACTION && controller_id == 0) { + if (reason == OFPR_ACTION && controller_id == 0 && !userdata) { struct ofpact_output *output; output = ofpact_put_OUTPUT(ofpacts); @@ -705,15 +821,39 @@ parse_CONTROLLER(char *arg, struct ofpbuf *ofpacts, controller->max_len = max_len; controller->reason = reason; controller->controller_id = controller_id; + + if (userdata) { + size_t start_ofs = ofpacts->size; + const char *end = ofpbuf_put_hex(ofpacts, userdata, NULL); + if (*end) { + return xstrdup("bad hex digit in `controller' " + "action `userdata'"); + } + size_t userdata_len = ofpacts->size - start_ofs; + controller = ofpacts->header; + controller->userdata_len = userdata_len; + } + ofpact_finish(ofpacts, &controller->ofpact); } return NULL; } +static void +format_hex_arg(struct ds *s, const uint8_t *data, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (i) { + ds_put_char(s, '.'); + } + ds_put_format(s, "%02"PRIx8, data[i]); + } +} + static void format_CONTROLLER(const struct ofpact_controller *a, struct ds *s) { - if (a->reason == OFPR_ACTION && a->controller_id == 0) { + if (a->reason == OFPR_ACTION && !a->controller_id && !a->userdata_len) { ds_put_format(s, "CONTROLLER:%"PRIu16, a->max_len); } else { enum ofp_packet_in_reason reason = a->reason; @@ -732,6 +872,11 @@ format_CONTROLLER(const struct ofpact_controller *a, struct ds *s) if (a->controller_id != 0) { ds_put_format(s, "id=%"PRIu16",", a->controller_id); } + if (a->userdata_len) { + ds_put_cstr(s, "userdata="); + format_hex_arg(s, a->userdata, a->userdata_len); + ds_put_char(s, ','); + } ds_chomp(s, ','); ds_put_char(s, ')'); } @@ -4400,15 +4545,8 @@ parse_NOTE(const char *arg, struct ofpbuf *ofpacts, static void format_NOTE(const struct ofpact_note *a, struct ds *s) { - size_t i; - ds_put_cstr(s, "note:"); - for (i = 0; i < a->length; i++) { - if (i) { - ds_put_char(s, '.'); - } - ds_put_format(s, "%02"PRIx8, a->data[i]); - } + format_hex_arg(s, a->data, a->length); } /* Exit action. */ diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index 5dec177fbf1..d4125e61a3f 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, 2014, 2015 Nicira, Inc. + * Copyright (c) 2012, 2013, 2014, 2015, 2016 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ /* Output. */ \ OFPACT(OUTPUT, ofpact_output, ofpact, "output") \ OFPACT(GROUP, ofpact_group, ofpact, "group") \ - OFPACT(CONTROLLER, ofpact_controller, ofpact, "controller") \ + OFPACT(CONTROLLER, ofpact_controller, userdata, "controller") \ OFPACT(ENQUEUE, ofpact_enqueue, ofpact, "enqueue") \ OFPACT(OUTPUT_REG, ofpact_output_reg, ofpact, "output_reg") \ OFPACT(BUNDLE, ofpact_bundle, slaves, "bundle") \ @@ -245,6 +245,11 @@ struct ofpact_controller { uint16_t max_len; /* Maximum length to send to controller. */ uint16_t controller_id; /* Controller ID to send packet-in. */ enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */ + + /* Arbitrary data to include in the packet-in message (currently, only in + * NXT_PACKET_IN2). */ + uint16_t userdata_len; + uint8_t userdata[]; }; /* OFPACT_ENQUEUE. diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 74f0de6902b..41873ba26da 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -94,6 +94,17 @@ ofp_packet_to_string(const void *data, size_t len) return ds_cstr(&ds); } +static void +format_hex_arg(struct ds *s, const uint8_t *data, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (i) { + ds_put_char(s, '.'); + } + ds_put_format(s, "%02"PRIx8, data[i]); + } +} + static void ofp_print_packet_in(struct ds *string, const struct ofp_header *oh, int verbosity) @@ -141,6 +152,12 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh, } ds_put_char(string, '\n'); + if (pin.userdata_len) { + ds_put_cstr(string, " userdata="); + format_hex_arg(string, pin.userdata, pin.userdata_len); + ds_put_char(string, '\n'); + } + if (verbosity > 0) { char *packet = ofp_packet_to_string(pin.packet, pin.packet_len); ds_put_cstr(string, packet); diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 37e6e8a436c..b22011249ef 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -3361,6 +3361,11 @@ decode_nx_packet_in2(const struct ofp_header *oh, &pin->flow_metadata); break; + case NXPINT_USERDATA: + pin->userdata = payload.msg; + pin->userdata_len = ofpbuf_msgsize(&payload); + break; + default: error = OFPPROP_UNKNOWN(true, "NX_PACKET_IN2", type); break; @@ -3596,6 +3601,10 @@ ofputil_encode_nx_packet_in2(const struct ofputil_packet_in *pin, oxm_put_raw(msg, &pin->flow_metadata, version); ofpprop_end(msg, start); + if (pin->userdata_len) { + ofpprop_put(msg, NXPINT_USERDATA, pin->userdata, pin->userdata_len); + } + ofpmsg_update_length(msg); return msg; } diff --git a/lib/ofp-util.h b/lib/ofp-util.h index 9d9bb7200c0..05b10ea06fb 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -441,6 +441,10 @@ struct ofputil_packet_in { * that case, 'cookie' is UINT64_MAX. */ uint8_t table_id; /* OpenFlow table ID. */ ovs_be64 cookie; /* Flow's cookie. */ + + /* Arbitrary user-provided data. */ + uint8_t *userdata; + size_t userdata_len; }; struct ofpbuf *ofputil_encode_packet_in(const struct ofputil_packet_in *, diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c index 03b35204542..923975a2677 100644 --- a/ofproto/connmgr.c +++ b/ofproto/connmgr.c @@ -2248,5 +2248,6 @@ void ofproto_async_msg_free(struct ofproto_async_msg *am) { free(am->pin.up.packet); + free(am->pin.up.userdata); free(am); } diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 64c45de72f5..1be30203a96 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -3602,7 +3602,8 @@ flood_packets(struct xlate_ctx *ctx, bool all) static void execute_controller_action(struct xlate_ctx *ctx, int len, enum ofp_packet_in_reason reason, - uint16_t controller_id) + uint16_t controller_id, + const uint8_t *userdata, size_t userdata_len) { struct dp_packet *packet; @@ -3638,6 +3639,10 @@ execute_controller_action(struct xlate_ctx *ctx, int len, .reason = reason, .table_id = ctx->table_id, .cookie = ctx->rule_cookie, + .userdata = (userdata_len + ? xmemdup(userdata, userdata_len) + : NULL), + .userdata_len = userdata_len, }, .max_len = len, }, @@ -3775,7 +3780,7 @@ compose_dec_ttl(struct xlate_ctx *ctx, struct ofpact_cnt_ids *ids) for (i = 0; i < ids->n_controllers; i++) { execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL, - ids->cnt_ids[i]); + ids->cnt_ids[i], NULL, 0); } /* Stop processing for current table. */ @@ -3824,7 +3829,8 @@ compose_dec_mpls_ttl_action(struct xlate_ctx *ctx) set_mpls_lse_ttl(&flow->mpls_lse[0], ttl); return false; } else { - execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL, 0); + execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL, 0, + NULL, 0); } } @@ -3862,7 +3868,7 @@ xlate_output_action(struct xlate_ctx *ctx, (ctx->in_group ? OFPR_GROUP : ctx->in_action_set ? OFPR_ACTION_SET : OFPR_ACTION), - 0); + 0, NULL, 0); break; case OFPP_NONE: break; @@ -4473,7 +4479,9 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, controller = ofpact_get_CONTROLLER(a); execute_controller_action(ctx, controller->max_len, controller->reason, - controller->controller_id); + controller->controller_id, + controller->userdata, + controller->userdata_len); break; case OFPACT_ENQUEUE: diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index adfaeb68135..826e6e657b5 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -1411,6 +1411,7 @@ add_internal_flows(struct ofproto_dpif *ofproto) controller->max_len = UINT16_MAX; controller->controller_id = 0; controller->reason = OFPR_IMPLICIT_MISS; + ofpact_finish(&ofpacts, &controller->ofpact); error = add_internal_miss_flow(ofproto, id++, &ofpacts, &ofproto->miss_rule); diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at index f3e5277e333..e16de3273a5 100644 --- a/tests/ofp-actions.at +++ b/tests/ofp-actions.at @@ -113,6 +113,13 @@ ffff 0010 00002320 0013 000a 0014 0000 # actions=controller(reason=invalid_ttl,max_len=1234,id=5678) ffff 0010 00002320 0014 04d2 162e 02 00 +# actions=controller(reason=invalid_ttl,max_len=1234,id=5678,userdata=01.02.03.04.05) +ffff 0038 00002320 0025 000000000000 dnl +0000 0008 04d2 0000 dnl +0001 0008 162e 0000 dnl +0002 0005 02 000000 dnl +0003 0009 0102030405 00000000000000 + # actions=dec_ttl(32768,12345,90,765,1024) ffff 0020 00002320 0015 000500000000 80003039005A02fd 0400000000000000 diff --git a/tests/ofp-print.at b/tests/ofp-print.at index 54980fca7fd..10ec04bbf63 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -2849,7 +2849,7 @@ AT_CLEANUP AT_SETUP([NX_PACKET_IN2]) AT_KEYWORDS([ofp-print]) AT_CHECK([ovs-ofctl ofp-print " -01 04 0088 00000000 00002320 0000001e +01 04 0098 00000000 00002320 0000001e 0000 0034 82 82 82 82 82 82 80 81 81 81 81 81 81 00 00 50 08 00 45 00 00 28 00 00 00 00 00 06 32 05 53 53 @@ -2859,9 +2859,12 @@ AT_CHECK([ovs-ofctl ofp-print " 0003 0005 07 000000 0004 0010 00000000 fedcba9876543210 0005 0005 01 000000 -0006 0010 80000408 5a5a5a5a5a5a5a5a" +0006 0010 80000408 5a5a5a5a5a5a5a5a +0007 0009 0102030405 00000000000000 +" ], [0], [dnl NXT_PACKET_IN2 (xid=0x0): table_id=7 cookie=0xfedcba9876543210 total_len=64 metadata=0x5a5a5a5a5a5a5a5a (via action) data_len=48 buffer=0x00000114 + userdata=01.02.03.04.05 ip,dl_vlan=80,dl_vlan_pcp=0,dl_src=80:81:81:81:81:81,dl_dst=82:82:82:82:82:82,nw_src=0.0.0.0,nw_dst=0.0.0.0,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0 ]) AT_CLEANUP diff --git a/tests/ofproto.at b/tests/ofproto.at index d036a2015fa..bede254f8fa 100644 --- a/tests/ofproto.at +++ b/tests/ofproto.at @@ -3353,8 +3353,8 @@ OFPT_BARRIER_REPLY (OF1.1): OVS_VSWITCHD_STOP AT_CLEANUP -dnl This test checks that metadata is encoded in NXT_PACKET_IN2. -AT_SETUP([ofproto - packet-out with metadata (NXT_PACKET_IN2)]) +dnl This test checks that metadata and userdata are encoded in NXT_PACKET_IN2. +AT_SETUP([ofproto - packet-out with metadata and userdata (NXT_PACKET_IN2)]) OVS_VSWITCHD_START # Start a monitor listening for packet-ins. @@ -3365,7 +3365,7 @@ ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log AT_CAPTURE_FILE([monitor.log]) # Send a packet-out with a load action to set some metadata, and forward to controller -AT_CHECK([ovs-ofctl packet-out br0 controller 'load(0xfafafafa5a5a5a5a->OXM_OF_METADATA[[0..63]]), load(0xaa->NXM_NX_PKT_MARK[[]]), controller' '0001020304050010203040501234']) +AT_CHECK([ovs-ofctl packet-out br0 controller 'load(0xfafafafa5a5a5a5a->OXM_OF_METADATA[[0..63]]), load(0xaa->NXM_NX_PKT_MARK[[]]), controller(userdata=01.02.03.04.05)' '0001020304050010203040501234']) # Stop the monitor and check its output. ovs-appctl -t ovs-ofctl ofctl/barrier @@ -3373,6 +3373,7 @@ ovs-appctl -t ovs-ofctl exit AT_CHECK([sed 's/ (xid=0x[[0-9a-fA-F]]*)//' monitor.log], [0], [dnl NXT_PACKET_IN2: total_len=14 pkt_mark=0xaa,metadata=0xfafafafa5a5a5a5a,in_port=CONTROLLER (via action) data_len=14 (unbuffered) + userdata=01.02.03.04.05 vlan_tci=0x0000,dl_src=00:10:20:30:40:50,dl_dst=00:01:02:03:04:05,dl_type=0x1234 OFPT_BARRIER_REPLY: ]) diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 6cbb65babbf..49e95a71c38 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -1523,12 +1523,18 @@ default connection ID for each controller connection, and a given controller connection will only have a nonzero connection ID if its controller uses the \fBNXT_SET_CONTROLLER_ID\fR Nicira extension to OpenFlow. +.IP "\fBuserdata=\fIhh\fR...\fR" +Supplies the bytes represented as hex digits \fIhh\fR as additional +data to the controller in the packet-in message. Pairs of hex digits +may be separated by periods for readability. +. .RE .IP -Any \fIreason\fR other than \fBaction\fR and any nonzero -\fIcontroller-id\fR uses a Nicira vendor extension that, as of this -writing, is only known to be implemented by Open vSwitch (version 1.6 -or later). +If any \fIreason\fR other than \fBaction\fR or any nonzero +\fIcontroller-id\fR is supplied, Open vSwitch extension +\fBNXAST_CONTROLLER\fR, supported by Open vSwitch 1.6 and later, is +used. If \fBuserdata\fR is supplied, then \fBNXAST_CONTROLLER2\fR, +supported by Open vSwitch 2.6 and later, is used. . .IP \fBcontroller\fR .IQ \fBcontroller\fR[\fB:\fInbytes\fR] @@ -2868,8 +2874,10 @@ little reason to use it with those versions of OpenFlow). . .IP "\fBnxt_packet_in2\fR" This uses the \fBNXT_PACKET_IN2\fR message, which is extensible and -should avoid the need to define new formats later. Open vSwitch 2.6 -and later support this format. +should avoid the need to define new formats later. In particular, +this format supports passing arbitrary user-provided data to a +controller using the \fBuserdata\fB option on the \fBcontroller\fR +action. Open vSwitch 2.6 and later support this format. . .RE .IP