mirror of
https://github.com/openvswitch/ovs
synced 2025-10-27 15:18:06 +00:00
356 lines
12 KiB
C
356 lines
12 KiB
C
|
|
/*
|
||
|
|
* Copyright (c) 2010, 2011, 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.
|
||
|
|
* You may obtain a copy of the License at:
|
||
|
|
*
|
||
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
* See the License for the specific language governing permissions and
|
||
|
|
* limitations under the License.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <config.h>
|
||
|
|
#include "ox-stat.h"
|
||
|
|
#include "byte-order.h"
|
||
|
|
#include "openvswitch/ofp-errors.h"
|
||
|
|
#include "openvswitch/compiler.h"
|
||
|
|
#include "openvswitch/ofpbuf.h"
|
||
|
|
#include "openvswitch/vlog.h"
|
||
|
|
#include "unaligned.h"
|
||
|
|
|
||
|
|
VLOG_DEFINE_THIS_MODULE(ox_stat);
|
||
|
|
|
||
|
|
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
|
||
|
|
|
||
|
|
/* OXS header
|
||
|
|
* ==========
|
||
|
|
*
|
||
|
|
* The header is 32 bits long. It looks like this:
|
||
|
|
*
|
||
|
|
* |31 16 15 9 8 7 0
|
||
|
|
* +----------------------------------+---------------+-+------------------+
|
||
|
|
* | oxs_class | oxs_field |r| oxs_length |
|
||
|
|
* +----------------------------------+---------------+-+------------------+
|
||
|
|
*
|
||
|
|
* where r stands for oxs_reserved. It is followed by oxs_length bytes of
|
||
|
|
* payload (the statistic's value).
|
||
|
|
*
|
||
|
|
* Internally, we represent a standard OXS header as a 64-bit integer with the
|
||
|
|
* above information in the most-significant bits.
|
||
|
|
*
|
||
|
|
*
|
||
|
|
* Experimenter OXS
|
||
|
|
* ================
|
||
|
|
*
|
||
|
|
* The header is 64 bits long. It looks like the diagram above except that a
|
||
|
|
* 32-bit experimenter ID, which we call oxs_experimenter and which identifies
|
||
|
|
* a vendor, is inserted just before the payload. Experimenter OXSs are
|
||
|
|
* identified by an all-1-bits oxs_class (OFPXSC_EXPERIMENTER). The oxs_length
|
||
|
|
* value *includes* the experimenter ID, so that the real payload is only
|
||
|
|
* oxs_length - 4 bytes long.
|
||
|
|
*
|
||
|
|
* Internally, we represent an experimenter OXS header as a 64-bit integer with
|
||
|
|
* the standard header in the upper 32 bits and the experimenter ID in the
|
||
|
|
* lower 32 bits. (It would be more convenient to swap the positions of the
|
||
|
|
* two 32-bit words, but this would be more error-prone because experimenter
|
||
|
|
* OXSs are very rarely used, so accidentally passing one through a 32-bit type
|
||
|
|
* somewhere in the OVS code would be hard to find.)
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* OXS Class IDs.
|
||
|
|
* The high order bit differentiate reserved classes from member classes.
|
||
|
|
* Classes 0x0000 to 0x7FFF are member classes, allocated by ONF.
|
||
|
|
* Classes 0x8000 to 0xFFFE are reserved classes, reserved for standardisation.
|
||
|
|
*/
|
||
|
|
enum ofp_oxs_class {
|
||
|
|
OFPXSC_OPENFLOW_BASIC = 0x8002, /* Basic stats class for OpenFlow */
|
||
|
|
OFPXSC_EXPERIMENTER = 0xFFFF, /* Experimenter class */
|
||
|
|
};
|
||
|
|
|
||
|
|
/* Functions for extracting raw field values from OXS headers. */
|
||
|
|
static uint32_t oxs_experimenter(uint64_t header) { return header; }
|
||
|
|
static int oxs_class(uint64_t header) { return header >> 48; }
|
||
|
|
static int oxs_field(uint64_t header) { return (header >> 41) & 0x7f; }
|
||
|
|
static int oxs_length(uint64_t header) { return (header >> 32) & 0xff; }
|
||
|
|
|
||
|
|
static bool
|
||
|
|
is_experimenter_oxs(uint64_t header)
|
||
|
|
{
|
||
|
|
return oxs_class(header) == OFPXSC_EXPERIMENTER;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* The OXS header "length" field is somewhat tricky:
|
||
|
|
*
|
||
|
|
* - For a standard OXS header, the length is the number of bytes of the
|
||
|
|
* payload, and the payload consists of just the value.
|
||
|
|
*
|
||
|
|
* - For an experimenter OXS header, the length is the number of bytes in
|
||
|
|
* the payload plus 4 (the length of the experimenter ID). That is, the
|
||
|
|
* experimenter ID is included in oxs_length.
|
||
|
|
*
|
||
|
|
* This function returns the length of the experimenter ID field in 'header'.
|
||
|
|
* That is, for an experimenter OXS (when an experimenter ID is present), it
|
||
|
|
* returns 4, and for a standard OXS (when no experimenter ID is present), it
|
||
|
|
* returns 0. */
|
||
|
|
static int
|
||
|
|
oxs_experimenter_len(uint64_t header)
|
||
|
|
{
|
||
|
|
return is_experimenter_oxs(header) ? 4 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns the number of bytes that follow the header for an OXS entry with the
|
||
|
|
* given 'header'. */
|
||
|
|
static int
|
||
|
|
oxs_payload_len(uint64_t header)
|
||
|
|
{
|
||
|
|
return oxs_length(header) - oxs_experimenter_len(header);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns the number of bytes in the header for an OXS entry with the given
|
||
|
|
* 'header'. */
|
||
|
|
static int
|
||
|
|
oxs_header_len(uint64_t header)
|
||
|
|
{
|
||
|
|
return 4 + oxs_experimenter_len(header);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Assembles an OXS header from its components. */
|
||
|
|
#define OXS_HEADER(EXPERIMENTER, CLASS, FIELD, LENGTH) \
|
||
|
|
(((uint64_t) (CLASS) << 48) | \
|
||
|
|
((uint64_t) (FIELD) << 41) | \
|
||
|
|
((uint64_t) (LENGTH) << 32) | \
|
||
|
|
(EXPERIMENTER))
|
||
|
|
|
||
|
|
#define OXS_HEADER_FMT "%#"PRIx32":%d:%d:%d"
|
||
|
|
#define OXS_HEADER_ARGS(HEADER) \
|
||
|
|
oxs_experimenter(HEADER), oxs_class(HEADER), oxs_field(HEADER), \
|
||
|
|
oxs_length(HEADER)
|
||
|
|
|
||
|
|
/* Currently defined OXS. */
|
||
|
|
#define OXS_OF_DURATION OXS_HEADER (0, 0x8002, OFPXST_OFB_DURATION, 8)
|
||
|
|
#define OXS_OF_IDLE_TIME OXS_HEADER (0, 0x8002, OFPXST_OFB_IDLE_TIME, 8)
|
||
|
|
#define OXS_OF_FLOW_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_FLOW_COUNT, 4)
|
||
|
|
#define OXS_OF_PACKET_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_PACKET_COUNT, 8)
|
||
|
|
#define OXS_OF_BYTE_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_BYTE_COUNT, 8)
|
||
|
|
|
||
|
|
/* Header for a group of OXS statistics. */
|
||
|
|
struct ofp_oxs_stat {
|
||
|
|
ovs_be16 reserved; /* Must be zero. */
|
||
|
|
ovs_be16 length; /* Stats Length */
|
||
|
|
};
|
||
|
|
BUILD_ASSERT_DECL(sizeof(struct ofp_oxs_stat) == 4);
|
||
|
|
|
||
|
|
static int oxs_pull_header__(struct ofpbuf *b, uint64_t *header);
|
||
|
|
static enum ofperr oxs_pull_raw(const uint8_t *, unsigned int stat_len,
|
||
|
|
struct oxs_stats *, uint8_t *oxs_field_set);
|
||
|
|
|
||
|
|
static int
|
||
|
|
oxs_pull_header__(struct ofpbuf *b, uint64_t *header)
|
||
|
|
{
|
||
|
|
if (b->size < 4) {
|
||
|
|
goto bad_len;
|
||
|
|
}
|
||
|
|
|
||
|
|
*header = ((uint64_t) ntohl(get_unaligned_be32(b->data))) << 32;
|
||
|
|
if (is_experimenter_oxs(*header)) {
|
||
|
|
if (b->size < 8) {
|
||
|
|
goto bad_len;
|
||
|
|
}
|
||
|
|
*header = ntohll(get_unaligned_be64(b->data));
|
||
|
|
}
|
||
|
|
if (oxs_length(*header) < oxs_experimenter_len(*header)) {
|
||
|
|
VLOG_WARN_RL(&rl, "OXS header "OXS_HEADER_FMT" has invalid length %d "
|
||
|
|
"(minimum is %d)",
|
||
|
|
OXS_HEADER_ARGS(*header), oxs_length(*header),
|
||
|
|
oxs_header_len(*header));
|
||
|
|
goto error;
|
||
|
|
}
|
||
|
|
ofpbuf_pull(b, oxs_header_len(*header));
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
bad_len:
|
||
|
|
VLOG_DBG_RL(&rl, "encountered partial (%"PRIu32"-byte) OXS entry",
|
||
|
|
b->size);
|
||
|
|
error:
|
||
|
|
*header = 0;
|
||
|
|
return OFPERR_OFPBMC_BAD_LEN;
|
||
|
|
}
|
||
|
|
|
||
|
|
static enum ofperr
|
||
|
|
oxs_pull_entry__(struct ofpbuf *b, struct oxs_stats *stats,
|
||
|
|
uint8_t *oxs_field_set)
|
||
|
|
{
|
||
|
|
uint64_t header;
|
||
|
|
enum ofperr error = oxs_pull_header__(b, &header);
|
||
|
|
if (error) {
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
unsigned int payload_len = oxs_payload_len(header);
|
||
|
|
const void *payload = ofpbuf_try_pull(b, payload_len);
|
||
|
|
if (!payload) {
|
||
|
|
return OFPERR_OFPBMC_BAD_LEN;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (header) {
|
||
|
|
case OXS_OF_DURATION: {
|
||
|
|
uint64_t duration = ntohll(get_unaligned_be64(payload));
|
||
|
|
stats->duration_sec = duration >> 32;
|
||
|
|
stats->duration_nsec = duration;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case OXS_OF_IDLE_TIME:
|
||
|
|
stats->idle_age = ntohll(get_unaligned_be64(payload)) >> 32;
|
||
|
|
break;
|
||
|
|
case OXS_OF_PACKET_COUNT:
|
||
|
|
stats->packet_count = ntohll(get_unaligned_be64(payload));
|
||
|
|
break;
|
||
|
|
case OXS_OF_BYTE_COUNT:
|
||
|
|
stats->byte_count = ntohll(get_unaligned_be64(payload));
|
||
|
|
break;
|
||
|
|
case OXS_OF_FLOW_COUNT:
|
||
|
|
stats->flow_count = ntohl(get_unaligned_be32(payload));
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
/* Unknown header. */
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if (oxs_field_set
|
||
|
|
&& oxs_class(header) == OFPXSC_OPENFLOW_BASIC
|
||
|
|
&& oxs_field(header) < CHAR_BIT * sizeof *oxs_field_set) {
|
||
|
|
*oxs_field_set |= 1 << oxs_field(header);
|
||
|
|
}
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
static enum ofperr
|
||
|
|
oxs_pull_raw(const uint8_t * p, unsigned int stat_len,
|
||
|
|
struct oxs_stats *stats, uint8_t *oxs_field_set)
|
||
|
|
{
|
||
|
|
struct ofpbuf b = ofpbuf_const_initializer(p, stat_len);
|
||
|
|
while (b.size) {
|
||
|
|
const uint8_t *pos = b.data;
|
||
|
|
enum ofperr error = oxs_pull_entry__(&b, stats, oxs_field_set);
|
||
|
|
if (error && error != OFPERR_OFPBMC_BAD_FIELD) {
|
||
|
|
VLOG_DBG_RL(&rl, "error parsing OXS at offset %"PRIdPTR" "
|
||
|
|
"within match (%s)",
|
||
|
|
pos - p, ofperr_to_string(error));
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Retrieve struct ofp_oxs_stat from 'b', followed by the flow entry
|
||
|
|
* statistics in OXS format.
|
||
|
|
*
|
||
|
|
* Returns error if message parsing fails, otherwise returns zero . */
|
||
|
|
enum ofperr
|
||
|
|
oxs_pull_stat(struct ofpbuf *b, struct oxs_stats *stats,
|
||
|
|
uint16_t *statlen, uint8_t *oxs_field_set)
|
||
|
|
{
|
||
|
|
memset(stats, 0xff, sizeof *stats);
|
||
|
|
|
||
|
|
struct ofp_oxs_stat *oxs = b->data;
|
||
|
|
if (b->size < sizeof *oxs) {
|
||
|
|
return OFPERR_OFPBMC_BAD_LEN;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint16_t stat_len = ntohs(oxs->length);
|
||
|
|
if (stat_len < sizeof *oxs) {
|
||
|
|
return OFPERR_OFPBMC_BAD_LEN;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint8_t *p = ofpbuf_try_pull(b, ROUND_UP(stat_len, 8));
|
||
|
|
if (!p) {
|
||
|
|
VLOG_DBG_RL(&rl, "oxs length %u, rounded up to a "
|
||
|
|
"multiple of 8, is longer than space in message (max "
|
||
|
|
"length %" PRIu32 ")", stat_len, b->size);
|
||
|
|
return OFPERR_OFPBMC_BAD_LEN;
|
||
|
|
}
|
||
|
|
*statlen = ROUND_UP(stat_len, 8);
|
||
|
|
return oxs_pull_raw(p + sizeof *oxs, stat_len - sizeof *oxs, stats,
|
||
|
|
oxs_field_set);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
oxs_put__(struct ofpbuf *b, uint64_t header,
|
||
|
|
const void *value, size_t value_size)
|
||
|
|
{
|
||
|
|
if (is_experimenter_oxs(header)) {
|
||
|
|
ovs_be64 be64 = htonll(header);
|
||
|
|
ofpbuf_put(b, &be64, sizeof be64);
|
||
|
|
} else {
|
||
|
|
ovs_be32 be32 = htonl(header >> 32);
|
||
|
|
ofpbuf_put(b, &be32, sizeof be32);
|
||
|
|
}
|
||
|
|
|
||
|
|
ovs_assert(oxs_payload_len(header) == value_size);
|
||
|
|
ofpbuf_put(b, value, value_size);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
oxs_put32(struct ofpbuf *b, uint64_t header, uint32_t value_)
|
||
|
|
{
|
||
|
|
ovs_be32 value = htonl(value_);
|
||
|
|
oxs_put__(b, header, &value, sizeof value);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
oxs_put64(struct ofpbuf *b, uint64_t header, uint64_t value_)
|
||
|
|
{
|
||
|
|
ovs_be64 value = htonll(value_);
|
||
|
|
oxs_put__(b, header, &value, sizeof value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Appends to 'b' an struct ofp_oxs_stat followed by the flow entry statistics
|
||
|
|
* in OXS format , plus enough zero bytes to pad the data appended out to a
|
||
|
|
* multiple of 8.
|
||
|
|
*
|
||
|
|
* Specify the OpenFlow version in use as 'version'.
|
||
|
|
*
|
||
|
|
* This function can cause 'b''s data to be reallocated.
|
||
|
|
*
|
||
|
|
* Returns the number of bytes appended to 'b', excluding the padding.Never
|
||
|
|
* returns zero. */
|
||
|
|
void
|
||
|
|
oxs_put_stats(struct ofpbuf *b, const struct oxs_stats *stats)
|
||
|
|
{
|
||
|
|
size_t start = b->size;
|
||
|
|
|
||
|
|
/* Put empty header. */
|
||
|
|
struct ofp_oxs_stat *oxs;
|
||
|
|
ofpbuf_put_zeros(b, sizeof *oxs);
|
||
|
|
|
||
|
|
/* Put stats. */
|
||
|
|
if (stats->duration_sec != UINT32_MAX) {
|
||
|
|
oxs_put64(b, OXS_OF_DURATION,
|
||
|
|
(((uint64_t) stats->duration_sec << 32)
|
||
|
|
| stats->duration_nsec));
|
||
|
|
}
|
||
|
|
if (stats->idle_age != UINT32_MAX) {
|
||
|
|
oxs_put64(b, OXS_OF_IDLE_TIME, (uint64_t) stats->idle_age << 32);
|
||
|
|
}
|
||
|
|
if (stats->packet_count != UINT64_MAX) {
|
||
|
|
oxs_put64(b, OXS_OF_PACKET_COUNT, stats->packet_count);
|
||
|
|
}
|
||
|
|
if (stats->byte_count != UINT64_MAX) {
|
||
|
|
oxs_put64(b, OXS_OF_BYTE_COUNT, stats->byte_count);
|
||
|
|
}
|
||
|
|
if (stats->flow_count != UINT32_MAX) {
|
||
|
|
oxs_put32(b, OXS_OF_FLOW_COUNT, stats->flow_count);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Fill in size in header, then pad to multiple of 8 bytes. */
|
||
|
|
oxs = ofpbuf_at(b, start, sizeof *oxs);
|
||
|
|
oxs->length = htons(b->size - start);
|
||
|
|
ofpbuf_put_zeros(b, PAD_SIZE(b->size - start, 8));
|
||
|
|
}
|