From 57d52057650716ee354ebbe588d2a884ccabc81f Mon Sep 17 00:00:00 2001 From: Justin Pettit Date: Mon, 15 Sep 2008 15:27:04 -0700 Subject: [PATCH] Add support for OFPFC_MODIFY Flow Mod command. The OFPFC_MODIFY command allows the controller to modify the actions of existing flows. When it does this, it does not reset counters or timers. --- datapath/chain.c | 21 ++++++++ datapath/chain.h | 3 ++ datapath/datapath.c | 7 +-- datapath/flow.c | 67 +++++++++++++++++++++----- datapath/flow.h | 18 +++++-- datapath/forward.c | 56 +++++++++++++++++++-- datapath/hwtable_dummy/hwtable_dummy.c | 19 ++++++++ datapath/table-hash.c | 39 +++++++++++++++ datapath/table-linear.c | 18 +++++++ datapath/table.h | 6 +++ datapath/table_t.c | 4 +- include/openflow.h | 6 +-- lib/ofp-print.c | 21 ++++++-- switch/chain.c | 21 ++++++++ switch/chain.h | 3 ++ switch/datapath.c | 65 ++++++++++++++++++++++--- switch/switch-flow.c | 33 ++++++++++--- switch/switch-flow.h | 14 ++++-- switch/table-hash.c | 40 +++++++++++++++ switch/table-linear.c | 18 +++++++ switch/table.h | 6 +++ utilities/dpctl.8 | 6 +++ utilities/dpctl.c | 42 +++++++++++++--- 23 files changed, 478 insertions(+), 55 deletions(-) diff --git a/datapath/chain.c b/datapath/chain.c index 87b8b793e..36749f306 100644 --- a/datapath/chain.c +++ b/datapath/chain.c @@ -101,6 +101,27 @@ int chain_insert(struct sw_chain *chain, struct sw_flow *flow) return -ENOBUFS; } +/* Modifies actions in 'chain' that match 'key'. Returns the number of + * flows that were modified. + * + * Expensive in the general case as currently implemented, since it requires + * iterating through the entire contents of each table for keys that contain + * wildcards. Relatively cheap for fully specified keys. */ +int +chain_modify(struct sw_chain *chain, const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + int count = 0; + int i; + + for (i = 0; i < chain->n_tables; i++) { + struct sw_table *t = chain->tables[i]; + count += t->modify(t, key, actions, n_actions); + } + + return count; +} + /* Deletes from 'chain' any and all flows that match 'key'. Returns the number * of flows that were deleted. * diff --git a/datapath/chain.h b/datapath/chain.h index 42208d694..dbbae416a 100644 --- a/datapath/chain.h +++ b/datapath/chain.h @@ -5,6 +5,7 @@ struct sw_flow; struct sw_flow_key; +struct ofp_action; struct datapath; @@ -24,6 +25,8 @@ struct sw_chain { struct sw_chain *chain_create(struct datapath *); struct sw_flow *chain_lookup(struct sw_chain *, const struct sw_flow_key *); int chain_insert(struct sw_chain *, struct sw_flow *); +int chain_modify(struct sw_chain *, const struct sw_flow_key *, + const struct ofp_action *, int); int chain_delete(struct sw_chain *, const struct sw_flow_key *, uint16_t, int); int chain_timeout(struct sw_chain *); void chain_destroy(struct sw_chain *); diff --git a/datapath/datapath.c b/datapath/datapath.c index 8eb066017..598b34701 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -1272,13 +1272,14 @@ static int flow_stats_init(struct datapath *dp, const void *body, int body_len, static int flow_stats_dump_callback(struct sw_flow *flow, void *private) { + struct sw_flow_actions *sf_acts = rcu_dereference(flow->sf_acts); struct flow_stats_state *s = private; struct ofp_flow_stats *ofs; int actions_length; int length; - actions_length = sizeof *ofs->actions * flow->n_actions; - length = sizeof *ofs + sizeof *ofs->actions * flow->n_actions; + actions_length = sizeof *ofs->actions * sf_acts->n_actions; + length = sizeof *ofs + actions_length; if (length + s->bytes_used > s->bytes_allocated) return 1; @@ -1305,7 +1306,7 @@ static int flow_stats_dump_callback(struct sw_flow *flow, void *private) memset(ofs->pad2, 0, sizeof ofs->pad2); ofs->packet_count = cpu_to_be64(flow->packet_count); ofs->byte_count = cpu_to_be64(flow->byte_count); - memcpy(ofs->actions, flow->actions, actions_length); + memcpy(ofs->actions, sf_acts->actions, actions_length); s->bytes_used += length; return 0; diff --git a/datapath/flow.c b/datapath/flow.c index 71dd8235d..2093dbba2 100644 --- a/datapath/flow.c +++ b/datapath/flow.c @@ -142,12 +142,12 @@ void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from) memcpy(to->dl_src, from->dl_src, ETH_ALEN); memcpy(to->dl_dst, from->dl_dst, ETH_ALEN); to->dl_type = from->dl_type; - to->nw_src = from->nw_src; - to->nw_dst = from->nw_dst; + to->nw_src = from->nw_src; + to->nw_dst = from->nw_dst; to->nw_proto = from->nw_proto; - to->tp_src = from->tp_src; - to->tp_dst = from->tp_dst; - to->pad = 0; + to->tp_src = from->tp_src; + to->tp_dst = from->tp_dst; + to->pad = 0; } int flow_timeout(struct sw_flow *flow) @@ -168,17 +168,20 @@ EXPORT_SYMBOL(flow_timeout); * flags 'flags'. Returns the new flow or a null pointer on failure. */ struct sw_flow *flow_alloc(int n_actions, gfp_t flags) { + struct sw_flow_actions *sfa; + int size = sizeof *sfa + (n_actions * sizeof sfa->actions[0]); struct sw_flow *flow = kmem_cache_alloc(flow_cache, flags); if (unlikely(!flow)) return NULL; - flow->n_actions = n_actions; - flow->actions = kmalloc(n_actions * sizeof *flow->actions, - flags); - if (unlikely(!flow->actions) && n_actions > 0) { + sfa = kmalloc(size, flags); + if (unlikely(!sfa)) { kmem_cache_free(flow_cache, flow); return NULL; } + sfa->n_actions = n_actions; + flow->sf_acts = sfa; + return flow; } @@ -187,14 +190,13 @@ void flow_free(struct sw_flow *flow) { if (unlikely(!flow)) return; - if (flow->actions) - kfree(flow->actions); + kfree(flow->sf_acts); kmem_cache_free(flow_cache, flow); } EXPORT_SYMBOL(flow_free); /* RCU callback used by flow_deferred_free. */ -static void rcu_callback(struct rcu_head *rcu) +static void rcu_free_flow_callback(struct rcu_head *rcu) { struct sw_flow *flow = container_of(rcu, struct sw_flow, rcu); flow_free(flow); @@ -204,10 +206,49 @@ static void rcu_callback(struct rcu_head *rcu) * The caller must hold rcu_read_lock for this to be sensible. */ void flow_deferred_free(struct sw_flow *flow) { - call_rcu(&flow->rcu, rcu_callback); + call_rcu(&flow->rcu, rcu_free_flow_callback); } EXPORT_SYMBOL(flow_deferred_free); +/* RCU callback used by flow_deferred_free_acts. */ +static void rcu_free_acts_callback(struct rcu_head *rcu) +{ + struct sw_flow_actions *sf_acts = container_of(rcu, + struct sw_flow_actions, rcu); + kfree(sf_acts); +} + +/* Schedules 'sf_acts' to be freed after the next RCU grace period. + * The caller must hold rcu_read_lock for this to be sensible. */ +void flow_deferred_free_acts(struct sw_flow_actions *sf_acts) +{ + call_rcu(&sf_acts->rcu, rcu_free_acts_callback); +} +EXPORT_SYMBOL(flow_deferred_free_acts); + +/* Copies 'actions' into a newly allocated structure for use by 'flow' + * and safely frees the structure that defined the previous actions. */ +void flow_replace_acts(struct sw_flow *flow, const struct ofp_action *actions, + int n_actions) +{ + struct sw_flow_actions *sfa; + struct sw_flow_actions *orig_sfa = flow->sf_acts; + int size = sizeof *sfa + (n_actions * sizeof sfa->actions[0]); + + sfa = kmalloc(size, GFP_ATOMIC); + if (unlikely(!sfa)) + return; + + sfa->n_actions = n_actions; + memcpy(sfa->actions, actions, n_actions * sizeof sfa->actions[0]); + + rcu_assign_pointer(flow->sf_acts, sfa); + flow_deferred_free_acts(orig_sfa); + + return; +} +EXPORT_SYMBOL(flow_replace_acts); + /* Prints a representation of 'key' to the kernel log. */ void print_flow(const struct sw_flow_key *key) { diff --git a/datapath/flow.h b/datapath/flow.h index cd58327c6..3b25ad5d6 100644 --- a/datapath/flow.h +++ b/datapath/flow.h @@ -56,6 +56,16 @@ static inline void check_key_align(void) BUILD_BUG_ON(sizeof(struct sw_flow_key) != 44); } +/* We keep actions as a separate structure because we need to be able to + * swap them out atomically when the modify command comes from a Flow + * Modify message. */ +struct sw_flow_actions { + unsigned int n_actions; + struct rcu_head rcu; + + struct ofp_action actions[0]; +}; + /* Locking: * * - Readers must take rcu_read_lock and hold it the entire time that the flow @@ -69,11 +79,9 @@ struct sw_flow { uint16_t priority; /* Only used on entries with wildcards. */ uint16_t idle_timeout; /* Idle time before discarding (seconds). */ uint16_t hard_timeout; /* Hard expiration time (seconds) */ - unsigned long used; /* Last used time (in jiffies). */ + unsigned long used; /* Last used time (in jiffies). */ - /* FIXME? Probably most flows have only a single action. */ - unsigned int n_actions; - struct ofp_action *actions; + struct sw_flow_actions *sf_acts; /* For use by table implementation. */ struct list_head node; @@ -96,6 +104,8 @@ int flow_del_matches(const struct sw_flow_key *, const struct sw_flow_key *, struct sw_flow *flow_alloc(int n_actions, gfp_t flags); void flow_free(struct sw_flow *); void flow_deferred_free(struct sw_flow *); +void flow_deferred_free_acts(struct sw_flow_actions *); +void flow_replace_acts(struct sw_flow *, const struct ofp_action *, int); int flow_extract(struct sk_buff *, uint16_t in_port, struct sw_flow_key *); void flow_extract_match(struct sw_flow_key* to, const struct ofp_match* from); void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from); diff --git a/datapath/forward.c b/datapath/forward.c index 433465537..7d7757167 100644 --- a/datapath/forward.c +++ b/datapath/forward.c @@ -56,9 +56,10 @@ int run_flow_through_tables(struct sw_chain *chain, struct sk_buff *skb, flow = chain_lookup(chain, &key); if (likely(flow != NULL)) { + struct sw_flow_actions *sf_acts = rcu_dereference(flow->sf_acts); flow_used(flow, skb); execute_actions(chain->dp, skb, &key, - flow->actions, flow->n_actions, 0); + sf_acts->actions, sf_acts->n_actions, 0); return 0; } else { return -ESRCH; @@ -454,12 +455,12 @@ add_flow(struct sw_chain *chain, const struct ofp_flow_mod *ofm) flow->idle_timeout = ntohs(ofm->idle_timeout); flow->hard_timeout = ntohs(ofm->hard_timeout); flow->used = jiffies; - flow->n_actions = n_actions; flow->init_time = jiffies; flow->byte_count = 0; flow->packet_count = 0; spin_lock_init(&flow->lock); - memcpy(flow->actions, ofm->actions, n_actions * sizeof *flow->actions); + memcpy(flow->sf_acts->actions, ofm->actions, + n_actions * sizeof *flow->sf_acts->actions); /* Act. */ error = chain_insert(chain, flow); @@ -487,6 +488,53 @@ error: return error; } +static int +mod_flow(struct sw_chain *chain, const struct ofp_flow_mod *ofm) +{ + int error = -ENOMEM; + int i; + int n_actions; + struct sw_flow_key key; + + /* To prevent loops, make sure there's no action to send to the + * OFP_TABLE virtual port. + */ + n_actions = (ntohs(ofm->header.length) - sizeof *ofm) + / sizeof *ofm->actions; + for (i=0; iactions[i]; + + if (a->type == htons(OFPAT_OUTPUT) + && (a->arg.output.port == htons(OFPP_TABLE) + || a->arg.output.port == htons(OFPP_NONE) + || a->arg.output.port == ofm->match.in_port)) { + /* xxx Send fancy new error message? */ + goto error; + } + } + + flow_extract_match(&key, &ofm->match); + chain_modify(chain, &key, ofm->actions, n_actions); + + if (ntohl(ofm->buffer_id) != (uint32_t) -1) { + struct sk_buff *skb = retrieve_skb(ntohl(ofm->buffer_id)); + if (skb) { + struct sw_flow_key skb_key; + flow_extract(skb, ntohs(ofm->match.in_port), &skb_key); + execute_actions(chain->dp, skb, &skb_key, + ofm->actions, n_actions, 0); + } + else + error = -ESRCH; + } + return error; + +error: + if (ntohl(ofm->buffer_id) != (uint32_t) -1) + discard_skb(ntohl(ofm->buffer_id)); + return error; +} + static int recv_flow(struct sw_chain *chain, const struct sender *sender, const void *msg) { @@ -495,6 +543,8 @@ recv_flow(struct sw_chain *chain, const struct sender *sender, const void *msg) if (command == OFPFC_ADD) { return add_flow(chain, ofm); + } else if (command == OFPFC_MODIFY) { + return mod_flow(chain, ofm); } else if (command == OFPFC_DELETE) { struct sw_flow_key key; flow_extract_match(&key, &ofm->match); diff --git a/datapath/hwtable_dummy/hwtable_dummy.c b/datapath/hwtable_dummy/hwtable_dummy.c index e3e0eb411..b78d36431 100644 --- a/datapath/hwtable_dummy/hwtable_dummy.c +++ b/datapath/hwtable_dummy/hwtable_dummy.c @@ -97,6 +97,24 @@ static int table_dummy_insert(struct sw_table *swt, struct sw_flow *flow) return 0; } +static int table_dummy_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_dummy *td = (struct sw_table_dummy *) swt; + struct sw_flow *flow; + unsigned int count = 0; + + list_for_each_entry (flow, &td->flows, node) { + if (flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + /* xxx Do whatever is necessary to modify the entry in hardware */ + count++; + } + } + return count; +} + static int do_delete(struct sw_table *swt, struct sw_flow *flow) { @@ -233,6 +251,7 @@ static struct sw_table *table_dummy_create(void) swt = &td->swt; swt->lookup = table_dummy_lookup; swt->insert = table_dummy_insert; + swt->modify = table_dummy_modify; swt->delete = table_dummy_delete; swt->timeout = table_dummy_timeout; swt->destroy = table_dummy_destroy; diff --git a/datapath/table-hash.c b/datapath/table-hash.c index b31e8f00c..f6a41de5c 100644 --- a/datapath/table-hash.c +++ b/datapath/table-hash.c @@ -75,6 +75,35 @@ static int table_hash_insert(struct sw_table *swt, struct sw_flow *flow) return retval; } +static int table_hash_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_hash *th = (struct sw_table_hash *) swt; + unsigned int count = 0; + + if (key->wildcards == 0) { + struct sw_flow **bucket = find_bucket(swt, key); + struct sw_flow *flow = *bucket; + if (flow && flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + count = 1; + } + } else { + unsigned int i; + + for (i = 0; i <= th->bucket_mask; i++) { + struct sw_flow **bucket = &th->buckets[i]; + struct sw_flow *flow = *bucket; + if (flow && flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + count++; + } + } + } + return count; +} + /* Caller must update n_flows. */ static int do_delete(struct sw_flow **bucket, struct sw_flow *flow) { @@ -261,6 +290,15 @@ static int table_hash2_insert(struct sw_table *swt, struct sw_flow *flow) return table_hash_insert(t2->subtable[1], flow); } +static int table_hash2_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_hash2 *t2 = (struct sw_table_hash2 *) swt; + return (table_hash_modify(t2->subtable[0], key, actions, n_actions) + + table_hash_modify(t2->subtable[1], key, actions, n_actions)); +} + static int table_hash2_delete(struct sw_table *swt, const struct sw_flow_key *key, uint16_t priority, int strict) @@ -344,6 +382,7 @@ struct sw_table *table_hash2_create(unsigned int poly0, unsigned int buckets0, swt = &t2->swt; swt->lookup = table_hash2_lookup; swt->insert = table_hash2_insert; + swt->modify = table_hash2_modify; swt->delete = table_hash2_delete; swt->timeout = table_hash2_timeout; swt->destroy = table_hash2_destroy; diff --git a/datapath/table-linear.c b/datapath/table-linear.c index a9c7dcc25..4a10df550 100644 --- a/datapath/table-linear.c +++ b/datapath/table-linear.c @@ -77,6 +77,23 @@ static int table_linear_insert(struct sw_table *swt, struct sw_flow *flow) return 1; } +static int table_linear_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_linear *tl = (struct sw_table_linear *) swt; + struct sw_flow *flow; + unsigned int count = 0; + + list_for_each_entry (flow, &tl->flows, node) { + if (flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + count++; + } + } + return count; +} + static int do_delete(struct sw_table *swt, struct sw_flow *flow) { list_del_rcu(&flow->node); @@ -181,6 +198,7 @@ struct sw_table *table_linear_create(unsigned int max_flows) swt = &tl->swt; swt->lookup = table_linear_lookup; swt->insert = table_linear_insert; + swt->modify = table_linear_modify; swt->delete = table_linear_delete; swt->timeout = table_linear_timeout; swt->destroy = table_linear_destroy; diff --git a/datapath/table.h b/datapath/table.h index c47e1e60c..8cbcfb7a6 100644 --- a/datapath/table.h +++ b/datapath/table.h @@ -8,6 +8,7 @@ struct sw_flow; struct sw_flow_key; +struct ofp_action; struct datapath; /* Table statistics. */ @@ -54,6 +55,11 @@ struct sw_table { * retained by the caller. */ int (*insert)(struct sw_table *table, struct sw_flow *flow); + /* Modifies the actions in 'table' that match 'key'. Returns the + * number of flows that were modified. */ + int (*modify)(struct sw_table *table, const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions); + /* Deletes from 'table' any and all flows that match 'key' from * 'table'. If 'strict' set, wildcards and priority must match. * Returns the number of flows that were deleted. */ diff --git a/datapath/table_t.c b/datapath/table_t.c index 5f7cd1cb4..a824804b4 100644 --- a/datapath/table_t.c +++ b/datapath/table_t.c @@ -35,9 +35,9 @@ static struct sw_flow *flow_zalloc(int n_actions, gfp_t flags) { struct sw_flow *flow = flow_alloc(n_actions, flags); if (flow) { - struct ofp_action *actions = flow->actions; + struct sw_flow_actions *sfa = flow->sf_acts; memset(flow, 0, sizeof *flow); - flow->actions = actions; + flow->sf_acts = sfa; } return flow; } diff --git a/include/openflow.h b/include/openflow.h index 77dbc52a2..e04cfbedb 100644 --- a/include/openflow.h +++ b/include/openflow.h @@ -353,7 +353,6 @@ OFP_ASSERT(sizeof(struct ofp_packet_out) == 16); enum ofp_flow_mod_command { OFPFC_ADD, /* New flow. */ OFPFC_MODIFY, /* Modify all matching flows. */ - OFPFC_MODIFY_STRICT, /* Strictly match wildcards and priority. */ OFPFC_DELETE, /* Delete all matching flows. */ OFPFC_DELETE_STRICT /* Strictly match wildcards and priority. */ }; @@ -433,10 +432,11 @@ struct ofp_flow_mod { uint16_t idle_timeout; /* Idle time before discarding (seconds). */ uint16_t hard_timeout; /* Max time before discarding (seconds). */ uint16_t priority; /* Priority level of flow entry. */ - uint32_t buffer_id; /* Buffered packet to apply to (or -1). */ + uint32_t buffer_id; /* Buffered packet to apply to (or -1). + Not meaningful for OFPFC_DELETE*. */ uint32_t reserved; /* Reserved for future use. */ struct ofp_action actions[0]; /* The number of actions is inferred from - the length field in the header. */ + the length field in the header. */ }; OFP_ASSERT(sizeof(struct ofp_flow_mod) == 60); diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 1f16396a1..95d180bd9 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -558,9 +558,24 @@ ofp_print_flow_mod(struct ds *string, const void *oh, size_t len, const struct ofp_flow_mod *ofm = oh; ofp_print_match(string, &ofm->match, verbosity); - ds_put_format(string, " cmd:%d idle:%d hard:%d pri:%d buf:%#x", - ntohs(ofm->command), ntohs(ofm->idle_timeout), - ntohs(ofm->hard_timeout), + switch (ntohs(ofm->command)) { + case OFPFC_ADD: + ds_put_cstr(string, " ADD: "); + break; + case OFPFC_MODIFY: + ds_put_cstr(string, " MOD: "); + break; + case OFPFC_DELETE: + ds_put_cstr(string, " DEL: "); + break; + case OFPFC_DELETE_STRICT: + ds_put_cstr(string, " DEL_STRICT: "); + break; + default: + ds_put_format(string, " cmd:%d ", ntohs(ofm->command)); + } + ds_put_format(string, "idle:%d hard:%d pri:%d buf:%#x", + ntohs(ofm->idle_timeout), ntohs(ofm->hard_timeout), ofm->match.wildcards ? ntohs(ofm->priority) : (uint16_t)-1, ntohl(ofm->buffer_id)); ofp_print_actions(string, ofm->actions, diff --git a/switch/chain.c b/switch/chain.c index 8419070b3..0388951da 100644 --- a/switch/chain.c +++ b/switch/chain.c @@ -114,6 +114,27 @@ chain_insert(struct sw_chain *chain, struct sw_flow *flow) return -ENOBUFS; } +/* Modifies actions in 'chain' that match 'key'. Returns the number of + * flows that were modified. + * + * Expensive in the general case as currently implemented, since it requires + * iterating through the entire contents of each table for keys that contain + * wildcards. Relatively cheap for fully specified keys. */ +int +chain_modify(struct sw_chain *chain, const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + int count = 0; + int i; + + for (i = 0; i < chain->n_tables; i++) { + struct sw_table *t = chain->tables[i]; + count += t->modify(t, key, actions, n_actions); + } + + return count; +} + /* Deletes from 'chain' any and all flows that match 'key'. Returns the number * of flows that were deleted. * diff --git a/switch/chain.h b/switch/chain.h index 7e5b0ad03..eaccad41f 100644 --- a/switch/chain.h +++ b/switch/chain.h @@ -38,6 +38,7 @@ struct sw_flow; struct sw_flow_key; +struct ofp_action; struct list; #define TABLE_LINEAR_MAX_FLOWS 100 @@ -55,6 +56,8 @@ struct sw_chain { struct sw_chain *chain_create(void); struct sw_flow *chain_lookup(struct sw_chain *, const struct sw_flow_key *); int chain_insert(struct sw_chain *, struct sw_flow *); +int chain_modify(struct sw_chain *, const struct sw_flow_key *, + const struct ofp_action *, int); int chain_delete(struct sw_chain *, const struct sw_flow_key *, uint16_t, int); void chain_timeout(struct sw_chain *, struct list *deleted); void chain_destroy(struct sw_chain *); diff --git a/switch/datapath.c b/switch/datapath.c index 7ed4f2d8a..24617efbf 100644 --- a/switch/datapath.c +++ b/switch/datapath.c @@ -809,7 +809,7 @@ fill_flow_stats(struct buffer *buffer, struct sw_flow *flow, int table_idx, time_t now) { struct ofp_flow_stats *ofs; - int length = sizeof *ofs + sizeof *ofs->actions * flow->n_actions; + int length = sizeof *ofs + sizeof *ofs->actions * flow->sf_acts->n_actions; ofs = buffer_put_uninit(buffer, length); ofs->length = htons(length); ofs->table_id = table_idx; @@ -833,8 +833,8 @@ fill_flow_stats(struct buffer *buffer, struct sw_flow *flow, memset(ofs->pad2, 0, sizeof ofs->pad2); ofs->packet_count = htonll(flow->packet_count); ofs->byte_count = htonll(flow->byte_count); - memcpy(ofs->actions, flow->actions, - sizeof *ofs->actions * flow->n_actions); + memcpy(ofs->actions, flow->sf_acts->actions, + sizeof *ofs->actions * flow->sf_acts->n_actions); } @@ -866,7 +866,8 @@ int run_flow_through_tables(struct datapath *dp, struct buffer *buffer, if (flow != NULL) { flow_used(flow, buffer); execute_actions(dp, buffer, port_no(dp, p), - &key, flow->actions, flow->n_actions, false); + &key, flow->sf_acts->actions, + flow->sf_acts->n_actions, false); return 0; } else { return -ESRCH; @@ -1178,10 +1179,11 @@ add_flow(struct datapath *dp, const struct ofp_flow_mod *ofm) flow->idle_timeout = ntohs(ofm->idle_timeout); flow->hard_timeout = ntohs(ofm->hard_timeout); flow->used = flow->created = time_now(); - flow->n_actions = n_actions; + flow->sf_acts->n_actions = n_actions; flow->byte_count = 0; flow->packet_count = 0; - memcpy(flow->actions, ofm->actions, n_actions * sizeof *flow->actions); + memcpy(flow->sf_acts->actions, ofm->actions, + n_actions * sizeof *flow->sf_acts->actions); /* Act. */ error = chain_insert(dp->chain, flow); @@ -1212,6 +1214,55 @@ error: return error; } +static int +mod_flow(struct datapath *dp, const struct ofp_flow_mod *ofm) +{ + int error = -ENOMEM; + int n_actions; + int i; + struct sw_flow_key key; + + + /* To prevent loops, make sure there's no action to send to the + * OFP_TABLE virtual port. + */ + n_actions = (ntohs(ofm->header.length) - sizeof *ofm) + / sizeof *ofm->actions; + for (i=0; iactions[i]; + + if (a->type == htons(OFPAT_OUTPUT) + && (a->arg.output.port == htons(OFPP_TABLE) + || a->arg.output.port == htons(OFPP_NONE) + || a->arg.output.port == ofm->match.in_port)) { + /* xxx Send fancy new error message? */ + goto error; + } + } + + flow_extract_match(&key, &ofm->match); + chain_modify(dp->chain, &key, ofm->actions, n_actions); + + if (ntohl(ofm->buffer_id) != UINT32_MAX) { + struct buffer *buffer = retrieve_buffer(ntohl(ofm->buffer_id)); + if (buffer) { + struct sw_flow_key skb_key; + uint16_t in_port = ntohs(ofm->match.in_port); + flow_extract(buffer, in_port, &skb_key.flow); + execute_actions(dp, buffer, in_port, &skb_key, + ofm->actions, n_actions, false); + } else { + error = -ESRCH; + } + } + return error; + +error: + if (ntohl(ofm->buffer_id) != (uint32_t) -1) + discard_buffer(ntohl(ofm->buffer_id)); + return error; +} + static int recv_flow(struct datapath *dp, const struct sender *sender UNUSED, const void *msg) @@ -1221,6 +1272,8 @@ recv_flow(struct datapath *dp, const struct sender *sender UNUSED, if (command == OFPFC_ADD) { return add_flow(dp, ofm); + } else if (command == OFPFC_MODIFY) { + return mod_flow(dp, ofm); } else if (command == OFPFC_DELETE) { struct sw_flow_key key; flow_extract_match(&key, &ofm->match); diff --git a/switch/switch-flow.c b/switch/switch-flow.c index 16a7177e1..cc462c0ef 100644 --- a/switch/switch-flow.c +++ b/switch/switch-flow.c @@ -171,16 +171,18 @@ flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from) struct sw_flow * flow_alloc(int n_actions) { + struct sw_flow_actions *sfa; struct sw_flow *flow = malloc(sizeof *flow); if (!flow) return NULL; - flow->n_actions = n_actions; - flow->actions = malloc(n_actions * sizeof *flow->actions); - if (!flow->actions && n_actions > 0) { + sfa = malloc(n_actions * sizeof sfa->actions[0]); + if (!sfa) { free(flow); return NULL; } + sfa->n_actions = n_actions; + flow->sf_acts = sfa; return flow; } @@ -191,12 +193,31 @@ flow_free(struct sw_flow *flow) if (!flow) { return; } - if (flow->actions) { - free(flow->actions); - } + free(flow->sf_acts); free(flow); } +/* Copies 'actions' into a newly allocated structure for use by 'flow' + * and frees the structure that defined the previous actions. */ +void flow_replace_acts(struct sw_flow *flow, const struct ofp_action *actions, + int n_actions) +{ + struct sw_flow_actions *sfa; + int size = sizeof *sfa + (n_actions * sizeof sfa->actions[0]); + + sfa = malloc(size); + if (unlikely(!sfa)) + return; + + sfa->n_actions = n_actions; + memcpy(sfa->actions, actions, n_actions * sizeof sfa->actions[0]); + + free(flow->sf_acts); + flow->sf_acts = sfa; + + return; +} + /* Prints a representation of 'key' to the kernel log. */ void print_flow(const struct sw_flow_key *key) diff --git a/switch/switch-flow.h b/switch/switch-flow.h index 0f6c72161..0eb8c8996 100644 --- a/switch/switch-flow.h +++ b/switch/switch-flow.h @@ -35,6 +35,7 @@ #define SWITCH_FLOW_H 1 #include +#include "openflow.h" #include "flow.h" #include "list.h" @@ -48,6 +49,11 @@ struct sw_flow_key { uint32_t nw_dst_mask; /* 1-bit in each significant nw_dst bit. */ }; +struct sw_flow_actions { + unsigned int n_actions; + struct ofp_action actions[0]; +}; + struct sw_flow { struct sw_flow_key key; @@ -60,14 +66,12 @@ struct sw_flow { uint64_t byte_count; /* Number of bytes seen. */ uint8_t reason; /* Reason flow expired (one of OFPER_*). */ + struct sw_flow_actions *sf_acts; + /* Private to table implementations. */ struct list node; struct list iter_node; unsigned long int serial; - - /* Actions (XXX probably most flows have only a single action). */ - unsigned int n_actions; - struct ofp_action *actions; }; int flow_matches_1wild(const struct sw_flow_key *, const struct sw_flow_key *); @@ -77,6 +81,8 @@ int flow_del_matches(const struct sw_flow_key *, const struct sw_flow_key *, struct sw_flow *flow_alloc(int n_actions); void flow_free(struct sw_flow *); void flow_deferred_free(struct sw_flow *); +void flow_deferred_free_acts(struct sw_flow_actions *); +void flow_replace_acts(struct sw_flow *, const struct ofp_action *, int); void flow_extract_match(struct sw_flow_key* to, const struct ofp_match* from); void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from); diff --git a/switch/table-hash.c b/switch/table-hash.c index 7e675a149..d8d17db14 100644 --- a/switch/table-hash.c +++ b/switch/table-hash.c @@ -97,6 +97,35 @@ static int table_hash_insert(struct sw_table *swt, struct sw_flow *flow) return retval; } +static int table_hash_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_hash *th = (struct sw_table_hash *) swt; + unsigned int count = 0; + + if (key->wildcards == 0) { + struct sw_flow **bucket = find_bucket(swt, key); + struct sw_flow *flow = *bucket; + if (flow && flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + count = 1; + } + } else { + unsigned int i; + + for (i = 0; i <= th->bucket_mask; i++) { + struct sw_flow **bucket = &th->buckets[i]; + struct sw_flow *flow = *bucket; + if (flow && flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + count++; + } + } + } + return count; +} + /* Caller must update n_flows. */ static void do_delete(struct sw_flow **bucket) @@ -234,6 +263,7 @@ struct sw_table *table_hash_create(unsigned int polynomial, swt = &th->swt; swt->lookup = table_hash_lookup; swt->insert = table_hash_insert; + swt->modify = table_hash_modify; swt->delete = table_hash_delete; swt->timeout = table_hash_timeout; swt->destroy = table_hash_destroy; @@ -275,6 +305,15 @@ static int table_hash2_insert(struct sw_table *swt, struct sw_flow *flow) return table_hash_insert(t2->subtable[1], flow); } +static int table_hash2_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_hash2 *t2 = (struct sw_table_hash2 *) swt; + return (table_hash_modify(t2->subtable[0], key, actions, n_actions) + + table_hash_modify(t2->subtable[1], key, actions, n_actions)); +} + static int table_hash2_delete(struct sw_table *swt, const struct sw_flow_key *key, uint16_t priority, int strict) @@ -359,6 +398,7 @@ struct sw_table *table_hash2_create(unsigned int poly0, unsigned int buckets0, swt = &t2->swt; swt->lookup = table_hash2_lookup; swt->insert = table_hash2_insert; + swt->modify = table_hash2_modify; swt->delete = table_hash2_delete; swt->timeout = table_hash2_timeout; swt->destroy = table_hash2_destroy; diff --git a/switch/table-linear.c b/switch/table-linear.c index 355bda279..474d5ee3c 100644 --- a/switch/table-linear.c +++ b/switch/table-linear.c @@ -105,6 +105,23 @@ static int table_linear_insert(struct sw_table *swt, struct sw_flow *flow) return 1; } +static int table_linear_modify(struct sw_table *swt, + const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions) +{ + struct sw_table_linear *tl = (struct sw_table_linear *) swt; + struct sw_flow *flow; + unsigned int count = 0; + + LIST_FOR_EACH (flow, struct sw_flow, node, &tl->flows) { + if (flow_matches_1wild(&flow->key, key)) { + flow_replace_acts(flow, actions, n_actions); + count++; + } + } + return count; +} + static void do_delete(struct sw_flow *flow) { @@ -207,6 +224,7 @@ struct sw_table *table_linear_create(unsigned int max_flows) swt = &tl->swt; swt->lookup = table_linear_lookup; swt->insert = table_linear_insert; + swt->modify = table_linear_modify; swt->delete = table_linear_delete; swt->timeout = table_linear_timeout; swt->destroy = table_linear_destroy; diff --git a/switch/table.h b/switch/table.h index 1068a48fa..9a48c4b5d 100644 --- a/switch/table.h +++ b/switch/table.h @@ -41,6 +41,7 @@ struct sw_flow; struct sw_flow_key; +struct ofp_action; struct list; /* Table statistics. */ @@ -83,6 +84,11 @@ struct sw_table { * retained by the caller. */ int (*insert)(struct sw_table *table, struct sw_flow *flow); + /* Modifies entries in 'table' that match 'key'. Returns the + * number of flows that were modified. */ + int (*modify)(struct sw_table *table, const struct sw_flow_key *key, + const struct ofp_action *actions, int n_actions); + /* Deletes from 'table' any and all flows that match 'key' from * 'table'. If 'strict' set, wildcards must match. Returns the * number of flows that were deleted. */ diff --git a/utilities/dpctl.8 b/utilities/dpctl.8 index 4c863e559..870ffb907 100644 --- a/utilities/dpctl.8 +++ b/utilities/dpctl.8 @@ -169,6 +169,12 @@ Add flow entries as described in \fIfile\fR to the datapath \fIswitch\fR's tables. Each line in \fIfile\fR is a flow entry in the format described in \fBFLOW SYNTAX\fR, below. +.TP +\fBmod-flows \fIswitch flow\fR +Modify the actions in entries from the datapath \fIswitch\fR's tables +that match \fIflow\fR. See \fBFLOW SYNTAX\fR, below, for the syntax +of \fIflows\fR. + .TP \fBdel-flows \fIswitch \fR[\fIflow\fR] Deletes entries from the datapath \fIswitch\fR's tables that match diff --git a/utilities/dpctl.c b/utilities/dpctl.c index af0593f5c..bd89065d6 100644 --- a/utilities/dpctl.c +++ b/utilities/dpctl.c @@ -204,7 +204,8 @@ usage(void) " dump-aggregate SWITCH FLOW print aggregate stats for FLOWs\n" " add-flow SWITCH FLOW add flow described by FLOW\n" " add-flows SWITCH FILE add flows from FILE\n" - " del-flows SWITCH FLOW delete matching FLOWs\n" + " mod-flows SWITCH FLOW modify actions of matching FLOWs\n" + " del-flows SWITCH [FLOW] delete matching FLOWs\n" " monitor SWITCH print packets received from SWITCH\n" "\nFor local datapaths, remote switches, and controllers:\n" " probe VCONN probe whether VCONN is up\n" @@ -799,8 +800,6 @@ static void do_add_flow(int argc, char *argv[]) size_t size; int n_actions = MAX_ADD_ACTS; - open_vconn(argv[1], &vconn); - /* Parse and send. */ size = sizeof *ofm + (sizeof ofm->actions[0] * MAX_ADD_ACTS); ofm = make_openflow(size, OFPT_FLOW_MOD, &buffer); @@ -816,6 +815,7 @@ static void do_add_flow(int argc, char *argv[]) /* xxx Should we use the buffer library? */ buffer->size -= (MAX_ADD_ACTS - n_actions) * sizeof ofm->actions[0]; + open_vconn(argv[1], &vconn); send_openflow_buffer(vconn, buffer); vconn_close(vconn); } @@ -823,7 +823,6 @@ static void do_add_flow(int argc, char *argv[]) static void do_add_flows(int argc, char *argv[]) { struct vconn *vconn; - FILE *file; char line[1024]; @@ -874,17 +873,43 @@ static void do_add_flows(int argc, char *argv[]) fclose(file); } -static void do_del_flows(int argc, char *argv[]) +static void do_mod_flows(int argc, char *argv[]) { + uint16_t idle_timeout, hard_timeout; struct vconn *vconn; - uint16_t priority; + struct buffer *buffer; + struct ofp_flow_mod *ofm; + size_t size; + int n_actions = MAX_ADD_ACTS; + + /* Parse and send. */ + size = sizeof *ofm + (sizeof ofm->actions[0] * MAX_ADD_ACTS); + ofm = make_openflow(size, OFPT_FLOW_MOD, &buffer); + str_to_flow(argv[2], &ofm->match, &ofm->actions[0], &n_actions, + NULL, NULL, &idle_timeout, &hard_timeout); + ofm->command = htons(OFPFC_MODIFY); + ofm->idle_timeout = htons(idle_timeout); + ofm->hard_timeout = htons(hard_timeout); + ofm->buffer_id = htonl(UINT32_MAX); + ofm->priority = htons(0); + ofm->reserved = htonl(0); + + /* xxx Should we use the buffer library? */ + buffer->size -= (MAX_ADD_ACTS - n_actions) * sizeof ofm->actions[0]; open_vconn(argv[1], &vconn); + send_openflow_buffer(vconn, buffer); + vconn_close(vconn); +} + +static void do_del_flows(int argc, char *argv[]) +{ + struct vconn *vconn; + uint16_t priority; struct buffer *buffer; struct ofp_flow_mod *ofm; size_t size; - /* Parse and send. */ size = sizeof *ofm; ofm = make_openflow(size, OFPT_FLOW_MOD, &buffer); @@ -897,8 +922,8 @@ static void do_del_flows(int argc, char *argv[]) ofm->priority = htons(priority); ofm->reserved = htonl(0); + open_vconn(argv[1], &vconn); send_openflow_buffer(vconn, buffer); - vconn_close(vconn); } @@ -1140,6 +1165,7 @@ static struct command all_commands[] = { { "dump-aggregate", 1, 2, do_dump_aggregate }, { "add-flow", 2, 2, do_add_flow }, { "add-flows", 2, 2, do_add_flows }, + { "mod-flows", 2, 2, do_mod_flows }, { "del-flows", 1, 2, do_del_flows }, { "dump-ports", 1, 1, do_dump_ports }, { "mod-port", 3, 3, do_mod_port }, -- 2.43.0