From 638a19b04578669b5d43cb4d91391a9e1da93dc7 Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Thu, 20 Jun 2013 17:26:18 +0300 Subject: [PATCH] ofp-util: Support for OpenFlow 1.3 meters. Signed-off-by: Jarno Rajahalme Signed-off-by: Ben Pfaff --- include/openflow/openflow-1.3.h | 2 +- include/openflow/openflow-common.h | 1 + lib/ofp-actions.c | 32 ++- lib/ofp-actions.h | 16 +- lib/ofp-msgs.h | 6 +- lib/ofp-parse.c | 212 ++++++++++++++++- lib/ofp-parse.h | 4 + lib/ofp-print.c | 265 ++++++++++++++++++++- lib/ofp-util.c | 362 +++++++++++++++++++++++++++++ lib/ofp-util.h | 87 +++++++ ofproto/ofproto-dpif-xlate.c | 4 + tests/ofp-print.at | 89 +++++++ 12 files changed, 1059 insertions(+), 21 deletions(-) diff --git a/include/openflow/openflow-1.3.h b/include/openflow/openflow-1.3.h index 231bba980..1071d3dbe 100644 --- a/include/openflow/openflow-1.3.h +++ b/include/openflow/openflow-1.3.h @@ -402,7 +402,7 @@ struct ofp13_meter_stats { ovs_be32 duration_sec; /* Time meter has been alive in seconds. */ ovs_be32 duration_nsec; /* Time meter has been alive in nanoseconds beyond duration_sec. */ - /* struct ofp13_meter_band_stats band_stats[0]; The band_stats length is + struct ofp13_meter_band_stats band_stats[0]; /* The band_stats length is inferred from the length field. */ }; OFP_ASSERT(sizeof(struct ofp13_meter_stats) == 40); diff --git a/include/openflow/openflow-common.h b/include/openflow/openflow-common.h index 3cc22c9a8..ee7913650 100644 --- a/include/openflow/openflow-common.h +++ b/include/openflow/openflow-common.h @@ -352,6 +352,7 @@ enum ofp_flow_removed_reason { OFPRR_DELETE, /* Evicted by a DELETE flow mod. */ OFPRR_GROUP_DELETE, /* Group was removed. */ OFPRR_EVICTION, /* Switch eviction to free resources. */ + OFPRR_METER_DELETE, /* Meter was removed. */ }; /* What changed about the physical port */ diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 53398119d..99494a277 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -1087,6 +1087,16 @@ ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow, goto exit; } + if (insts[OVSINST_OFPIT13_METER]) { + const struct ofp13_instruction_meter *oim; + struct ofpact_meter *om; + + oim = (const struct ofp13_instruction_meter *) + insts[OVSINST_OFPIT13_METER]; + + om = ofpact_put_METER(ofpacts); + om->meter_id = ntohl(oim->meter_id); + } if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) { const union ofp_action *actions; size_t n_actions; @@ -1229,6 +1239,7 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports) case OFPACT_CLEAR_ACTIONS: case OFPACT_WRITE_METADATA: + case OFPACT_METER: case OFPACT_GOTO_TABLE: return 0; @@ -1272,7 +1283,9 @@ ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len) OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { enum ovs_instruction_type next; - if (a->type == OFPACT_CLEAR_ACTIONS) { + if (a->type == OFPACT_METER) { + next = OVSINST_OFPIT13_METER; + } else if (a->type == OFPACT_CLEAR_ACTIONS) { next = OVSINST_OFPIT11_CLEAR_ACTIONS; } else if (a->type == OFPACT_WRITE_METADATA) { next = OVSINST_OFPIT11_WRITE_METADATA; @@ -1552,6 +1565,7 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out) case OFPACT_SET_L4_DST_PORT: case OFPACT_CLEAR_ACTIONS: case OFPACT_GOTO_TABLE: + case OFPACT_METER: NOT_REACHED(); } } @@ -1644,6 +1658,7 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out) case OFPACT_PUSH_VLAN: case OFPACT_CLEAR_ACTIONS: case OFPACT_GOTO_TABLE: + case OFPACT_METER: /* XXX */ break; @@ -1816,6 +1831,7 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out) case OFPACT_CLEAR_ACTIONS: case OFPACT_GOTO_TABLE: + case OFPACT_METER: NOT_REACHED(); case OFPACT_CONTROLLER: @@ -1896,6 +1912,13 @@ ofpacts_put_openflow11_instructions(const struct ofpact ofpacts[], oiwm = instruction_put_OFPIT11_WRITE_METADATA(openflow); oiwm->metadata = om->metadata; oiwm->metadata_mask = om->mask; + } else if (a->type == OFPACT_METER) { + const struct ofpact_meter *om; + struct ofp13_instruction_meter *oim; + + om = ofpact_get_METER(a); + oim = instruction_put_OFPIT13_METER(openflow); + oim->meter_id = htonl(om->meter_id); } else if (!ofpact_is_instruction(a)) { /* Apply-actions */ const size_t ofs = openflow->size; @@ -1965,6 +1988,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) case OFPACT_SAMPLE: case OFPACT_CLEAR_ACTIONS: case OFPACT_GOTO_TABLE: + case OFPACT_METER: default: return false; } @@ -2291,6 +2315,12 @@ ofpact_format(const struct ofpact *a, struct ds *s) OVSINST_OFPIT11_GOTO_TABLE), ofpact_get_GOTO_TABLE(a)->table_id); break; + + case OFPACT_METER: + ds_put_format(s, "%s:%"PRIu32, + ofpact_instruction_name_from_type(OVSINST_OFPIT13_METER), + ofpact_get_METER(a)->meter_id); + break; } } diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index af6383359..799f64c8d 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -97,9 +97,10 @@ DEFINE_OFPACT(SAMPLE, ofpact_sample, ofpact) \ \ /* Instructions */ \ + DEFINE_OFPACT(METER, ofpact_meter, ofpact) \ /* XXX Write-Actions */ \ - DEFINE_OFPACT(WRITE_METADATA, ofpact_metadata, ofpact) \ DEFINE_OFPACT(CLEAR_ACTIONS, ofpact_null, ofpact) \ + DEFINE_OFPACT(WRITE_METADATA, ofpact_metadata, ofpact) \ DEFINE_OFPACT(GOTO_TABLE, ofpact_goto_table, ofpact) /* enum ofpact_type, with a member OFPACT_ for each action. */ @@ -374,6 +375,14 @@ struct ofpact_metadata { ovs_be64 mask; }; +/* OFPACT_METER. + * + * Used for OFPIT13_METER. */ +struct ofpact_meter { + struct ofpact ofpact; + uint32_t meter_id; +}; + /* OFPACT_RESUBMIT. * * Used for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE. */ @@ -601,6 +610,10 @@ void ofpact_pad(struct ofpbuf *); * It is enforced on parser from text string. */ #define OVS_INSTRUCTIONS \ + DEFINE_INST(OFPIT13_METER, \ + ofp13_instruction_meter, false, \ + "meter") \ + \ DEFINE_INST(OFPIT11_APPLY_ACTIONS, \ ofp11_instruction_actions, true, \ "apply_actions") \ @@ -639,6 +652,7 @@ ofpact_is_instruction(const struct ofpact *a) { /* XXX Write-Actions */ return a->type == OFPACT_CLEAR_ACTIONS + || a->type == OFPACT_METER || a->type == OFPACT_WRITE_METADATA || a->type == OFPACT_GOTO_TABLE; } diff --git a/lib/ofp-msgs.h b/lib/ofp-msgs.h index 66ec44825..2501e383f 100644 --- a/lib/ofp-msgs.h +++ b/lib/ofp-msgs.h @@ -217,7 +217,7 @@ enum ofpraw { /* NXT 1.0+ (19): struct nx_async_config. */ OFPRAW_NXT_SET_ASYNC_CONFIG, - /* OFPT 1.3+ (29): struct ofp13_meter_mod. */ + /* OFPT 1.3+ (29): struct ofp13_meter_mod, uint8_t[8][]. */ OFPRAW_OFPT13_METER_MOD, /* Standard statistics. */ @@ -315,13 +315,13 @@ enum ofpraw { /* OFPST 1.3+ (9): struct ofp13_meter_multipart_request. */ OFPRAW_OFPST13_METER_REQUEST, - /* OFPST 1.3+ (9): struct ofp13_meter_stats[]. */ + /* OFPST 1.3+ (9): uint8_t[8][]. */ OFPRAW_OFPST13_METER_REPLY, /* OFPST 1.3+ (10): struct ofp13_meter_multipart_request. */ OFPRAW_OFPST13_METER_CONFIG_REQUEST, - /* OFPST 1.3+ (10): struct ofp13_meter_config[]. */ + /* OFPST 1.3+ (10): uint8_t[8][]. */ OFPRAW_OFPST13_METER_CONFIG_REPLY, /* OFPST 1.3+ (11): void. */ diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index 1890050c6..b0f4d934b 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -45,14 +45,14 @@ static void ofp_fatal(const char *flow, bool verbose, const char *format, ...) NO_RETURN; static uint8_t -str_to_table_id(const char *str) +str_to_u8(const char *str, const char *name) { - int table_id; + int value; - if (!str_to_int(str, 10, &table_id) || table_id < 0 || table_id > 255) { - ovs_fatal(0, "invalid table \"%s\"", str); + if (!str_to_int(str, 10, &value) || value < 0 || value > 255) { + ovs_fatal(0, "invalid %s \"%s\"", name, str); } - return table_id; + return value; } static uint16_t @@ -705,6 +705,10 @@ parse_named_instruction(enum ovs_instruction_type type, ofpact_put_CLEAR_ACTIONS(ofpacts); break; + case OVSINST_OFPIT13_METER: + ofpact_put_METER(ofpacts)->meter_id = str_to_u32(arg); + break; + case OVSINST_OFPIT11_WRITE_METADATA: parse_metadata(ofpacts, arg); break; @@ -715,7 +719,7 @@ parse_named_instruction(enum ovs_instruction_type type, if (!table_s || !table_s[0]) { ovs_fatal(0, "instruction goto-table needs table id"); } - ogt->table_id = str_to_table_id(table_s); + ogt->table_id = str_to_u8(table_s, "table"); break; } } @@ -948,7 +952,7 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, } if (!strcmp(name, "table")) { - fm->table_id = str_to_table_id(value); + fm->table_id = str_to_u8(value, name); } else if (!strcmp(name, "out_port")) { if (!ofputil_port_from_string(name, &fm->out_port)) { ofp_fatal(str_, verbose, "%s is not a valid OpenFlow port", @@ -1025,6 +1029,198 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, free(string); } +/* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man + * page) into 'mm' for sending the specified meter_mod 'command' to a switch. + */ +void +parse_ofp_meter_mod_str(struct ofputil_meter_mod *mm, const char *str_, + int command, bool verbose) +{ + enum { + F_METER = 1 << 0, + F_FLAGS = 1 << 1, + F_BANDS = 1 << 2, + } fields; + char *string = xstrdup(str_); + char *save_ptr = NULL; + char *band_str = NULL; + char *name; + + switch (command) { + case -1: + fields = F_METER; + break; + + case OFPMC13_ADD: + fields = F_METER | F_FLAGS | F_BANDS; + break; + + case OFPMC13_DELETE: + fields = F_METER; + break; + + case OFPMC13_MODIFY: + fields = F_METER | F_FLAGS | F_BANDS; + break; + + default: + NOT_REACHED(); + } + + mm->command = command; + mm->meter.meter_id = 0; + mm->meter.flags = 0; + if (fields & F_BANDS) { + band_str = strstr(string, "band"); + if (!band_str) { + ofp_fatal(str_, verbose, "must specify bands"); + } + *band_str = '\0'; + + band_str = strchr(band_str + 1, '='); + if (!band_str) { + ofp_fatal(str_, verbose, "must specify bands"); + } + + band_str++; + } + for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; + name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { + + if (fields & F_FLAGS && !strcmp(name, "kbps")) { + mm->meter.flags |= OFPMF13_KBPS; + } else if (fields & F_FLAGS && !strcmp(name, "pktps")) { + mm->meter.flags |= OFPMF13_PKTPS; + } else if (fields & F_FLAGS && !strcmp(name, "burst")) { + mm->meter.flags |= OFPMF13_BURST; + } else if (fields & F_FLAGS && !strcmp(name, "stats")) { + mm->meter.flags |= OFPMF13_STATS; + } else { + char *value; + + value = strtok_r(NULL, ", \t\r\n", &save_ptr); + if (!value) { + ofp_fatal(str_, verbose, "field %s missing value", name); + } + + if (!strcmp(name, "meter")) { + if (!strcmp(value, "all")) { + mm->meter.meter_id = OFPM13_ALL; + } else if (!strcmp(value, "controller")) { + mm->meter.meter_id = OFPM13_CONTROLLER; + } else if (!strcmp(value, "slowpath")) { + mm->meter.meter_id = OFPM13_SLOWPATH; + } else { + mm->meter.meter_id = str_to_u32(value); + if (mm->meter.meter_id > OFPM13_MAX) { + ofp_fatal(str_, verbose, "invalid value for %s", name); + } + } + } else { + ofp_fatal(str_, verbose, "unknown keyword %s", name); + } + } + } + if (fields & F_METER && !mm->meter.meter_id) { + ofp_fatal(str_, verbose, "must specify 'meter'"); + } + if (fields & F_FLAGS && !mm->meter.flags) { + ofp_fatal(str_, verbose, + "meter must specify either 'kbps' or 'pktps'"); + } + + if (fields & F_BANDS) { + struct ofpbuf bands; + uint16_t n_bands = 0; + struct ofputil_meter_band *band = NULL; + int i; + + ofpbuf_init(&bands, 64); + + for (name = strtok_r(band_str, "=, \t\r\n", &save_ptr); name; + name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { + + char *value; + + value = strtok_r(NULL, ", \t\r\n", &save_ptr); + if (!value) { + ofp_fatal(str_, verbose, "field %s missing value", name); + } + + if (!strcmp(name, "type")) { + /* Start a new band */ + band = ofpbuf_put_zeros(&bands, sizeof *band); + n_bands++; + + if (!strcmp(value, "drop")) { + band->type = OFPMBT13_DROP; + } else if (!strcmp(value, "dscp_remark")) { + band->type = OFPMBT13_DSCP_REMARK; + } else { + ofp_fatal(str_, verbose, "field %s unknown value %s", name, + value); + } + } else if (!band || !band->type) { + ofp_fatal(str_, verbose, + "band must start with the 'type' keyword"); + } else if (!strcmp(name, "rate")) { + band->rate = str_to_u32(value); + } else if (!strcmp(name, "burst_size")) { + band->burst_size = str_to_u32(value); + } else if (!strcmp(name, "prec_level")) { + band->prec_level = str_to_u8(value, name); + } else { + ofp_fatal(str_, verbose, "unknown keyword %s", name); + } + } + /* validate bands */ + if (!n_bands) { + ofp_fatal(str_, verbose, "meter must have bands"); + } + + mm->meter.n_bands = n_bands; + mm->meter.bands = ofpbuf_steal_data(&bands); + + for (i = 0; i < n_bands; ++i) { + band = &mm->meter.bands[i]; + + if (!band->type) { + ofp_fatal(str_, verbose, "band must have 'type'"); + } + if (band->type == OFPMBT13_DSCP_REMARK) { + if (!band->prec_level) { + ofp_fatal(str_, verbose, "'dscp_remark' band must have" + " 'prec_level'"); + } + } else { + if (band->prec_level) { + ofp_fatal(str_, verbose, "Only 'dscp_remark' band may have" + " 'prec_level'"); + } + } + if (!band->rate) { + ofp_fatal(str_, verbose, "band must have 'rate'"); + } + if (mm->meter.flags & OFPMF13_BURST) { + if (!band->burst_size) { + ofp_fatal(str_, verbose, "band must have 'burst_size' " + "when 'burst' flag is set"); + } + } else { + if (band->burst_size) { + ofp_fatal(str_, verbose, "band may have 'burst_size' only " + "when 'burst' flag is set"); + } + } + } + } else { + mm->meter.n_bands = 0; + mm->meter.bands = NULL; + } + + free(string); +} + /* Convert 'str_' (as described in the documentation for the "monitor" command * in the ovs-ofctl man page) into 'fmr'. */ void @@ -1074,7 +1270,7 @@ parse_flow_monitor_request(struct ofputil_flow_monitor_request *fmr, } if (!strcmp(name, "table")) { - fmr->table_id = str_to_table_id(value); + fmr->table_id = str_to_u8(value, name); } else if (!strcmp(name, "out_port")) { fmr->out_port = u16_to_ofp(atoi(value)); } else if (mf_from_name(name)) { diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h index d2d3c3cff..6ee25a407 100644 --- a/lib/ofp-parse.h +++ b/lib/ofp-parse.h @@ -28,6 +28,7 @@ struct ofpbuf; struct ofputil_flow_mod; struct ofputil_flow_monitor_request; struct ofputil_flow_stats_request; +struct ofputil_meter_mod; void parse_ofp_str(struct ofputil_flow_mod *, int command, const char *str_, bool verbose); @@ -45,6 +46,9 @@ void parse_ofpacts(const char *, struct ofpbuf *ofpacts); char *parse_ofp_exact_flow(struct flow *, const char *); +void parse_ofp_meter_mod_str(struct ofputil_meter_mod *, const char *string, + int command, bool verbose); + void parse_flow_monitor_request(struct ofputil_flow_monitor_request *, const char *); diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 42fd9a6d6..76bd09cad 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -878,6 +878,8 @@ ofp_flow_removed_reason_to_string(enum ofp_flow_removed_reason reason, return "group_delete"; case OFPRR_EVICTION: return "eviction"; + case OFPRR_METER_DELETE: + return "meter_delete"; default: snprintf(reasonbuf, bufsize, "%d", (int) reason); return reasonbuf; @@ -954,6 +956,236 @@ ofp_print_port_mod(struct ds *string, const struct ofp_header *oh) } } +static void +ofp_print_meter_flags(struct ds *s, uint16_t flags) +{ + if (flags & OFPMF13_KBPS) { + ds_put_cstr(s, "kbps "); + } + if (flags & OFPMF13_PKTPS) { + ds_put_cstr(s, "pktps "); + } + if (flags & OFPMF13_BURST) { + ds_put_cstr(s, "burst "); + } + if (flags & OFPMF13_STATS) { + ds_put_cstr(s, "stats "); + } + + flags &= ~(OFPMF13_KBPS | OFPMF13_PKTPS | OFPMF13_BURST | OFPMF13_STATS); + if (flags) { + ds_put_format(s, "flags:0x%"PRIx16" ", flags); + } +} + +static void +ofp_print_meter_band(struct ds *s, uint16_t flags, + const struct ofputil_meter_band *mb) +{ + ds_put_cstr(s, "\ntype="); + switch (mb->type) { + case OFPMBT13_DROP: + ds_put_cstr(s, "drop"); + break; + case OFPMBT13_DSCP_REMARK: + ds_put_cstr(s, "dscp_remark"); + break; + default: + ds_put_format(s, "%u", mb->type); + } + + ds_put_format(s, " rate=%"PRIu32, mb->rate); + + if (flags & OFPMF13_BURST) { + ds_put_format(s, " burst_size=%"PRIu32, mb->burst_size); + } + if (mb->type == OFPMBT13_DSCP_REMARK) { + ds_put_format(s, " prec_level=%"PRIu8, mb->prec_level); + } +} + +static void +ofp_print_meter_stats(struct ds *s, const struct ofputil_meter_stats *ms) +{ + uint16_t i; + + ds_put_format(s, "meter:%"PRIu32" ", ms->meter_id); + ds_put_format(s, "flow_count:%"PRIu32" ", ms->flow_count); + ds_put_format(s, "packet_in_count:%"PRIu64" ", ms->packet_in_count); + ds_put_format(s, "byte_in_count:%"PRIu64" ", ms->byte_in_count); + ds_put_cstr(s, "duration:"); + ofp_print_duration(s, ms->duration_sec, ms->duration_nsec); + ds_put_char(s, ' '); + + ds_put_cstr(s, "bands:\n"); + for (i = 0; i < ms->n_bands; ++i) { + ds_put_format(s, "%d: ", i); + ds_put_format(s, "packet_count:%"PRIu64" ", ms->bands[i].packet_count); + ds_put_format(s, "byte_count:%"PRIu64"\n", ms->bands[i].byte_count); + } +} + +static void +ofp_print_meter_config(struct ds *s, const struct ofputil_meter_config *mc) +{ + uint16_t i; + + ds_put_format(s, "meter=%"PRIu32" ", mc->meter_id); + + ofp_print_meter_flags(s, mc->flags); + + ds_put_cstr(s, "bands="); + for (i = 0; i < mc->n_bands; ++i) { + ofp_print_meter_band(s, mc->flags, &mc->bands[i]); + } + ds_put_char(s, '\n'); +} + +static void +ofp_print_meter_mod(struct ds *s, const struct ofp_header *oh) +{ + struct ofputil_meter_mod mm; + struct ofpbuf bands; + enum ofperr error; + + ofpbuf_init(&bands, 64); + error = ofputil_decode_meter_mod(oh, &mm, &bands); + if (error) { + ofpbuf_uninit(&bands); + ofp_print_error(s, error); + return; + } + + switch (mm.command) { + case OFPMC13_ADD: + ds_put_cstr(s, " ADD "); + break; + case OFPMC13_MODIFY: + ds_put_cstr(s, " MOD "); + break; + case OFPMC13_DELETE: + ds_put_cstr(s, " DEL "); + break; + default: + ds_put_format(s, " cmd:%d ", mm.command); + } + + ofp_print_meter_config(s, &mm.meter); + ofpbuf_uninit(&bands); +} + +static void +ofp_print_meter_stats_request(struct ds *s, const struct ofp_header *oh) +{ + uint32_t meter_id; + + ofputil_decode_meter_request(oh, &meter_id); + + ds_put_format(s, " meter=%"PRIu32, meter_id); +} + +static const char * +ofputil_meter_capabilities_to_name(uint32_t bit) +{ + enum ofp13_meter_flags flag = bit; + + switch (flag) { + case OFPMF13_KBPS: return "kbps"; + case OFPMF13_PKTPS: return "pktps"; + case OFPMF13_BURST: return "burst"; + case OFPMF13_STATS: return "stats"; + } + + return NULL; +} + +static const char * +ofputil_meter_band_types_to_name(uint32_t bit) +{ + /* + * Note: Meter band types start from 1. We assume that the lowest bit + * in the band_types corresponds to DROP band type (1). + */ + switch (bit) { + case 1 << (OFPMBT13_DROP - 1): return "drop"; + case 1 << (OFPMBT13_DSCP_REMARK - 1): return "dscp_remark"; + } + + return NULL; +} + +static void +ofp_print_meter_features_reply(struct ds *s, const struct ofp_header *oh) +{ + struct ofputil_meter_features mf; + + ofputil_decode_meter_features(oh, &mf); + + ds_put_format(s, "\nmax_meter:%"PRIu32, mf.max_meters); + ds_put_format(s, " max_bands:%"PRIu8, mf.max_bands); + ds_put_format(s, " max_color:%"PRIu8"\n", mf.max_color); + + ds_put_cstr(s, "band_types: "); + ofp_print_bit_names(s, mf.band_types, + ofputil_meter_band_types_to_name, ' '); + ds_put_char(s, '\n'); + + ds_put_cstr(s, "capabilities: "); + ofp_print_bit_names(s, mf.capabilities, + ofputil_meter_capabilities_to_name, ' '); + ds_put_char(s, '\n'); +} + +static void +ofp_print_meter_config_reply(struct ds *s, const struct ofp_header *oh) +{ + struct ofpbuf bands; + struct ofpbuf b; + + ofpbuf_use_const(&b, oh, ntohs(oh->length)); + ofpbuf_init(&bands, 64); + for (;;) { + struct ofputil_meter_config mc; + int retval; + + retval = ofputil_decode_meter_config(&b, &mc, &bands); + if (retval) { + if (retval != EOF) { + ofp_print_error(s, retval); + } + break; + } + ds_put_char(s, '\n'); + ofp_print_meter_config(s, &mc); + } + ofpbuf_uninit(&bands); +} + +static void +ofp_print_meter_stats_reply(struct ds *s, const struct ofp_header *oh) +{ + struct ofpbuf bands; + struct ofpbuf b; + + ofpbuf_use_const(&b, oh, ntohs(oh->length)); + ofpbuf_init(&bands, 64); + for (;;) { + struct ofputil_meter_stats ms; + int retval; + + retval = ofputil_decode_meter_stats(&b, &ms, &bands); + if (retval) { + if (retval != EOF) { + ofp_print_error(s, retval); + } + break; + } + ds_put_char(s, '\n'); + ofp_print_meter_stats(s, &ms); + } + ofpbuf_uninit(&bands); +} + static void ofp_print_error(struct ds *string, enum ofperr error) { @@ -1910,19 +2142,12 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw, case OFPTYPE_QUEUE_GET_CONFIG_REPLY: case OFPTYPE_GET_ASYNC_REQUEST: case OFPTYPE_GET_ASYNC_REPLY: - case OFPTYPE_METER_MOD: case OFPTYPE_GROUP_REQUEST: case OFPTYPE_GROUP_REPLY: case OFPTYPE_GROUP_DESC_REQUEST: case OFPTYPE_GROUP_DESC_REPLY: case OFPTYPE_GROUP_FEATURES_REQUEST: case OFPTYPE_GROUP_FEATURES_REPLY: - case OFPTYPE_METER_REQUEST: - case OFPTYPE_METER_REPLY: - case OFPTYPE_METER_CONFIG_REQUEST: - case OFPTYPE_METER_CONFIG_REPLY: - case OFPTYPE_METER_FEATURES_REQUEST: - case OFPTYPE_METER_FEATURES_REPLY: case OFPTYPE_TABLE_FEATURES_REQUEST: case OFPTYPE_TABLE_FEATURES_REPLY: ofp_print_not_implemented(string); @@ -1980,6 +2205,10 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw, ofp_print_port_mod(string, oh); break; + case OFPTYPE_METER_MOD: + ofp_print_meter_mod(string, oh); + break; + case OFPTYPE_BARRIER_REQUEST: case OFPTYPE_BARRIER_REPLY: break; @@ -1989,8 +2218,30 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw, ofp_print_role_message(string, oh); break; + case OFPTYPE_METER_REQUEST: + case OFPTYPE_METER_CONFIG_REQUEST: + ofp_print_stats_request(string, oh); + ofp_print_meter_stats_request(string, oh); + break; + + case OFPTYPE_METER_REPLY: + ofp_print_stats_reply(string, oh); + ofp_print_meter_stats_reply(string, oh); + break; + + case OFPTYPE_METER_CONFIG_REPLY: + ofp_print_stats_reply(string, oh); + ofp_print_meter_config_reply(string, oh); + break; + + case OFPTYPE_METER_FEATURES_REPLY: + ofp_print_stats_reply(string, oh); + ofp_print_meter_features_reply(string, oh); + break; + case OFPTYPE_DESC_STATS_REQUEST: case OFPTYPE_PORT_DESC_STATS_REQUEST: + case OFPTYPE_METER_FEATURES_REQUEST: ofp_print_stats_request(string, oh); break; diff --git a/lib/ofp-util.c b/lib/ofp-util.c index de73eba52..6c584157a 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -1631,6 +1631,368 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm, return 0; } +static enum ofperr +ofputil_pull_bands(struct ofpbuf *msg, size_t len, uint16_t *n_bands, + struct ofpbuf *bands) +{ + const struct ofp13_meter_band_header *ombh; + struct ofputil_meter_band *mb; + uint16_t n = 0; + + ombh = ofpbuf_try_pull(msg, len); + if (!ombh) { + return OFPERR_OFPBRC_BAD_LEN; + } + + while (len >= sizeof (struct ofp13_meter_band_drop)) { + size_t ombh_len = ntohs(ombh->len); + /* All supported band types have the same length */ + if (ombh_len != sizeof (struct ofp13_meter_band_drop)) { + return OFPERR_OFPBRC_BAD_LEN; + } + mb = ofpbuf_put_uninit(bands, sizeof *mb); + mb->type = ntohs(ombh->type); + mb->rate = ntohl(ombh->rate); + mb->burst_size = ntohl(ombh->burst_size); + mb->prec_level = (mb->type == OFPMBT13_DSCP_REMARK) ? + ((struct ofp13_meter_band_dscp_remark *)ombh)->prec_level : 0; + n++; + len -= ombh_len; + ombh = (struct ofp13_meter_band_header *)(((char *)ombh) + ombh_len); + } + if (len) { + return OFPERR_OFPBRC_BAD_LEN; + } + *n_bands = n; + return 0; +} + +enum ofperr +ofputil_decode_meter_mod(const struct ofp_header *oh, + struct ofputil_meter_mod *mm, + struct ofpbuf *bands) +{ + const struct ofp13_meter_mod *omm; + struct ofpbuf b; + + ofpbuf_use_const(&b, oh, ntohs(oh->length)); + ofpraw_pull_assert(&b); + omm = ofpbuf_pull(&b, sizeof *omm); + + /* Translate the message. */ + mm->command = ntohs(omm->command); + mm->meter.meter_id = ntohl(omm->meter_id); + + if (mm->command == OFPMC13_DELETE) { + mm->meter.flags = 0; + mm->meter.n_bands = 0; + mm->meter.bands = NULL; + } else { + enum ofperr error; + + mm->meter.flags = ntohs(omm->flags); + mm->meter.bands = bands->data; + + error = ofputil_pull_bands(&b, b.size, &mm->meter.n_bands, + bands); + if (error) { + return error; + } + } + return 0; +} + +void +ofputil_decode_meter_request(const struct ofp_header *oh, uint32_t *meter_id) +{ + const struct ofp13_meter_multipart_request *omr = ofpmsg_body(oh); + *meter_id = ntohl(omr->meter_id); +} + +struct ofpbuf * +ofputil_encode_meter_request(enum ofp_version ofp_version, + enum ofputil_meter_request_type type, + uint32_t meter_id) +{ + struct ofpbuf *msg; + + enum ofpraw raw; + + switch (type) { + case OFPUTIL_METER_CONFIG: + raw = OFPRAW_OFPST13_METER_CONFIG_REQUEST; + break; + case OFPUTIL_METER_STATS: + raw = OFPRAW_OFPST13_METER_REQUEST; + break; + default: + case OFPUTIL_METER_FEATURES: + raw = OFPRAW_OFPST13_METER_FEATURES_REQUEST; + break; + } + + msg = ofpraw_alloc(raw, ofp_version, 0); + + if (type != OFPUTIL_METER_FEATURES) { + struct ofp13_meter_multipart_request *omr; + omr = ofpbuf_put_zeros(msg, sizeof *omr); + omr->meter_id = htonl(meter_id); + } + return msg; +} + +static void +ofputil_put_bands(uint16_t n_bands, const struct ofputil_meter_band *mb, + struct ofpbuf *msg) +{ + uint16_t n = 0; + + for (n = 0; n < n_bands; ++n) { + /* Currently all band types have same size */ + struct ofp13_meter_band_dscp_remark *ombh; + size_t ombh_len = sizeof *ombh; + + ombh = ofpbuf_put_zeros(msg, ombh_len); + + ombh->type = htons(mb->type); + ombh->len = htons(ombh_len); + ombh->rate = htonl(mb->rate); + ombh->burst_size = htonl(mb->burst_size); + ombh->prec_level = mb->prec_level; + + mb++; + } +} + +/* Encode a meter stat for 'mc' and append it to 'replies'. */ +void +ofputil_append_meter_config(struct list *replies, + const struct ofputil_meter_config *mc) +{ + struct ofpbuf *msg = ofpbuf_from_list(list_back(replies)); + size_t start_ofs = msg->size; + struct ofp13_meter_config *reply = ofpbuf_put_uninit(msg, sizeof *reply); + reply->flags = htons(mc->flags); + reply->meter_id = htonl(mc->meter_id); + + ofputil_put_bands(mc->n_bands, mc->bands, msg); + + reply->length = htons(msg->size - start_ofs); + + ofpmp_postappend(replies, start_ofs); +} + +/* Encode a meter stat for 'ms' and append it to 'replies'. */ +void +ofputil_append_meter_stats(struct list *replies, + const struct ofputil_meter_stats *ms) +{ + struct ofp13_meter_stats *reply; + uint16_t n = 0; + uint16_t len; + + len = sizeof *reply + ms->n_bands * sizeof(struct ofp13_meter_band_stats); + reply = ofpmp_append(replies, len); + + reply->meter_id = htonl(ms->meter_id); + reply->len = htons(len); + memset(reply->pad, 0, sizeof reply->pad); + reply->flow_count = htonl(ms->flow_count); + reply->packet_in_count = htonll(ms->packet_in_count); + reply->byte_in_count = htonll(ms->byte_in_count); + reply->duration_sec = htonl(ms->duration_sec); + reply->duration_nsec = htonl(ms->duration_nsec); + + for (n = 0; n < ms->n_bands; ++n) { + const struct ofputil_meter_band_stats *src = &ms->bands[n]; + struct ofp13_meter_band_stats *dst = &reply->band_stats[n]; + + dst->packet_band_count = htonll(src->packet_count); + dst->byte_band_count = htonll(src->byte_count); + } +} + +/* Converts an OFPMP_METER_CONFIG reply in 'msg' into an abstract + * ofputil_meter_config in 'mc', with mc->bands pointing to bands decoded into + * 'bands'. The caller must have initialized 'bands' and retains ownership of + * it across the call. + * + * Multiple OFPST13_METER_CONFIG replies can be packed into a single OpenFlow + * message. Calling this function multiple times for a single 'msg' iterates + * through the replies. 'bands' is cleared for each reply. + * + * Returns 0 if successful, EOF if no replies were left in this 'msg', + * otherwise a positive errno value. */ +int +ofputil_decode_meter_config(struct ofpbuf *msg, + struct ofputil_meter_config *mc, + struct ofpbuf *bands) +{ + const struct ofp13_meter_config *omc; + enum ofperr err; + + /* Pull OpenFlow headers for the first call. */ + if (!msg->l2) { + ofpraw_pull_assert(msg); + } + + if (!msg->size) { + return EOF; + } + + omc = ofpbuf_try_pull(msg, sizeof *omc); + if (!omc) { + VLOG_WARN_RL(&bad_ofmsg_rl, "OFPMP_METER_CONFIG reply has %zu " + "leftover bytes at end", msg->size); + return OFPERR_OFPBRC_BAD_LEN; + } + + ofpbuf_clear(bands); + err = ofputil_pull_bands(msg, ntohs(omc->length) - sizeof *omc, + &mc->n_bands, bands); + if (err) { + return err; + } + mc->meter_id = ntohl(omc->meter_id); + mc->flags = ntohs(omc->flags); + mc->bands = bands->data; + + return 0; +} + +static enum ofperr +ofputil_pull_band_stats(struct ofpbuf *msg, size_t len, uint16_t *n_bands, + struct ofpbuf *bands) +{ + const struct ofp13_meter_band_stats *ombs; + struct ofputil_meter_band_stats *mbs; + uint16_t n, i; + + n = len / sizeof *ombs; + if (len != n * sizeof *ombs) { + return OFPERR_OFPBRC_BAD_LEN; + } + + ombs = ofpbuf_pull(msg, len); + + mbs = ofpbuf_put_uninit(bands, len); + + for (i = 0; i < n; ++i) { + mbs[i].packet_count = ntohll(ombs[i].packet_band_count); + mbs[i].byte_count = ntohll(ombs[i].byte_band_count); + } + *n_bands = n; + return 0; +} + +/* Converts an OFPMP_METER reply in 'msg' into an abstract + * ofputil_meter_stats in 'ms', with ms->bands pointing to band stats + * decoded into 'bands'. + * + * Multiple OFPMP_METER replies can be packed into a single OpenFlow + * message. Calling this function multiple times for a single 'msg' iterates + * through the replies. 'bands' is cleared for each reply. + * + * Returns 0 if successful, EOF if no replies were left in this 'msg', + * otherwise a positive errno value. */ +int +ofputil_decode_meter_stats(struct ofpbuf *msg, + struct ofputil_meter_stats *ms, + struct ofpbuf *bands) +{ + const struct ofp13_meter_stats *oms; + uint16_t len; + enum ofperr err; + + /* Pull OpenFlow headers for the first call. */ + if (!msg->l2) { + ofpraw_pull_assert(msg); + } + + if (!msg->size) { + return EOF; + } + + oms = ofpbuf_try_pull(msg, sizeof *oms); + if (!oms) { + VLOG_WARN_RL(&bad_ofmsg_rl, "OFPMP_METER reply has %zu leftover " + "bytes at end", msg->size); + return OFPERR_OFPBRC_BAD_LEN; + } + len = ntohs(oms->len); + len -= sizeof *oms; + + ofpbuf_clear(bands); + err = ofputil_pull_band_stats(msg, len, &ms->n_bands, bands); + if (err) { + return err; + } + ms->meter_id = ntohl(oms->meter_id); + ms->flow_count = ntohl(oms->flow_count); + ms->packet_in_count = ntohll(oms->packet_in_count); + ms->byte_in_count = ntohll(oms->byte_in_count); + ms->duration_sec = ntohl(oms->duration_sec); + ms->duration_nsec = ntohl(oms->duration_nsec); + ms->bands = bands->data; + + return 0; +} + +void +ofputil_decode_meter_features(const struct ofp_header *oh, + struct ofputil_meter_features *mf) +{ + const struct ofp13_meter_features *omf = ofpmsg_body(oh); + + mf->max_meters = ntohl(omf->max_meter); + mf->band_types = ntohl(omf->band_types); + mf->capabilities = ntohl(omf->capabilities); + mf->max_bands = omf->max_bands; + mf->max_color = omf->max_color; +} + +struct ofpbuf * +ofputil_encode_meter_features_reply(const struct ofputil_meter_features *mf, + const struct ofp_header *request) +{ + struct ofpbuf *reply; + struct ofp13_meter_features *omf; + + reply = ofpraw_alloc_stats_reply(request, 0); + omf = ofpbuf_put_zeros(reply, sizeof *omf); + + omf->max_meter = htonl(mf->max_meters); + omf->band_types = htonl(mf->band_types); + omf->capabilities = htonl(mf->capabilities); + omf->max_bands = mf->max_bands; + omf->max_color = mf->max_color; + + return reply; +} + +struct ofpbuf * +ofputil_encode_meter_mod(enum ofp_version ofp_version, + const struct ofputil_meter_mod *mm) +{ + struct ofpbuf *msg; + + struct ofp13_meter_mod *omm; + + msg = ofpraw_alloc(OFPRAW_OFPT13_METER_MOD, ofp_version, + NXM_TYPICAL_LEN + mm->meter.n_bands * 16); + omm = ofpbuf_put_zeros(msg, sizeof *omm); + omm->command = htons(mm->command); + if (mm->command != OFPMC13_DELETE) { + omm->flags = htons(mm->meter.flags); + } + omm->meter_id = htonl(mm->meter.meter_id); + + ofputil_put_bands(mm->meter.n_bands, mm->meter.bands, msg); + + ofpmsg_update_length(msg); + return msg; +} + static ovs_be16 ofputil_tid_command(const struct ofputil_flow_mod *fm, enum ofputil_protocol protocol) diff --git a/lib/ofp-util.h b/lib/ofp-util.h index eaa3af221..57d35c4de 100644 --- a/lib/ofp-util.h +++ b/lib/ofp-util.h @@ -514,6 +514,93 @@ enum ofperr ofputil_decode_port_mod(const struct ofp_header *, struct ofpbuf *ofputil_encode_port_mod(const struct ofputil_port_mod *, enum ofputil_protocol); +struct ofputil_meter_band { + uint16_t type; + uint8_t pad; + uint8_t prec_level; /* Non-zero if type == OFPMBT_DSCP_REMARK */ + uint32_t rate; + uint32_t burst_size; +}; + +struct ofputil_meter_band_stats { + uint64_t packet_count; + uint64_t byte_count; +}; + +struct ofputil_meter_config { + uint32_t meter_id; + uint16_t flags; + uint16_t n_bands; + struct ofputil_meter_band *bands; +}; + +/* Abstract ofp_meter_mod. */ +struct ofputil_meter_mod { + uint16_t command; + struct ofputil_meter_config meter; +}; + +struct ofputil_meter_stats { + uint32_t meter_id; + uint32_t flow_count; + uint64_t packet_in_count; + uint64_t byte_in_count; + uint32_t duration_sec; + uint32_t duration_nsec; + uint16_t n_bands; + struct ofputil_meter_band_stats *bands; /* band stats */ +}; + +struct ofputil_meter_features { + uint32_t max_meters; /* Maximum number of meters */ + uint32_t band_types; /* Can support max 32 band types */ + uint32_t capabilities; /* Supported flags */ + uint8_t max_bands; + uint8_t max_color; +}; + +enum ofperr ofputil_decode_meter_mod(const struct ofp_header *, + struct ofputil_meter_mod *, + struct ofpbuf *bands); +struct ofpbuf *ofputil_encode_meter_mod(enum ofp_version ofp_version, + const struct ofputil_meter_mod *); + +void ofputil_decode_meter_features(const struct ofp_header *, + struct ofputil_meter_features *); +struct ofpbuf *ofputil_encode_meter_features_reply(const struct + ofputil_meter_features *, + const struct ofp_header * + request); +void ofputil_decode_meter_request(const struct ofp_header *, + uint32_t *meter_id); + +void ofputil_append_meter_config(struct list *replies, + const struct ofputil_meter_config *omc); + +void ofputil_append_meter_stats(struct list *replies, + const struct ofputil_meter_stats *oms); + +enum ofputil_meter_request_type { + OFPUTIL_METER_FEATURES, + OFPUTIL_METER_CONFIG, + OFPUTIL_METER_STATS +}; + +struct ofpbuf *ofputil_encode_meter_request(enum ofp_version, + enum ofputil_meter_request_type, + uint32_t meter_id); + +int ofputil_decode_meter_stats(struct ofpbuf *msg, + struct ofputil_meter_stats *ms, + struct ofpbuf *bands); + +int ofputil_decode_meter_config(struct ofpbuf *msg, + struct ofputil_meter_config *mc, + struct ofpbuf *bands); + +/* Type for meter_id in ofproto provider interface, UINT32_MAX if none */ +typedef struct { uint32_t uint32; } ofproto_meter_id; + /* Abstract ofp_role_request and reply. */ struct ofputil_role_request { enum ofp12_controller_role role; diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index a52a8cffe..36f54ee24 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -1739,6 +1739,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, flow->metadata |= metadata->metadata & metadata->mask; break; + case OFPACT_METER: + /* Not implemented yet. */ + break; + case OFPACT_GOTO_TABLE: { /* It is assumed that goto-table is the last action. */ struct ofpact_goto_table *ogt = ofpact_get_GOTO_TABLE(a); diff --git a/tests/ofp-print.at b/tests/ofp-print.at index caf0ecc07..b3e3eb501 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -1581,6 +1581,95 @@ OFPST_PORT_DESC reply (xid=0x0): ]) AT_CLEANUP +AT_SETUP([OFPT_METER_MOD request - OF1.3]) +AT_KEYWORDS([ofp-print]) +AT_CHECK([ovs-ofctl ofp-print "\ +04 1d 00 20 00 00 00 02 00 00 00 0d 00 00 00 05 \ +00 01 00 10 00 00 04 00 00 00 00 80 00 00 00 00 \ +"], [0], [dnl +OFPT_METER_MOD (OF1.3) (xid=0x2): ADD meter=5 kbps burst stats bands= +type=drop rate=1024 burst_size=128 +]) +AT_CLEANUP + +AT_SETUP([OFPST_METER request - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "041200180000000200090000000000000000000100000000"], [0], [dnl +OFPST_METER request (OF1.3) (xid=0x2): meter=1 +]) +AT_CLEANUP + +AT_SETUP([OFPST_METER_CONFIG request - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "0412001800000002000a0000000000000000000100000000"], [0], [dnl +OFPST_METER_CONFIG request (OF1.3) (xid=0x2): meter=1 +]) +AT_CLEANUP + +AT_SETUP([OFPST_METER_FEATURES request - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST]) +AT_CHECK([ovs-ofctl ofp-print "0412001000000002000b000000000000"], [0], [dnl +OFPST_METER_FEATURES request (OF1.3) (xid=0x2): +]) +AT_CLEANUP + +AT_SETUP([OFPST_METER_FEATURES reply - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) +AT_CHECK([ovs-ofctl ofp-print "\ +04 13 00 20 00 00 00 02 00 0b 00 00 00 00 00 00 \ +00 01 00 00 00 00 00 03 00 00 00 0F 10 02 00 00 \ +"], [0], [dnl +OFPST_METER_FEATURES reply (OF1.3) (xid=0x2): +max_meter:65536 max_bands:16 max_color:2 +band_types: drop dscp_remark +capabilities: kbps pktps burst stats +]) +AT_CLEANUP + +AT_SETUP([OFPST_METER_CONFIG reply - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) +AT_CHECK([ovs-ofctl ofp-print "\ +04 13 00 50 00 00 00 02 00 0a 00 00 00 00 00 00 \ +00 28 00 05 00 00 00 01 \ +00 01 00 10 00 01 00 00 00 00 05 00 00 00 00 00 \ +00 02 00 10 00 10 00 00 00 00 f0 00 00 00 00 00 \ +00 18 00 09 00 00 00 02 \ +00 01 00 10 00 02 00 00 00 00 00 00 00 00 00 00 \ +"], [0], [dnl +OFPST_METER_CONFIG reply (OF1.3) (xid=0x2): +meter=1 kbps burst bands= +type=drop rate=65536 burst_size=1280 +type=dscp_remark rate=1048576 burst_size=61440 prec_level=0 + +meter=2 kbps stats bands= +type=drop rate=131072 +]) +AT_CLEANUP + +AT_SETUP([OFPST_METER reply - OF1.3]) +AT_KEYWORDS([ofp-print OFPT_STATS_REPLY]) +AT_CHECK([ovs-ofctl ofp-print "\ +04 13 00 90 00 00 00 02 00 09 00 00 00 00 00 00 \ +00 00 00 01 00 48 00 00 00 00 00 00 00 00 00 05 \ +00 00 00 00 00 00 10 00 00 00 00 00 00 02 30 00 \ +00 00 01 8a 9a 6e 23 44 \ +00 00 00 00 00 00 00 7e 00 00 00 00 00 00 34 33 \ +00 00 00 00 00 00 00 e7 00 00 00 00 00 00 94 2e \ +00 00 00 02 00 38 00 00 00 00 00 00 00 00 00 02 \ +00 00 00 00 00 00 02 00 00 00 00 00 00 00 30 00 \ +00 00 01 87 9a 23 6e 44 \ +00 00 00 00 00 00 00 2a 00 00 00 00 00 00 04 33 \ +"], [0], [dnl +OFPST_METER reply (OF1.3) (xid=0x2): +meter:1 flow_count:5 packet_in_count:4096 byte_in_count:143360 duration:394.2590909252s bands: +0: packet_count:126 byte_count:13363 +1: packet_count:231 byte_count:37934 + +meter:2 flow_count:2 packet_in_count:512 byte_in_count:12288 duration:391.2586013252s bands: +0: packet_count:42 byte_count:1075 +]) +AT_CLEANUP + AT_SETUP([OFPT_BARRIER_REQUEST - OF1.0]) AT_KEYWORDS([ofp-print]) AT_CHECK([ovs-ofctl ofp-print '01 12 00 08 00 00 00 01'], [0], [dnl -- 2.43.0