]>
Commit | Line | Data |
---|---|---|
064af421 | 1 | /* |
eadd1644 | 2 | * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc. |
064af421 | 3 | * |
a14bc59f BP |
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: | |
064af421 | 7 | * |
a14bc59f BP |
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. | |
064af421 BP |
15 | */ |
16 | ||
17 | /* "White box" tests for classifier. | |
18 | * | |
19 | * With very few exceptions, these tests obtain complete coverage of every | |
20 | * basic block and every branch in the classifier implementation, e.g. a clean | |
21 | * report from "gcov -b". (Covering the exceptions would require finding | |
22 | * collisions in the hash function used for flow data, etc.) | |
23 | * | |
24 | * This test should receive a clean report from "valgrind --leak-check=full": | |
25 | * it frees every heap block that it allocates. | |
26 | */ | |
27 | ||
28 | #include <config.h> | |
064af421 BP |
29 | #include <errno.h> |
30 | #include <limits.h> | |
10a24935 | 31 | #include "byte-order.h" |
3223e977 | 32 | #include "command-line.h" |
064af421 | 33 | #include "flow.h" |
0596e897 | 34 | #include "ofp-util.h" |
064af421 | 35 | #include "packets.h" |
b028db44 | 36 | #include "random.h" |
c0a56d9f | 37 | #include "unaligned.h" |
eadd1644 | 38 | #include "ovstest.h" |
064af421 BP |
39 | #undef NDEBUG |
40 | #include <assert.h> | |
41 | ||
3d91d909 JR |
42 | /* We need access to classifier internal definitions to be able to fully |
43 | * test them. The alternative would be to expose them all in the classifier | |
44 | * API. */ | |
45 | #include "classifier.c" | |
46 | ||
b5d97350 | 47 | /* Fields in a rule. */ |
d8ae4d67 | 48 | #define CLS_FIELDS \ |
296e07ac JG |
49 | /* struct flow all-caps */ \ |
50 | /* member name name */ \ | |
51 | /* ----------- -------- */ \ | |
52 | CLS_FIELD(tunnel.tun_id, TUN_ID) \ | |
53 | CLS_FIELD(metadata, METADATA) \ | |
54 | CLS_FIELD(nw_src, NW_SRC) \ | |
55 | CLS_FIELD(nw_dst, NW_DST) \ | |
56 | CLS_FIELD(in_port, IN_PORT) \ | |
57 | CLS_FIELD(vlan_tci, VLAN_TCI) \ | |
58 | CLS_FIELD(dl_type, DL_TYPE) \ | |
59 | CLS_FIELD(tp_src, TP_SRC) \ | |
60 | CLS_FIELD(tp_dst, TP_DST) \ | |
61 | CLS_FIELD(dl_src, DL_SRC) \ | |
62 | CLS_FIELD(dl_dst, DL_DST) \ | |
63 | CLS_FIELD(nw_proto, NW_PROTO) \ | |
64 | CLS_FIELD(nw_tos, NW_DSCP) | |
b5d97350 BP |
65 | |
66 | /* Field indexes. | |
67 | * | |
68 | * (These are also indexed into struct classifier's 'tables' array.) */ | |
69 | enum { | |
0bdc4bec | 70 | #define CLS_FIELD(MEMBER, NAME) CLS_F_IDX_##NAME, |
b5d97350 BP |
71 | CLS_FIELDS |
72 | #undef CLS_FIELD | |
73 | CLS_N_FIELDS | |
74 | }; | |
75 | ||
76 | /* Field information. */ | |
77 | struct cls_field { | |
78 | int ofs; /* Offset in struct flow. */ | |
79 | int len; /* Length in bytes. */ | |
b5d97350 BP |
80 | const char *name; /* Name (for debugging). */ |
81 | }; | |
82 | ||
83 | static const struct cls_field cls_fields[CLS_N_FIELDS] = { | |
0bdc4bec | 84 | #define CLS_FIELD(MEMBER, NAME) \ |
b5d97350 BP |
85 | { offsetof(struct flow, MEMBER), \ |
86 | sizeof ((struct flow *)0)->MEMBER, \ | |
b5d97350 BP |
87 | #NAME }, |
88 | CLS_FIELDS | |
89 | #undef CLS_FIELD | |
90 | }; | |
91 | ||
064af421 BP |
92 | struct test_rule { |
93 | int aux; /* Auxiliary data. */ | |
94 | struct cls_rule cls_rule; /* Classifier rule data. */ | |
95 | }; | |
96 | ||
97 | static struct test_rule * | |
98 | test_rule_from_cls_rule(const struct cls_rule *rule) | |
99 | { | |
100 | return rule ? CONTAINER_OF(rule, struct test_rule, cls_rule) : NULL; | |
101 | } | |
102 | ||
f2f3f5cb BP |
103 | static void |
104 | test_rule_destroy(struct test_rule *rule) | |
105 | { | |
106 | if (rule) { | |
107 | cls_rule_destroy(&rule->cls_rule); | |
108 | free(rule); | |
109 | } | |
110 | } | |
111 | ||
48d28ac1 BP |
112 | static struct test_rule *make_rule(int wc_fields, unsigned int priority, |
113 | int value_pat); | |
114 | static void free_rule(struct test_rule *); | |
115 | static struct test_rule *clone_rule(const struct test_rule *); | |
116 | ||
064af421 BP |
117 | /* Trivial (linear) classifier. */ |
118 | struct tcls { | |
119 | size_t n_rules; | |
120 | size_t allocated_rules; | |
121 | struct test_rule **rules; | |
122 | }; | |
123 | ||
124 | static void | |
125 | tcls_init(struct tcls *tcls) | |
126 | { | |
127 | tcls->n_rules = 0; | |
128 | tcls->allocated_rules = 0; | |
129 | tcls->rules = NULL; | |
130 | } | |
131 | ||
132 | static void | |
133 | tcls_destroy(struct tcls *tcls) | |
134 | { | |
135 | if (tcls) { | |
136 | size_t i; | |
137 | ||
138 | for (i = 0; i < tcls->n_rules; i++) { | |
f2f3f5cb | 139 | test_rule_destroy(tcls->rules[i]); |
064af421 BP |
140 | } |
141 | free(tcls->rules); | |
142 | } | |
143 | } | |
144 | ||
064af421 BP |
145 | static bool |
146 | tcls_is_empty(const struct tcls *tcls) | |
147 | { | |
148 | return tcls->n_rules == 0; | |
149 | } | |
150 | ||
151 | static struct test_rule * | |
152 | tcls_insert(struct tcls *tcls, const struct test_rule *rule) | |
153 | { | |
154 | size_t i; | |
155 | ||
064af421 BP |
156 | for (i = 0; i < tcls->n_rules; i++) { |
157 | const struct cls_rule *pos = &tcls->rules[i]->cls_rule; | |
193eb874 BP |
158 | if (cls_rule_equal(pos, &rule->cls_rule)) { |
159 | /* Exact match. */ | |
48d28ac1 BP |
160 | free_rule(tcls->rules[i]); |
161 | tcls->rules[i] = clone_rule(rule); | |
064af421 | 162 | return tcls->rules[i]; |
af7b73f4 | 163 | } else if (pos->priority < rule->cls_rule.priority) { |
064af421 BP |
164 | break; |
165 | } | |
166 | } | |
167 | ||
168 | if (tcls->n_rules >= tcls->allocated_rules) { | |
169 | tcls->rules = x2nrealloc(tcls->rules, &tcls->allocated_rules, | |
170 | sizeof *tcls->rules); | |
171 | } | |
172 | if (i != tcls->n_rules) { | |
173 | memmove(&tcls->rules[i + 1], &tcls->rules[i], | |
174 | sizeof *tcls->rules * (tcls->n_rules - i)); | |
175 | } | |
48d28ac1 | 176 | tcls->rules[i] = clone_rule(rule); |
064af421 BP |
177 | tcls->n_rules++; |
178 | return tcls->rules[i]; | |
179 | } | |
180 | ||
181 | static void | |
182 | tcls_remove(struct tcls *cls, const struct test_rule *rule) | |
183 | { | |
184 | size_t i; | |
185 | ||
186 | for (i = 0; i < cls->n_rules; i++) { | |
187 | struct test_rule *pos = cls->rules[i]; | |
188 | if (pos == rule) { | |
f2f3f5cb BP |
189 | test_rule_destroy(pos); |
190 | ||
064af421 BP |
191 | memmove(&cls->rules[i], &cls->rules[i + 1], |
192 | sizeof *cls->rules * (cls->n_rules - i - 1)); | |
f2f3f5cb | 193 | |
064af421 BP |
194 | cls->n_rules--; |
195 | return; | |
196 | } | |
197 | } | |
428b2edd | 198 | OVS_NOT_REACHED(); |
064af421 BP |
199 | } |
200 | ||
064af421 | 201 | static bool |
5cb7a798 | 202 | match(const struct cls_rule *wild_, const struct flow *fixed) |
064af421 | 203 | { |
5cb7a798 | 204 | struct match wild; |
064af421 BP |
205 | int f_idx; |
206 | ||
5cb7a798 | 207 | minimatch_expand(&wild_->match, &wild); |
064af421 | 208 | for (f_idx = 0; f_idx < CLS_N_FIELDS; f_idx++) { |
d8ae4d67 BP |
209 | bool eq; |
210 | ||
0bdc4bec | 211 | if (f_idx == CLS_F_IDX_NW_SRC) { |
5cb7a798 BP |
212 | eq = !((fixed->nw_src ^ wild.flow.nw_src) |
213 | & wild.wc.masks.nw_src); | |
d8ae4d67 | 214 | } else if (f_idx == CLS_F_IDX_NW_DST) { |
5cb7a798 BP |
215 | eq = !((fixed->nw_dst ^ wild.flow.nw_dst) |
216 | & wild.wc.masks.nw_dst); | |
73f33563 | 217 | } else if (f_idx == CLS_F_IDX_TP_SRC) { |
5cb7a798 BP |
218 | eq = !((fixed->tp_src ^ wild.flow.tp_src) |
219 | & wild.wc.masks.tp_src); | |
73f33563 | 220 | } else if (f_idx == CLS_F_IDX_TP_DST) { |
5cb7a798 BP |
221 | eq = !((fixed->tp_dst ^ wild.flow.tp_dst) |
222 | & wild.wc.masks.tp_dst); | |
73c0ce34 | 223 | } else if (f_idx == CLS_F_IDX_DL_SRC) { |
5cb7a798 BP |
224 | eq = eth_addr_equal_except(fixed->dl_src, wild.flow.dl_src, |
225 | wild.wc.masks.dl_src); | |
73c0ce34 | 226 | } else if (f_idx == CLS_F_IDX_DL_DST) { |
5cb7a798 BP |
227 | eq = eth_addr_equal_except(fixed->dl_dst, wild.flow.dl_dst, |
228 | wild.wc.masks.dl_dst); | |
66642cb4 | 229 | } else if (f_idx == CLS_F_IDX_VLAN_TCI) { |
5cb7a798 BP |
230 | eq = !((fixed->vlan_tci ^ wild.flow.vlan_tci) |
231 | & wild.wc.masks.vlan_tci); | |
8368c090 | 232 | } else if (f_idx == CLS_F_IDX_TUN_ID) { |
296e07ac JG |
233 | eq = !((fixed->tunnel.tun_id ^ wild.flow.tunnel.tun_id) |
234 | & wild.wc.masks.tunnel.tun_id); | |
7525e578 | 235 | } else if (f_idx == CLS_F_IDX_METADATA) { |
5cb7a798 BP |
236 | eq = !((fixed->metadata ^ wild.flow.metadata) |
237 | & wild.wc.masks.metadata); | |
2486e66a | 238 | } else if (f_idx == CLS_F_IDX_NW_DSCP) { |
5cb7a798 BP |
239 | eq = !((fixed->nw_tos ^ wild.flow.nw_tos) & |
240 | (wild.wc.masks.nw_tos & IP_DSCP_MASK)); | |
851d3105 | 241 | } else if (f_idx == CLS_F_IDX_NW_PROTO) { |
5cb7a798 BP |
242 | eq = !((fixed->nw_proto ^ wild.flow.nw_proto) |
243 | & wild.wc.masks.nw_proto); | |
e2170cff | 244 | } else if (f_idx == CLS_F_IDX_DL_TYPE) { |
5cb7a798 BP |
245 | eq = !((fixed->dl_type ^ wild.flow.dl_type) |
246 | & wild.wc.masks.dl_type); | |
0bdc4bec | 247 | } else if (f_idx == CLS_F_IDX_IN_PORT) { |
4e022ec0 AW |
248 | eq = !((fixed->in_port.ofp_port |
249 | ^ wild.flow.in_port.ofp_port) | |
250 | & wild.wc.masks.in_port.ofp_port); | |
d8ae4d67 | 251 | } else { |
428b2edd | 252 | OVS_NOT_REACHED(); |
064af421 BP |
253 | } |
254 | ||
d8ae4d67 BP |
255 | if (!eq) { |
256 | return false; | |
064af421 | 257 | } |
064af421 BP |
258 | } |
259 | return true; | |
260 | } | |
261 | ||
262 | static struct cls_rule * | |
3c4486a5 | 263 | tcls_lookup(const struct tcls *cls, const struct flow *flow) |
064af421 BP |
264 | { |
265 | size_t i; | |
266 | ||
267 | for (i = 0; i < cls->n_rules; i++) { | |
268 | struct test_rule *pos = cls->rules[i]; | |
3c4486a5 | 269 | if (match(&pos->cls_rule, flow)) { |
064af421 BP |
270 | return &pos->cls_rule; |
271 | } | |
272 | } | |
273 | return NULL; | |
274 | } | |
275 | ||
276 | static void | |
3c4486a5 | 277 | tcls_delete_matches(struct tcls *cls, const struct cls_rule *target) |
064af421 BP |
278 | { |
279 | size_t i; | |
280 | ||
281 | for (i = 0; i < cls->n_rules; ) { | |
282 | struct test_rule *pos = cls->rules[i]; | |
5cb7a798 BP |
283 | if (!minimask_has_extra(&pos->cls_rule.match.mask, |
284 | &target->match.mask)) { | |
285 | struct flow flow; | |
286 | ||
287 | miniflow_expand(&pos->cls_rule.match.flow, &flow); | |
288 | if (match(target, &flow)) { | |
289 | tcls_remove(cls, pos); | |
290 | continue; | |
291 | } | |
064af421 | 292 | } |
5cb7a798 | 293 | i++; |
064af421 BP |
294 | } |
295 | } | |
296 | \f | |
3c4486a5 | 297 | static ovs_be32 nw_src_values[] = { CONSTANT_HTONL(0xc0a80001), |
965f03d8 | 298 | CONSTANT_HTONL(0xc0a04455) }; |
3c4486a5 | 299 | static ovs_be32 nw_dst_values[] = { CONSTANT_HTONL(0xc0a80002), |
965f03d8 | 300 | CONSTANT_HTONL(0xc0a04455) }; |
b9298d3f BP |
301 | static ovs_be64 tun_id_values[] = { |
302 | 0, | |
303 | CONSTANT_HTONLL(UINT64_C(0xfedcba9876543210)) }; | |
7525e578 JS |
304 | static ovs_be64 metadata_values[] = { |
305 | 0, | |
306 | CONSTANT_HTONLL(UINT64_C(0xfedcba9876543210)) }; | |
4e022ec0 | 307 | static ofp_port_t in_port_values[] = { OFP_PORT_C(1), OFPP_LOCAL }; |
66642cb4 | 308 | static ovs_be16 vlan_tci_values[] = { CONSTANT_HTONS(101), CONSTANT_HTONS(0) }; |
3c4486a5 | 309 | static ovs_be16 dl_type_values[] |
965f03d8 | 310 | = { CONSTANT_HTONS(ETH_TYPE_IP), CONSTANT_HTONS(ETH_TYPE_ARP) }; |
3c4486a5 | 311 | static ovs_be16 tp_src_values[] = { CONSTANT_HTONS(49362), |
965f03d8 | 312 | CONSTANT_HTONS(80) }; |
3c4486a5 | 313 | static ovs_be16 tp_dst_values[] = { CONSTANT_HTONS(6667), CONSTANT_HTONS(22) }; |
064af421 BP |
314 | static uint8_t dl_src_values[][6] = { { 0x00, 0x02, 0xe3, 0x0f, 0x80, 0xa4 }, |
315 | { 0x5e, 0x33, 0x7f, 0x5f, 0x1e, 0x99 } }; | |
316 | static uint8_t dl_dst_values[][6] = { { 0x4a, 0x27, 0x71, 0xae, 0x64, 0xc1 }, | |
317 | { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } }; | |
6767a2cc | 318 | static uint8_t nw_proto_values[] = { IPPROTO_TCP, IPPROTO_ICMP }; |
2486e66a | 319 | static uint8_t nw_dscp_values[] = { 48, 0 }; |
064af421 BP |
320 | |
321 | static void *values[CLS_N_FIELDS][2]; | |
322 | ||
323 | static void | |
324 | init_values(void) | |
325 | { | |
659586ef JG |
326 | values[CLS_F_IDX_TUN_ID][0] = &tun_id_values[0]; |
327 | values[CLS_F_IDX_TUN_ID][1] = &tun_id_values[1]; | |
328 | ||
7525e578 JS |
329 | values[CLS_F_IDX_METADATA][0] = &metadata_values[0]; |
330 | values[CLS_F_IDX_METADATA][1] = &metadata_values[1]; | |
331 | ||
064af421 BP |
332 | values[CLS_F_IDX_IN_PORT][0] = &in_port_values[0]; |
333 | values[CLS_F_IDX_IN_PORT][1] = &in_port_values[1]; | |
334 | ||
66642cb4 BP |
335 | values[CLS_F_IDX_VLAN_TCI][0] = &vlan_tci_values[0]; |
336 | values[CLS_F_IDX_VLAN_TCI][1] = &vlan_tci_values[1]; | |
959a2ecd | 337 | |
064af421 BP |
338 | values[CLS_F_IDX_DL_SRC][0] = dl_src_values[0]; |
339 | values[CLS_F_IDX_DL_SRC][1] = dl_src_values[1]; | |
340 | ||
341 | values[CLS_F_IDX_DL_DST][0] = dl_dst_values[0]; | |
342 | values[CLS_F_IDX_DL_DST][1] = dl_dst_values[1]; | |
343 | ||
344 | values[CLS_F_IDX_DL_TYPE][0] = &dl_type_values[0]; | |
345 | values[CLS_F_IDX_DL_TYPE][1] = &dl_type_values[1]; | |
346 | ||
347 | values[CLS_F_IDX_NW_SRC][0] = &nw_src_values[0]; | |
348 | values[CLS_F_IDX_NW_SRC][1] = &nw_src_values[1]; | |
349 | ||
350 | values[CLS_F_IDX_NW_DST][0] = &nw_dst_values[0]; | |
351 | values[CLS_F_IDX_NW_DST][1] = &nw_dst_values[1]; | |
352 | ||
353 | values[CLS_F_IDX_NW_PROTO][0] = &nw_proto_values[0]; | |
354 | values[CLS_F_IDX_NW_PROTO][1] = &nw_proto_values[1]; | |
355 | ||
2486e66a JP |
356 | values[CLS_F_IDX_NW_DSCP][0] = &nw_dscp_values[0]; |
357 | values[CLS_F_IDX_NW_DSCP][1] = &nw_dscp_values[1]; | |
834377ea | 358 | |
064af421 BP |
359 | values[CLS_F_IDX_TP_SRC][0] = &tp_src_values[0]; |
360 | values[CLS_F_IDX_TP_SRC][1] = &tp_src_values[1]; | |
361 | ||
362 | values[CLS_F_IDX_TP_DST][0] = &tp_dst_values[0]; | |
363 | values[CLS_F_IDX_TP_DST][1] = &tp_dst_values[1]; | |
364 | } | |
365 | ||
366 | #define N_NW_SRC_VALUES ARRAY_SIZE(nw_src_values) | |
367 | #define N_NW_DST_VALUES ARRAY_SIZE(nw_dst_values) | |
659586ef | 368 | #define N_TUN_ID_VALUES ARRAY_SIZE(tun_id_values) |
7525e578 | 369 | #define N_METADATA_VALUES ARRAY_SIZE(metadata_values) |
064af421 | 370 | #define N_IN_PORT_VALUES ARRAY_SIZE(in_port_values) |
66642cb4 | 371 | #define N_VLAN_TCI_VALUES ARRAY_SIZE(vlan_tci_values) |
064af421 BP |
372 | #define N_DL_TYPE_VALUES ARRAY_SIZE(dl_type_values) |
373 | #define N_TP_SRC_VALUES ARRAY_SIZE(tp_src_values) | |
374 | #define N_TP_DST_VALUES ARRAY_SIZE(tp_dst_values) | |
375 | #define N_DL_SRC_VALUES ARRAY_SIZE(dl_src_values) | |
376 | #define N_DL_DST_VALUES ARRAY_SIZE(dl_dst_values) | |
377 | #define N_NW_PROTO_VALUES ARRAY_SIZE(nw_proto_values) | |
2486e66a | 378 | #define N_NW_DSCP_VALUES ARRAY_SIZE(nw_dscp_values) |
064af421 BP |
379 | |
380 | #define N_FLOW_VALUES (N_NW_SRC_VALUES * \ | |
381 | N_NW_DST_VALUES * \ | |
659586ef | 382 | N_TUN_ID_VALUES * \ |
064af421 | 383 | N_IN_PORT_VALUES * \ |
66642cb4 | 384 | N_VLAN_TCI_VALUES * \ |
064af421 BP |
385 | N_DL_TYPE_VALUES * \ |
386 | N_TP_SRC_VALUES * \ | |
387 | N_TP_DST_VALUES * \ | |
388 | N_DL_SRC_VALUES * \ | |
389 | N_DL_DST_VALUES * \ | |
834377ea | 390 | N_NW_PROTO_VALUES * \ |
2486e66a | 391 | N_NW_DSCP_VALUES) |
064af421 BP |
392 | |
393 | static unsigned int | |
394 | get_value(unsigned int *x, unsigned n_values) | |
395 | { | |
396 | unsigned int rem = *x % n_values; | |
397 | *x /= n_values; | |
398 | return rem; | |
399 | } | |
400 | ||
064af421 BP |
401 | static void |
402 | compare_classifiers(struct classifier *cls, struct tcls *tcls) | |
0b4f2078 | 403 | OVS_REQ_RDLOCK(cls->rwlock) |
064af421 | 404 | { |
70d3fbe7 | 405 | static const int confidence = 500; |
064af421 BP |
406 | unsigned int i; |
407 | ||
408 | assert(classifier_count(cls) == tcls->n_rules); | |
70d3fbe7 | 409 | for (i = 0; i < confidence; i++) { |
476f36e8 | 410 | struct cls_rule *cr0, *cr1, *cr2; |
ae412e7d | 411 | struct flow flow; |
476f36e8 | 412 | struct flow_wildcards wc; |
064af421 | 413 | unsigned int x; |
064af421 | 414 | |
476f36e8 | 415 | flow_wildcards_init_catchall(&wc); |
b028db44 | 416 | x = random_range(N_FLOW_VALUES); |
51c14ddd | 417 | memset(&flow, 0, sizeof flow); |
064af421 BP |
418 | flow.nw_src = nw_src_values[get_value(&x, N_NW_SRC_VALUES)]; |
419 | flow.nw_dst = nw_dst_values[get_value(&x, N_NW_DST_VALUES)]; | |
296e07ac | 420 | flow.tunnel.tun_id = tun_id_values[get_value(&x, N_TUN_ID_VALUES)]; |
7525e578 | 421 | flow.metadata = metadata_values[get_value(&x, N_METADATA_VALUES)]; |
4e022ec0 AW |
422 | flow.in_port.ofp_port = in_port_values[get_value(&x, |
423 | N_IN_PORT_VALUES)]; | |
66642cb4 | 424 | flow.vlan_tci = vlan_tci_values[get_value(&x, N_VLAN_TCI_VALUES)]; |
064af421 BP |
425 | flow.dl_type = dl_type_values[get_value(&x, N_DL_TYPE_VALUES)]; |
426 | flow.tp_src = tp_src_values[get_value(&x, N_TP_SRC_VALUES)]; | |
427 | flow.tp_dst = tp_dst_values[get_value(&x, N_TP_DST_VALUES)]; | |
428 | memcpy(flow.dl_src, dl_src_values[get_value(&x, N_DL_SRC_VALUES)], | |
429 | ETH_ADDR_LEN); | |
430 | memcpy(flow.dl_dst, dl_dst_values[get_value(&x, N_DL_DST_VALUES)], | |
431 | ETH_ADDR_LEN); | |
432 | flow.nw_proto = nw_proto_values[get_value(&x, N_NW_PROTO_VALUES)]; | |
2486e66a | 433 | flow.nw_tos = nw_dscp_values[get_value(&x, N_NW_DSCP_VALUES)]; |
064af421 | 434 | |
c424c2f7 DDP |
435 | /* This assertion is here to suppress a GCC 4.9 array-bounds warning */ |
436 | ovs_assert(cls->cls->n_tries <= CLS_MAX_TRIES); | |
437 | ||
476f36e8 | 438 | cr0 = classifier_lookup(cls, &flow, &wc); |
3c4486a5 BP |
439 | cr1 = tcls_lookup(tcls, &flow); |
440 | assert((cr0 == NULL) == (cr1 == NULL)); | |
441 | if (cr0 != NULL) { | |
442 | const struct test_rule *tr0 = test_rule_from_cls_rule(cr0); | |
443 | const struct test_rule *tr1 = test_rule_from_cls_rule(cr1); | |
444 | ||
193eb874 | 445 | assert(cls_rule_equal(cr0, cr1)); |
3c4486a5 | 446 | assert(tr0->aux == tr1->aux); |
064af421 | 447 | } |
476f36e8 JR |
448 | cr2 = classifier_lookup(cls, &flow, NULL); |
449 | assert(cr2 == cr0); | |
064af421 BP |
450 | } |
451 | } | |
452 | ||
064af421 BP |
453 | static void |
454 | destroy_classifier(struct classifier *cls) | |
455 | { | |
5ecc9d81 | 456 | struct test_rule *rule, *next_rule; |
5ecc9d81 | 457 | |
5f0476ce JR |
458 | CLS_FOR_EACH_SAFE (rule, next_rule, cls_rule, cls) { |
459 | fat_rwlock_wrlock(&cls->rwlock); | |
5ecc9d81 | 460 | classifier_remove(cls, &rule->cls_rule); |
5f0476ce | 461 | fat_rwlock_unlock(&cls->rwlock); |
48d28ac1 | 462 | free_rule(rule); |
5ecc9d81 | 463 | } |
064af421 BP |
464 | classifier_destroy(cls); |
465 | } | |
466 | ||
fe7cfa5c JR |
467 | static void |
468 | pvector_verify(struct pvector *pvec) | |
469 | { | |
470 | void *ptr OVS_UNUSED; | |
471 | unsigned int priority, prev_priority = UINT_MAX; | |
472 | ||
473 | PVECTOR_FOR_EACH (ptr, pvec) { | |
474 | priority = cursor__.vector[cursor__.entry_idx].priority; | |
475 | if (priority > prev_priority) { | |
476 | VLOG_ABORT("Priority vector is out of order (%u > %u)", | |
477 | priority, prev_priority); | |
478 | } | |
479 | prev_priority = priority; | |
480 | } | |
481 | } | |
482 | ||
064af421 | 483 | static void |
0b4f2078 | 484 | check_tables(const struct classifier *cls, int n_tables, int n_rules, |
5f0476ce | 485 | int n_dups) OVS_EXCLUDED(cls->rwlock) |
064af421 | 486 | { |
03868246 | 487 | const struct cls_subtable *table; |
955f579d | 488 | struct test_rule *test_rule; |
064af421 | 489 | int found_tables = 0; |
064af421 | 490 | int found_rules = 0; |
b5d97350 | 491 | int found_dups = 0; |
955f579d | 492 | int found_rules2 = 0; |
064af421 | 493 | |
fe7cfa5c JR |
494 | pvector_verify(&cls->cls->subtables); |
495 | ||
f2c21402 | 496 | CMAP_FOR_EACH (table, cmap_node, &cls->cls->subtables_map) { |
627fb667 | 497 | const struct cls_match *head; |
4d935a6b JR |
498 | unsigned int max_priority = 0; |
499 | unsigned int max_count = 0; | |
fe7cfa5c JR |
500 | bool found = false; |
501 | const struct cls_subtable *iter; | |
502 | ||
503 | /* Locate the subtable from 'subtables'. */ | |
504 | PVECTOR_FOR_EACH (iter, &cls->cls->subtables) { | |
505 | if (iter == table) { | |
506 | if (found) { | |
507 | VLOG_ABORT("Subtable %p duplicated in 'subtables'.", | |
508 | table); | |
509 | } | |
510 | found = true; | |
511 | } | |
512 | } | |
513 | if (!found) { | |
514 | VLOG_ABORT("Subtable %p not found from 'subtables'.", table); | |
515 | } | |
b5d97350 | 516 | |
f2c21402 | 517 | assert(!cmap_is_empty(&table->rules)); |
064af421 | 518 | |
064af421 | 519 | found_tables++; |
f2c21402 | 520 | CMAP_FOR_EACH (head, cmap_node, &table->rules) { |
b5d97350 | 521 | unsigned int prev_priority = UINT_MAX; |
627fb667 | 522 | const struct cls_match *rule; |
b5d97350 | 523 | |
4d935a6b JR |
524 | if (head->priority > max_priority) { |
525 | max_priority = head->priority; | |
526 | max_count = 1; | |
527 | } else if (head->priority == max_priority) { | |
528 | ++max_count; | |
529 | } | |
530 | ||
b5d97350 BP |
531 | found_rules++; |
532 | LIST_FOR_EACH (rule, list, &head->list) { | |
533 | assert(rule->priority < prev_priority); | |
4d935a6b JR |
534 | assert(rule->priority <= table->max_priority); |
535 | ||
b5d97350 BP |
536 | prev_priority = rule->priority; |
537 | found_rules++; | |
538 | found_dups++; | |
5f0476ce | 539 | fat_rwlock_rdlock(&cls->rwlock); |
627fb667 JR |
540 | assert(classifier_find_rule_exactly(cls, rule->cls_rule) |
541 | == rule->cls_rule); | |
5f0476ce | 542 | fat_rwlock_unlock(&cls->rwlock); |
b5d97350 BP |
543 | } |
544 | } | |
4d935a6b JR |
545 | assert(table->max_priority == max_priority); |
546 | assert(table->max_count == max_count); | |
064af421 BP |
547 | } |
548 | ||
f2c21402 | 549 | assert(found_tables == cmap_count(&cls->cls->subtables_map)); |
fe7cfa5c | 550 | assert(found_tables == pvector_count(&cls->cls->subtables)); |
f2c21402 | 551 | assert(n_tables == -1 || n_tables == cmap_count(&cls->cls->subtables_map)); |
064af421 | 552 | assert(n_rules == -1 || found_rules == n_rules); |
b5d97350 | 553 | assert(n_dups == -1 || found_dups == n_dups); |
955f579d | 554 | |
5f0476ce | 555 | CLS_FOR_EACH (test_rule, cls_rule, cls) { |
955f579d BP |
556 | found_rules2++; |
557 | } | |
558 | assert(found_rules == found_rules2); | |
064af421 BP |
559 | } |
560 | ||
561 | static struct test_rule * | |
562 | make_rule(int wc_fields, unsigned int priority, int value_pat) | |
563 | { | |
564 | const struct cls_field *f; | |
565 | struct test_rule *rule; | |
81a76618 | 566 | struct match match; |
064af421 | 567 | |
81a76618 | 568 | match_init_catchall(&match); |
064af421 BP |
569 | for (f = &cls_fields[0]; f < &cls_fields[CLS_N_FIELDS]; f++) { |
570 | int f_idx = f - cls_fields; | |
d8ae4d67 | 571 | int value_idx = (value_pat & (1u << f_idx)) != 0; |
81a76618 | 572 | memcpy((char *) &match.flow + f->ofs, |
d8ae4d67 BP |
573 | values[f_idx][value_idx], f->len); |
574 | ||
0bdc4bec | 575 | if (f_idx == CLS_F_IDX_NW_SRC) { |
b8266395 | 576 | match.wc.masks.nw_src = OVS_BE32_MAX; |
d8ae4d67 | 577 | } else if (f_idx == CLS_F_IDX_NW_DST) { |
b8266395 | 578 | match.wc.masks.nw_dst = OVS_BE32_MAX; |
73f33563 | 579 | } else if (f_idx == CLS_F_IDX_TP_SRC) { |
b8266395 | 580 | match.wc.masks.tp_src = OVS_BE16_MAX; |
73f33563 | 581 | } else if (f_idx == CLS_F_IDX_TP_DST) { |
b8266395 | 582 | match.wc.masks.tp_dst = OVS_BE16_MAX; |
73c0ce34 | 583 | } else if (f_idx == CLS_F_IDX_DL_SRC) { |
81a76618 | 584 | memset(match.wc.masks.dl_src, 0xff, ETH_ADDR_LEN); |
73c0ce34 | 585 | } else if (f_idx == CLS_F_IDX_DL_DST) { |
81a76618 | 586 | memset(match.wc.masks.dl_dst, 0xff, ETH_ADDR_LEN); |
66642cb4 | 587 | } else if (f_idx == CLS_F_IDX_VLAN_TCI) { |
b8266395 | 588 | match.wc.masks.vlan_tci = OVS_BE16_MAX; |
8368c090 | 589 | } else if (f_idx == CLS_F_IDX_TUN_ID) { |
b8266395 | 590 | match.wc.masks.tunnel.tun_id = OVS_BE64_MAX; |
7525e578 | 591 | } else if (f_idx == CLS_F_IDX_METADATA) { |
b8266395 | 592 | match.wc.masks.metadata = OVS_BE64_MAX; |
5d9499c4 | 593 | } else if (f_idx == CLS_F_IDX_NW_DSCP) { |
81a76618 | 594 | match.wc.masks.nw_tos |= IP_DSCP_MASK; |
851d3105 | 595 | } else if (f_idx == CLS_F_IDX_NW_PROTO) { |
81a76618 | 596 | match.wc.masks.nw_proto = UINT8_MAX; |
e2170cff | 597 | } else if (f_idx == CLS_F_IDX_DL_TYPE) { |
b8266395 | 598 | match.wc.masks.dl_type = OVS_BE16_MAX; |
0bdc4bec | 599 | } else if (f_idx == CLS_F_IDX_IN_PORT) { |
e2711da9 | 600 | match.wc.masks.in_port.ofp_port = u16_to_ofp(UINT16_MAX); |
064af421 | 601 | } else { |
428b2edd | 602 | OVS_NOT_REACHED(); |
064af421 BP |
603 | } |
604 | } | |
81a76618 BP |
605 | |
606 | rule = xzalloc(sizeof *rule); | |
607 | cls_rule_init(&rule->cls_rule, &match, wc_fields ? priority : UINT_MAX); | |
064af421 BP |
608 | return rule; |
609 | } | |
610 | ||
48d28ac1 BP |
611 | static struct test_rule * |
612 | clone_rule(const struct test_rule *src) | |
613 | { | |
614 | struct test_rule *dst; | |
615 | ||
616 | dst = xmalloc(sizeof *dst); | |
617 | dst->aux = src->aux; | |
618 | cls_rule_clone(&dst->cls_rule, &src->cls_rule); | |
619 | return dst; | |
620 | } | |
621 | ||
622 | static void | |
623 | free_rule(struct test_rule *rule) | |
624 | { | |
625 | cls_rule_destroy(&rule->cls_rule); | |
626 | free(rule); | |
627 | } | |
628 | ||
064af421 BP |
629 | static void |
630 | shuffle(unsigned int *p, size_t n) | |
631 | { | |
632 | for (; n > 1; n--, p++) { | |
b028db44 | 633 | unsigned int *q = &p[random_range(n)]; |
064af421 BP |
634 | unsigned int tmp = *p; |
635 | *p = *q; | |
636 | *q = tmp; | |
637 | } | |
638 | } | |
5cb7a798 BP |
639 | |
640 | static void | |
641 | shuffle_u32s(uint32_t *p, size_t n) | |
642 | { | |
643 | for (; n > 1; n--, p++) { | |
b028db44 | 644 | uint32_t *q = &p[random_range(n)]; |
5cb7a798 BP |
645 | uint32_t tmp = *p; |
646 | *p = *q; | |
647 | *q = tmp; | |
648 | } | |
649 | } | |
064af421 | 650 | \f |
5cb7a798 BP |
651 | /* Classifier tests. */ |
652 | ||
13751fd8 JR |
653 | static enum mf_field_id trie_fields[2] = { |
654 | MFF_IPV4_DST, MFF_IPV4_SRC | |
655 | }; | |
656 | ||
064af421 BP |
657 | /* Tests an empty classifier. */ |
658 | static void | |
3223e977 | 659 | test_empty(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
064af421 BP |
660 | { |
661 | struct classifier cls; | |
662 | struct tcls tcls; | |
663 | ||
476f36e8 | 664 | classifier_init(&cls, flow_segment_u32s); |
06f81620 | 665 | fat_rwlock_wrlock(&cls.rwlock); |
13751fd8 | 666 | classifier_set_prefix_fields(&cls, trie_fields, ARRAY_SIZE(trie_fields)); |
064af421 BP |
667 | tcls_init(&tcls); |
668 | assert(classifier_is_empty(&cls)); | |
669 | assert(tcls_is_empty(&tcls)); | |
670 | compare_classifiers(&cls, &tcls); | |
06f81620 | 671 | fat_rwlock_unlock(&cls.rwlock); |
064af421 BP |
672 | classifier_destroy(&cls); |
673 | tcls_destroy(&tcls); | |
674 | } | |
675 | ||
676 | /* Destroys a null classifier. */ | |
677 | static void | |
3223e977 | 678 | test_destroy_null(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
064af421 BP |
679 | { |
680 | classifier_destroy(NULL); | |
681 | } | |
682 | ||
683 | /* Tests classification with one rule at a time. */ | |
684 | static void | |
3223e977 | 685 | test_single_rule(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
064af421 BP |
686 | { |
687 | unsigned int wc_fields; /* Hilarious. */ | |
688 | ||
689 | for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) { | |
690 | struct classifier cls; | |
691 | struct test_rule *rule, *tcls_rule; | |
692 | struct tcls tcls; | |
693 | ||
694 | rule = make_rule(wc_fields, | |
695 | hash_bytes(&wc_fields, sizeof wc_fields, 0), 0); | |
696 | ||
476f36e8 | 697 | classifier_init(&cls, flow_segment_u32s); |
06f81620 | 698 | fat_rwlock_wrlock(&cls.rwlock); |
13751fd8 JR |
699 | classifier_set_prefix_fields(&cls, trie_fields, |
700 | ARRAY_SIZE(trie_fields)); | |
064af421 BP |
701 | tcls_init(&tcls); |
702 | ||
703 | tcls_rule = tcls_insert(&tcls, rule); | |
08944c1d | 704 | classifier_insert(&cls, &rule->cls_rule); |
064af421 | 705 | compare_classifiers(&cls, &tcls); |
5f0476ce JR |
706 | fat_rwlock_unlock(&cls.rwlock); |
707 | check_tables(&cls, 1, 1, 0); | |
064af421 | 708 | |
5f0476ce | 709 | fat_rwlock_wrlock(&cls.rwlock); |
064af421 BP |
710 | classifier_remove(&cls, &rule->cls_rule); |
711 | tcls_remove(&tcls, tcls_rule); | |
712 | assert(classifier_is_empty(&cls)); | |
713 | assert(tcls_is_empty(&tcls)); | |
714 | compare_classifiers(&cls, &tcls); | |
5f0476ce | 715 | fat_rwlock_unlock(&cls.rwlock); |
064af421 | 716 | |
48d28ac1 | 717 | free_rule(rule); |
064af421 BP |
718 | classifier_destroy(&cls); |
719 | tcls_destroy(&tcls); | |
720 | } | |
721 | } | |
722 | ||
723 | /* Tests replacing one rule by another. */ | |
724 | static void | |
3223e977 | 725 | test_rule_replacement(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
064af421 BP |
726 | { |
727 | unsigned int wc_fields; | |
728 | ||
729 | for (wc_fields = 0; wc_fields < (1u << CLS_N_FIELDS); wc_fields++) { | |
730 | struct classifier cls; | |
f0f4410a BP |
731 | struct test_rule *rule1; |
732 | struct test_rule *rule2; | |
064af421 BP |
733 | struct tcls tcls; |
734 | ||
735 | rule1 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX); | |
736 | rule2 = make_rule(wc_fields, OFP_DEFAULT_PRIORITY, UINT_MAX); | |
737 | rule2->aux += 5; | |
738 | rule2->aux += 5; | |
739 | ||
476f36e8 | 740 | classifier_init(&cls, flow_segment_u32s); |
06f81620 | 741 | fat_rwlock_wrlock(&cls.rwlock); |
13751fd8 JR |
742 | classifier_set_prefix_fields(&cls, trie_fields, |
743 | ARRAY_SIZE(trie_fields)); | |
064af421 | 744 | tcls_init(&tcls); |
f0f4410a | 745 | tcls_insert(&tcls, rule1); |
08944c1d | 746 | classifier_insert(&cls, &rule1->cls_rule); |
064af421 | 747 | compare_classifiers(&cls, &tcls); |
5f0476ce JR |
748 | fat_rwlock_unlock(&cls.rwlock); |
749 | check_tables(&cls, 1, 1, 0); | |
064af421 BP |
750 | tcls_destroy(&tcls); |
751 | ||
752 | tcls_init(&tcls); | |
f0f4410a | 753 | tcls_insert(&tcls, rule2); |
5f0476ce JR |
754 | |
755 | fat_rwlock_wrlock(&cls.rwlock); | |
064af421 | 756 | assert(test_rule_from_cls_rule( |
08944c1d | 757 | classifier_replace(&cls, &rule2->cls_rule)) == rule1); |
48d28ac1 | 758 | free_rule(rule1); |
064af421 | 759 | compare_classifiers(&cls, &tcls); |
06f81620 | 760 | fat_rwlock_unlock(&cls.rwlock); |
5f0476ce JR |
761 | check_tables(&cls, 1, 1, 0); |
762 | ||
763 | tcls_destroy(&tcls); | |
064af421 BP |
764 | destroy_classifier(&cls); |
765 | } | |
766 | } | |
767 | ||
768 | static int | |
b5d97350 | 769 | factorial(int n_items) |
064af421 | 770 | { |
b5d97350 BP |
771 | int n, i; |
772 | ||
773 | n = 1; | |
774 | for (i = 2; i <= n_items; i++) { | |
775 | n *= i; | |
776 | } | |
777 | return n; | |
064af421 BP |
778 | } |
779 | ||
b5d97350 BP |
780 | static void |
781 | swap(int *a, int *b) | |
064af421 | 782 | { |
b5d97350 BP |
783 | int tmp = *a; |
784 | *a = *b; | |
785 | *b = tmp; | |
064af421 BP |
786 | } |
787 | ||
064af421 | 788 | static void |
b5d97350 | 789 | reverse(int *a, int n) |
064af421 | 790 | { |
b5d97350 | 791 | int i; |
064af421 | 792 | |
b5d97350 BP |
793 | for (i = 0; i < n / 2; i++) { |
794 | int j = n - (i + 1); | |
795 | swap(&a[i], &a[j]); | |
796 | } | |
797 | } | |
064af421 | 798 | |
b5d97350 BP |
799 | static bool |
800 | next_permutation(int *a, int n) | |
801 | { | |
802 | int k; | |
064af421 | 803 | |
b5d97350 BP |
804 | for (k = n - 2; k >= 0; k--) { |
805 | if (a[k] < a[k + 1]) { | |
806 | int l; | |
064af421 | 807 | |
b5d97350 BP |
808 | for (l = n - 1; ; l--) { |
809 | if (a[l] > a[k]) { | |
810 | swap(&a[k], &a[l]); | |
811 | reverse(a + (k + 1), n - (k + 1)); | |
812 | return true; | |
064af421 BP |
813 | } |
814 | } | |
815 | } | |
816 | } | |
b5d97350 | 817 | return false; |
064af421 BP |
818 | } |
819 | ||
b5d97350 | 820 | /* Tests classification with rules that have the same matching criteria. */ |
064af421 | 821 | static void |
b5d97350 BP |
822 | test_many_rules_in_one_list (int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
823 | { | |
824 | enum { N_RULES = 3 }; | |
825 | int n_pris; | |
064af421 | 826 | |
b5d97350 BP |
827 | for (n_pris = N_RULES; n_pris >= 1; n_pris--) { |
828 | int ops[N_RULES * 2]; | |
829 | int pris[N_RULES]; | |
830 | int n_permutations; | |
831 | int i; | |
064af421 | 832 | |
b5d97350 BP |
833 | pris[0] = 0; |
834 | for (i = 1; i < N_RULES; i++) { | |
835 | pris[i] = pris[i - 1] + (n_pris > i); | |
836 | } | |
064af421 | 837 | |
b5d97350 BP |
838 | for (i = 0; i < N_RULES * 2; i++) { |
839 | ops[i] = i / 2; | |
064af421 | 840 | } |
064af421 | 841 | |
b5d97350 BP |
842 | n_permutations = 0; |
843 | do { | |
844 | struct test_rule *rules[N_RULES]; | |
845 | struct test_rule *tcls_rules[N_RULES]; | |
846 | int pri_rules[N_RULES]; | |
847 | struct classifier cls; | |
848 | struct tcls tcls; | |
064af421 | 849 | |
b5d97350 | 850 | n_permutations++; |
064af421 | 851 | |
b5d97350 BP |
852 | for (i = 0; i < N_RULES; i++) { |
853 | rules[i] = make_rule(456, pris[i], 0); | |
854 | tcls_rules[i] = NULL; | |
855 | pri_rules[i] = -1; | |
856 | } | |
064af421 | 857 | |
476f36e8 | 858 | classifier_init(&cls, flow_segment_u32s); |
06f81620 | 859 | fat_rwlock_wrlock(&cls.rwlock); |
13751fd8 JR |
860 | classifier_set_prefix_fields(&cls, trie_fields, |
861 | ARRAY_SIZE(trie_fields)); | |
5f0476ce | 862 | fat_rwlock_unlock(&cls.rwlock); |
b5d97350 | 863 | tcls_init(&tcls); |
064af421 | 864 | |
b5d97350 BP |
865 | for (i = 0; i < ARRAY_SIZE(ops); i++) { |
866 | int j = ops[i]; | |
867 | int m, n; | |
064af421 | 868 | |
5f0476ce | 869 | fat_rwlock_wrlock(&cls.rwlock); |
b5d97350 BP |
870 | if (!tcls_rules[j]) { |
871 | struct test_rule *displaced_rule; | |
872 | ||
873 | tcls_rules[j] = tcls_insert(&tcls, rules[j]); | |
874 | displaced_rule = test_rule_from_cls_rule( | |
08944c1d | 875 | classifier_replace(&cls, &rules[j]->cls_rule)); |
b5d97350 BP |
876 | if (pri_rules[pris[j]] >= 0) { |
877 | int k = pri_rules[pris[j]]; | |
878 | assert(displaced_rule != NULL); | |
879 | assert(displaced_rule != rules[j]); | |
880 | assert(pris[j] == displaced_rule->cls_rule.priority); | |
881 | tcls_rules[k] = NULL; | |
882 | } else { | |
883 | assert(displaced_rule == NULL); | |
884 | } | |
885 | pri_rules[pris[j]] = j; | |
886 | } else { | |
887 | classifier_remove(&cls, &rules[j]->cls_rule); | |
888 | tcls_remove(&tcls, tcls_rules[j]); | |
889 | tcls_rules[j] = NULL; | |
890 | pri_rules[pris[j]] = -1; | |
891 | } | |
5f0476ce JR |
892 | compare_classifiers(&cls, &tcls); |
893 | fat_rwlock_unlock(&cls.rwlock); | |
064af421 | 894 | |
b5d97350 BP |
895 | n = 0; |
896 | for (m = 0; m < N_RULES; m++) { | |
897 | n += tcls_rules[m] != NULL; | |
064af421 | 898 | } |
b5d97350 | 899 | check_tables(&cls, n > 0, n, n - 1); |
064af421 | 900 | } |
b5d97350 | 901 | |
5f0476ce | 902 | fat_rwlock_wrlock(&cls.rwlock); |
b5d97350 | 903 | for (i = 0; i < N_RULES; i++) { |
627fb667 JR |
904 | if (rules[i]->cls_rule.cls_match) { |
905 | classifier_remove(&cls, &rules[i]->cls_rule); | |
906 | } | |
48d28ac1 | 907 | free_rule(rules[i]); |
b5d97350 | 908 | } |
627fb667 JR |
909 | fat_rwlock_unlock(&cls.rwlock); |
910 | classifier_destroy(&cls); | |
911 | tcls_destroy(&tcls); | |
b5d97350 BP |
912 | } while (next_permutation(ops, ARRAY_SIZE(ops))); |
913 | assert(n_permutations == (factorial(N_RULES * 2) >> N_RULES)); | |
064af421 BP |
914 | } |
915 | } | |
916 | ||
b5d97350 BP |
917 | static int |
918 | count_ones(unsigned long int x) | |
064af421 | 919 | { |
b5d97350 | 920 | int n = 0; |
064af421 | 921 | |
b5d97350 | 922 | while (x) { |
8472a3ce | 923 | x = zero_rightmost_1bit(x); |
b5d97350 BP |
924 | n++; |
925 | } | |
064af421 | 926 | |
b5d97350 BP |
927 | return n; |
928 | } | |
064af421 | 929 | |
b5d97350 BP |
930 | static bool |
931 | array_contains(int *array, int n, int value) | |
932 | { | |
933 | int i; | |
064af421 | 934 | |
b5d97350 BP |
935 | for (i = 0; i < n; i++) { |
936 | if (array[i] == value) { | |
937 | return true; | |
064af421 BP |
938 | } |
939 | } | |
b5d97350 BP |
940 | |
941 | return false; | |
064af421 BP |
942 | } |
943 | ||
b5d97350 BP |
944 | /* Tests classification with two rules at a time that fall into the same |
945 | * table but different lists. */ | |
064af421 | 946 | static void |
3223e977 | 947 | test_many_rules_in_one_table(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
064af421 | 948 | { |
b5d97350 | 949 | int iteration; |
064af421 | 950 | |
b5d97350 BP |
951 | for (iteration = 0; iteration < 50; iteration++) { |
952 | enum { N_RULES = 20 }; | |
953 | struct test_rule *rules[N_RULES]; | |
954 | struct test_rule *tcls_rules[N_RULES]; | |
955 | struct classifier cls; | |
956 | struct tcls tcls; | |
957 | int value_pats[N_RULES]; | |
958 | int value_mask; | |
959 | int wcf; | |
960 | int i; | |
064af421 | 961 | |
b5d97350 | 962 | do { |
b028db44 | 963 | wcf = random_uint32() & ((1u << CLS_N_FIELDS) - 1); |
b5d97350 BP |
964 | value_mask = ~wcf & ((1u << CLS_N_FIELDS) - 1); |
965 | } while ((1 << count_ones(value_mask)) < N_RULES); | |
064af421 | 966 | |
476f36e8 | 967 | classifier_init(&cls, flow_segment_u32s); |
06f81620 | 968 | fat_rwlock_wrlock(&cls.rwlock); |
13751fd8 JR |
969 | classifier_set_prefix_fields(&cls, trie_fields, |
970 | ARRAY_SIZE(trie_fields)); | |
5f0476ce | 971 | fat_rwlock_unlock(&cls.rwlock); |
b5d97350 | 972 | tcls_init(&tcls); |
064af421 | 973 | |
b5d97350 | 974 | for (i = 0; i < N_RULES; i++) { |
b028db44 | 975 | unsigned int priority = random_uint32(); |
064af421 | 976 | |
b5d97350 | 977 | do { |
b028db44 | 978 | value_pats[i] = random_uint32() & value_mask; |
b5d97350 | 979 | } while (array_contains(value_pats, i, value_pats[i])); |
064af421 | 980 | |
b5d97350 BP |
981 | rules[i] = make_rule(wcf, priority, value_pats[i]); |
982 | tcls_rules[i] = tcls_insert(&tcls, rules[i]); | |
5f0476ce JR |
983 | |
984 | fat_rwlock_wrlock(&cls.rwlock); | |
08944c1d | 985 | classifier_insert(&cls, &rules[i]->cls_rule); |
5f0476ce JR |
986 | compare_classifiers(&cls, &tcls); |
987 | fat_rwlock_unlock(&cls.rwlock); | |
b5d97350 BP |
988 | |
989 | check_tables(&cls, 1, i + 1, 0); | |
b5d97350 BP |
990 | } |
991 | ||
992 | for (i = 0; i < N_RULES; i++) { | |
993 | tcls_remove(&tcls, tcls_rules[i]); | |
5f0476ce | 994 | fat_rwlock_wrlock(&cls.rwlock); |
b5d97350 | 995 | classifier_remove(&cls, &rules[i]->cls_rule); |
5f0476ce JR |
996 | compare_classifiers(&cls, &tcls); |
997 | fat_rwlock_unlock(&cls.rwlock); | |
48d28ac1 | 998 | free_rule(rules[i]); |
b5d97350 BP |
999 | |
1000 | check_tables(&cls, i < N_RULES - 1, N_RULES - (i + 1), 0); | |
064af421 | 1001 | } |
b5d97350 BP |
1002 | |
1003 | classifier_destroy(&cls); | |
1004 | tcls_destroy(&tcls); | |
064af421 BP |
1005 | } |
1006 | } | |
1007 | ||
b5d97350 BP |
1008 | /* Tests classification with many rules at a time that fall into random lists |
1009 | * in 'n' tables. */ | |
064af421 | 1010 | static void |
b5d97350 | 1011 | test_many_rules_in_n_tables(int n_tables) |
064af421 BP |
1012 | { |
1013 | enum { MAX_RULES = 50 }; | |
b5d97350 | 1014 | int wcfs[10]; |
064af421 | 1015 | int iteration; |
b5d97350 BP |
1016 | int i; |
1017 | ||
1018 | assert(n_tables < 10); | |
1019 | for (i = 0; i < n_tables; i++) { | |
1020 | do { | |
b028db44 | 1021 | wcfs[i] = random_uint32() & ((1u << CLS_N_FIELDS) - 1); |
b5d97350 BP |
1022 | } while (array_contains(wcfs, i, wcfs[i])); |
1023 | } | |
064af421 BP |
1024 | |
1025 | for (iteration = 0; iteration < 30; iteration++) { | |
1026 | unsigned int priorities[MAX_RULES]; | |
1027 | struct classifier cls; | |
1028 | struct tcls tcls; | |
064af421 | 1029 | |
b028db44 | 1030 | random_set_seed(iteration + 1); |
064af421 BP |
1031 | for (i = 0; i < MAX_RULES; i++) { |
1032 | priorities[i] = i * 129; | |
1033 | } | |
1034 | shuffle(priorities, ARRAY_SIZE(priorities)); | |
1035 | ||
476f36e8 | 1036 | classifier_init(&cls, flow_segment_u32s); |
06f81620 | 1037 | fat_rwlock_wrlock(&cls.rwlock); |
13751fd8 JR |
1038 | classifier_set_prefix_fields(&cls, trie_fields, |
1039 | ARRAY_SIZE(trie_fields)); | |
5f0476ce | 1040 | fat_rwlock_unlock(&cls.rwlock); |
064af421 BP |
1041 | tcls_init(&tcls); |
1042 | ||
1043 | for (i = 0; i < MAX_RULES; i++) { | |
1044 | struct test_rule *rule; | |
1045 | unsigned int priority = priorities[i]; | |
b028db44 BP |
1046 | int wcf = wcfs[random_range(n_tables)]; |
1047 | int value_pat = random_uint32() & ((1u << CLS_N_FIELDS) - 1); | |
064af421 BP |
1048 | rule = make_rule(wcf, priority, value_pat); |
1049 | tcls_insert(&tcls, rule); | |
5f0476ce | 1050 | fat_rwlock_wrlock(&cls.rwlock); |
08944c1d | 1051 | classifier_insert(&cls, &rule->cls_rule); |
064af421 | 1052 | compare_classifiers(&cls, &tcls); |
5f0476ce JR |
1053 | fat_rwlock_unlock(&cls.rwlock); |
1054 | check_tables(&cls, -1, i + 1, -1); | |
064af421 BP |
1055 | } |
1056 | ||
1057 | while (!classifier_is_empty(&cls)) { | |
5ecc9d81 BP |
1058 | struct test_rule *rule, *next_rule; |
1059 | struct test_rule *target; | |
5ecc9d81 | 1060 | |
b028db44 | 1061 | target = clone_rule(tcls.rules[random_range(tcls.n_rules)]); |
5ecc9d81 | 1062 | |
5f0476ce JR |
1063 | CLS_FOR_EACH_TARGET_SAFE (rule, next_rule, cls_rule, &cls, |
1064 | &target->cls_rule) { | |
1065 | fat_rwlock_wrlock(&cls.rwlock); | |
5ecc9d81 | 1066 | classifier_remove(&cls, &rule->cls_rule); |
5f0476ce | 1067 | fat_rwlock_unlock(&cls.rwlock); |
48d28ac1 | 1068 | free_rule(rule); |
5ecc9d81 | 1069 | } |
5f0476ce | 1070 | |
5ecc9d81 | 1071 | tcls_delete_matches(&tcls, &target->cls_rule); |
5f0476ce | 1072 | fat_rwlock_rdlock(&cls.rwlock); |
064af421 | 1073 | compare_classifiers(&cls, &tcls); |
5f0476ce | 1074 | fat_rwlock_unlock(&cls.rwlock); |
b5d97350 | 1075 | check_tables(&cls, -1, -1, -1); |
48d28ac1 | 1076 | free_rule(target); |
064af421 | 1077 | } |
064af421 BP |
1078 | |
1079 | destroy_classifier(&cls); | |
1080 | tcls_destroy(&tcls); | |
1081 | } | |
1082 | } | |
b5d97350 BP |
1083 | |
1084 | static void | |
1085 | test_many_rules_in_two_tables(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
1086 | { | |
1087 | test_many_rules_in_n_tables(2); | |
1088 | } | |
1089 | ||
1090 | static void | |
1091 | test_many_rules_in_five_tables(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
1092 | { | |
1093 | test_many_rules_in_n_tables(5); | |
1094 | } | |
064af421 | 1095 | \f |
5cb7a798 BP |
1096 | /* Miniflow tests. */ |
1097 | ||
1098 | static uint32_t | |
1099 | random_value(void) | |
1100 | { | |
1101 | static const uint32_t values[] = | |
1102 | { 0xffffffff, 0xaaaaaaaa, 0x55555555, 0x80000000, | |
1103 | 0x00000001, 0xface0000, 0x00d00d1e, 0xdeadbeef }; | |
1104 | ||
b028db44 | 1105 | return values[random_range(ARRAY_SIZE(values))]; |
5cb7a798 BP |
1106 | } |
1107 | ||
1108 | static bool | |
1109 | choose(unsigned int n, unsigned int *idxp) | |
1110 | { | |
1111 | if (*idxp < n) { | |
1112 | return true; | |
1113 | } else { | |
1114 | *idxp -= n; | |
1115 | return false; | |
1116 | } | |
1117 | } | |
1118 | ||
1119 | static bool | |
1120 | init_consecutive_values(int n_consecutive, struct flow *flow, | |
1121 | unsigned int *idxp) | |
1122 | { | |
1123 | uint32_t *flow_u32 = (uint32_t *) flow; | |
1124 | ||
1125 | if (choose(FLOW_U32S - n_consecutive + 1, idxp)) { | |
1126 | int i; | |
1127 | ||
1128 | for (i = 0; i < n_consecutive; i++) { | |
1129 | flow_u32[*idxp + i] = random_value(); | |
1130 | } | |
1131 | return true; | |
1132 | } else { | |
1133 | return false; | |
1134 | } | |
1135 | } | |
1136 | ||
1137 | static bool | |
1138 | next_random_flow(struct flow *flow, unsigned int idx) | |
1139 | { | |
1140 | uint32_t *flow_u32 = (uint32_t *) flow; | |
1141 | int i; | |
1142 | ||
1143 | memset(flow, 0, sizeof *flow); | |
1144 | ||
1145 | /* Empty flow. */ | |
1146 | if (choose(1, &idx)) { | |
1147 | return true; | |
1148 | } | |
1149 | ||
1150 | /* All flows with a small number of consecutive nonzero values. */ | |
1151 | for (i = 1; i <= 4; i++) { | |
1152 | if (init_consecutive_values(i, flow, &idx)) { | |
1153 | return true; | |
1154 | } | |
1155 | } | |
1156 | ||
1157 | /* All flows with a large number of consecutive nonzero values. */ | |
1158 | for (i = FLOW_U32S - 4; i <= FLOW_U32S; i++) { | |
1159 | if (init_consecutive_values(i, flow, &idx)) { | |
1160 | return true; | |
1161 | } | |
1162 | } | |
1163 | ||
1164 | /* All flows with exactly two nonconsecutive nonzero values. */ | |
1165 | if (choose((FLOW_U32S - 1) * (FLOW_U32S - 2) / 2, &idx)) { | |
1166 | int ofs1; | |
1167 | ||
1168 | for (ofs1 = 0; ofs1 < FLOW_U32S - 2; ofs1++) { | |
1169 | int ofs2; | |
1170 | ||
1171 | for (ofs2 = ofs1 + 2; ofs2 < FLOW_U32S; ofs2++) { | |
1172 | if (choose(1, &idx)) { | |
1173 | flow_u32[ofs1] = random_value(); | |
1174 | flow_u32[ofs2] = random_value(); | |
1175 | return true; | |
1176 | } | |
1177 | } | |
1178 | } | |
428b2edd | 1179 | OVS_NOT_REACHED(); |
5cb7a798 BP |
1180 | } |
1181 | ||
1182 | /* 16 randomly chosen flows with N >= 3 nonzero values. */ | |
1183 | if (choose(16 * (FLOW_U32S - 4), &idx)) { | |
1184 | int n = idx / 16 + 3; | |
1185 | int i; | |
1186 | ||
1187 | for (i = 0; i < n; i++) { | |
1188 | flow_u32[i] = random_value(); | |
1189 | } | |
1190 | shuffle_u32s(flow_u32, FLOW_U32S); | |
1191 | ||
1192 | return true; | |
1193 | } | |
1194 | ||
1195 | return false; | |
1196 | } | |
1197 | ||
1198 | static void | |
1199 | any_random_flow(struct flow *flow) | |
1200 | { | |
1201 | static unsigned int max; | |
1202 | if (!max) { | |
1203 | while (next_random_flow(flow, max)) { | |
1204 | max++; | |
1205 | } | |
1206 | } | |
1207 | ||
1208 | next_random_flow(flow, random_range(max)); | |
1209 | } | |
1210 | ||
1211 | static void | |
1212 | toggle_masked_flow_bits(struct flow *flow, const struct flow_wildcards *mask) | |
1213 | { | |
1214 | const uint32_t *mask_u32 = (const uint32_t *) &mask->masks; | |
1215 | uint32_t *flow_u32 = (uint32_t *) flow; | |
1216 | int i; | |
1217 | ||
1218 | for (i = 0; i < FLOW_U32S; i++) { | |
1219 | if (mask_u32[i] != 0) { | |
1220 | uint32_t bit; | |
1221 | ||
1222 | do { | |
1223 | bit = 1u << random_range(32); | |
1224 | } while (!(bit & mask_u32[i])); | |
1225 | flow_u32[i] ^= bit; | |
1226 | } | |
1227 | } | |
1228 | } | |
1229 | ||
1230 | static void | |
1231 | wildcard_extra_bits(struct flow_wildcards *mask) | |
1232 | { | |
1233 | uint32_t *mask_u32 = (uint32_t *) &mask->masks; | |
1234 | int i; | |
1235 | ||
1236 | for (i = 0; i < FLOW_U32S; i++) { | |
1237 | if (mask_u32[i] != 0) { | |
1238 | uint32_t bit; | |
1239 | ||
1240 | do { | |
1241 | bit = 1u << random_range(32); | |
1242 | } while (!(bit & mask_u32[i])); | |
1243 | mask_u32[i] &= ~bit; | |
1244 | } | |
1245 | } | |
1246 | } | |
1247 | ||
1248 | static void | |
1249 | test_miniflow(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
1250 | { | |
1251 | struct flow flow; | |
1252 | unsigned int idx; | |
1253 | ||
1254 | random_set_seed(0xb3faca38); | |
1255 | for (idx = 0; next_random_flow(&flow, idx); idx++) { | |
1256 | const uint32_t *flow_u32 = (const uint32_t *) &flow; | |
1257 | struct miniflow miniflow, miniflow2, miniflow3; | |
1258 | struct flow flow2, flow3; | |
1259 | struct flow_wildcards mask; | |
1260 | struct minimask minimask; | |
1261 | int i; | |
1262 | ||
1263 | /* Convert flow to miniflow. */ | |
1264 | miniflow_init(&miniflow, &flow); | |
1265 | ||
1266 | /* Check that the flow equals its miniflow. */ | |
1267 | assert(miniflow_get_vid(&miniflow) == vlan_tci_to_vid(flow.vlan_tci)); | |
1268 | for (i = 0; i < FLOW_U32S; i++) { | |
28a560d9 JR |
1269 | assert(MINIFLOW_GET_TYPE(&miniflow, uint32_t, i * 4) |
1270 | == flow_u32[i]); | |
5cb7a798 BP |
1271 | } |
1272 | ||
1273 | /* Check that the miniflow equals itself. */ | |
1274 | assert(miniflow_equal(&miniflow, &miniflow)); | |
1275 | ||
1276 | /* Convert miniflow back to flow and verify that it's the same. */ | |
1277 | miniflow_expand(&miniflow, &flow2); | |
1278 | assert(flow_equal(&flow, &flow2)); | |
1279 | ||
1280 | /* Check that copying a miniflow works properly. */ | |
1281 | miniflow_clone(&miniflow2, &miniflow); | |
1282 | assert(miniflow_equal(&miniflow, &miniflow2)); | |
1283 | assert(miniflow_hash(&miniflow, 0) == miniflow_hash(&miniflow2, 0)); | |
1284 | miniflow_expand(&miniflow2, &flow3); | |
1285 | assert(flow_equal(&flow, &flow3)); | |
1286 | ||
1287 | /* Check that masked matches work as expected for identical flows and | |
1288 | * miniflows. */ | |
1289 | do { | |
1290 | next_random_flow(&mask.masks, 1); | |
1291 | } while (flow_wildcards_is_catchall(&mask)); | |
1292 | minimask_init(&minimask, &mask); | |
1293 | assert(minimask_is_catchall(&minimask) | |
1294 | == flow_wildcards_is_catchall(&mask)); | |
1295 | assert(miniflow_equal_in_minimask(&miniflow, &miniflow2, &minimask)); | |
1296 | assert(miniflow_equal_flow_in_minimask(&miniflow, &flow2, &minimask)); | |
1297 | assert(miniflow_hash_in_minimask(&miniflow, &minimask, 0x12345678) == | |
1298 | flow_hash_in_minimask(&flow, &minimask, 0x12345678)); | |
1299 | ||
1300 | /* Check that masked matches work as expected for differing flows and | |
1301 | * miniflows. */ | |
1302 | toggle_masked_flow_bits(&flow2, &mask); | |
1303 | assert(!miniflow_equal_flow_in_minimask(&miniflow, &flow2, &minimask)); | |
1304 | miniflow_init(&miniflow3, &flow2); | |
1305 | assert(!miniflow_equal_in_minimask(&miniflow, &miniflow3, &minimask)); | |
1306 | ||
1307 | /* Clean up. */ | |
1308 | miniflow_destroy(&miniflow); | |
1309 | miniflow_destroy(&miniflow2); | |
1310 | miniflow_destroy(&miniflow3); | |
1311 | minimask_destroy(&minimask); | |
1312 | } | |
1313 | } | |
1314 | ||
1315 | static void | |
1316 | test_minimask_has_extra(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
1317 | { | |
1318 | struct flow_wildcards catchall; | |
1319 | struct minimask minicatchall; | |
1320 | struct flow flow; | |
1321 | unsigned int idx; | |
1322 | ||
1323 | flow_wildcards_init_catchall(&catchall); | |
1324 | minimask_init(&minicatchall, &catchall); | |
1325 | assert(minimask_is_catchall(&minicatchall)); | |
1326 | ||
1327 | random_set_seed(0x2ec7905b); | |
1328 | for (idx = 0; next_random_flow(&flow, idx); idx++) { | |
1329 | struct flow_wildcards mask; | |
1330 | struct minimask minimask; | |
1331 | ||
1332 | mask.masks = flow; | |
1333 | minimask_init(&minimask, &mask); | |
1334 | assert(!minimask_has_extra(&minimask, &minimask)); | |
1335 | assert(minimask_has_extra(&minicatchall, &minimask) | |
1336 | == !minimask_is_catchall(&minimask)); | |
1337 | if (!minimask_is_catchall(&minimask)) { | |
1338 | struct minimask minimask2; | |
1339 | ||
1340 | wildcard_extra_bits(&mask); | |
1341 | minimask_init(&minimask2, &mask); | |
1342 | assert(minimask_has_extra(&minimask2, &minimask)); | |
1343 | assert(!minimask_has_extra(&minimask, &minimask2)); | |
1344 | minimask_destroy(&minimask2); | |
1345 | } | |
1346 | ||
1347 | minimask_destroy(&minimask); | |
1348 | } | |
f2f3f5cb BP |
1349 | |
1350 | minimask_destroy(&minicatchall); | |
5cb7a798 BP |
1351 | } |
1352 | ||
1353 | static void | |
1354 | test_minimask_combine(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
1355 | { | |
1356 | struct flow_wildcards catchall; | |
1357 | struct minimask minicatchall; | |
1358 | struct flow flow; | |
1359 | unsigned int idx; | |
1360 | ||
1361 | flow_wildcards_init_catchall(&catchall); | |
1362 | minimask_init(&minicatchall, &catchall); | |
1363 | assert(minimask_is_catchall(&minicatchall)); | |
1364 | ||
1365 | random_set_seed(0x181bf0cd); | |
1366 | for (idx = 0; next_random_flow(&flow, idx); idx++) { | |
1367 | struct minimask minimask, minimask2, minicombined; | |
1368 | struct flow_wildcards mask, mask2, combined, combined2; | |
1369 | uint32_t storage[FLOW_U32S]; | |
1370 | struct flow flow2; | |
1371 | ||
1372 | mask.masks = flow; | |
1373 | minimask_init(&minimask, &mask); | |
1374 | ||
1375 | minimask_combine(&minicombined, &minimask, &minicatchall, storage); | |
1376 | assert(minimask_is_catchall(&minicombined)); | |
1377 | ||
1378 | any_random_flow(&flow2); | |
1379 | mask2.masks = flow2; | |
1380 | minimask_init(&minimask2, &mask2); | |
1381 | ||
1382 | minimask_combine(&minicombined, &minimask, &minimask2, storage); | |
368eefac | 1383 | flow_wildcards_and(&combined, &mask, &mask2); |
5cb7a798 BP |
1384 | minimask_expand(&minicombined, &combined2); |
1385 | assert(flow_wildcards_equal(&combined, &combined2)); | |
1386 | ||
1387 | minimask_destroy(&minimask); | |
1388 | minimask_destroy(&minimask2); | |
1389 | } | |
f2f3f5cb BP |
1390 | |
1391 | minimask_destroy(&minicatchall); | |
5cb7a798 BP |
1392 | } |
1393 | \f | |
3223e977 | 1394 | static const struct command commands[] = { |
5cb7a798 | 1395 | /* Classifier tests. */ |
3223e977 BP |
1396 | {"empty", 0, 0, test_empty}, |
1397 | {"destroy-null", 0, 0, test_destroy_null}, | |
1398 | {"single-rule", 0, 0, test_single_rule}, | |
1399 | {"rule-replacement", 0, 0, test_rule_replacement}, | |
b5d97350 | 1400 | {"many-rules-in-one-list", 0, 0, test_many_rules_in_one_list}, |
3223e977 | 1401 | {"many-rules-in-one-table", 0, 0, test_many_rules_in_one_table}, |
b5d97350 BP |
1402 | {"many-rules-in-two-tables", 0, 0, test_many_rules_in_two_tables}, |
1403 | {"many-rules-in-five-tables", 0, 0, test_many_rules_in_five_tables}, | |
5cb7a798 BP |
1404 | |
1405 | /* Miniflow and minimask tests. */ | |
1406 | {"miniflow", 0, 0, test_miniflow}, | |
eadd1644 AZ |
1407 | {"minimask_has_extra", 0, 0, test_minimask_has_extra}, |
1408 | {"minimask_combine", 0, 0, test_minimask_combine}, | |
5cb7a798 | 1409 | |
3223e977 BP |
1410 | {NULL, 0, 0, NULL}, |
1411 | }; | |
064af421 | 1412 | |
eadd1644 AZ |
1413 | static void |
1414 | test_classifier_main(int argc, char *argv[]) | |
064af421 | 1415 | { |
b5d97350 | 1416 | set_program_name(argv[0]); |
064af421 | 1417 | init_values(); |
3223e977 | 1418 | run_command(argc - 1, argv + 1, commands); |
064af421 | 1419 | } |
eadd1644 AZ |
1420 | |
1421 | OVSTEST_REGISTER("test-classifier", test_classifier_main); |