/src/openvswitch/lib/ox-stat.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at: |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <config.h> |
18 | | #include "ox-stat.h" |
19 | | #include "byte-order.h" |
20 | | #include "openvswitch/ofp-errors.h" |
21 | | #include "openvswitch/compiler.h" |
22 | | #include "openvswitch/ofpbuf.h" |
23 | | #include "openvswitch/vlog.h" |
24 | | #include "unaligned.h" |
25 | | |
26 | | VLOG_DEFINE_THIS_MODULE(ox_stat); |
27 | | |
28 | | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); |
29 | | |
30 | | /* OXS header |
31 | | * ========== |
32 | | * |
33 | | * The header is 32 bits long. It looks like this: |
34 | | * |
35 | | * |31 16 15 9 8 7 0 |
36 | | * +----------------------------------+---------------+-+------------------+ |
37 | | * | oxs_class | oxs_field |r| oxs_length | |
38 | | * +----------------------------------+---------------+-+------------------+ |
39 | | * |
40 | | * where r stands for oxs_reserved. It is followed by oxs_length bytes of |
41 | | * payload (the statistic's value). |
42 | | * |
43 | | * Internally, we represent a standard OXS header as a 64-bit integer with the |
44 | | * above information in the most-significant bits. |
45 | | * |
46 | | * |
47 | | * Experimenter OXS |
48 | | * ================ |
49 | | * |
50 | | * The header is 64 bits long. It looks like the diagram above except that a |
51 | | * 32-bit experimenter ID, which we call oxs_experimenter and which identifies |
52 | | * a vendor, is inserted just before the payload. Experimenter OXSs are |
53 | | * identified by an all-1-bits oxs_class (OFPXSC_EXPERIMENTER). The oxs_length |
54 | | * value *includes* the experimenter ID, so that the real payload is only |
55 | | * oxs_length - 4 bytes long. |
56 | | * |
57 | | * Internally, we represent an experimenter OXS header as a 64-bit integer with |
58 | | * the standard header in the upper 32 bits and the experimenter ID in the |
59 | | * lower 32 bits. (It would be more convenient to swap the positions of the |
60 | | * two 32-bit words, but this would be more error-prone because experimenter |
61 | | * OXSs are very rarely used, so accidentally passing one through a 32-bit type |
62 | | * somewhere in the OVS code would be hard to find.) |
63 | | */ |
64 | | |
65 | | /* OXS Class IDs. |
66 | | * The high order bit differentiate reserved classes from member classes. |
67 | | * Classes 0x0000 to 0x7FFF are member classes, allocated by ONF. |
68 | | * Classes 0x8000 to 0xFFFE are reserved classes, reserved for standardisation. |
69 | | */ |
70 | | enum ofp_oxs_class { |
71 | | OFPXSC_OPENFLOW_BASIC = 0x8002, /* Basic stats class for OpenFlow */ |
72 | | OFPXSC_EXPERIMENTER = 0xFFFF, /* Experimenter class */ |
73 | | }; |
74 | | |
75 | | /* Functions for extracting raw field values from OXS headers. */ |
76 | 0 | static uint32_t oxs_experimenter(uint64_t header) { return header; } |
77 | 0 | static int oxs_class(uint64_t header) { return header >> 48; } |
78 | 0 | static int oxs_field(uint64_t header) { return (header >> 41) & 0x7f; } |
79 | 0 | static int oxs_length(uint64_t header) { return (header >> 32) & 0xff; } |
80 | | |
81 | | static bool |
82 | | is_experimenter_oxs(uint64_t header) |
83 | 0 | { |
84 | 0 | return oxs_class(header) == OFPXSC_EXPERIMENTER; |
85 | 0 | } |
86 | | |
87 | | /* The OXS header "length" field is somewhat tricky: |
88 | | * |
89 | | * - For a standard OXS header, the length is the number of bytes of the |
90 | | * payload, and the payload consists of just the value. |
91 | | * |
92 | | * - For an experimenter OXS header, the length is the number of bytes in |
93 | | * the payload plus 4 (the length of the experimenter ID). That is, the |
94 | | * experimenter ID is included in oxs_length. |
95 | | * |
96 | | * This function returns the length of the experimenter ID field in 'header'. |
97 | | * That is, for an experimenter OXS (when an experimenter ID is present), it |
98 | | * returns 4, and for a standard OXS (when no experimenter ID is present), it |
99 | | * returns 0. */ |
100 | | static int |
101 | | oxs_experimenter_len(uint64_t header) |
102 | 0 | { |
103 | 0 | return is_experimenter_oxs(header) ? 4 : 0; |
104 | 0 | } |
105 | | |
106 | | /* Returns the number of bytes that follow the header for an OXS entry with the |
107 | | * given 'header'. */ |
108 | | static int |
109 | | oxs_payload_len(uint64_t header) |
110 | 0 | { |
111 | 0 | return oxs_length(header) - oxs_experimenter_len(header); |
112 | 0 | } |
113 | | |
114 | | /* Returns the number of bytes in the header for an OXS entry with the given |
115 | | * 'header'. */ |
116 | | static int |
117 | | oxs_header_len(uint64_t header) |
118 | 0 | { |
119 | 0 | return 4 + oxs_experimenter_len(header); |
120 | 0 | } |
121 | | |
122 | | /* Assembles an OXS header from its components. */ |
123 | | #define OXS_HEADER(EXPERIMENTER, CLASS, FIELD, LENGTH) \ |
124 | 0 | (((uint64_t) (CLASS) << 48) | \ |
125 | 0 | ((uint64_t) (FIELD) << 41) | \ |
126 | 0 | ((uint64_t) (LENGTH) << 32) | \ |
127 | 0 | (EXPERIMENTER)) |
128 | | |
129 | | #define OXS_HEADER_FMT "%#"PRIx32":%d:%d:%d" |
130 | | #define OXS_HEADER_ARGS(HEADER) \ |
131 | | oxs_experimenter(HEADER), oxs_class(HEADER), oxs_field(HEADER), \ |
132 | | oxs_length(HEADER) |
133 | | |
134 | | /* Currently defined OXS. */ |
135 | 0 | #define OXS_OF_DURATION OXS_HEADER (0, 0x8002, OFPXST_OFB_DURATION, 8) |
136 | 0 | #define OXS_OF_IDLE_TIME OXS_HEADER (0, 0x8002, OFPXST_OFB_IDLE_TIME, 8) |
137 | 0 | #define OXS_OF_FLOW_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_FLOW_COUNT, 4) |
138 | 0 | #define OXS_OF_PACKET_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_PACKET_COUNT, 8) |
139 | 0 | #define OXS_OF_BYTE_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_BYTE_COUNT, 8) |
140 | | |
141 | | /* Header for a group of OXS statistics. */ |
142 | | struct ofp_oxs_stat { |
143 | | ovs_be16 reserved; /* Must be zero. */ |
144 | | ovs_be16 length; /* Stats Length */ |
145 | | }; |
146 | | BUILD_ASSERT_DECL(sizeof(struct ofp_oxs_stat) == 4); |
147 | | |
148 | | static int oxs_pull_header__(struct ofpbuf *b, uint64_t *header); |
149 | | static enum ofperr oxs_pull_raw(const uint8_t *, unsigned int stat_len, |
150 | | struct oxs_stats *, uint8_t *oxs_field_set); |
151 | | |
152 | | static int |
153 | | oxs_pull_header__(struct ofpbuf *b, uint64_t *header) |
154 | 0 | { |
155 | 0 | if (b->size < 4) { |
156 | 0 | goto bad_len; |
157 | 0 | } |
158 | | |
159 | 0 | *header = ((uint64_t) ntohl(get_unaligned_be32(b->data))) << 32; |
160 | 0 | if (is_experimenter_oxs(*header)) { |
161 | 0 | if (b->size < 8) { |
162 | 0 | goto bad_len; |
163 | 0 | } |
164 | 0 | *header = ntohll(get_unaligned_be64(b->data)); |
165 | 0 | } |
166 | 0 | if (oxs_length(*header) < oxs_experimenter_len(*header)) { |
167 | 0 | VLOG_WARN_RL(&rl, "OXS header "OXS_HEADER_FMT" has invalid length %d " |
168 | 0 | "(minimum is %d)", |
169 | 0 | OXS_HEADER_ARGS(*header), oxs_length(*header), |
170 | 0 | oxs_header_len(*header)); |
171 | 0 | goto error; |
172 | 0 | } |
173 | 0 | ofpbuf_pull(b, oxs_header_len(*header)); |
174 | |
|
175 | 0 | return 0; |
176 | | |
177 | 0 | bad_len: |
178 | 0 | VLOG_DBG_RL(&rl, "encountered partial (%"PRIu32"-byte) OXS entry", |
179 | 0 | b->size); |
180 | 0 | error: |
181 | 0 | *header = 0; |
182 | 0 | return OFPERR_OFPBMC_BAD_LEN; |
183 | 0 | } |
184 | | |
185 | | static enum ofperr |
186 | | oxs_pull_entry__(struct ofpbuf *b, struct oxs_stats *stats, |
187 | | uint8_t *oxs_field_set) |
188 | 0 | { |
189 | 0 | uint64_t header; |
190 | 0 | enum ofperr error = oxs_pull_header__(b, &header); |
191 | 0 | if (error) { |
192 | 0 | return error; |
193 | 0 | } |
194 | | |
195 | 0 | unsigned int payload_len = oxs_payload_len(header); |
196 | 0 | const void *payload = ofpbuf_try_pull(b, payload_len); |
197 | 0 | if (!payload) { |
198 | 0 | return OFPERR_OFPBMC_BAD_LEN; |
199 | 0 | } |
200 | | |
201 | 0 | switch (header) { |
202 | 0 | case OXS_OF_DURATION: { |
203 | 0 | uint64_t duration = ntohll(get_unaligned_be64(payload)); |
204 | 0 | stats->duration_sec = duration >> 32; |
205 | 0 | stats->duration_nsec = duration; |
206 | 0 | } |
207 | 0 | break; |
208 | 0 | case OXS_OF_IDLE_TIME: |
209 | 0 | stats->idle_age = ntohll(get_unaligned_be64(payload)) >> 32; |
210 | 0 | break; |
211 | 0 | case OXS_OF_PACKET_COUNT: |
212 | 0 | stats->packet_count = ntohll(get_unaligned_be64(payload)); |
213 | 0 | break; |
214 | 0 | case OXS_OF_BYTE_COUNT: |
215 | 0 | stats->byte_count = ntohll(get_unaligned_be64(payload)); |
216 | 0 | break; |
217 | 0 | case OXS_OF_FLOW_COUNT: |
218 | 0 | stats->flow_count = ntohl(get_unaligned_be32(payload)); |
219 | 0 | break; |
220 | | |
221 | 0 | default: |
222 | | /* Unknown header. */ |
223 | 0 | return 0; |
224 | 0 | } |
225 | 0 | if (oxs_field_set |
226 | 0 | && oxs_class(header) == OFPXSC_OPENFLOW_BASIC |
227 | 0 | && oxs_field(header) < CHAR_BIT * sizeof *oxs_field_set) { |
228 | 0 | *oxs_field_set |= 1 << oxs_field(header); |
229 | 0 | } |
230 | 0 | return error; |
231 | 0 | } |
232 | | |
233 | | static enum ofperr |
234 | | oxs_pull_raw(const uint8_t * p, unsigned int stat_len, |
235 | | struct oxs_stats *stats, uint8_t *oxs_field_set) |
236 | 0 | { |
237 | 0 | struct ofpbuf b = ofpbuf_const_initializer(p, stat_len); |
238 | 0 | while (b.size) { |
239 | 0 | const uint8_t *pos = b.data; |
240 | 0 | enum ofperr error = oxs_pull_entry__(&b, stats, oxs_field_set); |
241 | 0 | if (error && error != OFPERR_OFPBMC_BAD_FIELD) { |
242 | 0 | VLOG_DBG_RL(&rl, "error parsing OXS at offset %"PRIdPTR" " |
243 | 0 | "within match (%s)", |
244 | 0 | pos - p, ofperr_to_string(error)); |
245 | 0 | return error; |
246 | 0 | } |
247 | 0 | } |
248 | 0 | return 0; |
249 | 0 | } |
250 | | |
251 | | /* Retrieve struct ofp_oxs_stat from 'b', followed by the flow entry |
252 | | * statistics in OXS format. |
253 | | * |
254 | | * Returns error if message parsing fails, otherwise returns zero . */ |
255 | | enum ofperr |
256 | | oxs_pull_stat(struct ofpbuf *b, struct oxs_stats *stats, |
257 | | uint16_t *statlen, uint8_t *oxs_field_set) |
258 | 0 | { |
259 | 0 | memset(stats, 0xff, sizeof *stats); |
260 | |
|
261 | 0 | struct ofp_oxs_stat *oxs = b->data; |
262 | 0 | if (b->size < sizeof *oxs) { |
263 | 0 | return OFPERR_OFPBMC_BAD_LEN; |
264 | 0 | } |
265 | | |
266 | 0 | uint16_t stat_len = ntohs(oxs->length); |
267 | 0 | if (stat_len < sizeof *oxs) { |
268 | 0 | return OFPERR_OFPBMC_BAD_LEN; |
269 | 0 | } |
270 | | |
271 | 0 | uint8_t *p = ofpbuf_try_pull(b, ROUND_UP(stat_len, 8)); |
272 | 0 | if (!p) { |
273 | 0 | VLOG_DBG_RL(&rl, "oxs length %u, rounded up to a " |
274 | 0 | "multiple of 8, is longer than space in message (max " |
275 | 0 | "length %" PRIu32 ")", stat_len, b->size); |
276 | 0 | return OFPERR_OFPBMC_BAD_LEN; |
277 | 0 | } |
278 | 0 | *statlen = ROUND_UP(stat_len, 8); |
279 | 0 | return oxs_pull_raw(p + sizeof *oxs, stat_len - sizeof *oxs, stats, |
280 | 0 | oxs_field_set); |
281 | 0 | } |
282 | | |
283 | | static void |
284 | | oxs_put__(struct ofpbuf *b, uint64_t header, |
285 | | const void *value, size_t value_size) |
286 | 0 | { |
287 | 0 | if (is_experimenter_oxs(header)) { |
288 | 0 | ovs_be64 be64 = htonll(header); |
289 | 0 | ofpbuf_put(b, &be64, sizeof be64); |
290 | 0 | } else { |
291 | 0 | ovs_be32 be32 = htonl(header >> 32); |
292 | 0 | ofpbuf_put(b, &be32, sizeof be32); |
293 | 0 | } |
294 | |
|
295 | 0 | ovs_assert(oxs_payload_len(header) == value_size); |
296 | 0 | ofpbuf_put(b, value, value_size); |
297 | 0 | } |
298 | | |
299 | | static void |
300 | | oxs_put32(struct ofpbuf *b, uint64_t header, uint32_t value_) |
301 | 0 | { |
302 | 0 | ovs_be32 value = htonl(value_); |
303 | 0 | oxs_put__(b, header, &value, sizeof value); |
304 | 0 | } |
305 | | |
306 | | static void |
307 | | oxs_put64(struct ofpbuf *b, uint64_t header, uint64_t value_) |
308 | 0 | { |
309 | 0 | ovs_be64 value = htonll(value_); |
310 | 0 | oxs_put__(b, header, &value, sizeof value); |
311 | 0 | } |
312 | | |
313 | | /* Appends to 'b' an struct ofp_oxs_stat followed by the flow entry statistics |
314 | | * in OXS format , plus enough zero bytes to pad the data appended out to a |
315 | | * multiple of 8. |
316 | | * |
317 | | * Specify the OpenFlow version in use as 'version'. |
318 | | * |
319 | | * This function can cause 'b''s data to be reallocated. |
320 | | * |
321 | | * Returns the number of bytes appended to 'b', excluding the padding.Never |
322 | | * returns zero. */ |
323 | | void |
324 | | oxs_put_stats(struct ofpbuf *b, const struct oxs_stats *stats) |
325 | 0 | { |
326 | 0 | size_t start = b->size; |
327 | | |
328 | | /* Put empty header. */ |
329 | 0 | struct ofp_oxs_stat *oxs; |
330 | 0 | ofpbuf_put_zeros(b, sizeof *oxs); |
331 | | |
332 | | /* Put stats. */ |
333 | 0 | if (stats->duration_sec != UINT32_MAX) { |
334 | 0 | oxs_put64(b, OXS_OF_DURATION, |
335 | 0 | (((uint64_t) stats->duration_sec << 32) |
336 | 0 | | stats->duration_nsec)); |
337 | 0 | } |
338 | 0 | if (stats->idle_age != UINT32_MAX) { |
339 | 0 | oxs_put64(b, OXS_OF_IDLE_TIME, (uint64_t) stats->idle_age << 32); |
340 | 0 | } |
341 | 0 | if (stats->packet_count != UINT64_MAX) { |
342 | 0 | oxs_put64(b, OXS_OF_PACKET_COUNT, stats->packet_count); |
343 | 0 | } |
344 | 0 | if (stats->byte_count != UINT64_MAX) { |
345 | 0 | oxs_put64(b, OXS_OF_BYTE_COUNT, stats->byte_count); |
346 | 0 | } |
347 | 0 | if (stats->flow_count != UINT32_MAX) { |
348 | 0 | oxs_put32(b, OXS_OF_FLOW_COUNT, stats->flow_count); |
349 | 0 | } |
350 | | |
351 | | /* Fill in size in header, then pad to multiple of 8 bytes. */ |
352 | 0 | oxs = ofpbuf_at(b, start, sizeof *oxs); |
353 | 0 | oxs->length = htons(b->size - start); |
354 | 0 | ofpbuf_put_zeros(b, PAD_SIZE(b->size - start, 8)); |
355 | 0 | } |