This patch is creates an issue where dump-flow might have missing or duplicate flows. I will post other patches to fix it.
--8<--------------------------cut here-------------------------->8-- Following patch introduces a timer based event to rehash flow-hash table. It makes finding collisions difficult to for an attacker. Suggested-by: Herbert Xu <herb...@gondor.apana.org.au> Signed-off-by: Pravin B Shelar <pshe...@nicira.com> --- datapath/datapath.c | 171 ++++++++++++++++++++++++++++++++++---------------- datapath/datapath.h | 2 + datapath/flow.c | 84 +++++++++++++++++-------- datapath/flow.h | 7 ++- 4 files changed, 180 insertions(+), 84 deletions(-) diff --git a/datapath/datapath.c b/datapath/datapath.c index acbd3bf..ae6b39a 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -63,6 +63,10 @@ #error Kernels before 2.6.18 or after 3.2 are not supported by this version of Open vSwitch. #endif +#define REBUILD_INTERVAL (HZ * 10 * 60) + +static struct timer_list ovs_table_rebuild_timer; + int (*ovs_dp_ioctl_hook)(struct net_device *dev, struct ifreq *rq, int cmd); EXPORT_SYMBOL(ovs_dp_ioctl_hook); @@ -313,7 +317,6 @@ void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb) stats_counter = &stats->n_missed; goto out; } - OVS_CB(skb)->flow = flow; } @@ -493,14 +496,16 @@ static int flush_flows(int dp_ifindex) if (!dp) return -ENODEV; - old_table = genl_dereference(dp->table); new_table = ovs_flow_tbl_alloc(TBL_MIN_BUCKETS); if (!new_table) return -ENOMEM; + spin_lock_bh(&dp->table_lock); + old_table = rcu_dereference(dp->table); rcu_assign_pointer(dp->table, new_table); ovs_flow_tbl_deferred_destroy(old_table); + spin_unlock_bh(&dp->table_lock); return 0; } @@ -815,9 +820,12 @@ static struct genl_ops dp_packet_genl_ops[] = { static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats) { int i; - struct flow_table *table = genl_dereference(dp->table); + struct flow_table *table; + rcu_read_lock(); + table = rcu_dereference(dp->table); stats->n_flows = ovs_flow_tbl_count(table); + rcu_read_unlock(); stats->n_hit = stats->n_missed = stats->n_lost = 0; for_each_possible_cpu(i) { @@ -949,7 +957,7 @@ static struct sk_buff *ovs_flow_cmd_alloc_info(struct sw_flow *flow) len += NLMSG_ALIGN(sizeof(struct ovs_header)); - return genlmsg_new(len, GFP_KERNEL); + return genlmsg_new(len, GFP_ATOMIC); } static struct sk_buff *ovs_flow_cmd_build_info(struct sw_flow *flow, @@ -977,6 +985,8 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) struct sk_buff *reply; struct datapath *dp; struct flow_table *table; + struct sw_flow_actions *old_acts; + struct nlattr *acts_attrs; int error; int key_len; @@ -1003,16 +1013,27 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) if (!dp) goto error; - table = genl_dereference(dp->table); + rcu_read_lock(); + table = rcu_dereference(dp->table); flow = ovs_flow_tbl_lookup(table, &key, key_len); + rcu_read_unlock(); if (!flow) { struct sw_flow_actions *acts; + spin_lock_bh(&dp->table_lock); + /* Retry with lock. */ + table = rcu_dereference(dp->table); + flow = ovs_flow_tbl_lookup(table, &key, key_len); + if (flow) { + spin_unlock_bh(&dp->table_lock); + goto found; + } /* Bail out if we're not allowed to create a new flow. */ error = -ENOENT; - if (info->genlhdr->cmd == OVS_FLOW_CMD_SET) + if (info->genlhdr->cmd == OVS_FLOW_CMD_SET) { + spin_unlock_bh(&dp->table_lock); goto error; - + } /* Expand table, if necessary, to make room. */ if (ovs_flow_tbl_need_to_expand(table)) { struct flow_table *new_table; @@ -1021,13 +1042,14 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) if (!IS_ERR(new_table)) { rcu_assign_pointer(dp->table, new_table); ovs_flow_tbl_deferred_destroy(table); - table = genl_dereference(dp->table); + table = rcu_dereference(dp->table); } } /* Allocate flow. */ flow = ovs_flow_alloc(); if (IS_ERR(flow)) { + spin_unlock_bh(&dp->table_lock); error = PTR_ERR(flow); goto error; } @@ -1037,63 +1059,65 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) /* Obtain actions. */ acts = ovs_flow_actions_alloc(a[OVS_FLOW_ATTR_ACTIONS]); error = PTR_ERR(acts); - if (IS_ERR(acts)) + if (IS_ERR(acts)) { + spin_unlock_bh(&dp->table_lock); goto error_free_flow; + } rcu_assign_pointer(flow->sf_acts, acts); /* Put flow in bucket. */ flow->hash = ovs_flow_hash(&key, key_len); ovs_flow_tbl_insert(table, flow); + spin_unlock_bh(&dp->table_lock); reply = ovs_flow_cmd_build_info(flow, dp, info->snd_pid, info->snd_seq, OVS_FLOW_CMD_NEW); - } else { - /* We found a matching flow. */ - struct sw_flow_actions *old_acts; - struct nlattr *acts_attrs; - - /* Bail out if we're not allowed to modify an existing flow. - * We accept NLM_F_CREATE in place of the intended NLM_F_EXCL - * because Generic Netlink treats the latter as a dump - * request. We also accept NLM_F_EXCL in case that bug ever - * gets fixed. - */ - error = -EEXIST; - if (info->genlhdr->cmd == OVS_FLOW_CMD_NEW && - info->nlhdr->nlmsg_flags & (NLM_F_CREATE | NLM_F_EXCL)) - goto error; + goto reply; + } +found: - /* Update actions. */ - old_acts = rcu_dereference_protected(flow->sf_acts, - lockdep_genl_is_held()); - acts_attrs = a[OVS_FLOW_ATTR_ACTIONS]; - if (acts_attrs && - (old_acts->actions_len != nla_len(acts_attrs) || - memcmp(old_acts->actions, nla_data(acts_attrs), - old_acts->actions_len))) { - struct sw_flow_actions *new_acts; - - new_acts = ovs_flow_actions_alloc(acts_attrs); - error = PTR_ERR(new_acts); - if (IS_ERR(new_acts)) - goto error; + /* Bail out if we're not allowed to modify an existing flow. + * We accept NLM_F_CREATE in place of the intended NLM_F_EXCL + * because Generic Netlink treats the latter as a dump + * request. We also accept NLM_F_EXCL in case that bug ever + * gets fixed. + */ + error = -EEXIST; + if (info->genlhdr->cmd == OVS_FLOW_CMD_NEW && + info->nlhdr->nlmsg_flags & (NLM_F_CREATE | NLM_F_EXCL)) + goto error; - rcu_assign_pointer(flow->sf_acts, new_acts); - ovs_flow_deferred_free_acts(old_acts); - } + /* Update actions. */ + old_acts = rcu_dereference_protected(flow->sf_acts, + lockdep_genl_is_held()); + acts_attrs = a[OVS_FLOW_ATTR_ACTIONS]; + if (acts_attrs && + (old_acts->actions_len != nla_len(acts_attrs) || + memcmp(old_acts->actions, nla_data(acts_attrs), old_acts->actions_len))) { + struct sw_flow_actions *new_acts; + + new_acts = ovs_flow_actions_alloc(acts_attrs); + error = PTR_ERR(new_acts); + if (IS_ERR(new_acts)) + goto error; - reply = ovs_flow_cmd_build_info(flow, dp, info->snd_pid, - info->snd_seq, OVS_FLOW_CMD_NEW); + rcu_assign_pointer(flow->sf_acts, new_acts); + ovs_flow_deferred_free_acts(old_acts); + } - /* Clear stats. */ - if (a[OVS_FLOW_ATTR_CLEAR]) { - spin_lock_bh(&flow->lock); - clear_stats(flow); - spin_unlock_bh(&flow->lock); - } + reply = ovs_flow_cmd_build_info(flow, dp, info->snd_pid, + info->snd_seq, OVS_FLOW_CMD_NEW); + + /* Clear stats. */ + if (a[OVS_FLOW_ATTR_CLEAR]) { + spin_lock_bh(&flow->lock); + clear_stats(flow); + spin_unlock_bh(&flow->lock); } + +reply: if (!IS_ERR(reply)) genl_notify(reply, genl_info_net(info), info->snd_pid, ovs_dp_flow_multicast_group.id, info->nlhdr, @@ -1132,8 +1156,10 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info) if (!dp) return -ENODEV; - table = genl_dereference(dp->table); + rcu_read_lock(); + table = rcu_dereference(dp->table); flow = ovs_flow_tbl_lookup(table, &key, key_len); + rcu_read_unlock(); if (!flow) return -ENOENT; @@ -1167,16 +1193,22 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info) if (!dp) return -ENODEV; - table = genl_dereference(dp->table); + spin_lock_bh(&dp->table_lock); + table = rcu_dereference(dp->table); flow = ovs_flow_tbl_lookup(table, &key, key_len); - if (!flow) + if (!flow) { + spin_unlock_bh(&dp->table_lock); return -ENOENT; + } reply = ovs_flow_cmd_alloc_info(flow); - if (!reply) + if (!reply) { + spin_unlock_bh(&dp->table_lock); return -ENOMEM; + } ovs_flow_tbl_remove(table, flow); + spin_unlock_bh(&dp->table_lock); err = ovs_flow_cmd_fill_info(flow, dp, reply, info->snd_pid, info->snd_seq, 0, OVS_FLOW_CMD_DEL); @@ -1199,7 +1231,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) if (!dp) return -ENODEV; - table = genl_dereference(dp->table); + rcu_read_lock(); + table = rcu_dereference(dp->table); for (;;) { struct sw_flow *flow; @@ -1220,6 +1253,7 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) cb->args[0] = bucket; cb->args[1] = obj; } + rcu_read_unlock(); return skb->len; } @@ -1371,6 +1405,7 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info) * /sys/class/net/<devname>/brif later, if sysfs is enabled. */ dp->ifobj.kset = NULL; kobject_init(&dp->ifobj, &dp_ktype); + spin_lock_init(&dp->table_lock); /* Allocate table. */ err = -ENOMEM; @@ -1422,7 +1457,7 @@ err_destroy_local_port: err_destroy_percpu: free_percpu(dp->stats_percpu); err_destroy_table: - ovs_flow_tbl_destroy(genl_dereference(dp->table)); + ovs_flow_tbl_destroy(rcu_dereference(dp->table)); err_free_dp: kfree(dp); err_put_module: @@ -2039,6 +2074,27 @@ error: return err; } +static void ovs_table_rebuild(unsigned long dummy) +{ + struct datapath *dp; + unsigned long now = jiffies; + + list_for_each_entry_rcu(dp, &dps, list_node) { + struct flow_table *new_table; + + spin_lock(&dp->table_lock); + new_table = ovs_flow_tbl_rehash(rcu_dereference(dp->table)); + + if (!IS_ERR(new_table)) { + ovs_flow_tbl_deferred_destroy(rcu_dereference(dp->table)); + rcu_assign_pointer(dp->table, new_table); + } + spin_unlock(&dp->table_lock); + } + + mod_timer(&ovs_table_rebuild_timer, now + REBUILD_INTERVAL); +} + static int __init dp_init(void) { struct sk_buff *dummy_skb; @@ -2069,6 +2125,10 @@ static int __init dp_init(void) if (err < 0) goto error_unreg_notifier; + setup_timer(&ovs_table_rebuild_timer, ovs_table_rebuild, 0); + ovs_table_rebuild_timer.expires = jiffies + REBUILD_INTERVAL; + add_timer(&ovs_table_rebuild_timer); + return 0; error_unreg_notifier: @@ -2086,6 +2146,7 @@ error: static void dp_cleanup(void) { rcu_barrier(); + del_timer(&ovs_table_rebuild_timer); dp_unregister_genl(ARRAY_SIZE(dp_genl_families)); unregister_netdevice_notifier(&ovs_dp_device_notifier); ovs_vport_exit(); diff --git a/datapath/datapath.h b/datapath/datapath.h index 27151b9..76487f6 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -80,6 +80,8 @@ struct datapath { /* Flow table. */ struct flow_table __rcu *table; + /* Protects table and table updates. */ + spinlock_t table_lock; /* Switch ports. */ struct vport __rcu *ports[DP_MAX_PORTS]; diff --git a/datapath/flow.c b/datapath/flow.c index a357077..9e64458 100644 --- a/datapath/flow.c +++ b/datapath/flow.c @@ -47,7 +47,6 @@ #include "vlan.h" static struct kmem_cache *flow_cache; -static unsigned int hash_seed __read_mostly; static int check_header(struct sk_buff *skb, int len) { @@ -263,7 +262,7 @@ struct sw_flow_actions *ovs_flow_actions_alloc(const struct nlattr *actions) if (actions_len > 2 * DP_MAX_PORTS * nla_total_size(4)) return ERR_PTR(-EINVAL); - sfa = kmalloc(sizeof(*sfa) + actions_len, GFP_KERNEL); + sfa = kmalloc(sizeof(*sfa) + actions_len, GFP_ATOMIC); if (!sfa) return ERR_PTR(-ENOMEM); @@ -276,7 +275,7 @@ struct sw_flow *ovs_flow_alloc(void) { struct sw_flow *flow; - flow = kmem_cache_alloc(flow_cache, GFP_KERNEL); + flow = kmem_cache_alloc(flow_cache, GFP_ATOMIC); if (!flow) return ERR_PTR(-ENOMEM); @@ -290,7 +289,7 @@ struct sw_flow *ovs_flow_alloc(void) static struct hlist_head *find_bucket(struct flow_table *table, u32 hash) { - hash = jhash_1word(hash, hash_seed); + hash = jhash_1word(hash, table->hash_seed); return flex_array_get(table->buckets, (hash & (table->n_buckets - 1))); } @@ -301,11 +300,11 @@ static struct flex_array *alloc_buckets(unsigned int n_buckets) int i, err; buckets = flex_array_alloc(sizeof(struct hlist_head *), - n_buckets, GFP_KERNEL); + n_buckets, GFP_ATOMIC); if (!buckets) return NULL; - err = flex_array_prealloc(buckets, 0, n_buckets, GFP_KERNEL); + err = flex_array_prealloc(buckets, 0, n_buckets, GFP_ATOMIC); if (err) { flex_array_free(buckets); return NULL; @@ -325,7 +324,7 @@ static void free_buckets(struct flex_array *buckets) struct flow_table *ovs_flow_tbl_alloc(int new_size) { - struct flow_table *table = kmalloc(sizeof(*table), GFP_KERNEL); + struct flow_table *table = kmalloc(sizeof(*table), GFP_ATOMIC); if (!table) return NULL; @@ -338,6 +337,9 @@ struct flow_table *ovs_flow_tbl_alloc(int new_size) } table->n_buckets = new_size; table->count = 0; + table->node_ver = 0; + table->keep_flows = 0; + get_random_bytes(&table->hash_seed, sizeof(u32)); return table; } @@ -355,17 +357,22 @@ void ovs_flow_tbl_destroy(struct flow_table *table) if (!table) return; + if (table->keep_flows) + goto skip_flows; + for (i = 0; i < table->n_buckets; i++) { struct sw_flow *flow; struct hlist_head *head = flex_array_get(table->buckets, i); struct hlist_node *node, *n; + int ver = table->node_ver; - hlist_for_each_entry_safe(flow, node, n, head, hash_node) { - hlist_del_init_rcu(&flow->hash_node); + hlist_for_each_entry_safe(flow, node, n, head, hash_node[ver]) { + hlist_del_rcu(&flow->hash_node[ver]); flow_free(flow); } } +skip_flows: free_buckets(table->buckets); kfree(table); } @@ -390,12 +397,14 @@ struct sw_flow *ovs_flow_tbl_next(struct flow_table *table, u32 *bucket, u32 *la struct sw_flow *flow; struct hlist_head *head; struct hlist_node *n; + int ver; int i; + ver = table->node_ver; while (*bucket < table->n_buckets) { i = 0; head = flex_array_get(table->buckets, *bucket); - hlist_for_each_entry_rcu(flow, n, head, hash_node) { + hlist_for_each_entry_rcu(flow, n, head, hash_node[ver]) { if (i < *last) { i++; continue; @@ -410,32 +419,53 @@ struct sw_flow *ovs_flow_tbl_next(struct flow_table *table, u32 *bucket, u32 *la return NULL; } -struct flow_table *ovs_flow_tbl_expand(struct flow_table *table) +static void flow_table_copy_flows(struct flow_table *old, struct flow_table *new) { - struct flow_table *new_table; - int n_buckets = table->n_buckets * 2; + int old_ver; int i; - new_table = ovs_flow_tbl_alloc(n_buckets); - if (!new_table) - return ERR_PTR(-ENOMEM); + old_ver = old->node_ver; + new->node_ver = !old_ver; - for (i = 0; i < table->n_buckets; i++) { + /* Insert in new table. */ + for (i = 0; i < old->n_buckets; i++) { struct sw_flow *flow; struct hlist_head *head; - struct hlist_node *n, *pos; + struct hlist_node *n; - head = flex_array_get(table->buckets, i); + head = flex_array_get(old->buckets, i); - hlist_for_each_entry_safe(flow, n, pos, head, hash_node) { - hlist_del_init_rcu(&flow->hash_node); - ovs_flow_tbl_insert(new_table, flow); + hlist_for_each_entry(flow, n, head, hash_node[old_ver]) { + ovs_flow_tbl_insert(new, flow); } } + old->keep_flows = 1; +} + +static struct flow_table *__flow_tbl_rehash(struct flow_table *table, int n_buckets) +{ + struct flow_table *new_table; + + new_table = ovs_flow_tbl_alloc(n_buckets); + if (!new_table) + return ERR_PTR(-ENOMEM); + + flow_table_copy_flows(table, new_table); return new_table; } +struct flow_table *ovs_flow_tbl_rehash(struct flow_table *table) +{ + return __flow_tbl_rehash(table, table->n_buckets); +} + +struct flow_table *ovs_flow_tbl_expand(struct flow_table *table) +{ + return __flow_tbl_rehash(table, (table->n_buckets * 2)); +} + + /* RCU callback used by ovs_flow_deferred_free. */ static void rcu_free_flow_callback(struct rcu_head *rcu) { @@ -828,7 +858,7 @@ struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *table, hash = ovs_flow_hash(key, key_len); head = find_bucket(table, hash); - hlist_for_each_entry_rcu(flow, n, head, hash_node) { + hlist_for_each_entry_rcu(flow, n, head, hash_node[table->node_ver]) { if (flow->hash == hash && !memcmp(&flow->key, key, key_len)) { @@ -843,14 +873,14 @@ void ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow) struct hlist_head *head; head = find_bucket(table, flow->hash); - hlist_add_head_rcu(&flow->hash_node, head); + hlist_add_head_rcu(&flow->hash_node[table->node_ver], head); table->count++; } void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow) { - if (!hlist_unhashed(&flow->hash_node)) { - hlist_del_init_rcu(&flow->hash_node); + if (!hlist_unhashed(&flow->hash_node[table->node_ver])) { + hlist_del_init_rcu(&flow->hash_node[table->node_ver]); table->count--; BUG_ON(table->count < 0); } @@ -1398,8 +1428,6 @@ int ovs_flow_init(void) if (flow_cache == NULL) return -ENOMEM; - get_random_bytes(&hash_seed, sizeof(hash_seed)); - return 0; } diff --git a/datapath/flow.h b/datapath/flow.h index 36e738d..16e3310 100644 --- a/datapath/flow.h +++ b/datapath/flow.h @@ -96,7 +96,7 @@ struct sw_flow_key { struct sw_flow { struct rcu_head rcu; - struct hlist_node hash_node; + struct hlist_node hash_node[2]; u32 hash; struct sw_flow_key key; @@ -174,6 +174,9 @@ struct flow_table { struct flex_array *buckets; unsigned int count, n_buckets; struct rcu_head rcu; + int node_ver; + u32 hash_seed; + bool keep_flows; }; static inline int ovs_flow_tbl_count(struct flow_table *table) @@ -186,6 +189,8 @@ static inline int ovs_flow_tbl_need_to_expand(struct flow_table *table) return (table->count > table->n_buckets); } +struct flow_table *ovs_flow_tbl_rehash(struct flow_table *table); +void flow_table_clear_flows(struct flow_table *old); struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *table, struct sw_flow_key *key, int len); void ovs_flow_tbl_destroy(struct flow_table *table); -- 1.7.1 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev