]>
Commit | Line | Data |
---|---|---|
c7b02b80 S |
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 | static uint32_t oxs_experimenter(uint64_t header) { return header; } | |
77 | static int oxs_class(uint64_t header) { return header >> 48; } | |
78 | static int oxs_field(uint64_t header) { return (header >> 41) & 0x7f; } | |
79 | static int oxs_length(uint64_t header) { return (header >> 32) & 0xff; } | |
80 | ||
81 | static bool | |
82 | is_experimenter_oxs(uint64_t header) | |
83 | { | |
84 | return oxs_class(header) == OFPXSC_EXPERIMENTER; | |
85 | } | |
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 | { | |
103 | return is_experimenter_oxs(header) ? 4 : 0; | |
104 | } | |
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 | { | |
111 | return oxs_length(header) - oxs_experimenter_len(header); | |
112 | } | |
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 | { | |
119 | return 4 + oxs_experimenter_len(header); | |
120 | } | |
121 | ||
122 | /* Assembles an OXS header from its components. */ | |
123 | #define OXS_HEADER(EXPERIMENTER, CLASS, FIELD, LENGTH) \ | |
124 | (((uint64_t) (CLASS) << 48) | \ | |
125 | ((uint64_t) (FIELD) << 41) | \ | |
126 | ((uint64_t) (LENGTH) << 32) | \ | |
127 | (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 | #define OXS_OF_DURATION OXS_HEADER (0, 0x8002, OFPXST_OFB_DURATION, 8) | |
136 | #define OXS_OF_IDLE_TIME OXS_HEADER (0, 0x8002, OFPXST_OFB_IDLE_TIME, 8) | |
137 | #define OXS_OF_FLOW_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_FLOW_COUNT, 4) | |
138 | #define OXS_OF_PACKET_COUNT OXS_HEADER (0, 0x8002, OFPXST_OFB_PACKET_COUNT, 8) | |
139 | #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 | { | |
155 | if (b->size < 4) { | |
156 | goto bad_len; | |
157 | } | |
158 | ||
159 | *header = ((uint64_t) ntohl(get_unaligned_be32(b->data))) << 32; | |
160 | if (is_experimenter_oxs(*header)) { | |
161 | if (b->size < 8) { | |
162 | goto bad_len; | |
163 | } | |
164 | *header = ntohll(get_unaligned_be64(b->data)); | |
165 | } | |
166 | if (oxs_length(*header) < oxs_experimenter_len(*header)) { | |
167 | VLOG_WARN_RL(&rl, "OXS header "OXS_HEADER_FMT" has invalid length %d " | |
168 | "(minimum is %d)", | |
169 | OXS_HEADER_ARGS(*header), oxs_length(*header), | |
170 | oxs_header_len(*header)); | |
171 | goto error; | |
172 | } | |
173 | ofpbuf_pull(b, oxs_header_len(*header)); | |
174 | ||
175 | return 0; | |
176 | ||
177 | bad_len: | |
178 | VLOG_DBG_RL(&rl, "encountered partial (%"PRIu32"-byte) OXS entry", | |
179 | b->size); | |
180 | error: | |
181 | *header = 0; | |
182 | return OFPERR_OFPBMC_BAD_LEN; | |
183 | } | |
184 | ||
185 | static enum ofperr | |
186 | oxs_pull_entry__(struct ofpbuf *b, struct oxs_stats *stats, | |
187 | uint8_t *oxs_field_set) | |
188 | { | |
189 | uint64_t header; | |
190 | enum ofperr error = oxs_pull_header__(b, &header); | |
191 | if (error) { | |
192 | return error; | |
193 | } | |
194 | ||
195 | unsigned int payload_len = oxs_payload_len(header); | |
196 | const void *payload = ofpbuf_try_pull(b, payload_len); | |
197 | if (!payload) { | |
198 | return OFPERR_OFPBMC_BAD_LEN; | |
199 | } | |
200 | ||
201 | switch (header) { | |
202 | case OXS_OF_DURATION: { | |
203 | uint64_t duration = ntohll(get_unaligned_be64(payload)); | |
204 | stats->duration_sec = duration >> 32; | |
205 | stats->duration_nsec = duration; | |
206 | } | |
207 | break; | |
208 | case OXS_OF_IDLE_TIME: | |
209 | stats->idle_age = ntohll(get_unaligned_be64(payload)) >> 32; | |
210 | break; | |
211 | case OXS_OF_PACKET_COUNT: | |
212 | stats->packet_count = ntohll(get_unaligned_be64(payload)); | |
213 | break; | |
214 | case OXS_OF_BYTE_COUNT: | |
215 | stats->byte_count = ntohll(get_unaligned_be64(payload)); | |
216 | break; | |
217 | case OXS_OF_FLOW_COUNT: | |
218 | stats->flow_count = ntohl(get_unaligned_be32(payload)); | |
219 | break; | |
220 | ||
221 | default: | |
222 | /* Unknown header. */ | |
223 | return 0; | |
224 | } | |
225 | if (oxs_field_set | |
226 | && oxs_class(header) == OFPXSC_OPENFLOW_BASIC | |
227 | && oxs_field(header) < CHAR_BIT * sizeof *oxs_field_set) { | |
228 | *oxs_field_set |= 1 << oxs_field(header); | |
229 | } | |
230 | return error; | |
231 | } | |
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 | { | |
237 | struct ofpbuf b = ofpbuf_const_initializer(p, stat_len); | |
238 | while (b.size) { | |
239 | const uint8_t *pos = b.data; | |
240 | enum ofperr error = oxs_pull_entry__(&b, stats, oxs_field_set); | |
241 | if (error && error != OFPERR_OFPBMC_BAD_FIELD) { | |
242 | VLOG_DBG_RL(&rl, "error parsing OXS at offset %"PRIdPTR" " | |
243 | "within match (%s)", | |
244 | pos - p, ofperr_to_string(error)); | |
245 | return error; | |
246 | } | |
247 | } | |
248 | return 0; | |
249 | } | |
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 | { | |
259 | memset(stats, 0xff, sizeof *stats); | |
260 | ||
261 | struct ofp_oxs_stat *oxs = b->data; | |
262 | if (b->size < sizeof *oxs) { | |
263 | return OFPERR_OFPBMC_BAD_LEN; | |
264 | } | |
265 | ||
266 | uint16_t stat_len = ntohs(oxs->length); | |
267 | if (stat_len < sizeof *oxs) { | |
268 | return OFPERR_OFPBMC_BAD_LEN; | |
269 | } | |
270 | ||
271 | uint8_t *p = ofpbuf_try_pull(b, ROUND_UP(stat_len, 8)); | |
272 | if (!p) { | |
273 | VLOG_DBG_RL(&rl, "oxs length %u, rounded up to a " | |
274 | "multiple of 8, is longer than space in message (max " | |
275 | "length %" PRIu32 ")", stat_len, b->size); | |
276 | return OFPERR_OFPBMC_BAD_LEN; | |
277 | } | |
278 | *statlen = ROUND_UP(stat_len, 8); | |
279 | return oxs_pull_raw(p + sizeof *oxs, stat_len - sizeof *oxs, stats, | |
280 | oxs_field_set); | |
281 | } | |
282 | ||
283 | static void | |
284 | oxs_put__(struct ofpbuf *b, uint64_t header, | |
285 | const void *value, size_t value_size) | |
286 | { | |
287 | if (is_experimenter_oxs(header)) { | |
288 | ovs_be64 be64 = htonll(header); | |
289 | ofpbuf_put(b, &be64, sizeof be64); | |
290 | } else { | |
291 | ovs_be32 be32 = htonl(header >> 32); | |
292 | ofpbuf_put(b, &be32, sizeof be32); | |
293 | } | |
294 | ||
295 | ovs_assert(oxs_payload_len(header) == value_size); | |
296 | ofpbuf_put(b, value, value_size); | |
297 | } | |
298 | ||
299 | static void | |
300 | oxs_put32(struct ofpbuf *b, uint64_t header, uint32_t value_) | |
301 | { | |
302 | ovs_be32 value = htonl(value_); | |
303 | oxs_put__(b, header, &value, sizeof value); | |
304 | } | |
305 | ||
306 | static void | |
307 | oxs_put64(struct ofpbuf *b, uint64_t header, uint64_t value_) | |
308 | { | |
309 | ovs_be64 value = htonll(value_); | |
310 | oxs_put__(b, header, &value, sizeof value); | |
311 | } | |
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 | { | |
326 | size_t start = b->size; | |
327 | ||
328 | /* Put empty header. */ | |
329 | struct ofp_oxs_stat *oxs; | |
330 | ofpbuf_put_zeros(b, sizeof *oxs); | |
331 | ||
332 | /* Put stats. */ | |
333 | if (stats->duration_sec != UINT32_MAX) { | |
334 | oxs_put64(b, OXS_OF_DURATION, | |
335 | (((uint64_t) stats->duration_sec << 32) | |
336 | | stats->duration_nsec)); | |
337 | } | |
338 | if (stats->idle_age != UINT32_MAX) { | |
339 | oxs_put64(b, OXS_OF_IDLE_TIME, (uint64_t) stats->idle_age << 32); | |
340 | } | |
341 | if (stats->packet_count != UINT64_MAX) { | |
342 | oxs_put64(b, OXS_OF_PACKET_COUNT, stats->packet_count); | |
343 | } | |
344 | if (stats->byte_count != UINT64_MAX) { | |
345 | oxs_put64(b, OXS_OF_BYTE_COUNT, stats->byte_count); | |
346 | } | |
347 | if (stats->flow_count != UINT32_MAX) { | |
348 | oxs_put32(b, OXS_OF_FLOW_COUNT, stats->flow_count); | |
349 | } | |
350 | ||
351 | /* Fill in size in header, then pad to multiple of 8 bytes. */ | |
352 | oxs = ofpbuf_at(b, start, sizeof *oxs); | |
353 | oxs->length = htons(b->size - start); | |
354 | ofpbuf_put_zeros(b, PAD_SIZE(b->size - start, 8)); | |
355 | } |