+// SPDX-License-Identifier: GPL-2.0-or-later
/* Kernel routing table updates using netlink over GNU/Linux system.
* Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
- *
- * This file is part of GNU Zebra.
- *
- * GNU Zebra is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * GNU Zebra 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; see the file COPYING; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <zebra.h>
#include "table.h"
#include "memory.h"
#include "rib.h"
-#include "thread.h"
+#include "frrevent.h"
#include "privs.h"
#include "nexthop.h"
#include "vrf.h"
return false;
}
+/* Is vni mcast group */
+static bool is_mac_vni_mcast_group(struct ethaddr *mac, vni_t vni,
+ struct in_addr grp_addr)
+{
+ if (!vni)
+ return false;
+
+ if (!is_zero_mac(mac))
+ return false;
+
+ if (!IN_MULTICAST(ntohl(grp_addr.s_addr)))
+ return false;
+
+ return true;
+}
+
/*
* The ipv4_ll data structure is used for all 5549
* additions to the kernel. Let's figure out the
return false;
}
-static inline int zebra2proto(int proto)
+int zebra2proto(int proto)
{
switch (proto) {
case ZEBRA_ROUTE_BABEL:
}
static int build_label_stack(struct mpls_label_stack *nh_label,
+ enum lsp_types_t nh_label_type,
mpls_lse_t *out_lse, char *label_buf,
size_t label_buf_size)
{
int num_labels = 0;
for (int i = 0; nh_label && i < nh_label->num_labels; i++) {
- if (nh_label->label[i] == MPLS_LABEL_IMPLICIT_NULL)
+ if (nh_label_type != ZEBRA_LSP_EVPN &&
+ nh_label->label[i] == MPLS_LABEL_IMPLICIT_NULL)
continue;
if (IS_ZEBRA_DEBUG_KERNEL) {
}
}
- out_lse[num_labels] =
- mpls_lse_encode(nh_label->label[i], 0, 0, 0);
+ if (nh_label_type == ZEBRA_LSP_EVPN)
+ out_lse[num_labels] = label2vni(&nh_label->label[i]);
+ else
+ out_lse[num_labels] =
+ mpls_lse_encode(nh_label->label[i], 0, 0, 0);
num_labels++;
}
return num_labels;
}
-static bool _netlink_route_encode_label_info(struct mpls_label_stack *nh_label,
+static bool _netlink_nexthop_encode_dvni_label(const struct nexthop *nexthop,
+ struct nlmsghdr *nlmsg,
+ mpls_lse_t *out_lse,
+ size_t buflen, char *label_buf)
+{
+ struct in_addr ipv4;
+
+ if (!nl_attr_put64(nlmsg, buflen, LWTUNNEL_IP_ID,
+ htonll((uint64_t)out_lse[0])))
+ return false;
+
+ if (nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX) {
+ if (!nl_attr_put(nlmsg, buflen, LWTUNNEL_IP_DST,
+ &nexthop->gate.ipv4, 4))
+ return false;
+
+ } else if (nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) {
+ if (IS_MAPPED_IPV6(&nexthop->gate.ipv6)) {
+ ipv4_mapped_ipv6_to_ipv4(&nexthop->gate.ipv6, &ipv4);
+ if (!nl_attr_put(nlmsg, buflen, LWTUNNEL_IP_DST, &ipv4,
+ 4))
+ return false;
+
+ } else {
+ if (!nl_attr_put(nlmsg, buflen, LWTUNNEL_IP_DST,
+ &nexthop->gate.ipv6, 16))
+ return false;
+ }
+ } else {
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug(
+ "%s: nexthop %pNHv %s must NEXTHOP_TYPE_IPV*_IFINDEX to be vxlan encapped",
+ __func__, nexthop, label_buf);
+
+ return false;
+ }
+
+ return true;
+}
+
+static bool _netlink_route_encode_label_info(const struct nexthop *nexthop,
struct nlmsghdr *nlmsg,
size_t buflen, struct rtmsg *rtmsg,
char *label_buf,
{
mpls_lse_t out_lse[MPLS_MAX_LABELS];
int num_labels;
+ struct rtattr *nest;
+ struct mpls_label_stack *nh_label;
+ enum lsp_types_t nh_label_type;
+
+ nh_label = nexthop->nh_label;
+ nh_label_type = nexthop->nh_label_type;
/*
* label_buf is *only* currently used within debugging.
*/
label_buf[0] = '\0';
- num_labels =
- build_label_stack(nh_label, out_lse, label_buf, label_buf_size);
+ num_labels = build_label_stack(nh_label, nh_label_type, out_lse,
+ label_buf, label_buf_size);
+
+ if (num_labels && nh_label_type == ZEBRA_LSP_EVPN) {
+ if (!nl_attr_put16(nlmsg, buflen, RTA_ENCAP_TYPE,
+ LWTUNNEL_ENCAP_IP))
+ return false;
+
+ nest = nl_attr_nest(nlmsg, buflen, RTA_ENCAP);
+ if (!nest)
+ return false;
+
+ if (_netlink_nexthop_encode_dvni_label(nexthop, nlmsg, out_lse,
+ buflen,
+ label_buf) == false)
+ return false;
- if (num_labels) {
+ nl_attr_nest_end(nlmsg, nest);
+
+ } else if (num_labels) {
/* Set the BoS bit */
out_lse[num_labels - 1] |= htonl(1 << MPLS_LS_S_SHIFT);
num_labels * sizeof(mpls_lse_t)))
return false;
} else {
- struct rtattr *nest;
-
if (!nl_attr_put16(nlmsg, buflen, RTA_ENCAP_TYPE,
LWTUNNEL_ENCAP_MPLS))
return false;
vrf = vrf_lookup_by_id(nexthop->vrf_id);
- if (!_netlink_route_encode_label_info(nexthop->nh_label, nlmsg,
- req_size, rtmsg, label_buf,
- sizeof(label_buf)))
+ if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
+ label_buf, sizeof(label_buf)))
return false;
if (nexthop->nh_srv6) {
ctx->table))
return false;
break;
- default:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX2:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_DX6:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_B6:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_BM:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_S:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_AS:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_AM:
+ case ZEBRA_SEG6_LOCAL_ACTION_END_BPF:
+ case ZEBRA_SEG6_LOCAL_ACTION_UNSPEC:
zlog_err("%s: unsupport seg6local behaviour action=%u",
__func__,
nexthop->nh_srv6->seg6local_action);
vrf = vrf_lookup_by_id(nexthop->vrf_id);
- if (!_netlink_route_encode_label_info(nexthop->nh_label, nlmsg,
- req_size, rtmsg, label_buf,
- sizeof(label_buf)))
+ if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
+ label_buf, sizeof(label_buf)))
return false;
if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK))
if (lla)
nl_attr_put(&req.n, sizeof(req), NDA_LLADDR, lla, llalen);
+ if (IS_ZEBRA_DEBUG_KERNEL) {
+ char ip_str[INET6_ADDRSTRLEN + 8];
+ struct interface *ifp = if_lookup_by_index_per_ns(
+ zebra_ns_lookup(ns_id), ifindex);
+ if (ifp) {
+ if (family == AF_INET6)
+ snprintfrr(ip_str, sizeof(ip_str), "ipv6 %pI6",
+ (struct in6_addr *)addr);
+ else
+ snprintfrr(ip_str, sizeof(ip_str), "ipv4 %pI4",
+ (in_addr_t *)addr);
+ zlog_debug(
+ "%s: %s ifname %s ifindex %u addr %s mac %pEA vrf %s(%u)",
+ __func__, nl_msg_type_to_str(cmd), ifp->name,
+ ifindex, ip_str, (struct ethaddr *)lla,
+ vrf_id_to_name(ifp->vrf->vrf_id),
+ ifp->vrf->vrf_id);
+ }
+ }
return netlink_talk(netlink_talk_filter, &req.n, &zns->netlink_cmd, zns,
false);
}
case BLACKHOLE_REJECT:
req->r.rtm_type = RTN_UNREACHABLE;
break;
- default:
+ case BLACKHOLE_UNSPEC:
+ case BLACKHOLE_NULL:
req->r.rtm_type = RTN_BLACKHOLE;
break;
}
if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_ONLINK))
req->nhm.nh_flags |= RTNH_F_ONLINK;
- num_labels =
- build_label_stack(nh->nh_label, out_lse,
- label_buf, sizeof(label_buf));
+ num_labels = build_label_stack(
+ nh->nh_label, nh->nh_label_type, out_lse,
+ label_buf, sizeof(label_buf));
+
+ if (num_labels && nh->nh_label_type == ZEBRA_LSP_EVPN) {
+ if (!nl_attr_put16(&req->n, buflen,
+ NHA_ENCAP_TYPE,
+ LWTUNNEL_ENCAP_IP))
+ return 0;
- if (num_labels) {
+ nest = nl_attr_nest(&req->n, buflen, NHA_ENCAP);
+ if (!nest)
+ return 0;
+
+ if (_netlink_nexthop_encode_dvni_label(
+ nh, &req->n, out_lse, buflen,
+ label_buf) == false)
+ return 0;
+
+ nl_attr_nest_end(&req->n, nest);
+
+ } else if (num_labels) {
/* Set the BoS bit */
out_lse[num_labels - 1] |=
htonl(1 << MPLS_LS_S_SHIFT);
if (op == DPLANE_OP_MAC_INSTALL || op == DPLANE_OP_MAC_DELETE) {
vlanid_t vid = dplane_ctx_mac_get_vlan(ctx);
+ vni_t vni = dplane_ctx_mac_get_vni(ctx);
if (vid > 0) {
if (!nl_attr_put16(&req->n, datalen, NDA_VLAN, vid))
return 0;
}
+ if (vni > 0) {
+ if (!nl_attr_put32(&req->n, datalen, NDA_SRC_VNI, vni))
+ return 0;
+ }
+
if (!nl_attr_put32(&req->n, datalen, NDA_MASTER,
dplane_ctx_mac_get_br_ifindex(ctx)))
return 0;
}
+ if (op == DPLANE_OP_VTEP_ADD || op == DPLANE_OP_VTEP_DELETE) {
+ vni_t vni = dplane_ctx_neigh_get_vni(ctx);
+
+ if (vni > 0) {
+ if (!nl_attr_put32(&req->n, datalen, NDA_SRC_VNI, vni))
+ return 0;
+ }
+ }
+
return NLMSG_ALIGN(req->n.nlmsg_len);
}
bool sticky;
bool local_inactive = false;
bool dp_static = false;
+ vni_t vni = 0;
uint32_t nhg_id = 0;
+ bool vni_mcast_grp = false;
ndm = NLMSG_DATA(h);
IPV4_MAX_BYTELEN);
snprintfrr(dst_buf, sizeof(dst_buf), " dst %pI4",
&vtep_ip);
- }
+ } else
+ memset(&vtep_ip, 0, sizeof(vtep_ip));
if (tb[NDA_NH_ID])
nhg_id = *(uint32_t *)RTA_DATA(tb[NDA_NH_ID]);
}
}
+ if (tb[NDA_SRC_VNI])
+ vni = *(vni_t *)RTA_DATA(tb[NDA_SRC_VNI]);
+
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("Rx %s AF_BRIDGE IF %u%s st 0x%x fl 0x%x MAC %pEA%s nhg %d",
- nl_msg_type_to_str(h->nlmsg_type),
- ndm->ndm_ifindex, vid_present ? vid_buf : "",
- ndm->ndm_state, ndm->ndm_flags, &mac,
- dst_present ? dst_buf : "", nhg_id);
+ zlog_debug(
+ "Rx %s AF_BRIDGE IF %u%s st 0x%x fl 0x%x MAC %pEA%s nhg %d vni %d",
+ nl_msg_type_to_str(h->nlmsg_type), ndm->ndm_ifindex,
+ vid_present ? vid_buf : "", ndm->ndm_state,
+ ndm->ndm_flags, &mac, dst_present ? dst_buf : "",
+ nhg_id, vni);
/* The interface should exist. */
ifp = if_lookup_by_index_per_ns(zebra_ns_lookup(ns_id),
return 0;
}
+ /* For per vni device, vni comes from device itself */
+ if (IS_ZEBRA_IF_VXLAN(ifp) && IS_ZEBRA_VXLAN_IF_VNI(zif)) {
+ struct zebra_vxlan_vni *vnip;
+
+ vnip = zebra_vxlan_if_vni_find(zif, 0);
+ vni = vnip->vni;
+ }
+
sticky = !!(ndm->ndm_flags & NTF_STICKY);
if (filter_vlan && vid != filter_vlan) {
return 0;
}
+ /*
+ * Check if this is a mcast group update (svd case)
+ */
+ vni_mcast_grp = is_mac_vni_mcast_group(&mac, vni, vtep_ip);
+
/* If add or update, do accordingly if learnt on a "local" interface; if
* the notification is over VxLAN, this has to be related to
* multi-homing,
*/
if (h->nlmsg_type == RTM_NEWNEIGH) {
/* Drop "permanent" entries. */
- if (ndm->ndm_state & NUD_PERMANENT) {
+ if (!vni_mcast_grp && (ndm->ndm_state & NUD_PERMANENT)) {
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug(
" Dropping entry because of NUD_PERMANENT");
return 0;
}
- if (IS_ZEBRA_IF_VXLAN(ifp))
+ if (IS_ZEBRA_IF_VXLAN(ifp)) {
+ if (!dst_present)
+ return 0;
+
+ if (vni_mcast_grp)
+ return zebra_vxlan_if_vni_mcast_group_add_update(
+ ifp, vni, &vtep_ip);
+
return zebra_vxlan_dp_network_mac_add(
- ifp, br_if, &mac, vid, nhg_id, sticky,
+ ifp, br_if, &mac, vid, vni, nhg_id, sticky,
!!(ndm->ndm_flags & NTF_EXT_LEARNED));
+ }
return zebra_vxlan_local_mac_add_update(ifp, br_if, &mac, vid,
sticky, local_inactive, dp_static);
return 0;
if (dst_present) {
- u_char zero_mac[6] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+ if (vni_mcast_grp)
+ return zebra_vxlan_if_vni_mcast_group_del(ifp, vni,
+ &vtep_ip);
+
+ if (is_zero_mac(&mac) && vni)
+ return zebra_vxlan_check_readd_vtep(ifp, vni, vtep_ip);
- if (!memcmp(zero_mac, mac.octet, ETH_ALEN))
- return zebra_vxlan_check_readd_vtep(ifp, vtep_ip);
return 0;
}
if (IS_ZEBRA_IF_VXLAN(ifp))
- return zebra_vxlan_dp_network_mac_del(ifp, br_if, &mac, vid);
+ return 0;
return zebra_vxlan_local_mac_del(ifp, br_if, &mac, vid);
}
* specific bridge and matching specific access VLAN (if VLAN-aware bridge).
*/
int netlink_macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp,
- struct interface *br_if)
+ struct interface *br_if, vlanid_t vid)
{
struct zebra_if *br_zif;
- struct zebra_if *zif;
- struct zebra_l2info_vxlan *vxl;
struct zebra_dplane_info dp_info;
int ret = 0;
/* Save VLAN we're filtering on, if needed. */
br_zif = (struct zebra_if *)br_if->info;
- zif = (struct zebra_if *)ifp->info;
- vxl = &zif->l2info.vxl;
if (IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif))
- filter_vlan = vxl->access_vlan;
+ filter_vlan = vid;
/* Get bridge FDB table for specific bridge - we do the VLAN filtering.
*/
/* Request for MAC FDB for a specific MAC address in VLAN from the kernel */
-static int netlink_request_specific_mac_in_bridge(struct zebra_ns *zns,
- int family, int type,
- struct interface *br_if,
- const struct ethaddr *mac,
- vlanid_t vid)
+static int netlink_request_specific_mac(struct zebra_ns *zns, int family,
+ int type, struct interface *ifp,
+ const struct ethaddr *mac, vlanid_t vid,
+ vni_t vni, uint8_t flags)
{
struct {
struct nlmsghdr n;
struct ndmsg ndm;
char buf[256];
} req;
- struct zebra_if *br_zif;
+ struct zebra_if *zif;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));
req.n.nlmsg_type = type; /* RTM_GETNEIGH */
req.n.nlmsg_flags = NLM_F_REQUEST;
req.ndm.ndm_family = family; /* AF_BRIDGE */
+ req.ndm.ndm_flags = flags;
/* req.ndm.ndm_state = NUD_REACHABLE; */
nl_attr_put(&req.n, sizeof(req), NDA_LLADDR, mac, 6);
- br_zif = (struct zebra_if *)br_if->info;
- if (IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(br_zif) && vid > 0)
- nl_attr_put16(&req.n, sizeof(req), NDA_VLAN, vid);
-
- nl_attr_put32(&req.n, sizeof(req), NDA_MASTER, br_if->ifindex);
+ zif = (struct zebra_if *)ifp->info;
+ /* Is this a read on a VXLAN interface? */
+ if (IS_ZEBRA_IF_VXLAN(ifp)) {
+ nl_attr_put32(&req.n, sizeof(req), NDA_VNI, vni);
+ /* TBD: Why is ifindex not filled in the non-vxlan case? */
+ req.ndm.ndm_ifindex = ifp->ifindex;
+ } else {
+ if (IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(zif) && vid > 0)
+ nl_attr_put16(&req.n, sizeof(req), NDA_VLAN, vid);
+ nl_attr_put32(&req.n, sizeof(req), NDA_MASTER, ifp->ifindex);
+ }
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug(
- "%s: Tx family %s IF %s(%u) vrf %s(%u) MAC %pEA vid %u",
- __func__, nl_family_to_str(req.ndm.ndm_family),
- br_if->name, br_if->ifindex, br_if->vrf->name,
- br_if->vrf->vrf_id, mac, vid);
+ zlog_debug("Tx %s %s IF %s(%u) MAC %pEA vid %u vni %u",
+ nl_msg_type_to_str(type),
+ nl_family_to_str(req.ndm.ndm_family), ifp->name,
+ ifp->ifindex, mac, vid, vni);
return netlink_request(&zns->netlink_cmd, &req);
}
/* Get bridge FDB table for specific bridge - we do the VLAN filtering.
*/
- ret = netlink_request_specific_mac_in_bridge(zns, AF_BRIDGE,
- RTM_GETNEIGH,
- br_if, mac, vid);
+ ret = netlink_request_specific_mac(zns, AF_BRIDGE, RTM_GETNEIGH, br_if,
+ mac, vid, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = netlink_parse_info(netlink_macfdb_table, &zns->netlink_cmd,
+ &dp_info, 1, 0);
+
+ return ret;
+}
+
+int netlink_macfdb_read_mcast_for_vni(struct zebra_ns *zns,
+ struct interface *ifp, vni_t vni)
+{
+ struct zebra_if *zif;
+ struct ethaddr mac = {.octet = {0}};
+ struct zebra_dplane_info dp_info;
+ int ret = 0;
+
+ zif = ifp->info;
+ if (IS_ZEBRA_VXLAN_IF_VNI(zif))
+ return 0;
+
+ zebra_dplane_info_from_zns(&dp_info, zns, true /*is_cmd*/);
+
+ /* Get specific FDB entry for BUM handling, if any */
+ ret = netlink_request_specific_mac(zns, AF_BRIDGE, RTM_GETNEIGH, ifp,
+ &mac, 0, vni, NTF_SELF);
if (ret < 0)
return ret;
static ssize_t netlink_neigh_msg_encoder(struct zebra_dplane_ctx *ctx,
void *buf, size_t buflen)
{
- ssize_t ret;
+ ssize_t ret = 0;
switch (dplane_ctx_get_op(ctx)) {
case DPLANE_OP_NEIGH_INSTALL:
case DPLANE_OP_NEIGH_TABLE_UPDATE:
ret = netlink_neigh_table_update_ctx(ctx, buf, buflen);
break;
- default:
+ case DPLANE_OP_ROUTE_INSTALL:
+ case DPLANE_OP_ROUTE_UPDATE:
+ case DPLANE_OP_ROUTE_DELETE:
+ case DPLANE_OP_ROUTE_NOTIFY:
+ case DPLANE_OP_NH_INSTALL:
+ case DPLANE_OP_NH_UPDATE:
+ case DPLANE_OP_NH_DELETE:
+ case DPLANE_OP_LSP_INSTALL:
+ case DPLANE_OP_LSP_UPDATE:
+ case DPLANE_OP_LSP_DELETE:
+ case DPLANE_OP_LSP_NOTIFY:
+ case DPLANE_OP_PW_INSTALL:
+ case DPLANE_OP_PW_UNINSTALL:
+ case DPLANE_OP_SYS_ROUTE_ADD:
+ case DPLANE_OP_SYS_ROUTE_DELETE:
+ case DPLANE_OP_ADDR_INSTALL:
+ case DPLANE_OP_ADDR_UNINSTALL:
+ case DPLANE_OP_MAC_INSTALL:
+ case DPLANE_OP_MAC_DELETE:
+ case DPLANE_OP_RULE_ADD:
+ case DPLANE_OP_RULE_DELETE:
+ case DPLANE_OP_RULE_UPDATE:
+ case DPLANE_OP_BR_PORT_UPDATE:
+ case DPLANE_OP_IPTABLE_ADD:
+ case DPLANE_OP_IPTABLE_DELETE:
+ case DPLANE_OP_IPSET_ADD:
+ case DPLANE_OP_IPSET_DELETE:
+ case DPLANE_OP_IPSET_ENTRY_ADD:
+ case DPLANE_OP_IPSET_ENTRY_DELETE:
+ case DPLANE_OP_GRE_SET:
+ case DPLANE_OP_INTF_ADDR_ADD:
+ case DPLANE_OP_INTF_ADDR_DEL:
+ case DPLANE_OP_INTF_NETCONFIG:
+ case DPLANE_OP_INTF_INSTALL:
+ case DPLANE_OP_INTF_UPDATE:
+ case DPLANE_OP_INTF_DELETE:
+ case DPLANE_OP_TC_QDISC_INSTALL:
+ case DPLANE_OP_TC_QDISC_UNINSTALL:
+ case DPLANE_OP_TC_CLASS_ADD:
+ case DPLANE_OP_TC_CLASS_DELETE:
+ case DPLANE_OP_TC_CLASS_UPDATE:
+ case DPLANE_OP_TC_FILTER_ADD:
+ case DPLANE_OP_TC_FILTER_DELETE:
+ case DPLANE_OP_TC_FILTER_UPDATE:
+ case DPLANE_OP_NONE:
ret = -1;
}