Adds additional indices to classifier tables to be able to stop matching as soon as it is evident that a match will not be found or if a single matching rule is found. Especaially avoids folding in the transport port masks, if they need not be examined.
The struct flow is re-organized to allow hashing in consecutive ranges. The indexing segment boundaries are defined in lib/flow.h to keep with the struct definition. Hopefully this aids in keeping them up-to-date as struct flow is being evolved. Signed-off-by: Jarno Rajahalme <jrajaha...@nicira.com> --- lib/classifier.c | 158 +++++++++++++++++++++++++++++++++++++++++++------- lib/classifier.h | 5 ++ lib/flow.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/flow.h | 73 +++++++++++++++++------ tests/classifier.at | 38 ++++++++++++ 5 files changed, 395 insertions(+), 39 deletions(-) diff --git a/lib/classifier.c b/lib/classifier.c index 53487a4..a92fc6f 100644 --- a/lib/classifier.c +++ b/lib/classifier.c @@ -42,7 +42,8 @@ static void update_tables_after_removal(struct classifier *, unsigned int del_priority); static struct cls_rule *find_match(const struct cls_table *, - const struct flow *); + const struct flow *, + struct flow_wildcards *); static struct cls_rule *find_equal(struct cls_table *, const struct miniflow *, uint32_t hash); static struct cls_rule *insert_rule(struct classifier *, @@ -297,8 +298,15 @@ classifier_remove(struct classifier *cls, struct cls_rule *rule) struct cls_partition *partition; struct cls_rule *head; struct cls_table *table; + int i; table = find_table(cls, &rule->match.mask); + + /* Remove rule node from indices. */ + for (i = 0; i < table->n_hindex; i++) { + hindex_remove(&table->hindex[i], &rule->hindex_node[i]); + } + head = find_equal(table, &rule->match.flow, rule->hmap_node.hash); if (head != rule) { list_remove(&rule->list); @@ -379,10 +387,7 @@ classifier_lookup(const struct classifier *cls, const struct flow *flow, continue; } - rule = find_match(table, flow); - if (wc) { - flow_wildcards_fold_minimask(wc, &table->mask); - } + rule = find_match(table, flow, wc); if (rule) { best = rule; LIST_FOR_EACH_CONTINUE (table, list_node, &cls->tables_priority) { @@ -395,10 +400,7 @@ classifier_lookup(const struct classifier *cls, const struct flow *flow, continue; } - rule = find_match(table, flow); - if (wc) { - flow_wildcards_fold_minimask(wc, &table->mask); - } + rule = find_match(table, flow, wc); if (rule && rule->priority > best->priority) { best = rule; } @@ -654,11 +656,45 @@ insert_table(struct classifier *cls, const struct minimask *mask) { uint32_t hash = minimask_hash(mask, 0); struct cls_table *table; + int i; + struct flow_wildcards wc, new; + uint8_t prev_u32ofs; + int n_index; table = xzalloc(sizeof *table); hmap_init(&table->rules); minimask_clone(&table->mask, mask); - hmap_insert(&cls->tables, &table->hmap_node, minimask_hash(mask, 0)); + + /* Init indices. */ + flow_wildcards_init_catchall(&new); + wc = new; + n_index = 0; + prev_u32ofs = 0; + for (i = 0; i < FLOW_SEGMENTS; i++) { + flow_wildcards_fold_minimask_range(&new, mask, prev_u32ofs, + flow_segments_u32[i]); + /* Add an index if it adds mask bits. */ + if (!flow_wildcards_equal(&new, &wc)) { + hindex_init(&table->hindex[n_index]); + table->flow_segment_ofs[n_index] = flow_segments_u32[i]; + n_index++; + wc = new; + } + prev_u32ofs = flow_segments_u32[i]; + } + /* Check if the rest of the table's mask adds any bits, + * and remove the last index if it doesn't. */ + if (n_index) { + flow_wildcards_fold_minimask_range(&new, mask, prev_u32ofs, FLOW_U32S); + if (flow_wildcards_equal(&new, &wc)) { + n_index--; + table->flow_segment_ofs[n_index] = 0; + hindex_destroy(&table->hindex[n_index]); + } + } + table->n_hindex = n_index; + + hmap_insert(&cls->tables, &table->hmap_node, hash); list_push_back(&cls->tables_priority, &table->list_node); table->tag = (minimask_get_metadata_mask(mask) == OVS_BE64_MAX ? tag_create_deterministic(hash) @@ -670,8 +706,14 @@ insert_table(struct classifier *cls, const struct minimask *mask) static void destroy_table(struct classifier *cls, struct cls_table *table) { + int i; + minimask_destroy(&table->mask); hmap_remove(&cls->tables, &table->hmap_node); + /* Destroy indices. */ + for (i = 0; i < table->n_hindex; i++) { + hindex_destroy(&table->hindex[i]); + } hmap_destroy(&table->rules); list_remove(&table->list_node); free(table); @@ -768,18 +810,69 @@ update_tables_after_removal(struct classifier *cls, struct cls_table *table, } static struct cls_rule * -find_match(const struct cls_table *table, const struct flow *flow) -{ - uint32_t hash = flow_hash_in_minimask(flow, &table->mask, 0); - struct cls_rule *rule; +find_match(const struct cls_table *table, const struct flow *flow, + struct flow_wildcards *wc) +{ + uint32_t hash = 0; + struct cls_rule *rule = NULL; + int i; + uint8_t prev_u32ofs = 0; + + /* Try to finish early with indices. */ + + for (i = 0; i < table->n_hindex; i++) { + struct hindex_node *inode; + + hash = flow_hash_in_minimask_range(flow, &table->mask, prev_u32ofs, + table->flow_segment_ofs[i], hash); + inode = hindex_node_with_hash(&table->hindex[i], hash); + if (!inode) { + /* No match, can stop immediately, but must fold in the mask + * covered so far. */ + if (wc) { + flow_wildcards_fold_minimask_range(wc, &table->mask, 0, + table->flow_segment_ofs[i]); + } + return NULL; + } + + /* Note: Hash collisions may make us look deeper than absolutely + * necessary (false positives). */ + + if (!inode->s) { + struct cls_rule *rl; + /* Found single candidate. */ + ASSIGN_CONTAINER(rl, inode, hindex_node[i]); + /* Do not check same rule again. */ + if (rl != rule) { + rule = rl; /* Update last rule we looked at. */ + + if (minimatch_matches_flow(&rule->match, flow)) { + /* Found match, no need to look further. */ + goto out; + } + /* No match, keep looking to avoid folding the full mask. */ + } + } + prev_u32ofs = table->flow_segment_ofs[i]; + } + + hash = flow_hash_in_minimask_range(flow, &table->mask, prev_u32ofs, + FLOW_U32S, hash); HMAP_FOR_EACH_WITH_HASH (rule, hmap_node, hash, &table->rules) { if (minimatch_matches_flow(&rule->match, flow)) { - return rule; + goto out; } } - return NULL; + rule = NULL; + + out: + if (wc) { + flow_wildcards_fold_minimask(wc, &table->mask); + } + return rule; } static struct cls_rule * @@ -796,24 +889,40 @@ find_equal(struct cls_table *table, const struct miniflow *flow, uint32_t hash) } static struct cls_rule * -insert_rule(struct classifier *cls, - struct cls_table *table, struct cls_rule *new) +insert_rule(struct classifier *cls, struct cls_table *table, + struct cls_rule *new) { struct cls_rule *head; struct cls_rule *old = NULL; + int i; + uint32_t hash = 0; + uint8_t prev_u32ofs = 0; + + /* Add new node to indices. TODO: Add only if top of list? */ + for (i = 0; i < table->n_hindex; i++) { + hash = miniflow_hash_in_minimask_range(&new->match.flow, + &new->match.mask, prev_u32ofs, + table->flow_segment_ofs[i], + hash); + hindex_insert(&table->hindex[i], &new->hindex_node[i], hash); + prev_u32ofs = table->flow_segment_ofs[i]; + } - new->hmap_node.hash = miniflow_hash_in_minimask(&new->match.flow, - &new->match.mask, 0); + hash = miniflow_hash_in_minimask_range(&new->match.flow, &new->match.mask, + prev_u32ofs, FLOW_U32S, hash); - head = find_equal(table, &new->match.flow, new->hmap_node.hash); + head = find_equal(table, &new->match.flow, hash); if (!head) { - hmap_insert(&table->rules, &new->hmap_node, new->hmap_node.hash); + hmap_insert(&table->rules, &new->hmap_node, hash); list_init(&new->list); goto out; } else { /* Scan the list for the insertion point that will keep the list in * order of decreasing priority. */ struct cls_rule *rule; + + new->hmap_node.hash = hash; /* Otherwise done by hmap_insert. */ + FOR_EACH_RULE_IN_LIST (rule, head) { if (new->priority >= rule->priority) { if (rule == head) { @@ -840,6 +949,11 @@ insert_rule(struct classifier *cls, out: if (!old) { update_tables_after_insertion(cls, table, new->priority); + } else { + /* Remove old node from indices. */ + for (i = 0; i < table->n_hindex; i++) { + hindex_remove(&table->hindex[i], &old->hindex_node[i]); + } } return old; } diff --git a/lib/classifier.h b/lib/classifier.h index 8e3bf61..d1f6eff 100644 --- a/lib/classifier.h +++ b/lib/classifier.h @@ -103,6 +103,7 @@ * - Only the main thread is allowed to iterate over rules. */ #include "flow.h" +#include "hindex.h" #include "hmap.h" #include "list.h" #include "match.h" @@ -138,6 +139,9 @@ struct cls_table { unsigned int max_priority; /* Max priority of any rule in the table. */ unsigned int max_count; /* Count of max_priority rules. */ tag_type tag; /* Tag generated from mask for partitioning. */ + struct hindex hindex[FLOW_SEGMENTS]; /* Search indices. */ + uint8_t n_hindex; /* How many indices to use. */ + uint8_t flow_segment_ofs[FLOW_SEGMENTS]; /* Ending u32 flow offsets. */ }; /* Returns true if 'table' is a "catch-all" table that will match every @@ -151,6 +155,7 @@ cls_table_is_catchall(const struct cls_table *table) /* A rule in a "struct cls_table". */ struct cls_rule { struct hmap_node hmap_node; /* Within struct cls_table 'rules'. */ + struct hindex_node hindex_node[FLOW_SEGMENTS]; struct list list; /* List of identical, lower-priority rules. */ struct minimatch match; /* Matching rule. */ unsigned int priority; /* Larger numbers are higher priorities. */ diff --git a/lib/flow.c b/lib/flow.c index 51851cf..f046d63 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -41,6 +41,13 @@ COVERAGE_DEFINE(flow_extract); COVERAGE_DEFINE(miniflow_malloc); +/* U32 indices for segmented flow classification. */ +uint8_t flow_segments_u32[FLOW_SEGMENTS] = { + FLOW_SEGMENT_1_ENDS_AT / 4, + FLOW_SEGMENT_2_ENDS_AT / 4, + FLOW_SEGMENT_3_ENDS_AT / 4, +}; + static struct arp_eth_header * pull_arp(struct ofpbuf *packet) { @@ -682,6 +689,58 @@ flow_wildcards_fold_minimask(struct flow_wildcards *wc, flow_union_with_miniflow(&wc->masks, &mask->masks); } +/* Perform a bitwise OR of miniflow 'src' flow data in range [start, end) + * with the equivalent fields in 'dst', storing the result in 'dst'. */ +static void +flow_union_with_miniflow_range(struct flow *dst, const struct miniflow *src, + uint8_t start, uint8_t end) +{ + uint32_t *dst_u32 = (uint32_t *) dst; + const uint32_t *p = src->values; + int i = 0; + int end_ofs = end; + uint32_t msk; + + while (start >= 32) { + p += popcount(src->map[i]); + i++; + start -= 32; + end_ofs -= 32; + dst_u32 += 32; + } + msk = (1u << start) - 1; /* 'start' LSBs set */ + + for (; i < MINI_N_MAPS; i++) { + uint32_t map = src->map[i]; + + if (start > 0) { + p += popcount(map & msk); /* Skip to start. */ + map &= ~msk; + start = 0; + } + + for (; map; map = zero_rightmost_1bit(map)) { + int ofs = raw_ctz(map); + if (ofs >= end_ofs) { + return; + } + dst_u32[ofs] |= *p++; + } + dst_u32 += 32; + end_ofs -= 32; + } +} + +/* Fold minimask 'mask''s wildcard mask into 'wc's wildcard mask + * in range [start, end). */ +void +flow_wildcards_fold_minimask_range(struct flow_wildcards *wc, + const struct minimask *mask, + uint8_t start, uint8_t end) +{ + flow_union_with_miniflow_range(&wc->masks, &mask->masks, start, end); +} + /* Returns a hash of the wildcards in 'wc'. */ uint32_t flow_wildcards_hash(const struct flow_wildcards *wc, uint32_t basis) @@ -1414,6 +1473,53 @@ miniflow_hash_in_minimask(const struct miniflow *flow, return mhash_finish(hash, (p - mask->masks.values) * 4); } +/* Returns a hash value for the bits of range [start, end) in 'flow', + * where there are 1-bits in 'mask', given 'hash'. + * + * The hash values returned by this function are the same as those returned by + * flow_hash_in_minimask_range(), only the form of the arguments differ. */ +uint32_t +miniflow_hash_in_minimask_range(const struct miniflow *flow, + const struct minimask *mask, + uint8_t start, uint8_t end, uint32_t hash) +{ + const uint32_t *p = mask->masks.values; + int i = 0; + uint32_t msk; + + while (start >= 32) { + p += popcount(mask->masks.map[i]); + i++; + start -= 32; + } + msk = (1u << start) - 1; /* 'start' LSB set */ + + for (; i < MINI_N_MAPS; i++) { + uint32_t map = mask->masks.map[i]; + + if (start > 0) { + p += popcount(map & msk); /* Skip to start. */ + map &= ~msk; + start = 0; + } + + for (; map; map = zero_rightmost_1bit(map)) { + if (*p) { + int ofs = raw_ctz(map) + i * 32; + if (ofs >= end) { + goto out; + } + hash = mhash_add(hash, miniflow_get(flow, ofs) & *p); + } + p++; + } + } + out: + /* RFC: Only finish at the end for compatability. */ + return end * 4 < sizeof(struct flow) ? hash : + mhash_finish(hash, (p - mask->masks.values) * 4); +} + /* Returns a hash value for the bits of 'flow' where there are 1-bits in * 'mask', given 'basis'. * @@ -1444,6 +1550,60 @@ flow_hash_in_minimask(const struct flow *flow, const struct minimask *mask, return mhash_finish(hash, (p - mask->masks.values) * 4); } + +/* Returns a hash value for the bits of range [start, end) in 'flow', + * where there are 1-bits in 'mask', given 'hash'. + * + * The hash values returned by this function are the same as those returned by + * miniflow_hash_in_minimask_range(), only the form of the arguments differ. */ +uint32_t +flow_hash_in_minimask_range(const struct flow *flow, + const struct minimask *mask, + uint8_t start, uint8_t end, uint32_t hash) +{ + const uint32_t *flow_u32 = (const uint32_t *) flow; + const uint32_t *p = mask->masks.values; + int i = 0; + int end_ofs = end; + uint32_t msk; + + while (start >= 32) { + p += popcount(mask->masks.map[i]); + i++; + start -= 32; + end_ofs -= 32; + flow_u32 += 32; + } + msk = (1u << start) - 1; /* 'start' LSBs set */ + + for (; i < MINI_N_MAPS; i++) { + uint32_t map = mask->masks.map[i]; + + if (start > 0) { + p += popcount(map & msk); /* Skip to start. */ + map &= ~msk; + start = 0; + } + + for (; map; map = zero_rightmost_1bit(map)) { + if (*p) { + int ofs = raw_ctz(map); + if (ofs >= end_ofs) { + goto out; + } + hash = mhash_add(hash, flow_u32[ofs] & *p); + } + p++; + } + flow_u32 += 32; + end_ofs -= 32; + } + out: + /* RFC: Only finish at the end for compatability. */ + return end * 4 < sizeof(struct flow) ? hash : + mhash_finish(hash, (p - mask->masks.values) * 4); +} + /* Initializes 'dst' as a copy of 'src'. The caller must eventually free 'dst' * with minimask_destroy(). */ diff --git a/lib/flow.h b/lib/flow.h index ad51496..b22a08a 100644 --- a/lib/flow.h +++ b/lib/flow.h @@ -90,42 +90,69 @@ union flow_in_port { * a 32-bit datapath port number. */ struct flow { + /* L1 */ struct flow_tnl tunnel; /* Encapsulating tunnel parameters. */ ovs_be64 metadata; /* OpenFlow Metadata. */ + uint32_t regs[FLOW_N_REGS]; /* Registers. */ + uint32_t skb_priority; /* Packet priority for QoS. */ + uint32_t pkt_mark; /* Packet mark. */ + union flow_in_port in_port; /* Input port.*/ + + /* L2 */ + uint8_t dl_src[6]; /* Ethernet source address. */ + uint8_t dl_dst[6]; /* Ethernet destination address. */ + ovs_be16 dl_type; /* Ethernet frame type. */ + ovs_be16 vlan_tci; /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */ + + /* L3 */ + ovs_be32 mpls_lse; /* MPLS label stack entry. */ struct in6_addr ipv6_src; /* IPv6 source address. */ struct in6_addr ipv6_dst; /* IPv6 destination address. */ struct in6_addr nd_target; /* IPv6 neighbor discovery (ND) target. */ - uint32_t skb_priority; /* Packet priority for QoS. */ - uint32_t regs[FLOW_N_REGS]; /* Registers. */ + ovs_be32 ipv6_label; /* IPv6 flow label. */ ovs_be32 nw_src; /* IPv4 source address. */ ovs_be32 nw_dst; /* IPv4 destination address. */ - ovs_be32 ipv6_label; /* IPv6 flow label. */ - union flow_in_port in_port; /* Input port.*/ - uint32_t pkt_mark; /* Packet mark. */ - ovs_be32 mpls_lse; /* MPLS label stack entry. */ - ovs_be16 vlan_tci; /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */ - ovs_be16 dl_type; /* Ethernet frame type. */ - ovs_be16 tp_src; /* TCP/UDP/SCTP source port. */ - ovs_be16 tp_dst; /* TCP/UDP/SCTP destination port. */ - uint8_t dl_src[6]; /* Ethernet source address. */ - uint8_t dl_dst[6]; /* Ethernet destination address. */ - uint8_t nw_proto; /* IP protocol or low 8 bits of ARP opcode. */ + uint8_t nw_frag; /* FLOW_FRAG_* flags. */ uint8_t nw_tos; /* IP ToS (including DSCP and ECN). */ + uint8_t nw_ttl; /* IP TTL/Hop Limit. */ + uint8_t nw_proto; /* IP protocol or low 8 bits of ARP opcode. */ uint8_t arp_sha[6]; /* ARP/ND source hardware address. */ uint8_t arp_tha[6]; /* ARP/ND target hardware address. */ - uint8_t nw_ttl; /* IP TTL/Hop Limit. */ - uint8_t nw_frag; /* FLOW_FRAG_* flags. Keep last for the - BUILD_ASSERT_DECL below */ + /* L4 */ + ovs_be16 tp_src; /* TCP/UDP/SCTP source port. */ + ovs_be16 tp_dst; /* TCP/UDP/SCTP destination port. + * Keep last for the BUILD_ASSERT_DECL below */ }; BUILD_ASSERT_DECL(sizeof(struct flow) % 4 == 0); #define FLOW_U32S (sizeof(struct flow) / 4) /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */ -BUILD_ASSERT_DECL(offsetof(struct flow, nw_frag) + 1 +BUILD_ASSERT_DECL(offsetof(struct flow, tp_dst) + 2 == sizeof(struct flow_tnl) + 152 && FLOW_WC_SEQ == 21); +/* Incremental points at which flow classification may be performed in + * segments. + * This is located here since this is dependent on the structure of the + * struct flow defined above: + * Each offset must be on a distint, successive U32 boundary srtictly + * within the struct flow. */ +enum { + FLOW_SEGMENTS = 3, + FLOW_SEGMENT_1_ENDS_AT = offsetof(struct flow, dl_src), + FLOW_SEGMENT_2_ENDS_AT = offsetof(struct flow, mpls_lse), + FLOW_SEGMENT_3_ENDS_AT = offsetof(struct flow, tp_src), +}; +extern uint8_t flow_segments_u32[FLOW_SEGMENTS]; +BUILD_ASSERT_DECL(FLOW_SEGMENT_1_ENDS_AT % 4 == 0); +BUILD_ASSERT_DECL(FLOW_SEGMENT_2_ENDS_AT % 4 == 0); +BUILD_ASSERT_DECL(FLOW_SEGMENT_3_ENDS_AT % 4 == 0); +BUILD_ASSERT_DECL( 0 < FLOW_SEGMENT_1_ENDS_AT); +BUILD_ASSERT_DECL(FLOW_SEGMENT_1_ENDS_AT < FLOW_SEGMENT_2_ENDS_AT); +BUILD_ASSERT_DECL(FLOW_SEGMENT_2_ENDS_AT < FLOW_SEGMENT_3_ENDS_AT); +BUILD_ASSERT_DECL(FLOW_SEGMENT_3_ENDS_AT < sizeof(struct flow)); + /* Represents the metadata fields of struct flow. */ struct flow_metadata { ovs_be64 tun_id; /* Encapsulating tunnel ID. */ @@ -262,6 +289,18 @@ bool flow_wildcards_has_extra(const struct flow_wildcards *, void flow_wildcards_fold_minimask(struct flow_wildcards *, const struct minimask *); +void flow_wildcards_fold_minimask_range(struct flow_wildcards *, + const struct minimask *, + uint8_t start, uint8_t end); +uint32_t flow_hash_in_minimask_range(const struct flow *, + const struct minimask *, + uint8_t start, uint8_t end, + uint32_t hash); +uint32_t miniflow_hash_in_minimask_range(const struct miniflow *, + const struct minimask *, + uint8_t start, uint8_t end, + uint32_t hash); + uint32_t flow_wildcards_hash(const struct flow_wildcards *, uint32_t basis); bool flow_wildcards_equal(const struct flow_wildcards *, const struct flow_wildcards *); diff --git a/tests/classifier.at b/tests/classifier.at index cf0cc44..546c8f7 100644 --- a/tests/classifier.at +++ b/tests/classifier.at @@ -22,3 +22,41 @@ m4_foreach( [AT_SETUP([miniflow - m4_bpatsubst(testname, [-], [ ])]) AT_CHECK([test-classifier testname], [0], [], []) AT_CLEANUP])]) + +AT_BANNER([flow classifier lookup segmentation]) +AT_SETUP([flow classifier - lookup segmentation]) +OVS_VSWITCHD_START +ADD_OF_PORTS([br0], [1], [2], [3]) +AT_DATA([flows.txt], [dnl +table=0 in_port=1 priority=16,tcp,nw_dst=10.1.0.0/255.255.0.0,action=output(3) +table=0 in_port=1 priority=32,tcp,nw_dst=10.1.2.15,action=output(2) +table=0 in_port=1 priority=33,tcp,nw_dst=10.1.2.15,tp_dst=80,action=drop +table=0 in_port=1 priority=0,ip,action=drop +table=0 in_port=2 priority=16,tcp,nw_dst=192.168.0.0/255.255.0.0,action=output(1) +table=0 in_port=2 priority=0,ip,action=drop +table=0 in_port=3 priority=16,tcp,nw_src=10.1.0.0/255.255.0.0,action=output(1) +table=0 in_port=3 priority=0,ip,action=drop +]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) +AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=2,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80'], [0], [stdout]) +AT_CHECK([tail -2 stdout], [0], + [Relevant fields: skb_priority=0,tcp,in_port=2,nw_dst=192.168.0.0/16,nw_frag=no +Datapath actions: 1 +]) +AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80'], [0], [stdout]) +AT_CHECK([tail -2 stdout], [0], + [Relevant fields: skb_priority=0,tcp,in_port=1,nw_dst=192.168.0.2,nw_frag=no +Datapath actions: drop +]) +AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=10.1.2.15,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=80'], [0], [stdout]) +AT_CHECK([tail -2 stdout], [0], + [Relevant fields: skb_priority=0,tcp,in_port=1,nw_dst=10.1.2.15,nw_frag=no,tp_dst=80 +Datapath actions: drop +]) +AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=10.1.2.15,nw_proto=6,nw_tos=0,nw_ttl=128,tp_src=8,tp_dst=79'], [0], [stdout]) +AT_CHECK([tail -2 stdout], [0], + [Relevant fields: skb_priority=0,tcp,in_port=1,nw_dst=10.1.2.15,nw_frag=no,tp_dst=79 +Datapath actions: 2 +]) +OVS_VSWITCHD_STOP +AT_CLEANUP -- 1.7.10.4 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev