From patchwork Sat Jul 14 11:38:58 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Tu X-Patchwork-Id: 943919 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="qvPmtD3R"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 41SSVm6jlZz9ryt for ; Sat, 14 Jul 2018 21:43:44 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 6B047CBF; Sat, 14 Jul 2018 11:40:04 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 737D5CB6 for ; Sat, 14 Jul 2018 11:40:02 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pl0-f65.google.com (mail-pl0-f65.google.com [209.85.160.65]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id A42AA67E for ; Sat, 14 Jul 2018 11:40:00 +0000 (UTC) Received: by mail-pl0-f65.google.com with SMTP id b1-v6so13293340pls.5 for ; Sat, 14 Jul 2018 04:40:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=TRgBe7cuUWaSEkGjtyLQ/Tm4+/cPbdrqNneV9wvkowk=; b=qvPmtD3Rwpi4qgdwJqgZOsoG9lf5iDufSc9g2Wd65mee5ap+i8G9AaaRVwLCFS9Xe4 HdfuivOnX1oJ1/rVXeCqESSv0p4v0PGZbFAPydG58e5ooOchGvRtRTd1j1Rhixx32BSA JXqoacTLuApZqDkOImELY2ft827MzswcdKj2p9HNMKwH8Rcms+sGDJFWXtGXZ/d6ltsv e0CbALyjvo2cR3tLqA2uWYSKl6egVp0Qm9DZ024ABrFyDJHEDFVxzNJN8mX3WmmMYDel CHHbMYKjR4aeUDRRoBIzwsCk9bQeFE40u2dXilKR26DZ/vALnp2LQpUnEdbeadftrsmU WlpQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=TRgBe7cuUWaSEkGjtyLQ/Tm4+/cPbdrqNneV9wvkowk=; b=atpZPxBGPo+3jps0t1buTgqYUOXBBFgDGXp7SjqUqj8QQOeJRf5bER1KAshZc65QEB TtpeVZcpPCzaEe8tsH5+kjq/AHoF8gdPmM5bpifyTdIw+NyB0U51XK2XZN5m3dA/md0v yfa2hGxDiwpjRw2On48BD1et43+ve5ztia+HAwWGu1jl4Ff7BdmlPCZWBMDdqgfFW9G4 vcAytDlKYr1SkGgodb7+hBBBticOX8aKxfRMMNaMfhK7yB2yX0a35seB50C28a9ZmTEZ lZQNejvMWjPYxi1LVvWogLUaqWirKavKcpHeyCS7hGdGrkuzcvW4cuW1yUDUWoc7AwuJ 0Gkg== X-Gm-Message-State: AOUpUlEBbPr7ghv3QqKhn3WcFPx4nZuTsf3aIaBSKqbUEqZ8W52seaM8 WPbQnM3uAFiqtHHOYZJdxg6NEPUi X-Google-Smtp-Source: AAOMgpej/0681QSE2sBXg2nWPy580J45hbSKGz5vfO/Mbk4G/BmSpNn+W8F/7hwszNiyMrxW1ZdENg== X-Received: by 2002:a17:902:7688:: with SMTP id m8-v6mr9800367pll.338.1531568399657; Sat, 14 Jul 2018 04:39:59 -0700 (PDT) Received: from sc9-mailhost3.vmware.com (c-73-231-16-221.hsd1.ca.comcast.net. [73.231.16.221]) by smtp.gmail.com with ESMTPSA id m21-v6sm35825267pgv.27.2018.07.14.04.39.56 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sat, 14 Jul 2018 04:39:59 -0700 (PDT) From: William Tu To: dev@openvswitch.org, iovisor-dev@lists.iovisor.org Date: Sat, 14 Jul 2018 04:38:58 -0700 Message-Id: <1531568345-80246-7-git-send-email-u9012063@gmail.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1531568345-80246-1-git-send-email-u9012063@gmail.com> References: <1531568345-80246-1-git-send-email-u9012063@gmail.com> X-Spam-Status: No, score=-1.7 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_ENVFROM_END_DIGIT,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=no version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [RFC PATCHv2 06/13] dpif-bpf-odp: Add bpf datapath interface and impl. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org From: Joe Stringer Add an implementation of the API between the userspace "Open vSwitch Datapath Protocol" and the BPF datapath. Signed-off-by: Joe Stringer Signed-off-by: William Tu Signed-off-by: Yifeng Sun Co-authored-by: William Tu Co-authored-by: Yifeng Sun --- lib/automake.mk | 12 + lib/dpif-bpf-odp.c | 945 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/dpif-bpf-odp.h | 47 +++ 3 files changed, 1004 insertions(+) create mode 100644 lib/dpif-bpf-odp.c create mode 100644 lib/dpif-bpf-odp.h diff --git a/lib/automake.mk b/lib/automake.mk index 8ecad12415a3..61fef23152d3 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -9,6 +9,7 @@ lib_LTLIBRARIES += lib/libopenvswitch.la lib_libopenvswitch_la_LIBADD = $(SSL_LIBS) lib_libopenvswitch_la_LIBADD += $(CAPNG_LDADD) +lib_libopenvswitch_la_LIBADD += $(BPF_LDADD) if WIN32 lib_libopenvswitch_la_LIBADD += ${PTHREAD_LIBS} @@ -358,6 +359,7 @@ endif if LINUX lib_libopenvswitch_la_SOURCES += \ + lib/bpf.h \ lib/dpif-netlink.c \ lib/dpif-netlink.h \ lib/dpif-netlink-rtnl.c \ @@ -383,6 +385,16 @@ lib_libopenvswitch_la_SOURCES += \ lib/tc.h endif +if HAVE_BPF +lib_libopenvswitch_la_SOURCES += \ + lib/bpf.c \ + lib/dpif-bpf.c \ + lib/dpif-bpf-odp.c \ + lib/dpif-bpf-odp.h \ + lib/perf-event.c \ + lib/perf-event.h +endif + if DPDK_NETDEV lib_libopenvswitch_la_SOURCES += \ lib/dpdk.c \ diff --git a/lib/dpif-bpf-odp.c b/lib/dpif-bpf-odp.c new file mode 100644 index 000000000000..cc2b2f32224b --- /dev/null +++ b/lib/dpif-bpf-odp.c @@ -0,0 +1,945 @@ +/* + * Copyright (c) 2017 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "dpif-bpf-odp.h" + +#include + +#include "bpf/odp-bpf.h" +#include "openvswitch/flow.h" +#include "openvswitch/vlog.h" +#include "netlink.h" +#include "util.h" + +VLOG_DEFINE_THIS_MODULE(dpif_bpf_odp); + +static void +ct_action_to_bpf(const struct nlattr *ct, struct bpf_action *dst) +{ + const struct nlattr *nla; + int left; + + NL_ATTR_FOR_EACH_UNSAFE(nla, left, ct, ct->nla_len) { + switch ((enum ovs_ct_attr)nla->nla_type) { + case OVS_CT_ATTR_COMMIT: + dst->u.ct.commit = true; + break; + case OVS_CT_ATTR_ZONE: + case OVS_CT_ATTR_MARK: + case OVS_CT_ATTR_LABELS: + case OVS_CT_ATTR_HELPER: + case OVS_CT_ATTR_NAT: + case OVS_CT_ATTR_FORCE_COMMIT: + case OVS_CT_ATTR_EVENTMASK: + default: + VLOG_INFO("Ignoring CT attribute %d", nla->nla_type); + break; + case OVS_CT_ATTR_UNSPEC: + case __OVS_CT_ATTR_MAX: + OVS_NOT_REACHED(); + } + } +} + +enum odp_key_fitness +odp_tun_to_bpf_tun(const struct nlattr *nla, size_t nla_len, + struct flow_tnl_t *tun) +{ + const struct nlattr *a; + size_t left; + + NL_ATTR_FOR_EACH(a, left, nla, nla_len) { + enum ovs_tunnel_key_attr type = nl_attr_type(a); + + switch (type) { + case OVS_TUNNEL_KEY_ATTR_ID: + tun->tun_id = ntohl(be64_to_be32(nl_attr_get_be64(a))); + break; + case OVS_TUNNEL_KEY_ATTR_IPV4_SRC: + tun->ip4.ip_src = ntohl(nl_attr_get_be32(a)); + tun->use_ipv6 = 0; + break; + case OVS_TUNNEL_KEY_ATTR_IPV4_DST: + tun->ip4.ip_dst = ntohl(nl_attr_get_be32(a)); + tun->use_ipv6 = 0; + break; + case OVS_TUNNEL_KEY_ATTR_TOS: + tun->ip_tos = nl_attr_get_u8(a); + break; + case OVS_TUNNEL_KEY_ATTR_TTL: + tun->ip_ttl = nl_attr_get_u8(a); + break; + case OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT: + //tun->flags |= FLOW_TNL_F_DONT_FRAGMENT; + // in bpf helper, there is no tun_flags extracted + break; + case OVS_TUNNEL_KEY_ATTR_TP_DST: + tun->tp_dst = nl_attr_get_be16(a); + break; + case OVS_TUNNEL_KEY_ATTR_TP_SRC: + tun->tp_src = nl_attr_get_be16(a); + break; + case OVS_TUNNEL_KEY_ATTR_IPV6_SRC: +#ifdef BPF_ENABLE_IPV6 + memcpy(&tun->ip6.ipv6_src, nl_attr_get(a), 16); + tun->use_ipv6 = 1; +#endif + break; + case OVS_TUNNEL_KEY_ATTR_IPV6_DST: +#ifdef BPF_ENABLE_IPV6 + memcpy(&tun->ip6.ipv6_dst, nl_attr_get(a), 16); + tun->use_ipv6 = 1; +#endif + break; + case OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS: /* Array of Geneve options. */ + if (nl_attr_get_size(a) != sizeof tun->gnvopt) { + VLOG_ERR("%s: geneve opts size is %ld, expect %ld", __func__, + nl_attr_get_size(a), sizeof tun->gnvopt); + } else { + memcpy(&tun->gnvopt, nl_attr_get(a), sizeof tun->gnvopt); + tun->gnvopt_valid = 1; + } + break; + case OVS_TUNNEL_KEY_ATTR_CSUM: /* No argument. CSUM packet. */ + case OVS_TUNNEL_KEY_ATTR_OAM: /* No argument. OAM frame. */ + case OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS: /* Nested OVS_VXLAN_EXT_* */ + case OVS_TUNNEL_KEY_ATTR_PAD: + case __OVS_TUNNEL_KEY_ATTR_MAX: + VLOG_INFO("%s: unknown type %d", __func__, type); + break; + default: + VLOG_INFO("%s: unknown type %d", __func__, type); + OVS_NOT_REACHED(); + } + } + + return ODP_FIT_PERFECT; +} + +/* Converts the OVS netlink-formatted action 'src' into a BPF action in 'dst'. + * + * Returns 0 on success, or a positive errno value on failure. + */ +int +odp_action_to_bpf_action(const struct nlattr *src, struct bpf_action *dst) +{ + enum ovs_action_attr type = nl_attr_type(src); + + switch (type) { + case OVS_ACTION_ATTR_PUSH_VLAN: { + const struct ovs_action_push_vlan *vlan = nl_attr_get(src); + dst->u.push_vlan = *vlan; + VLOG_DBG("push vlan tpid %x tci %x", vlan->vlan_tpid, vlan->vlan_tci); + break; + } + case OVS_ACTION_ATTR_CT: + ct_action_to_bpf(nl_attr_get(src), dst); + break; + case OVS_ACTION_ATTR_RECIRC: + dst->u.recirc_id = nl_attr_get_u32(src); + break; + case OVS_ACTION_ATTR_SAMPLE: + // XXX: ignore + return 1; + case OVS_ACTION_ATTR_USERSPACE: + if (nl_attr_get_size(src) <= sizeof dst->u.userspace.nlattr_data) { + size_t len = nl_attr_get_size(src); + memcpy(dst->u.userspace.nlattr_data, nl_attr_get(src), len); + dst->u.userspace.nlattr_len = len; + VLOG_INFO("size of userspace action is %ld", len); + } else { + VLOG_WARN("Size of userspace action too large: %ld > %ld", + nl_attr_get_size(src), + sizeof dst->u.userspace.nlattr_data); + return EOPNOTSUPP; + } + break; + case OVS_ACTION_ATTR_HASH: { + const struct ovs_action_hash *hash_act = nl_attr_get(src); + dst->u.hash = *hash_act; + break; + } + case OVS_ACTION_ATTR_SET: + case OVS_ACTION_ATTR_SET_MASKED: { + const struct nlattr *a; + + dst->is_set_tunnel = 0; + a = nl_attr_get(src); + dst->u.mset.key_type = nl_attr_type(a); + + switch (nl_attr_type(a)) { + case OVS_KEY_ATTR_TUNNEL: { + enum odp_key_fitness ret; + struct flow_tnl_t tunnel; + + dst->is_set_tunnel = 1; + tunnel.tun_id = 0; + ret = odp_tun_to_bpf_tun(nl_attr_get(a), nl_attr_get_size(a), + &tunnel); + if (ret != ODP_FIT_PERFECT) { + return EOPNOTSUPP; + } + + dst->u.tunnel.tunnel_id = tunnel.tun_id; + if (!tunnel.use_ipv6) + dst->u.tunnel.remote_ipv4 = tunnel.ip4.ip_dst; +#ifdef BPF_ENABLE_IPV6 + else + memcpy(dst->u.tunnel.remote_ipv6, tunnel.ip6.ipv6_dst, 16); +#endif + dst->u.tunnel.tunnel_tos = tunnel.ip_tos; + dst->u.tunnel.tunnel_ttl = tunnel.ip_ttl; + dst->u.tunnel.use_ipv6 = tunnel.use_ipv6; + + if (tunnel.gnvopt_valid) { + dst->u.tunnel.gnvopt = tunnel.gnvopt; + dst->u.tunnel.gnvopt_valid = 1; + } + break; + } + case OVS_KEY_ATTR_ETHERNET: { + struct ovs_key_ethernet *ether; + + //ovs_assert(nl_attr_get_size(a) == 2 * sizeof *ether); + + ether = &dst->u.mset.key.ether; + memcpy(ether, nl_attr_get(a), sizeof *ether); + break; + } + case OVS_KEY_ATTR_IPV4: { + struct ovs_key_ipv4 *ip; + + //ovs_assert(nl_attr_get_size(a) == 2 * sizeof *ip); + + ip = &dst->u.mset.key.ipv4; + memcpy(ip, nl_attr_get(a), sizeof *ip); + break; + } + default: + VLOG_INFO("%s: set/set_mask %d is not supported", __func__, + nl_attr_type(a)); + return EOPNOTSUPP; + } + break; + } + case OVS_ACTION_ATTR_TRUNC: { + const struct ovs_action_trunc *trunc = nl_attr_get(src); + dst->u.trunc = *trunc; + VLOG_INFO("truncate to %d byte", trunc->max_len); + break; + } + case OVS_ACTION_ATTR_POP_VLAN: + case OVS_ACTION_ATTR_PUSH_MPLS: + case OVS_ACTION_ATTR_POP_MPLS: + case OVS_ACTION_ATTR_PUSH_ETH: + case OVS_ACTION_ATTR_POP_ETH: + case OVS_ACTION_ATTR_TUNNEL_PUSH: + case OVS_ACTION_ATTR_TUNNEL_POP: + case OVS_ACTION_ATTR_CLONE: + case OVS_ACTION_ATTR_METER: + case OVS_ACTION_ATTR_CT_CLEAR: + case OVS_ACTION_ATTR_PUSH_NSH: + case OVS_ACTION_ATTR_POP_NSH: + VLOG_WARN("Unsupported action type %d", nl_attr_type(src)); + return EOPNOTSUPP; + case OVS_ACTION_ATTR_UNSPEC: + case OVS_ACTION_ATTR_OUTPUT: + case __OVS_ACTION_ATTR_MAX: + OVS_NOT_REACHED(); + } + + return 0; +} + +int +bpf_actions_to_odp_actions(struct bpf_action_batch *batch, struct ofpbuf *out) +{ + int i; + + for (i = 0; i < BPF_DP_MAX_ACTION; i++) { + struct bpf_action *act = &batch->actions[i]; + enum ovs_action_attr type = act->type; + + switch (type) { + case OVS_ACTION_ATTR_UNSPEC: + /* End of actions list. */ + return 0; + + case OVS_ACTION_ATTR_OUTPUT: { + /* XXX: ifindex to odp translation */ + nl_msg_put_u32(out, type, act->u.out.port); + break; + } + case OVS_ACTION_ATTR_PUSH_VLAN: { + nl_msg_put_unspec(out, type, &act->u.push_vlan, + sizeof act->u.push_vlan); + break; + } + case OVS_ACTION_ATTR_RECIRC: + nl_msg_put_u32(out, type, act->u.recirc_id); + break; + case OVS_ACTION_ATTR_TRUNC: + nl_msg_put_unspec(out, type, &act->u.trunc, sizeof act->u.trunc); + break; + case OVS_ACTION_ATTR_HASH: + nl_msg_put_unspec(out, type, &act->u.hash, sizeof act->u.hash); + break; + case OVS_ACTION_ATTR_PUSH_MPLS: + nl_msg_put_unspec(out, type, &act->u.mpls, sizeof act->u.mpls); + break; + case OVS_ACTION_ATTR_POP_MPLS: + nl_msg_put_be16(out, type, act->u.ethertype); + break; + case OVS_ACTION_ATTR_SAMPLE: { + VLOG_WARN("XXX FIXME attr sample"); + break; + } + case OVS_ACTION_ATTR_SET: { + // see parse_tc_flower_to_match + size_t start_ofs; + size_t tun_key_ofs; + struct ovs_action_set_tunnel *tun; + + tun = &act->u.tunnel; + start_ofs = nl_msg_start_nested(out, OVS_ACTION_ATTR_SET); + tun_key_ofs = nl_msg_start_nested(out, OVS_KEY_ATTR_TUNNEL); + + nl_msg_put_be64(out, OVS_TUNNEL_KEY_ATTR_ID, + be32_to_be64(htonl(tun->tunnel_id))); + + if (!tun->use_ipv6) { + if (tun->remote_ipv4) { + nl_msg_put_be32(out, OVS_TUNNEL_KEY_ATTR_IPV4_DST, + htonl(tun->remote_ipv4)); + } +#ifdef BPF_ENABLE_IPV6 + } else { + if (ipv6_addr_is_set((const struct in6_addr *)&tun->remote_ipv6)) { + nl_msg_put_in6_addr(out, OVS_TUNNEL_KEY_ATTR_IPV6_DST, + (const struct in6_addr *)&tun->remote_ipv6); + } +#endif + } + +#if 0 + if (!tnl_type || !strcmp(tnl_type, "geneve")) { + tun_metadata_to_geneve_nlattr(tun_key, tun_flow_key, key_buf, a); + } +#endif + nl_msg_end_nested(out, tun_key_ofs); + nl_msg_end_nested(out, start_ofs); + break; + } + case OVS_ACTION_ATTR_SET_MASKED: { + VLOG_WARN("XXX FXIME attr set masked"); + size_t offset = nl_msg_start_nested(out, OVS_ACTION_ATTR_SET_MASKED); + + nl_msg_end_nested(out, offset); + break; + } + + case OVS_ACTION_ATTR_USERSPACE: { + VLOG_WARN("XXX FXIME attr userspace"); +#if 0 + size_t offset; + struct ovs_action_userspace *au; + + au = &act->u.userspace; + + offset = nl_msg_start_nested(out, OVS_ACTION_ATTR_USERSPACE); + nl_msg_put_u32(out, OVS_USERSPACE_ATTR_PID, 123); + if (nlattr_len != 0) { + memcpy(nl_msg_put_unspec_zero(odp_actions, OVS_USERSPACE_ATTR_USERDATA, + MAX(8, userdata_size)), + userdata, userdata_size); + } + nl_msg_end_nested(out, offset); +#endif + break; + } + case OVS_ACTION_ATTR_CT: + case OVS_ACTION_ATTR_POP_VLAN: + case OVS_ACTION_ATTR_PUSH_ETH: + case OVS_ACTION_ATTR_POP_ETH: + case OVS_ACTION_ATTR_TUNNEL_PUSH: + case OVS_ACTION_ATTR_TUNNEL_POP: + case OVS_ACTION_ATTR_CLONE: + case OVS_ACTION_ATTR_METER: + case OVS_ACTION_ATTR_CT_CLEAR: + case OVS_ACTION_ATTR_PUSH_NSH: + case OVS_ACTION_ATTR_POP_NSH: + VLOG_WARN("Unexpected action type %d", type); + return EOPNOTSUPP; + case __OVS_ACTION_ATTR_MAX: + default: + OVS_NOT_REACHED(); + break; + } + } + return 0; +} + +/* Extracts packet metadata from the BPF-formatted flow key in 'key' into a + * flow structure in 'flow'. Returns an ODP_FIT_* value that indicates how well + * 'key' fits our expectations for what a flow key should contain. + * + * Note that flow->in_port will still contain an ifindex after this call, the + * caller is responsible for converting it to an odp_port number. + */ +void +bpf_flow_key_extract_metadata(const struct bpf_flow_key *key, + struct flow *flow) +{ + const struct pkt_metadata_t *md = &key->mds.md; + + /* metadata parsing */ + flow->packet_type = htonl(PT_ETH); + flow->in_port.odp_port = u32_to_odp(md->in_port); + flow->recirc_id = md->recirc_id; + flow->dp_hash = md->dp_hash; + flow->skb_priority = md->skb_priority; + flow->pkt_mark = md->pkt_mark; + flow->ct_state = md->ct_state; + flow->ct_zone = md->ct_zone; + flow->ct_mark = md->ct_mark; + if (flow->recirc_id != 0) { + VLOG_INFO("recirc_id = %d", flow->recirc_id); + } + + const struct flow_tnl_t *tun = &key->mds.tnl_md; + if (!tun->use_ipv6) { + flow->tunnel.ip_src = htonl(tun->ip4.ip_src); + flow->tunnel.ip_dst = htonl(tun->ip4.ip_dst); +#ifdef BPF_ENABLE_IPV6 + } else { + memcpy(&flow->tunnel.ipv6_src, tun->ip6.ipv6_src, 16); + memcpy(&flow->tunnel.ipv6_dst, tun->ip6.ipv6_dst, 16); +#endif + } + flow->tunnel.ip_tos = tun->ip_tos; + flow->tunnel.ip_ttl = tun->ip_ttl; + flow->tunnel.tun_id = htonll(tun->tun_id); + //flow->tunnel.flags = FLOW_TNL_F_DONT_FRAGMENT; // this causes key differs + flow->tunnel.flags = 0; + + if (tun->gnvopt_valid) { + memcpy(flow->tunnel.metadata.opts.gnv, &tun->gnvopt, + sizeof tun->gnvopt); + flow->tunnel.metadata.present.len = sizeof tun->gnvopt; + flow->tunnel.flags |= FLOW_TNL_F_UDPIF; + } + +//#define IP_DF 0x4000 /* Flag: "Don't Fragment" */ +// flow->tunnel.flags = 0x40; //htons(IP_DF); + /* TODO */ + /* + flow->ct_label = md.ct_label; + ct_nw_proto + ct_{nw,tp}_{src,dst} + flow_tnl_copy__() + */ +} + +/* XXX The caller must perform in_port translation. */ +void +bpf_metadata_from_flow(const struct flow *flow, struct ebpf_metadata_t *md) +{ + if (flow->packet_type != htonl(PT_ETH)) { + VLOG_WARN("Cannot convert flow to bpf metadata: non-ethernet"); + } + md->md.in_port = odp_to_u32(flow->in_port.odp_port); /* XXX */ + md->md.recirc_id = flow->recirc_id; + md->md.dp_hash = flow->dp_hash; + md->md.skb_priority = flow->skb_priority; + md->md.pkt_mark = flow->pkt_mark; + md->md.ct_state = flow->ct_state; + md->md.ct_zone = flow->ct_zone; + md->md.ct_mark = flow->ct_mark; + + /* TODO */ + /* + md->md.ct_label = flow.ct_label; + flow_tnl_copy__() + */ +} + +enum odp_key_fitness +bpf_flow_key_to_flow(const struct bpf_flow_key *key, struct flow *flow) +{ + const struct ebpf_headers_t *hdrs = &key->headers; + + memset(flow, 0, sizeof *flow); + bpf_flow_key_extract_metadata(key, flow); + + /* L2 */ + if (hdrs->valid & ETHER_VALID) { + memcpy(&flow->dl_dst, &hdrs->ethernet.dstAddr, sizeof(struct eth_addr)); + memcpy(&flow->dl_src, &hdrs->ethernet.srcAddr, sizeof(struct eth_addr)); + flow->dl_type = hdrs->ethernet.etherType; + } + if (hdrs->valid & VLAN_VALID) { + flow->vlans[0].tpid = hdrs->vlan.etherType; + flow->vlans[0].tci = htons(hdrs->vlan.tci) | htons(VLAN_CFI); + // extract_ + flow->dl_type = hdrs->vlan.etherType; + } + + /* L3 */ + if (hdrs->valid & IPV4_VALID) { + flow->nw_src = hdrs->ipv4.srcAddr; + flow->nw_dst = hdrs->ipv4.dstAddr; + flow->nw_ttl = hdrs->ipv4.ttl; + flow->nw_proto = hdrs->ipv4.protocol; +#ifdef BPF_ENABLE_IPV6 + } else if (hdrs->valid & IPV6_VALID) { + memcpy(&flow->ipv6_src, &hdrs->ipv6.srcAddr, sizeof flow->ipv6_src); + memcpy(&flow->ipv6_dst, &hdrs->ipv6.dstAddr, sizeof flow->ipv6_dst); + flow->ipv6_label = htonl(hdrs->ipv6.flowLabel); + /* XXX: flow->nw_frag */ + flow->nw_tos = hdrs->ipv6.trafficClass; + flow->nw_ttl = hdrs->ipv6.hopLimit; + flow->nw_proto = hdrs->ipv6.nextHdr; +#endif + } else if (hdrs->valid & ARP_VALID) { + memcpy(&flow->arp_sha, key->headers.arp.ar_sha, 6); + memcpy(&flow->arp_tha, key->headers.arp.ar_tha, 6); + memcpy(&flow->nw_src, key->headers.arp.ar_sip, 4); /* be32 */ + memcpy(&flow->nw_dst, key->headers.arp.ar_tip, 4); + + if (ntohs(key->headers.arp.ar_op) < 0xff) { + flow->nw_proto = ntohs(key->headers.arp.ar_op); + } else { + flow->nw_proto = 0; + } + } + + /* L4 */ + if (hdrs->valid & TCP_VALID) { + flow->tcp_flags = hdrs->tcp.flags; + flow->tp_src = hdrs->tcp.srcPort; + flow->tp_dst = hdrs->tcp.dstPort; + } else if (hdrs->valid & UDP_VALID) { + flow->tp_src = htons(hdrs->udp.srcPort); + flow->tp_dst = htons(hdrs->udp.dstPort); + } else if (hdrs->valid & ICMP_VALID) { + /* XXX: validate */ + flow->tp_src = htons(hdrs->icmp.type); // u8 to be16 + flow->tp_dst = htons(hdrs->icmp.code); + } else if (hdrs->valid & ICMPV6_VALID) { + flow->tp_src = htons(hdrs->icmpv6.type); // u8 to be16 + flow->tp_dst = htons(hdrs->icmpv6.code); + } /* XXX: IGMP */ + + return ODP_FIT_PERFECT; +} + +/* Converts the 'nla_len' bytes of OVS netlink-formatted flow key in 'nla' into + * the bpf flow structure in 'key'. Returns an ODP_FIT_* value that indicates + * how well 'nla' fits into the BPF flow key format. On success, 'in_port' will + * be populated with the in_port specified by 'nla', which the caller must + * convert from an ODP port number into an ifindex and place into 'key'. + */ +enum odp_key_fitness +odp_key_to_bpf_flow_key(const struct nlattr *nla, size_t nla_len, + struct bpf_flow_key *key, odp_port_t *in_port, + bool inner, bool verbose) +{ + bool found_in_port = false; + const struct nlattr *a; + size_t left; + + NL_ATTR_FOR_EACH(a, left, nla, nla_len) { + enum ovs_key_attr type = nl_attr_type(a); + + switch (type) { + case OVS_KEY_ATTR_PRIORITY: + key->mds.md.skb_priority = nl_attr_get_u32(a); + break; + case OVS_KEY_ATTR_IN_PORT: { + /* The caller must convert the ODP port number into ifindex. */ + *in_port = nl_attr_get_odp_port(a); + found_in_port = true; + break; + } + case OVS_KEY_ATTR_ETHERNET: { + const struct ovs_key_ethernet *eth = nl_attr_get(a); + + for (int i = 0; i < ARRAY_SIZE(eth->eth_dst.ea); i++) { + key->headers.ethernet.dstAddr[i] = eth->eth_dst.ea[i]; + key->headers.ethernet.srcAddr[i] = eth->eth_src.ea[i]; + } + key->headers.valid |= ETHER_VALID; + break; + } + case OVS_KEY_ATTR_VLAN: { + ovs_be16 tci = nl_attr_get_be16(a); + struct vlan_tag_t *vlan = inner ? &key->headers.cvlan + : &key->headers.vlan; + vlan->tci = ntohs(tci); + key->headers.vlan.tci = ntohs(tci); + /* etherType is set below in OVS_KEY_ATTR_ETHERTYPE. */ + key->headers.valid |= VLAN_VALID; + break; + } + case OVS_KEY_ATTR_ETHERTYPE: { + ovs_be16 dl_type; + + dl_type = nl_attr_get_be16(a); + key->headers.ethernet.etherType = dl_type; + key->headers.valid |= ETHER_VALID; + + if (dl_type == htons(ETH_P_IP)) { + key->headers.valid |= IPV4_VALID; + } else if (dl_type == htons(ETH_P_IPV6)) { + key->headers.valid |= IPV6_VALID; + } else if (dl_type == htons(ETH_P_ARP)) { + key->headers.valid |= ARP_VALID; + } else if (dl_type == htons(ETH_P_8021Q)) { + key->headers.vlan.etherType = htons(ETH_P_8021Q); + key->headers.valid |= VLAN_VALID; + } else if (dl_type == htons(ETH_P_8021AD)) { + key->headers.cvlan.etherType = htons(ETH_P_8021AD); + key->headers.valid |= CVLAN_VALID; + } else if (dl_type == htons(ETH_P_MPLS_UC) || + dl_type == htons(ETH_P_MPLS_MC)) { + key->headers.valid |= MPLS_VALID; + } else { + VLOG_WARN("%s dl_type %x not supported", + __func__, ntohs(dl_type)); + } + break; + } + case OVS_KEY_ATTR_IPV4: { + const struct ovs_key_ipv4 *ipv4 = nl_attr_get(a); + + key->headers.ipv4.srcAddr = ipv4->ipv4_src; + key->headers.ipv4.dstAddr = ipv4->ipv4_dst; + key->headers.ipv4.protocol = ipv4->ipv4_proto; + key->headers.ipv4.ttl = ipv4->ipv4_ttl; + /* XXX: ipv4->ipv4_frag; One of OVS_FRAG_TYPE_*. */ + key->headers.valid |= IPV4_VALID; + break; + } + case OVS_KEY_ATTR_IPV6: { +#ifdef BPF_ENABLE_IPV6 + const struct ovs_key_ipv6 *ipv6 = nl_attr_get(a); + + memcpy(&key->headers.ipv6.srcAddr, &ipv6->ipv6_src, + ARRAY_SIZE(key->headers.ipv6.srcAddr)); + memcpy(&key->headers.ipv6.dstAddr, &ipv6->ipv6_dst, + ARRAY_SIZE(key->headers.ipv6.dstAddr)); + key->headers.ipv6.flowLabel = ntohl(ipv6->ipv6_label); + key->headers.ipv6.nextHdr = ipv6->ipv6_proto; + key->headers.ipv6.trafficClass = ipv6->ipv6_tclass; + key->headers.ipv6.hopLimit = ipv6->ipv6_hlimit; + /* XXX: ipv6_frag; One of OVS_FRAG_TYPE_*. */ + key->headers.valid |= IPV6_VALID; +#endif + break; + } + case OVS_KEY_ATTR_TCP: { + const struct ovs_key_tcp *tcp = nl_attr_get(a); + + key->headers.tcp.srcPort = tcp->tcp_src; + key->headers.tcp.dstPort = tcp->tcp_dst; + key->headers.valid |= TCP_VALID; + break; + } + case OVS_KEY_ATTR_UDP: { + const struct ovs_key_udp *udp = nl_attr_get(a); + + key->headers.udp.srcPort = ntohs(udp->udp_src); + key->headers.udp.dstPort = ntohs(udp->udp_dst); + key->headers.valid |= UDP_VALID; + break; + } + case OVS_KEY_ATTR_ICMP: { + const struct ovs_key_icmp *icmp = nl_attr_get(a); + /* XXX: Double-check */ + key->headers.icmp.type = icmp->icmp_type; + key->headers.icmp.code = icmp->icmp_code; + key->headers.valid |= ICMP_VALID; + break; + } + case OVS_KEY_ATTR_ARP: { + const struct ovs_key_arp *arp = nl_attr_get(a); + + key->headers.arp.ar_op = arp->arp_op; + memcpy(key->headers.arp.ar_sip, &arp->arp_sip, 4); + memcpy(key->headers.arp.ar_tip, &arp->arp_tip, 4); /* be32 */ + memcpy(key->headers.arp.ar_sha, &arp->arp_sha, 6); + memcpy(key->headers.arp.ar_tha, &arp->arp_tha, 6); + key->headers.valid |= ARP_VALID; + break; + } + case OVS_KEY_ATTR_SKB_MARK: + key->mds.md.pkt_mark = nl_attr_get_u32(a); + break; + case OVS_KEY_ATTR_TCP_FLAGS: { + ovs_be16 flags_be = nl_attr_get_be16(a); + + key->headers.tcp.flags = flags_be; + key->headers.valid |= TCP_VALID; + break; + } + case OVS_KEY_ATTR_DP_HASH: + key->mds.md.dp_hash = nl_attr_get_u32(a); + break; + case OVS_KEY_ATTR_RECIRC_ID: + key->mds.md.recirc_id = nl_attr_get_u32(a); + break; + case OVS_KEY_ATTR_CT_STATE: + key->mds.md.ct_state = nl_attr_get_u32(a); + break; + case OVS_KEY_ATTR_CT_ZONE: + key->mds.md.ct_zone = nl_attr_get_u16(a); + break; + case OVS_KEY_ATTR_CT_MARK: + key->mds.md.ct_mark = nl_attr_get_u32(a); + break; + case OVS_KEY_ATTR_CT_LABELS: + memcpy(&key->mds.md.ct_label, nl_attr_get(a), + sizeof(key->mds.md.ct_label)); + break; + case OVS_KEY_ATTR_PACKET_TYPE: { + ovs_be32 pt = nl_attr_get_be32(a); + if (pt != htonl(PT_ETH)) { + return ODP_FIT_ERROR; + } + break; + } + case OVS_KEY_ATTR_MPLS: { + const struct ovs_key_mpls *mpls = nl_attr_get(a); + key->headers.mpls.top_lse = mpls->mpls_lse; + break; + } + case OVS_KEY_ATTR_ENCAP: { + enum odp_key_fitness ret; + ret = odp_key_to_bpf_flow_key(nl_attr_get(a), nl_attr_get_size(a), + key, in_port, true, verbose); + if (ret != ODP_FIT_PERFECT) { + return ret; + } + break; + } + case OVS_KEY_ATTR_TUNNEL: { + enum odp_key_fitness ret; + ret = odp_tun_to_bpf_tun(nl_attr_get(a), nl_attr_get_size(a), + &key->mds.tnl_md); + if (ret != ODP_FIT_PERFECT) { + VLOG_ERR("%s odp key to bpf tunnel key error", __func__); + return ret; + } + break; + } + case OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4: + case OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6: + case OVS_KEY_ATTR_ICMPV6: { + const struct ovs_key_icmpv6 *icmpv6 = nl_attr_get(a); + + key->headers.icmpv6.type = icmpv6->icmpv6_type; + key->headers.icmpv6.code = icmpv6->icmpv6_code; + key->headers.valid |= ICMPV6_VALID; + break; + } + case OVS_KEY_ATTR_ND: { + // XXX skip + break; + } + case OVS_KEY_ATTR_SCTP: + case OVS_KEY_ATTR_NSH: + { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 20); + struct ds ds = DS_EMPTY_INITIALIZER; + // compile error, remove it + //odp_format_key_attr(a, NULL, NULL, &ds, verbose); + VLOG_INFO_RL(&rl, "Cannot convert \'%s\'", ds_cstr(&ds)); + ds_destroy(&ds); + return ODP_FIT_ERROR; + } + case OVS_KEY_ATTR_UNSPEC: + case __OVS_KEY_ATTR_MAX: + default: + OVS_NOT_REACHED(); + } + } + + if (!inner && !found_in_port) { + VLOG_ERR("not found in_port"); + return ODP_FIT_ERROR; + } + + if (!inner && verbose) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); + struct ds ds = DS_EMPTY_INITIALIZER; + + ds_put_format(&ds, "%s\nODP:\n", __func__); + odp_flow_key_format(nla, nla_len, &ds); + ds_put_cstr(&ds, "\nBPF:\n"); + bpf_flow_key_format(&ds, key); + VLOG_INFO_RL(&rl, "%s", ds_cstr(&ds)); + ds_destroy(&ds); + } + + return ODP_FIT_PERFECT; +} + +#define TABSPACE " " + +static void +indent(struct ds *ds, struct ds *tab, const char *string) +{ + ds_put_format(ds, "%s%s", ds_cstr(tab), string); + ds_put_cstr(tab, TABSPACE); +} + +static void +trim(struct ds *ds, struct ds *tab) +{ + ds_chomp(ds, '\n'); + ds_put_char(ds, '\n'); + ds_truncate(tab, tab->length ? tab->length - strlen(TABSPACE) : 0); +} + +#define PUT_FIELD(STRUCT, NAME, FORMAT) \ + if (STRUCT->NAME) \ + ds_put_format(ds, #NAME"=%"FORMAT",", STRUCT->NAME) + +void +bpf_flow_key_format(struct ds *ds, const struct bpf_flow_key *key) +{ + struct ds tab = DS_EMPTY_INITIALIZER; + + indent(ds, &tab, "headers:\n"); + { + if (key->headers.valid & ETHER_VALID) { + const struct ethernet_t *eth = &key->headers.ethernet; + const struct eth_addr *src = (struct eth_addr *)ð->srcAddr; + const struct eth_addr *dst = (struct eth_addr *)ð->dstAddr; + + ds_put_format(ds, "%sethernet(", ds_cstr(&tab)); + PUT_FIELD(eth, etherType, "#"PRIx16); + ds_put_format(ds, "dst="ETH_ADDR_FMT",", ETH_ADDR_ARGS(*dst)); + ds_put_format(ds, "src="ETH_ADDR_FMT",", ETH_ADDR_ARGS(*src)); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } + if (key->headers.valid & IPV4_VALID) { + const struct ipv4_t *ipv4 = &key->headers.ipv4; + + ds_put_format(ds, "%sipv4(", ds_cstr(&tab)); + PUT_FIELD(ipv4, ttl, "#"PRIx8); + PUT_FIELD(ipv4, tos, "#"PRIx8); + PUT_FIELD(ipv4, protocol, "#"PRIx8); + ds_put_format(ds, "srcAddr="IP_FMT",", IP_ARGS(ipv4->srcAddr)); + ds_put_format(ds, "dstAddr="IP_FMT",", IP_ARGS(ipv4->dstAddr)); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } +#ifdef BPF_ENABLE_IPV6 + if (key->headers.valid & IPV6_VALID) { + const struct ipv6_t *ipv6 = &key->headers.ipv6; + + ds_put_format(ds, "%sipv6(", ds_cstr(&tab)); + PUT_FIELD(ipv6, version, "#"PRIx8); + PUT_FIELD(ipv6, trafficClass, "#"PRIx8); + PUT_FIELD(ipv6, flowLabel, "#"PRIx32); + PUT_FIELD(ipv6, payloadLen, "#"PRIx16); + PUT_FIELD(ipv6, nextHdr, "#"PRIx8); + PUT_FIELD(ipv6, hopLimit, "#"PRIx8); + ds_put_cstr(ds, "src="); + ipv6_format_addr((struct in6_addr *)&ipv6->srcAddr, ds); + ds_put_cstr(ds, ",dst="); + ipv6_format_addr((struct in6_addr *)&ipv6->dstAddr, ds); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } +#endif + if (key->headers.valid & ARP_VALID) { + const struct arp_rarp_t *arp = &key->headers.arp; + + ds_put_format(ds, "%sarp(", ds_cstr(&tab)); + PUT_FIELD(arp, ar_hrd, "#"PRIx16); + PUT_FIELD(arp, ar_pro, "#"PRIx16); + PUT_FIELD(arp, ar_hln, "#"PRIx8); + PUT_FIELD(arp, ar_pln, "#"PRIx8); + PUT_FIELD(arp, ar_op, "#"PRIx16); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } + if (key->headers.valid & TCP_VALID) { + const struct tcp_t *tcp = &key->headers.tcp; + + ds_put_format(ds, "%stcp(", ds_cstr(&tab)); + PUT_FIELD(tcp, srcPort, PRIu16); + PUT_FIELD(tcp, dstPort, PRIu16); + PUT_FIELD(tcp, flags, "#"PRIx16); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } + if (key->headers.valid & UDP_VALID) { + const struct udp_t *udp = &key->headers.udp; + + ds_put_format(ds, "%sudp(", ds_cstr(&tab)); + PUT_FIELD(udp, srcPort, PRIu16); + PUT_FIELD(udp, dstPort, PRIu16); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } + if (key->headers.valid & ICMP_VALID) { + const struct icmp_t *icmp = &key->headers.icmp; + + ds_put_format(ds, "%sicmp(", ds_cstr(&tab)); + PUT_FIELD(icmp, type, "#"PRIx8); + PUT_FIELD(icmp, code, "#"PRIx8); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } + if (key->headers.valid & VLAN_VALID) { + const struct vlan_tag_t *vlan = &key->headers.vlan; + + ds_put_format(ds, "%svlan(", ds_cstr(&tab)); + PUT_FIELD(vlan, pcp, "#"PRIx8); + PUT_FIELD(vlan, cfi, "#"PRIx8); + PUT_FIELD(vlan, vid, "#"PRIx16); + PUT_FIELD(vlan, tci, "#"PRIx16); + PUT_FIELD(vlan, etherType, "#"PRIx16); + ds_chomp(ds, ','); + ds_put_format(ds, ")\n"); + } + } + trim(ds, &tab); + indent(ds, &tab, "metadata:\n"); + { + indent(ds, &tab, "md:\n"); + { + ds_put_hex_dump(ds, &key->mds.md, sizeof key->mds.md, 0, false); + } + trim(ds, &tab); + indent(ds, &tab, "tnl_md:\n"); + { + ds_put_hex_dump(ds, &key->mds.tnl_md, sizeof key->mds.tnl_md, 0, + false); + } + trim(ds, &tab); + } + trim(ds, &tab); + ds_chomp(ds, '\n'); + + ds_destroy(&tab); +} diff --git a/lib/dpif-bpf-odp.h b/lib/dpif-bpf-odp.h new file mode 100644 index 000000000000..ddf9b5fec6af --- /dev/null +++ b/lib/dpif-bpf-odp.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DPIF_BPF_ODP_H +#define DPIF_BPF_ODP_H 1 + +#include "odp-util.h" + +struct flow; +struct flow_tnl_t; +struct nlattr; +struct bpf_flow_key; +struct bpf_action; +struct ebpf_metadata_t; +struct bpf_action_batch; + +int odp_action_to_bpf_action(const struct nlattr *, struct bpf_action *); +int bpf_actions_to_odp_actions(struct bpf_action_batch *, struct ofpbuf *out); +enum odp_key_fitness bpf_flow_key_to_flow(const struct bpf_flow_key *, + struct flow *); +void bpf_flow_key_extract_metadata(const struct bpf_flow_key *, + struct flow *flow); +void bpf_metadata_from_flow(const struct flow *flow, + struct ebpf_metadata_t *md); +enum odp_key_fitness odp_key_to_bpf_flow_key(const struct nlattr *, size_t, + struct bpf_flow_key *, + odp_port_t *in_port, + bool inner, bool verbose); +enum odp_key_fitness odp_tun_to_bpf_tun(const struct nlattr *nla, + size_t nla_len, + struct flow_tnl_t *tun); +void bpf_flow_key_format(struct ds *ds, const struct bpf_flow_key *key); + +#endif /* dpif-bpf-odp.h */