/* * Copyright (c) 2007-2011 Nicira Networks. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "flow.h"#include "datapath.h"#include <linux/uaccess.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/if_ether.h>#include <linux/if_vlan.h>#include <net/llc_pdu.h>#include <linux/kernel.h>#include <linux/jhash.h>#include <linux/jiffies.h>#include <linux/llc.h>#include <linux/module.h>#include <linux/in.h>#include <linux/rcupdate.h>#include <linux/if_arp.h>#include <linux/ip.h>#include <linux/ipv6.h>#include <linux/tcp.h>#include <linux/udp.h>#include <linux/icmp.h>#include <linux/icmpv6.h>#include <linux/rculist.h>#include <net/ip.h>#include <net/ipv6.h>#include <net/ndisc.h> static struct kmem_cache *flow_cache; static int check_header(struct sk_buff *skb, int len){ if (unlikely(skb->len < len)) return -EINVAL; if (unlikely(!pskb_may_pull(skb, len))) return -ENOMEM; return 0;} static bool arphdr_ok(struct sk_buff *skb){ return pskb_may_pull(skb, skb_network_offset(skb) + sizeof(struct arp_eth_header));} static int check_iphdr(struct sk_buff *skb){ unsigned int nh_ofs = skb_network_offset(skb); unsigned int ip_len; int err; err = check_header(skb, nh_ofs + sizeof(struct iphdr)); if (unlikely(err)) return err; ip_len = ip_hdrlen(skb); if (unlikely(ip_len < sizeof(struct iphdr) || skb->len < nh_ofs + ip_len)) return -EINVAL; skb_set_transport_header(skb, nh_ofs + ip_len); return 0;} static bool tcphdr_ok(struct sk_buff *skb){ int th_ofs = skb_transport_offset(skb); int tcp_len; if (unlikely(!pskb_may_pull(skb, th_ofs + sizeof(struct tcphdr)))) return false; tcp_len = tcp_hdrlen(skb); if (unlikely(tcp_len < sizeof(struct tcphdr) || skb->len < th_ofs + tcp_len)) return false; return true;} static bool udphdr_ok(struct sk_buff *skb){ return pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct udphdr));} static bool icmphdr_ok(struct sk_buff *skb){ return pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct icmphdr));} u64 ovs_flow_used_time(unsigned long flow_jiffies){ struct timespec cur_ts; u64 cur_ms, idle_ms; ktime_get_ts(&cur_ts); idle_ms = jiffies_to_msecs(jiffies - flow_jiffies); cur_ms = (u64)cur_ts.tv_sec * MSEC_PER_SEC + cur_ts.tv_nsec / NSEC_PER_MSEC; return cur_ms - idle_ms;} #define SW_FLOW_KEY_OFFSET(field) \ (offsetof(struct sw_flow_key, field) + \ FIELD_SIZEOF(struct sw_flow_key, field)) static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key, int *key_lenp){ unsigned int nh_ofs = skb_network_offset(skb); unsigned int nh_len; int payload_ofs; struct ipv6hdr *nh; uint8_t nexthdr; __be16 frag_off; int err; *key_lenp = SW_FLOW_KEY_OFFSET(ipv6.label); err = check_header(skb, nh_ofs + sizeof(*nh)); if (unlikely(err)) return err; nh = ipv6_hdr(skb); nexthdr = nh->nexthdr; payload_ofs = (u8 *)(nh + 1) - skb->data; key->ip.proto = NEXTHDR_NONE; key->ip.tos = ipv6_get_dsfield(nh); key->ip.ttl = nh->hop_limit; key->ipv6.label = *(__be32 *)nh & htonl(IPV6_FLOWINFO_FLOWLABEL); key->ipv6.addr.src = nh->saddr; key->ipv6.addr.dst = nh->daddr; payload_ofs = ipv6_skip_exthdr(skb, payload_ofs, &nexthdr, &frag_off); if (unlikely(payload_ofs < 0)) return -EINVAL; if (frag_off) { if (frag_off & htons(~0x7)) key->ip.frag = OVS_FRAG_TYPE_LATER; else key->ip.frag = OVS_FRAG_TYPE_FIRST; } nh_len = payload_ofs - nh_ofs; skb_set_transport_header(skb, nh_ofs + nh_len); key->ip.proto = nexthdr; return nh_len;} static bool icmp6hdr_ok(struct sk_buff *skb){ return pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct icmp6hdr));} #define TCP_FLAGS_OFFSET 13#define TCP_FLAG_MASK 0x3f void ovs_flow_used(struct sw_flow *flow, struct sk_buff *skb){ u8 tcp_flags = 0; if (flow->key.eth.type == htons(ETH_P_IP) && flow->key.ip.proto == IPPROTO_TCP && likely(skb->len >= skb_transport_offset(skb) + sizeof(struct tcphdr))) { u8 *tcp = (u8 *)tcp_hdr(skb); tcp_flags = *(tcp + TCP_FLAGS_OFFSET) & TCP_FLAG_MASK; } spin_lock(&flow->lock); flow->used = jiffies; flow->packet_count++; flow->byte_count += skb->len; flow->tcp_flags |= tcp_flags; spin_unlock(&flow->lock);} struct sw_flow_actions *ovs_flow_actions_alloc(const struct nlattr *actions){ int actions_len = nla_len(actions); struct sw_flow_actions *sfa; /* At least DP_MAX_PORTS actions are required to be able to flood a * packet to every port. Factor of 2 allows for setting VLAN tags, * etc. */ if (actions_len > 2 * DP_MAX_PORTS * nla_total_size(4)) return ERR_PTR(-EINVAL); sfa = kmalloc(sizeof(*sfa) + actions_len, GFP_KERNEL); if (!sfa) return ERR_PTR(-ENOMEM); sfa->actions_len = actions_len; memcpy(sfa->actions, nla_data(actions), actions_len); return sfa;} struct sw_flow *ovs_flow_alloc(void){ struct sw_flow *flow; flow = kmem_cache_alloc(flow_cache, GFP_KERNEL); if (!flow) return ERR_PTR(-ENOMEM); spin_lock_init(&flow->lock); flow->sf_acts = NULL; return flow;} static struct hlist_head *find_bucket(struct flow_table *table, u32 hash){ hash = jhash_1word(hash, table->hash_seed); return flex_array_get(table->buckets, (hash & (table->n_buckets - 1)));} static struct flex_array *alloc_buckets(unsigned int n_buckets){ struct flex_array *buckets; int i, err; buckets = flex_array_alloc(sizeof(struct hlist_head *), n_buckets, GFP_KERNEL); if (!buckets) return NULL; err = flex_array_prealloc(buckets, 0, n_buckets, GFP_KERNEL); if (err) { flex_array_free(buckets); return NULL; } for (i = 0; i < n_buckets; i++) INIT_HLIST_HEAD((struct hlist_head *) flex_array_get(buckets, i)); return buckets;} static void free_buckets(struct flex_array *buckets){ flex_array_free(buckets);} struct flow_table *ovs_flow_tbl_alloc(int new_size){ struct flow_table *table = kmalloc(sizeof(*table), GFP_KERNEL); if (!table) return NULL; table->buckets = alloc_buckets(new_size); if (!table->buckets) { kfree(table); return NULL; } table->n_buckets = new_size; table->count = 0; table->node_ver = 0; table->keep_flows = false; get_random_bytes(&table->hash_seed, sizeof(u32)); return table;} void ovs_flow_tbl_destroy(struct flow_table *table){ int i; 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[ver]) { hlist_del_rcu(&flow->hash_node[ver]); ovs_flow_free(flow); } } skip_flows: free_buckets(table->buckets); kfree(table);} static void flow_tbl_destroy_rcu_cb(struct rcu_head *rcu){ struct flow_table *table = container_of(rcu, struct flow_table, rcu); ovs_flow_tbl_destroy(table);} void ovs_flow_tbl_deferred_destroy(struct flow_table *table){ if (!table) return; call_rcu(&table->rcu, flow_tbl_destroy_rcu_cb);} struct sw_flow *ovs_flow_tbl_next(struct flow_table *table, u32 *bucket, u32 *last){ 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[ver]) { if (i < *last) { i++; continue; } *last = i + 1; return flow; } (*bucket)++; *last = 0; } return NULL;} static void flow_table_copy_flows(struct flow_table *old, struct flow_table *new){ int old_ver; int i; old_ver = old->node_ver; new->node_ver = !old_ver; /* Insert in new table. */ for (i = 0; i < old->n_buckets; i++) { struct sw_flow *flow; struct hlist_head *head; struct hlist_node *n; head = flex_array_get(old->buckets, i); hlist_for_each_entry(flow, n, head, hash_node[old_ver]) ovs_flow_tbl_insert(new, flow); } old->keep_flows = true;} 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);} void ovs_flow_free(struct sw_flow *flow){ if (unlikely(!flow)) return; kfree((struct sf_flow_acts __force *)flow->sf_acts); kmem_cache_free(flow_cache, flow);} /* RCU callback used by ovs_flow_deferred_free. */static void rcu_free_flow_callback(struct rcu_head *rcu){ struct sw_flow *flow = container_of(rcu, struct sw_flow, rcu); ovs_flow_free(flow);} /* Schedules 'flow' to be freed after the next RCU grace period. * The caller must hold rcu_read_lock for this to be sensible. */void ovs_flow_deferred_free(struct sw_flow *flow){ call_rcu(&flow->rcu, rcu_free_flow_callback);} /* RCU callback used by ovs_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 ovs_flow_deferred_free_acts(struct sw_flow_actions *sf_acts){ call_rcu(&sf_acts->rcu, rcu_free_acts_callback);} static int parse_vlan(struct sk_buff *skb, struct sw_flow_key *key){ struct qtag_prefix { __be16 eth_type; /* ETH_P_8021Q */ __be16 tci; }; struct qtag_prefix *qp; if (unlikely(skb->len < sizeof(struct qtag_prefix) + sizeof(__be16))) return 0; if (unlikely(!pskb_may_pull(skb, sizeof(struct qtag_prefix) + sizeof(__be16)))) return -ENOMEM; qp = (struct qtag_prefix *) skb->data; key->eth.tci = qp->tci | htons(VLAN_TAG_PRESENT); __skb_pull(skb, sizeof(struct qtag_prefix)); return 0;} static __be16 parse_ethertype(struct sk_buff *skb){ struct llc_snap_hdr { u8 dsap; /* Always 0xAA */ u8 ssap; /* Always 0xAA */ u8 ctrl; u8 oui[3]; __be16 ethertype; }; struct llc_snap_hdr *llc; __be16 proto; proto = *(__be16 *) skb->data; __skb_pull(skb, sizeof(__be16)); if (ntohs(proto) >= 1536) return proto; if (skb->len < sizeof(struct llc_snap_hdr)) return htons(ETH_P_802_2); if (unlikely(!pskb_may_pull(skb, sizeof(struct llc_snap_hdr)))) return htons(0); llc = (struct llc_snap_hdr *) skb->data; if (llc->dsap != LLC_SAP_SNAP || llc->ssap != LLC_SAP_SNAP || (llc->oui[0] | llc->oui[1] | llc->oui[2]) != 0) return htons(ETH_P_802_2); __skb_pull(skb, sizeof(struct llc_snap_hdr)); return llc->ethertype;} static int parse_icmpv6(struct sk_buff *skb, struct sw_flow_key *key,Lines 1–500 of 1346. Load more
/* * Copyright (c) 2007-2011 Nicira Networks. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "flow.h" #include "datapath.h" #include <linux/uaccess.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/if_ether.h> #include <linux/if_vlan.h> #include <net/llc_pdu.h> #include <linux/kernel.h> #include <linux/jhash.h> #include <linux/jiffies.h> #include <linux/llc.h> #include <linux/module.h> #include <linux/in.h> #include <linux/rcupdate.h> #include <linux/if_arp.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/icmp.h> #include <linux/icmpv6.h> #include <linux/rculist.h> #include <net/ip.h> #include <net/ipv6.h> #include <net/ndisc.h> static struct kmem_cache *flow_cache; static int check_header(struct sk_buff *skb, int len) { if (unlikely(skb->len < len)) return -EINVAL; if (unlikely(!pskb_may_pull(skb, len))) return -ENOMEM; return 0; } static bool arphdr_ok(struct sk_buff *skb) { return pskb_may_pull(skb, skb_network_offset(skb) + sizeof(struct arp_eth_header)); } static int check_iphdr(struct sk_buff *skb) { unsigned int nh_ofs = skb_network_offset(skb); unsigned int ip_len; int err; err = check_header(skb, nh_ofs + sizeof(struct iphdr)); if (unlikely(err)) return err; ip_len = ip_hdrlen(skb); if (unlikely(ip_len < sizeof(struct iphdr) || skb->len < nh_ofs + ip_len)) return -EINVAL; skb_set_transport_header(skb, nh_ofs + ip_len); return 0; } static bool tcphdr_ok(struct sk_buff *skb) { int th_ofs = skb_transport_offset(skb); int tcp_len; if (unlikely(!pskb_may_pull(skb, th_ofs + sizeof(struct tcphdr)))) return false; tcp_len = tcp_hdrlen(skb); if (unlikely(tcp_len < sizeof(struct tcphdr) || skb->len < th_ofs + tcp_len)) return false; return true; } static bool udphdr_ok(struct sk_buff *skb) { return pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct udphdr)); } static bool icmphdr_ok(struct sk_buff *skb) { return pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct icmphdr)); } u64 ovs_flow_used_time(unsigned long flow_jiffies) { struct timespec cur_ts; u64 cur_ms, idle_ms; ktime_get_ts(&cur_ts); idle_ms = jiffies_to_msecs(jiffies - flow_jiffies); cur_ms = (u64)cur_ts.tv_sec * MSEC_PER_SEC + cur_ts.tv_nsec / NSEC_PER_MSEC; return cur_ms - idle_ms; } #define SW_FLOW_KEY_OFFSET(field) \ (offsetof(struct sw_flow_key, field) + \ FIELD_SIZEOF(struct sw_flow_key, field)) static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key, int *key_lenp) { unsigned int nh_ofs = skb_network_offset(skb); unsigned int nh_len; int payload_ofs; struct ipv6hdr *nh; uint8_t nexthdr; __be16 frag_off; int err; *key_lenp = SW_FLOW_KEY_OFFSET(ipv6.label); err = check_header(skb, nh_ofs + sizeof(*nh)); if (unlikely(err)) return err; nh = ipv6_hdr(skb); nexthdr = nh->nexthdr; payload_ofs = (u8 *)(nh + 1) - skb->data; key->ip.proto = NEXTHDR_NONE; key->ip.tos = ipv6_get_dsfield(nh); key->ip.ttl = nh->hop_limit; key->ipv6.label = *(__be32 *)nh & htonl(IPV6_FLOWINFO_FLOWLABEL); key->ipv6.addr.src = nh->saddr; key->ipv6.addr.dst = nh->daddr; payload_ofs = ipv6_skip_exthdr(skb, payload_ofs, &nexthdr, &frag_off); if (unlikely(payload_ofs < 0)) return -EINVAL; if (frag_off) { if (frag_off & htons(~0x7)) key->ip.frag = OVS_FRAG_TYPE_LATER; else key->ip.frag = OVS_FRAG_TYPE_FIRST; } nh_len = payload_ofs - nh_ofs; skb_set_transport_header(skb, nh_ofs + nh_len); key->ip.proto = nexthdr; return nh_len; } static bool icmp6hdr_ok(struct sk_buff *skb) { return pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct icmp6hdr)); } #define TCP_FLAGS_OFFSET 13 #define TCP_FLAG_MASK 0x3f void ovs_flow_used(struct sw_flow *flow, struct sk_buff *skb) { u8 tcp_flags = 0; if (flow->key.eth.type == htons(ETH_P_IP) && flow->key.ip.proto == IPPROTO_TCP && likely(skb->len >= skb_transport_offset(skb) + sizeof(struct tcphdr))) { u8 *tcp = (u8 *)tcp_hdr(skb); tcp_flags = *(tcp + TCP_FLAGS_OFFSET) & TCP_FLAG_MASK; } spin_lock(&flow->lock); flow->used = jiffies; flow->packet_count++; flow->byte_count += skb->len; flow->tcp_flags |= tcp_flags; spin_unlock(&flow->lock); } struct sw_flow_actions *ovs_flow_actions_alloc(const struct nlattr *actions) { int actions_len = nla_len(actions); struct sw_flow_actions *sfa; /* At least DP_MAX_PORTS actions are required to be able to flood a * packet to every port. Factor of 2 allows for setting VLAN tags, * etc. */ if (actions_len > 2 * DP_MAX_PORTS * nla_total_size(4)) return ERR_PTR(-EINVAL); sfa = kmalloc(sizeof(*sfa) + actions_len, GFP_KERNEL); if (!sfa) return ERR_PTR(-ENOMEM); sfa->actions_len = actions_len; memcpy(sfa->actions, nla_data(actions), actions_len); return sfa; } struct sw_flow *ovs_flow_alloc(void) { struct sw_flow *flow; flow = kmem_cache_alloc(flow_cache, GFP_KERNEL); if (!flow) return ERR_PTR(-ENOMEM); spin_lock_init(&flow->lock); flow->sf_acts = NULL; return flow; } static struct hlist_head *find_bucket(struct flow_table *table, u32 hash) { hash = jhash_1word(hash, table->hash_seed); return flex_array_get(table->buckets, (hash & (table->n_buckets - 1))); } static struct flex_array *alloc_buckets(unsigned int n_buckets) { struct flex_array *buckets; int i, err; buckets = flex_array_alloc(sizeof(struct hlist_head *), n_buckets, GFP_KERNEL); if (!buckets) return NULL; err = flex_array_prealloc(buckets, 0, n_buckets, GFP_KERNEL); if (err) { flex_array_free(buckets); return NULL; } for (i = 0; i < n_buckets; i++) INIT_HLIST_HEAD((struct hlist_head *) flex_array_get(buckets, i)); return buckets; } static void free_buckets(struct flex_array *buckets) { flex_array_free(buckets); } struct flow_table *ovs_flow_tbl_alloc(int new_size) { struct flow_table *table = kmalloc(sizeof(*table), GFP_KERNEL); if (!table) return NULL; table->buckets = alloc_buckets(new_size); if (!table->buckets) { kfree(table); return NULL; } table->n_buckets = new_size; table->count = 0; table->node_ver = 0; table->keep_flows = false; get_random_bytes(&table->hash_seed, sizeof(u32)); return table; } void ovs_flow_tbl_destroy(struct flow_table *table) { int i; 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[ver]) { hlist_del_rcu(&flow->hash_node[ver]); ovs_flow_free(flow); } } skip_flows: free_buckets(table->buckets); kfree(table); } static void flow_tbl_destroy_rcu_cb(struct rcu_head *rcu) { struct flow_table *table = container_of(rcu, struct flow_table, rcu); ovs_flow_tbl_destroy(table); } void ovs_flow_tbl_deferred_destroy(struct flow_table *table) { if (!table) return; call_rcu(&table->rcu, flow_tbl_destroy_rcu_cb); } struct sw_flow *ovs_flow_tbl_next(struct flow_table *table, u32 *bucket, u32 *last) { 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[ver]) { if (i < *last) { i++; continue; } *last = i + 1; return flow; } (*bucket)++; *last = 0; } return NULL; } static void flow_table_copy_flows(struct flow_table *old, struct flow_table *new) { int old_ver; int i; old_ver = old->node_ver; new->node_ver = !old_ver; /* Insert in new table. */ for (i = 0; i < old->n_buckets; i++) { struct sw_flow *flow; struct hlist_head *head; struct hlist_node *n; head = flex_array_get(old->buckets, i); hlist_for_each_entry(flow, n, head, hash_node[old_ver]) ovs_flow_tbl_insert(new, flow); } old->keep_flows = true; } 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); } void ovs_flow_free(struct sw_flow *flow) { if (unlikely(!flow)) return; kfree((struct sf_flow_acts __force *)flow->sf_acts); kmem_cache_free(flow_cache, flow); } /* RCU callback used by ovs_flow_deferred_free. */ static void rcu_free_flow_callback(struct rcu_head *rcu) { struct sw_flow *flow = container_of(rcu, struct sw_flow, rcu); ovs_flow_free(flow); } /* Schedules 'flow' to be freed after the next RCU grace period. * The caller must hold rcu_read_lock for this to be sensible. */ void ovs_flow_deferred_free(struct sw_flow *flow) { call_rcu(&flow->rcu, rcu_free_flow_callback); } /* RCU callback used by ovs_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 ovs_flow_deferred_free_acts(struct sw_flow_actions *sf_acts) { call_rcu(&sf_acts->rcu, rcu_free_acts_callback); } static int parse_vlan(struct sk_buff *skb, struct sw_flow_key *key) { struct qtag_prefix { __be16 eth_type; /* ETH_P_8021Q */ __be16 tci; }; struct qtag_prefix *qp; if (unlikely(skb->len < sizeof(struct qtag_prefix) + sizeof(__be16))) return 0; if (unlikely(!pskb_may_pull(skb, sizeof(struct qtag_prefix) + sizeof(__be16)))) return -ENOMEM; qp = (struct qtag_prefix *) skb->data; key->eth.tci = qp->tci | htons(VLAN_TAG_PRESENT); __skb_pull(skb, sizeof(struct qtag_prefix)); return 0; } static __be16 parse_ethertype(struct sk_buff *skb) { struct llc_snap_hdr { u8 dsap; /* Always 0xAA */ u8 ssap; /* Always 0xAA */ u8 ctrl; u8 oui[3]; __be16 ethertype; }; struct llc_snap_hdr *llc; __be16 proto; proto = *(__be16 *) skb->data; __skb_pull(skb, sizeof(__be16)); if (ntohs(proto) >= 1536) return proto; if (skb->len < sizeof(struct llc_snap_hdr)) return htons(ETH_P_802_2); if (unlikely(!pskb_may_pull(skb, sizeof(struct llc_snap_hdr)))) return htons(0); llc = (struct llc_snap_hdr *) skb->data; if (llc->dsap != LLC_SAP_SNAP || llc->ssap != LLC_SAP_SNAP || (llc->oui[0] | llc->oui[1] | llc->oui[2]) != 0) return htons(ETH_P_802_2); __skb_pull(skb, sizeof(struct llc_snap_hdr)); return llc->ethertype; } static int parse_icmpv6(struct sk_buff *skb, struct sw_flow_key *key,