+ if (nla_attr_size(skb->len) > USHRT_MAX)
+ goto err_kfree_skbs;
+
+ len = sizeof(struct odp_header);
+ len += nla_total_size(skb->len);
+ len += nla_total_size(FLOW_BUFSIZE);
+ if (upcall_info->userdata)
+ len += nla_total_size(8);
+ if (upcall_info->sample_pool)
+ len += nla_total_size(4);
+ if (upcall_info->actions_len)
+ len += nla_total_size(upcall_info->actions_len);
+
+ user_skb = genlmsg_new(len, GFP_ATOMIC);
+ if (!user_skb) {
+ netlink_set_err(INIT_NET_GENL_SOCK, 0, group, -ENOBUFS);
+ goto err_kfree_skbs;
+ }
+
+ upcall = genlmsg_put(user_skb, 0, 0, &dp_packet_genl_family, 0, upcall_info->cmd);
+ upcall->dp_ifindex = dp->dp_ifindex;
+
+ nla = nla_nest_start(user_skb, ODP_PACKET_ATTR_KEY);
+ flow_to_nlattrs(upcall_info->key, user_skb);
+ nla_nest_end(user_skb, nla);
+
+ if (upcall_info->userdata)
+ nla_put_u64(user_skb, ODP_PACKET_ATTR_USERDATA, upcall_info->userdata);
+ if (upcall_info->sample_pool)
+ nla_put_u32(user_skb, ODP_PACKET_ATTR_SAMPLE_POOL, upcall_info->sample_pool);
+ if (upcall_info->actions_len) {
+ const struct nlattr *actions = upcall_info->actions;
+ u32 actions_len = upcall_info->actions_len;
+
+ nla = nla_nest_start(user_skb, ODP_PACKET_ATTR_ACTIONS);
+ memcpy(__skb_put(user_skb, actions_len), actions, actions_len);
+ nla_nest_end(user_skb, nla);
+ }
+
+ nla = __nla_reserve(user_skb, ODP_PACKET_ATTR_PACKET, skb->len);
+ if (skb->ip_summed == CHECKSUM_PARTIAL)
+ copy_and_csum_skb(skb, nla_data(nla));
+ else
+ skb_copy_bits(skb, 0, nla_data(nla), skb->len);
+
+ err = genlmsg_multicast(user_skb, 0, group, GFP_ATOMIC);
+ if (err)
+ goto err_kfree_skbs;
+
+ kfree_skb(skb);
+ skb = nskb;
+ } while (skb);
+ return 0;
+
+err_kfree_skbs:
+ kfree_skb(skb);
+ while ((skb = nskb) != NULL) {
+ nskb = skb->next;
+ kfree_skb(skb);
+ }
+ return err;
+}
+
+/* Called with genl_mutex. */
+static int flush_flows(int dp_ifindex)
+{
+ struct tbl *old_table;
+ struct tbl *new_table;
+ struct datapath *dp;
+
+ dp = get_dp(dp_ifindex);
+ if (!dp)
+ return -ENODEV;
+
+ old_table = get_table_protected(dp);
+ new_table = tbl_create(TBL_MIN_BUCKETS);
+ if (!new_table)
+ return -ENOMEM;
+
+ rcu_assign_pointer(dp->table, new_table);
+
+ tbl_deferred_destroy(old_table, flow_free_tbl);
+
+ return 0;
+}
+
+static int validate_actions(const struct nlattr *attr)
+{
+ const struct nlattr *a;
+ int rem;
+
+ nla_for_each_nested(a, attr, rem) {
+ static const u32 action_lens[ODP_ACTION_ATTR_MAX + 1] = {
+ [ODP_ACTION_ATTR_OUTPUT] = 4,
+ [ODP_ACTION_ATTR_CONTROLLER] = 8,
+ [ODP_ACTION_ATTR_SET_DL_TCI] = 2,
+ [ODP_ACTION_ATTR_STRIP_VLAN] = 0,
+ [ODP_ACTION_ATTR_SET_DL_SRC] = ETH_ALEN,
+ [ODP_ACTION_ATTR_SET_DL_DST] = ETH_ALEN,
+ [ODP_ACTION_ATTR_SET_NW_SRC] = 4,
+ [ODP_ACTION_ATTR_SET_NW_DST] = 4,
+ [ODP_ACTION_ATTR_SET_NW_TOS] = 1,
+ [ODP_ACTION_ATTR_SET_TP_SRC] = 2,
+ [ODP_ACTION_ATTR_SET_TP_DST] = 2,
+ [ODP_ACTION_ATTR_SET_TUNNEL] = 8,
+ [ODP_ACTION_ATTR_SET_PRIORITY] = 4,
+ [ODP_ACTION_ATTR_POP_PRIORITY] = 0,
+ [ODP_ACTION_ATTR_DROP_SPOOFED_ARP] = 0,
+ };
+ int type = nla_type(a);
+
+ if (type > ODP_ACTION_ATTR_MAX || nla_len(a) != action_lens[type])
+ return -EINVAL;
+
+ switch (type) {
+ case ODP_ACTION_ATTR_UNSPEC:
+ return -EINVAL;
+
+ case ODP_ACTION_ATTR_CONTROLLER:
+ case ODP_ACTION_ATTR_STRIP_VLAN:
+ case ODP_ACTION_ATTR_SET_DL_SRC:
+ case ODP_ACTION_ATTR_SET_DL_DST:
+ case ODP_ACTION_ATTR_SET_NW_SRC:
+ case ODP_ACTION_ATTR_SET_NW_DST:
+ case ODP_ACTION_ATTR_SET_TP_SRC:
+ case ODP_ACTION_ATTR_SET_TP_DST:
+ case ODP_ACTION_ATTR_SET_TUNNEL:
+ case ODP_ACTION_ATTR_SET_PRIORITY:
+ case ODP_ACTION_ATTR_POP_PRIORITY:
+ case ODP_ACTION_ATTR_DROP_SPOOFED_ARP: