From patchwork Sat Apr 2 00:37:11 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Tu X-Patchwork-Id: 605082 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (archives.nicira.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id 3qcK8W2MFGz9sRB for ; Sat, 2 Apr 2016 11:37:30 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=XGhbJEYX; dkim-atps=neutral Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 0E536108D4; Fri, 1 Apr 2016 17:37:29 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5]) by archives.nicira.com (Postfix) with ESMTPS id A3790108C0 for ; Fri, 1 Apr 2016 17:37:27 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id BA76C1614BC for ; Fri, 1 Apr 2016 18:37:26 -0600 (MDT) X-ASG-Debug-ID: 1459557444-0b32373537206250001-byXFYA Received: from mx3-pf2.cudamail.com ([192.168.14.1]) by bar6.cudamail.com with ESMTP id ya87HNgNpXCFUfRJ (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Fri, 01 Apr 2016 18:37:24 -0600 (MDT) X-Barracuda-Envelope-From: u9012063@gmail.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.1 Received: from unknown (HELO mail-pf0-f169.google.com) (209.85.192.169) by mx3-pf2.cudamail.com with ESMTPS (RC4-SHA encrypted); 2 Apr 2016 00:37:22 -0000 Received-SPF: pass (mx3-pf2.cudamail.com: SPF record at _netblocks.google.com designates 209.85.192.169 as permitted sender) X-Barracuda-RBL-Trusted-Forwarder: 209.85.192.169 Received: by mail-pf0-f169.google.com with SMTP id e128so80432494pfe.3 for ; Fri, 01 Apr 2016 17:37:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id; bh=hDTqAzEC5PDDmsDRwMF+cbs4parIdRgGOjhRyJ89S74=; b=XGhbJEYXVZyhBiARYqQnklv/zRC9R0q8PGG6t8Jl6o6TWHNdGDr6P7rwsGadmfcpA1 bA3rpPvytFr0NbG5Bcb4jImNBp91sMxuT2ZrhlMXbeKmpK90QKKM9MZSOpeuZHRYJ3Zl wmu6p72gNuoTM2IrMPcevRw7uHzn2Fm5XPgyMk35nRhnffdCDuAd8mzf9nDZ+hWpdz3S eUOI/8PILnZukW1xyP2TmB/1gfe7JSqm6G7E4GgQ1IA69F0wRG8Frshbtf0cCzfDMAF/ EvdCsQjmcikS73ckg/BiSaGE5s0Wr2Xb5CJ6fktKs4bpJQLgMfgxgEDj6h916GKZPoUb jLsQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:subject:date:message-id; bh=hDTqAzEC5PDDmsDRwMF+cbs4parIdRgGOjhRyJ89S74=; b=YS/k4wUSsl1hyyaI625d8K9nWGTMJ+Nv1oxaCJZSmznFV0mPn49kE16iMibWZtE6jy DoA/TOnB8AA+CsNMUBa6CECLbwig4TXalpxs0gK0Bn2V7e6pg/Frhabs7Q80ijFTkG/s KlVw5Q+CwAQwZmG9w5gIirGlrBwj0tfkv4lgRjvgQM8gjmScM5Wi6q21bH1AcMNzB7dQ L64tpG3r1te2BGLHijXcC6+E+ZJSex5H4oA6UejvWJw4J3r+pZi46EnN8trpjAoCJwFc y2lSeervHhzrLwAflvWQgURyTLwpEhU3ROFJy+W7TZ9U8p8IqAvBEXWm6uwne3KS30zA t5EA== X-Gm-Message-State: AD7BkJJmmHnctesz4RhkGC08s25gbEOzLjM327PtFTTBsC3XzDxKWadbReM+6h2tZ2sRxw== X-Received: by 10.98.10.147 with SMTP id 19mr2580644pfk.87.1459557441994; Fri, 01 Apr 2016 17:37:21 -0700 (PDT) Received: from vm-dev.localdomain ([208.91.1.34]) by smtp.gmail.com with ESMTPSA id 27sm22507292pfo.58.2016.04.01.17.37.21 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 01 Apr 2016 17:37:21 -0700 (PDT) X-CudaMail-Envelope-Sender: u9012063@gmail.com X-Barracuda-Apparent-Source-IP: 208.91.1.34 From: William Tu To: dev@openvswitch.org X-CudaMail-MID: CM-V2-331056308 X-CudaMail-DTE: 040116 X-CudaMail-Originating-IP: 209.85.192.169 Date: Fri, 1 Apr 2016 17:37:11 -0700 X-ASG-Orig-Subj: [##CM-V2-331056308##][PATCH v2] ofp-actions: Add a new action to truncate a packet. Message-Id: <1459557431-3365-1-git-send-email-u9012063@gmail.com> X-Mailer: git-send-email 2.5.0 X-GBUdb-Analysis: 0, 209.85.192.169, Ugly c=0.442133 p=-0.205479 Source Normal X-MessageSniffer-Rules: 0-0-0-32767-c X-Barracuda-Connect: UNKNOWN[192.168.14.1] X-Barracuda-Start-Time: 1459557444 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-Barracuda-BRTS-Status: 1 X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-Spam-Score: 1.60 X-Barracuda-Spam-Status: No, SCORE=1.60 using per-user scores of TAG_LEVEL=3.5 QUARANTINE_LEVEL=1000.0 KILL_LEVEL=4.0 tests=BSF_RULE7568M, BSF_RULE_7582B, BSF_SC5_MJ1963, DKIM_SIGNED, MAILTO_TO_SPAM_ADDR, RDNS_NONE X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.3.28382 Rule breakdown below pts rule name description ---- ---------------------- -------------------------------------------------- 0.00 DKIM_SIGNED Domain Keys Identified Mail: message has a signature 0.00 MAILTO_TO_SPAM_ADDR URI: Includes a link to a likely spammer email 0.50 BSF_RULE7568M Custom Rule 7568M 0.50 BSF_RULE_7582B Custom Rule 7582B 0.10 RDNS_NONE Delivered to trusted network by a host with no rDNS 0.50 BSF_SC5_MJ1963 Custom Rule MJ1963 Subject: [ovs-dev] [PATCH v2] ofp-actions: Add a new action to truncate a packet. X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dev-bounces@openvswitch.org Sender: "dev" The patch proposes adding a new action to support packet truncation. The new action is formatted as 'output(port=n,max_len=m)', as output to port n, with packet size being MIN(original_size, m). One use case is to enable port mirroring to send smaller packets to the destination port so that only useful packet information is mirrored/copied, saving some performance overhead of copying entire packet payload. Example use case is below as well as shown in the testcases: - Output to port 1 with max_len 100 bytes. - The output packet size on port 1 will be MIN(original_packet_size, 100). # ovs-ofctl add-flow br0 'actions=output(port=1,max_len=100)' - The scope of max_len is limited to output action itself. The following output:1 and output:2 will be the original packet size. # ovs-ofctl add-flow br0 'actions=output(port=1,max_len=100),output:1,output:2' Implementation/Limitaions: - The patch adds a new OpenFlow extension action OFPACT_OUTPUT_TRUNC, and a new datapath action, OVS_ACTION_ATTR_OUTPUT_TRUNC. - The minimum value of max_len is 60 byte (minimum Ethernet frame size). This is defined in OVS_ACTION_OUTPUT_MIN. - Only "output(port=[0-9]*,max_len=)" is supported. Output to IN_PORT, TABLE, NORMAL, FLOOD, ALL, and LOCAL are not supported. Signed-off-by: William Tu --- v1->v2 - Create new OpenFlow action, do not reuse OFPACT_OUTPUT - Create new datapath action --- datapath/actions.c | 18 +++- datapath/datapath.h | 1 + datapath/flow_netlink.c | 9 ++ datapath/linux/compat/include/linux/openvswitch.h | 9 ++ datapath/vport.c | 6 ++ lib/dp-packet.c | 1 + lib/dp-packet.h | 1 + lib/dpif-netdev.c | 28 ++++++ lib/dpif.c | 1 + lib/netdev.c | 8 ++ lib/odp-execute.c | 2 + lib/odp-util.c | 11 ++- lib/ofp-actions.c | 102 ++++++++++++++++++++++ lib/ofp-actions.h | 10 +++ ofproto/ofproto-dpif-sflow.c | 6 ++ ofproto/ofproto-dpif-xlate.c | 39 +++++++++ tests/ofproto-dpif.at | 53 +++++++++++ tests/system-traffic.at | 66 ++++++++++++++ 18 files changed, 366 insertions(+), 5 deletions(-) diff --git a/datapath/actions.c b/datapath/actions.c index dcf8591..a1a1241 100644 --- a/datapath/actions.c +++ b/datapath/actions.c @@ -738,10 +738,13 @@ err: } static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, - struct sw_flow_key *key) + uint16_t max_len, struct sw_flow_key *key) { struct vport *vport = ovs_vport_rcu(dp, out_port); + if (unlikely(max_len != 0)) + OVS_CB(skb)->max_len = max_len; + if (likely(vport)) { u16 mru = OVS_CB(skb)->mru; @@ -1034,6 +1037,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, * is slightly obscure just to avoid that. */ int prev_port = -1; + uint16_t max_len = 0; const struct nlattr *a; int rem; @@ -1045,9 +1049,10 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC); if (out_skb) - do_output(dp, out_skb, prev_port, key); + do_output(dp, out_skb, prev_port, max_len, key); prev_port = -1; + max_len = 0; } switch (nla_type(a)) { @@ -1055,6 +1060,13 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, prev_port = nla_get_u32(a); break; + case OVS_ACTION_ATTR_OUTPUT_TRUNC: { + struct ovs_action_output_trunc *output_trunc = nla_data(a); + prev_port = output_trunc->port; + max_len = output_trunc->max_len; + break; + } + case OVS_ACTION_ATTR_USERSPACE: output_userspace(dp, skb, key, a, attr, len); break; @@ -1126,7 +1138,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, } if (prev_port != -1) - do_output(dp, skb, prev_port, key); + do_output(dp, skb, prev_port, max_len, key); else consume_skb(skb); diff --git a/datapath/datapath.h b/datapath/datapath.h index ceb3372..178d81d 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -102,6 +102,7 @@ struct datapath { struct ovs_skb_cb { struct vport *input_vport; u16 mru; + u16 max_len; /* 0 means original packet size. */ }; #define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb) diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c index 6ffcc53..4fc4f7b 100644 --- a/datapath/flow_netlink.c +++ b/datapath/flow_netlink.c @@ -2170,6 +2170,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, /* Expected argument lengths, (u32)-1 for variable length. */ static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = { [OVS_ACTION_ATTR_OUTPUT] = sizeof(u32), + [OVS_ACTION_ATTR_OUTPUT_TRUNC] = sizeof(struct ovs_action_output_trunc), [OVS_ACTION_ATTR_RECIRC] = sizeof(u32), [OVS_ACTION_ATTR_USERSPACE] = (u32)-1, [OVS_ACTION_ATTR_PUSH_MPLS] = sizeof(struct ovs_action_push_mpls), @@ -2207,6 +2208,14 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, return -EINVAL; break; + case OVS_ACTION_ATTR_OUTPUT_TRUNC: { + const struct ovs_action_output_trunc *output_trunc = nla_data(a); + if (output_trunc->port >= DP_MAX_PORTS || + output_trunc->max_len < OVS_ACTION_OUTPUT_MIN) + return -EINVAL; + break; + } + case OVS_ACTION_ATTR_HASH: { const struct ovs_action_hash *act_hash = nla_data(a); diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h index 3b39ebb..4574eb1 100644 --- a/datapath/linux/compat/include/linux/openvswitch.h +++ b/datapath/linux/compat/include/linux/openvswitch.h @@ -600,6 +600,13 @@ enum ovs_userspace_attr { #define OVS_USERSPACE_ATTR_MAX (__OVS_USERSPACE_ATTR_MAX - 1) +struct ovs_action_output_trunc { + uint32_t port; + uint16_t max_len; /* 0 means original packet size. */ +}; +/* Minimum packet size max_len can have, 60 = ETH_MIN_FRAME_LEN. */ +#define OVS_ACTION_OUTPUT_MIN 60 + /** * struct ovs_action_push_mpls - %OVS_ACTION_ATTR_PUSH_MPLS action argument. * @mpls_lse: MPLS label stack entry to push. @@ -742,6 +749,7 @@ enum ovs_nat_attr { * enum ovs_action_attr - Action types. * * @OVS_ACTION_ATTR_OUTPUT: Output packet to port. + * @OVS_ACTION_ATTR_TRUNC: Output packet to port with truncated packet size. * @OVS_ACTION_ATTR_USERSPACE: Send packet to userspace according to nested * %OVS_USERSPACE_ATTR_* attributes. * @OVS_ACTION_ATTR_PUSH_VLAN: Push a new outermost 802.1Q header onto the @@ -788,6 +796,7 @@ enum ovs_nat_attr { enum ovs_action_attr { OVS_ACTION_ATTR_UNSPEC, OVS_ACTION_ATTR_OUTPUT, /* u32 port number. */ + OVS_ACTION_ATTR_OUTPUT_TRUNC, /* u32 port number + u16 max_len */ OVS_ACTION_ATTR_USERSPACE, /* Nested OVS_USERSPACE_ATTR_*. */ OVS_ACTION_ATTR_SET, /* One nested OVS_KEY_ATTR_*. */ OVS_ACTION_ATTR_PUSH_VLAN, /* struct ovs_action_push_vlan. */ diff --git a/datapath/vport.c b/datapath/vport.c index 44b9dfb..31a6128 100644 --- a/datapath/vport.c +++ b/datapath/vport.c @@ -487,6 +487,8 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, OVS_CB(skb)->input_vport = vport; OVS_CB(skb)->mru = 0; + OVS_CB(skb)->max_len = 0; + if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { u32 mark; @@ -615,6 +617,7 @@ static unsigned int packet_length(const struct sk_buff *skb) void ovs_vport_send(struct vport *vport, struct sk_buff *skb) { int mtu = vport->dev->mtu; + u16 max_len = OVS_CB(skb)->max_len; if (unlikely(packet_length(skb) > mtu && !skb_is_gso(skb))) { net_warn_ratelimited("%s: dropped over-mtu packet: %d > %d\n", @@ -624,6 +627,9 @@ void ovs_vport_send(struct vport *vport, struct sk_buff *skb) goto drop; } + if (unlikely(max_len >= OVS_ACTION_OUTPUT_MIN)) + skb_trim(skb, max_len); + skb->dev = vport->dev; vport->ops->send(skb); return; diff --git a/lib/dp-packet.c b/lib/dp-packet.c index aec7fe7..d32fa85 100644 --- a/lib/dp-packet.c +++ b/lib/dp-packet.c @@ -29,6 +29,7 @@ dp_packet_init__(struct dp_packet *b, size_t allocated, enum dp_packet_source so b->source = source; dp_packet_reset_offsets(b); pkt_metadata_init(&b->md, 0); + b->max_len = 0; } static void diff --git a/lib/dp-packet.h b/lib/dp-packet.h index 000b09d..9f10fa8 100644 --- a/lib/dp-packet.h +++ b/lib/dp-packet.h @@ -58,6 +58,7 @@ struct dp_packet { * or UINT16_MAX. */ uint16_t l4_ofs; /* Transport-level header offset, or UINT16_MAX. */ + uint16_t max_len; /* Max packet sent size. 0 means original size. */ struct pkt_metadata md; }; diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 119dc2d..4b9d559 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -3744,6 +3744,34 @@ dp_execute_cb(void *aux_, struct dp_packet **packets, int cnt, int i; switch ((enum ovs_action_attr)type) { + case OVS_ACTION_ATTR_OUTPUT_TRUNC: { + struct dp_packet *trunc_pkts[cnt]; + const struct ovs_action_output_trunc *output_trunc = + nl_attr_get_unspec(a, sizeof *output_trunc); + + p = dp_netdev_lookup_port(dp, output_trunc->port); + + if (output_trunc->max_len >= OVS_ACTION_OUTPUT_MIN) { + if (!may_steal) { + dp_netdev_clone_pkt_batch(trunc_pkts, packets, cnt); + packets = trunc_pkts; + } + for (i = 0; i < cnt; i++) { + packets[i]->max_len = output_trunc->max_len; + } + } + + if (OVS_LIKELY(p)) { + int tx_qid; + + atomic_read_relaxed(&pmd->tx_qid, &tx_qid); + + netdev_send(p->netdev, tx_qid, packets, cnt, may_steal); + return; + } + break; + } + case OVS_ACTION_ATTR_OUTPUT: p = dp_netdev_lookup_port(dp, u32_to_odp(nl_attr_get_u32(a))); if (OVS_LIKELY(p)) { diff --git a/lib/dpif.c b/lib/dpif.c index a784de7..272bd24 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -1099,6 +1099,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet **packets, int cnt, switch ((enum ovs_action_attr)type) { case OVS_ACTION_ATTR_CT: case OVS_ACTION_ATTR_OUTPUT: + case OVS_ACTION_ATTR_OUTPUT_TRUNC: case OVS_ACTION_ATTR_TUNNEL_PUSH: case OVS_ACTION_ATTR_TUNNEL_POP: case OVS_ACTION_ATTR_USERSPACE: diff --git a/lib/netdev.c b/lib/netdev.c index 3e50694..92201c6 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -758,6 +758,14 @@ netdev_send(struct netdev *netdev, int qid, struct dp_packet **buffers, return EOPNOTSUPP; } + for (int i = 0; i < cnt; i++) { + struct dp_packet *packet = buffers[i]; + if (packet->max_len >= OVS_ACTION_OUTPUT_MIN && + packet->max_len < dp_packet_size(packet)) { + dp_packet_set_size(packet, packet->max_len); + } + } + int error = netdev->netdev_class->send(netdev, qid, buffers, cnt, may_steal); if (!error) { diff --git a/lib/odp-execute.c b/lib/odp-execute.c index b5204b2..4281334 100644 --- a/lib/odp-execute.c +++ b/lib/odp-execute.c @@ -488,6 +488,7 @@ requires_datapath_assistance(const struct nlattr *a) switch (type) { /* These only make sense in the context of a datapath. */ case OVS_ACTION_ATTR_OUTPUT: + case OVS_ACTION_ATTR_OUTPUT_TRUNC: case OVS_ACTION_ATTR_TUNNEL_PUSH: case OVS_ACTION_ATTR_TUNNEL_POP: case OVS_ACTION_ATTR_USERSPACE: @@ -624,6 +625,7 @@ odp_execute_actions(void *dp, struct dp_packet **packets, int cnt, bool steal, break; case OVS_ACTION_ATTR_OUTPUT: + case OVS_ACTION_ATTR_OUTPUT_TRUNC: case OVS_ACTION_ATTR_TUNNEL_PUSH: case OVS_ACTION_ATTR_TUNNEL_POP: case OVS_ACTION_ATTR_USERSPACE: diff --git a/lib/odp-util.c b/lib/odp-util.c index b4689cc..a4b4802 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -107,6 +107,7 @@ odp_action_len(uint16_t type) switch ((enum ovs_action_attr) type) { case OVS_ACTION_ATTR_OUTPUT: return sizeof(uint32_t); + case OVS_ACTION_ATTR_OUTPUT_TRUNC: return sizeof(struct ovs_action_output_trunc); case OVS_ACTION_ATTR_TUNNEL_PUSH: return ATTR_LEN_VARIABLE; case OVS_ACTION_ATTR_TUNNEL_POP: return sizeof(uint32_t); case OVS_ACTION_ATTR_USERSPACE: return ATTR_LEN_VARIABLE; @@ -775,6 +776,14 @@ format_odp_action(struct ds *ds, const struct nlattr *a) case OVS_ACTION_ATTR_OUTPUT: ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a)); break; + case OVS_ACTION_ATTR_OUTPUT_TRUNC: { + const struct ovs_action_output_trunc *output_trunc = + nl_attr_get_unspec(a, sizeof *output_trunc); + ds_put_format(ds, "(port=%"PRIu32",max_len=%"PRIu16")", + output_trunc->port, output_trunc->max_len); + break; + } + break; case OVS_ACTION_ATTR_TUNNEL_POP: ds_put_format(ds, "tnl_pop(%"PRIu32")", nl_attr_get_u32(a)); break; @@ -1516,13 +1525,11 @@ parse_odp_action(const char *s, const struct simap *port_names, { uint32_t port; int n; - if (ovs_scan(s, "%"SCNi32"%n", &port, &n)) { nl_msg_put_u32(actions, OVS_ACTION_ATTR_OUTPUT, port); return n; } } - if (port_names) { int len = strcspn(s, delimiters); struct simap_node *node; diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index aac4ff0..9374380 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -299,6 +299,9 @@ enum ofp_raw_action_type { /* NX1.0+(36): struct nx_action_nat, ... */ NXAST_RAW_NAT, + /* NX1.0+(38): struct nx_action_output_trunc. */ + NXAST_RAW_OUTPUT_TRUNC, + /* ## ------------------ ## */ /* ## Debugging actions. ## */ /* ## ------------------ ## */ @@ -379,6 +382,7 @@ ofpact_next_flattened(const struct ofpact *ofpact) case OFPACT_CONTROLLER: case OFPACT_ENQUEUE: case OFPACT_OUTPUT_REG: + case OFPACT_OUTPUT_TRUNC: case OFPACT_BUNDLE: case OFPACT_SET_FIELD: case OFPACT_SET_VLAN_VID: @@ -536,6 +540,36 @@ encode_OUTPUT(const struct ofpact_output *output, } static char * OVS_WARN_UNUSED_RESULT +parse_truncate_subfield(struct ofpact_output_trunc *output_trunc, + const char *arg_) +{ + char *key, *value; + char *error = NULL; + char *arg = CONST_CAST(char *, arg_); + + while (ofputil_parse_key_value(&arg, &key, &value)) { + char *error; + if (!strcmp(key, "port")) { + if (!ofputil_port_from_string(value, &output_trunc->port)) { + return xasprintf("%s: output to unknown port", value); + } + } else if (!strcmp(key, "max_len")) { + error = str_to_u16(value, key, &output_trunc->max_len); + } else { + error = xasprintf("invalid key '%s' in output_trunc argument", + key); + } + if (error) { + return error; + } + } + if (!error && arg[0]) { + error = xstrdup("unexpected input following field syntax"); + } + return error; +} + +static char * OVS_WARN_UNUSED_RESULT parse_OUTPUT(const char *arg, struct ofpbuf *ofpacts, enum ofputil_protocol *usable_protocols OVS_UNUSED) { @@ -545,6 +579,10 @@ parse_OUTPUT(const char *arg, struct ofpbuf *ofpacts, output_reg = ofpact_put_OUTPUT_REG(ofpacts); output_reg->max_len = UINT16_MAX; return mf_parse_subfield(&output_reg->src, arg); + } else if (strstr(arg, "port=") && strstr(arg, "max_len=")) { + struct ofpact_output_trunc *output_trunc; + output_trunc = ofpact_put_OUTPUT_TRUNC(ofpacts); + return parse_truncate_subfield(output_trunc, arg); } else { struct ofpact_output *output; @@ -5512,6 +5550,62 @@ parse_NAT(char *arg, struct ofpbuf *ofpacts, return NULL; } +/* Truncate output action. */ +struct nx_action_output_trunc { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* At least 16. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_OUTPUT_TRUNC. */ + ovs_be16 max_len; /* Truncate packet to size bytes */ + ovs_be32 port; /* Output port */ +}; +OFP_ASSERT(sizeof(struct nx_action_output_trunc) == 16); + +static enum ofperr +decode_NXAST_RAW_OUTPUT_TRUNC(const struct nx_action_output_trunc *natrc, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + struct ofpact_output_trunc *output_trunc; + + output_trunc = ofpact_put_OUTPUT_TRUNC(out); + output_trunc->max_len = ntohs(natrc->max_len); + output_trunc->port = ntohl(natrc->port); + + if (output_trunc->max_len < OVS_ACTION_OUTPUT_MIN) { + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + return 0; +} + +static void +encode_OUTPUT_TRUNC(const struct ofpact_output_trunc *output_trunc, + enum ofp_version ofp_version OVS_UNUSED, + struct ofpbuf *out) +{ + struct nx_action_output_trunc *natrc = put_NXAST_OUTPUT_TRUNC(out); + + natrc->max_len = htons(output_trunc->max_len); + natrc->port = htonl(output_trunc->port); +} + +static char * OVS_WARN_UNUSED_RESULT +parse_OUTPUT_TRUNC(const char *arg OVS_UNUSED, struct ofpbuf *ofpacts OVS_UNUSED, + enum ofputil_protocol *usable_protocols OVS_UNUSED) +{ + OVS_NOT_REACHED(); /* output_trunc action uses parse_OUTPUT. */ + return NULL; +} + +static void +format_OUTPUT_TRUNC(const struct ofpact_output_trunc *a, struct ds *s) +{ + if (ofp_to_u16(a->port) < ofp_to_u16(OFPP_MAX)) { + ds_put_format(s, "%soutput%s(port=%"PRIu16",max_len=%"PRIu16")", + colors.special, colors.end, a->port, a->max_len); + } +} + /* Meter instruction. */ @@ -5906,6 +6000,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a) case OFPACT_NOTE: case OFPACT_OUTPUT: case OFPACT_OUTPUT_REG: + case OFPACT_OUTPUT_TRUNC: case OFPACT_POP_MPLS: case OFPACT_POP_QUEUE: case OFPACT_PUSH_MPLS: @@ -5934,6 +6029,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a) case OFPACT_DEC_TTL: case OFPACT_GROUP: case OFPACT_OUTPUT: + case OFPACT_OUTPUT_TRUNC: case OFPACT_POP_MPLS: case OFPACT_PUSH_MPLS: case OFPACT_PUSH_VLAN: @@ -6157,6 +6253,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type) case OFPACT_CONTROLLER: case OFPACT_ENQUEUE: case OFPACT_OUTPUT_REG: + case OFPACT_OUTPUT_TRUNC: case OFPACT_BUNDLE: case OFPACT_SET_VLAN_VID: case OFPACT_SET_VLAN_PCP: @@ -6585,6 +6682,10 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a, case OFPACT_OUTPUT_REG: return mf_check_src(&ofpact_get_OUTPUT_REG(a)->src, flow); + case OFPACT_OUTPUT_TRUNC: + return ofpact_check_output_port(ofpact_get_OUTPUT_TRUNC(a)->port, + max_ports); + case OFPACT_BUNDLE: return bundle_check(ofpact_get_BUNDLE(a), max_ports, flow); @@ -7261,6 +7362,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port) return port == OFPP_CONTROLLER; case OFPACT_OUTPUT_REG: + case OFPACT_OUTPUT_TRUNC: case OFPACT_BUNDLE: case OFPACT_SET_VLAN_VID: case OFPACT_SET_VLAN_PCP: diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index 4bd8854..2df7841 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -108,6 +108,7 @@ OFPACT(UNROLL_XLATE, ofpact_unroll_xlate, ofpact, "unroll_xlate") \ OFPACT(CT, ofpact_conntrack, ofpact, "ct") \ OFPACT(NAT, ofpact_nat, ofpact, "nat") \ + OFPACT(OUTPUT_TRUNC, ofpact_output_trunc,ofpact, "output_trunc") \ \ /* Debugging actions. \ * \ @@ -290,6 +291,15 @@ struct ofpact_output_reg { struct mf_subfield src; }; +/* OFPACT_OUTPUT_TRUNC. + * + * Used for NXAST_OUTPUT_TRUNC. */ +struct ofpact_output_trunc { + struct ofpact ofpact; + ofp_port_t port; /* Output port. */ + uint16_t max_len; /* Max send len. */ +}; + /* Bundle slave choice algorithm to apply. * * In the descriptions below, 'slaves' is the list of possible slaves in the diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c index fbc82b7..dde4cc6 100644 --- a/ofproto/ofproto-dpif-sflow.c +++ b/ofproto/ofproto-dpif-sflow.c @@ -1105,6 +1105,12 @@ dpif_sflow_read_actions(const struct flow *flow, sflow_actions->out_port = u32_to_odp(nl_attr_get_u32(a)); break; + case OVS_ACTION_ATTR_OUTPUT_TRUNC: { + const struct ovs_action_output_trunc *output = + nl_attr_get_unspec(a, sizeof *output); + sflow_actions->out_port = u32_to_odp(output->port); + break; + } case OVS_ACTION_ATTR_TUNNEL_POP: /* XXX: Do not handle this for now. It's not clear * if we should start with encap_depth == 1 when we diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index a02dc24..bf3d47a 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -3925,6 +3925,39 @@ xlate_output_reg_action(struct xlate_ctx *ctx, } static void +xlate_output_trunc_action(struct xlate_ctx *ctx, + ofp_port_t port, uint16_t max_len) +{ + odp_port_t odp_port; + struct ovs_action_output_trunc *output_trunc; + + switch (port) { + case OFPP_IN_PORT: + case OFPP_TABLE: + case OFPP_NORMAL: + case OFPP_FLOOD: + case OFPP_ALL: + case OFPP_CONTROLLER: + case OFPP_NONE: + case OFPP_LOCAL: + xlate_report(ctx, "output_trunc does not support this port"); + break; + default: + if (port != ctx->xin->flow.in_port.ofp_port) { + output_trunc = nl_msg_put_unspec_uninit(ctx->odp_actions, + OVS_ACTION_ATTR_OUTPUT_TRUNC, + sizeof *output_trunc); + odp_port = ofp_port_to_odp_port(ctx->xbridge, port); + output_trunc->port = odp_port; + output_trunc->max_len = max_len; + } else { + xlate_report(ctx, "skipping output to input port"); + } + break; + } +} + +static void xlate_enqueue_action(struct xlate_ctx *ctx, const struct ofpact_enqueue *enqueue) { @@ -4209,6 +4242,7 @@ freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end, for (; a < end; a = ofpact_next(a)) { switch (a->type) { case OFPACT_OUTPUT_REG: + case OFPACT_OUTPUT_TRUNC: case OFPACT_GROUP: case OFPACT_OUTPUT: case OFPACT_CONTROLLER: @@ -4714,6 +4748,11 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, xlate_output_reg_action(ctx, ofpact_get_OUTPUT_REG(a)); break; + case OFPACT_OUTPUT_TRUNC: + xlate_output_trunc_action(ctx, ofpact_get_OUTPUT_TRUNC(a)->port, + ofpact_get_OUTPUT_TRUNC(a)->max_len); + break; + case OFPACT_LEARN: xlate_learn_action(ctx, ofpact_get_LEARN(a)); break; diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index da29ac2..6d9197b 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -5309,6 +5309,59 @@ PORTNAME portName=p2 ])]) +AT_SETUP([ofproto-dpif - output_trunc action]) +OVS_VSWITCHD_START +add_of_ports br0 1 2 3 4 5 + +AT_CHECK([ovs-vsctl -- set Interface p1 type=dummy options:pcap=p1.pcap]) +AT_CHECK([ovs-vsctl -- set Interface p2 type=dummy options:pstream=punix:p2.sock]) +AT_CHECK([ovs-vsctl -- set Interface p3 type=dummy options:stream=unix:p2.sock]) +AT_CHECK([ovs-vsctl -- set Interface p4 type=dummy options:pstream=punix:p4.sock]) +AT_CHECK([ovs-vsctl -- set Interface p5 type=dummy options:stream=unix:p4.sock]) + +dnl output:2(max_len=32) shows here as truncated size +AT_CHECK([ovs-ofctl add-flow br0 'in_port=3,actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=5,actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=output(port=2,max_len=64),output:4']) + +dnl An 170 byte packet +AT_CHECK([ovs-appctl netdev-dummy/receive p1 '000c29c8a0a4005056c0000808004500009cb4a6000040019003c0a8da01c0a8da640800cb5fa762000556f431ad0009388e08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f']) + +AT_CHECK([ovs-ofctl parse-pcap p1.pcap], [0], [dnl +icmp,in_port=ANY,vlan_tci=0x0000,dl_src=00:50:56:c0:00:08,dl_dst=00:0c:29:c8:a0:a4,nw_src=192.168.218.1,nw_dst=192.168.218.100,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0 +]) +dnl packet with truncated size +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=64\).*/\1/p'], [0], [dnl +n_bytes=64 +]) +dnl dnl packet with original size +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=170\).*/\1/p'], [0], [dnl +n_bytes=170 +]) + +dnl More complicated case +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=3,actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=5,actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1,actions=output(port=2,max_len=64),output(port=2, max_len=128),output( port=4,max_len=60 ),output:2,output:4']) + +dnl An 170 byte packet +AT_CHECK([ovs-appctl netdev-dummy/receive p1 '000c29c8a0a4005056c0000808004500009cb4a6000040019003c0a8da01c0a8da640800cb5fa762000556f431ad0009388e08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f']) + +sleep 1 +dnl packet size: 64 + 128 + 170 = 362 +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=362\).*/\1/p'], [0], [dnl +n_bytes=362 +]) +dnl packet size: 60 + 170 = 230 +AT_CHECK([ovs-ofctl dump-flows br0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=230\).*/\1/p'], [0], [dnl +n_bytes=230 +]) + +OVS_VSWITCHD_STOP +AT_CLEANUP + + AT_SETUP([ofproto-dpif - sFlow packet sampling - IPv4 collector]) CHECK_SFLOW_SAMPLING_PACKET([127.0.0.1]) AT_CLEANUP diff --git a/tests/system-traffic.at b/tests/system-traffic.at index 28adbdc..cf8d3f3 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -49,6 +49,72 @@ NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -w 2 10.2.2.2 | FORMAT_PING OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP +AT_SETUP([datapath - output_trunc action]) +OVS_TRAFFIC_VSWITCHD_START() + +AT_CHECK([ovs-ofctl del-flows br0]) + +dnl Create p0 and ovs-p0(1) +ADD_NAMESPACES(at_ns0) +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24") +NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address e6:66:c1:11:11:11]) +NS_CHECK_EXEC([at_ns0], [arp -s 10.1.1.2 e6:66:c1:22:22:22]) + +dnl Create p1(3) and ovs-p1(2), packets received from ovs-p1 will appear in p1 +AT_CHECK([ip link add p1 type veth peer name ovs-p1]) +AT_CHECK([ip link set dev ovs-p1 up]) +AT_CHECK([ip link set dev p1 up]) +AT_CHECK([ovs-vsctl add-port br0 ovs-p1 -- set interface ovs-p1 ofport_request=2]) +dnl Use p1 to check the truncated packet +AT_CHECK([ovs-vsctl add-port br0 p1 -- set interface p1 ofport_request=3]) + +dnl Create p2(5) and ovs-p2(4) +AT_CHECK([ip link add p2 type veth peer name ovs-p2]) +AT_CHECK([ip link set dev ovs-p2 up]) +AT_CHECK([ip link set dev p2 up]) +AT_CHECK([ovs-vsctl add-port br0 ovs-p2 -- set interface ovs-p2 ofport_request=4]) +dnl Use p1 to check the truncated packet +AT_CHECK([ovs-vsctl add-port br0 p2 -- set interface p2 ofport_request=5]) + +dnl test1: basic +AT_CHECK([ovs-ofctl add-flow br0 'in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output(port=2,max_len=100),output:4']) + +NS_CHECK_EXEC([at_ns0], [ping -q -c 1 -s 1024 -w 0 10.1.1.2 | FORMAT_PING], [0], [dnl +1 packets transmitted, 0 received, 100% packet loss, time 0ms +]) +AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=100\).*/\1/p'], [0], [dnl +n_bytes=100 +]) +dnl packet with original size +AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=1066\).*/\1/p'], [0], [dnl +n_bytes=1066 +]) + +dnl test2: more complicated output actions +AT_CHECK([ovs-ofctl del-flows br0]) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=3 dl_dst=e6:66:c1:22:22:22 actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=5 dl_dst=e6:66:c1:22:22:22 actions=drop']) +AT_CHECK([ovs-ofctl add-flow br0 'in_port=1 dl_dst=e6:66:c1:22:22:22 actions=output(port=2,max_len=100),output:4,output(port=2,max_len=100),output(port=4,max_len=100),output:2']) + +NS_CHECK_EXEC([at_ns0], [ping -q -c 1 -s 1024 -w 0 10.1.1.2 | FORMAT_PING], [0], [dnl +1 packets transmitted, 0 received, 100% packet loss, time 0ms +]) +dnl 100 + 100 + 1066 = 1266 +AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=3" | sed -n 's/.*\(n\_bytes=1266\).*/\1/p'], [0], [dnl +n_bytes=1266 +]) +dnl 1066 + 100 = 1166 +AT_CHECK([ovs-ofctl dump-flows br0 table=0 | grep "in_port=5" | sed -n 's/.*\(n\_bytes=1166\).*/\1/p'], [0], [dnl +n_bytes=1166 +]) + +OVS_TRAFFIC_VSWITCHD_STOP +AT_CHECK([ip link del ovs-p1]) +AT_CHECK([ip link del ovs-p2]) +AT_CLEANUP + AT_SETUP([datapath - ping6 between two ports]) OVS_TRAFFIC_VSWITCHD_START()