Send an optional OVS_PACKET_ATTR_TIMESTAMP attribute in upcalls with the packet's timestamp. Add a corresponding tstamp attribute into struct dpif_upcall.
Enable Linux packet timestamping, only if at least one flow requires it since Linux packet timestamping is set on or off for all packets. Define a new DPIF_FP_TIMESTAMP flag for dpif_flow_put.flags to enable timestamping for the lifetime of a flow. Add a new netlink OVS_FLOW_ATTR_TIMESTAMP flag attribute to the Linux datapath's flow set/mod/get/dump commands. Call net_enable_timestamp() and net_disable_timestamp() only if that flag is set. Enable packet timestamping in the Linux datapath for flows with "sample" or "ipfix" actions. Send microsecond-precision timestamps received in upcalls in IPFIX flow records. Signed-off-by: Romain Lenglet <rleng...@vmware.com> --- datapath/datapath.c | 67 +++++++++++++++++++++++++++++++++++++++-- datapath/datapath.h | 3 ++ datapath/flow.h | 1 + include/linux/openvswitch.h | 15 +++++++++- lib/dpif-linux.c | 29 +++++++++++++++++- lib/dpif-netdev.c | 22 ++++++++++++-- lib/dpif-provider.h | 15 +++++++--- lib/dpif.c | 49 ++++++++++++++++++++++-------- lib/dpif.h | 8 +++-- ofproto/ofproto-dpif-ipfix.c | 71 +++++++++++++++++++++++++++++++++++--------- ofproto/ofproto-dpif-ipfix.h | 3 ++ ofproto/ofproto-dpif.c | 36 +++++++++++++++++----- utilities/ovs-dpctl.8.in | 8 +++-- utilities/ovs-dpctl.c | 30 +++++++++++++++---- 14 files changed, 301 insertions(+), 56 deletions(-) diff --git a/datapath/datapath.c b/datapath/datapath.c index 3cb58b0..9ed81da 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007-2012 Nicira, Inc. + * Copyright (c) 2007-2013 Nicira, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public @@ -41,6 +41,7 @@ #include <linux/wait.h> #include <asm/div64.h> #include <linux/highmem.h> +#include <linux/netdevice.h> #include <linux/netfilter_bridge.h> #include <linux/netfilter_ipv4.h> #include <linux/inetdevice.h> @@ -397,6 +398,10 @@ static size_t upcall_msg_size(const struct sk_buff *skb, + nla_total_size(skb->len) /* OVS_PACKET_ATTR_PACKET */ + nla_total_size(key_attr_size()); /* OVS_PACKET_ATTR_KEY */ + /* OVS_PACKET_ATTR_TIMESTAMP */ + if (skb->tstamp.tv64) + size += nla_total_size(sizeof(struct ovs_packet_tstamp)); + /* OVS_PACKET_ATTR_USERDATA */ if (userdata) size += NLA_ALIGN(userdata->nla_len); @@ -445,6 +450,15 @@ static int queue_userspace_packet(struct net *net, int dp_ifindex, ovs_flow_to_nlattrs(upcall_info->key, user_skb); nla_nest_end(user_skb, nla); + if (skb->tstamp.tv64) { + struct ovs_packet_tstamp ts; + struct timeval tv = ktime_to_timeval(skb->tstamp); + ts.sec = tv.tv_sec; + ts.usec = tv.tv_usec; + + nla_put(user_skb, OVS_PACKET_ATTR_TIMESTAMP, sizeof(ts), &ts); + } + if (upcall_info->userdata) __nla_put(user_skb, OVS_PACKET_ATTR_USERDATA, nla_len(upcall_info->userdata), @@ -996,6 +1010,7 @@ static const struct nla_policy flow_policy[OVS_FLOW_ATTR_MAX + 1] = { [OVS_FLOW_ATTR_KEY] = { .type = NLA_NESTED }, [OVS_FLOW_ATTR_ACTIONS] = { .type = NLA_NESTED }, [OVS_FLOW_ATTR_CLEAR] = { .type = NLA_FLAG }, + [OVS_FLOW_ATTR_TIMESTAMP] = { .type = NLA_FLAG }, }; static struct genl_family dp_flow_genl_family = { @@ -1111,7 +1126,8 @@ static size_t ovs_flow_cmd_msg_size(const struct sw_flow_actions *acts) + nla_total_size(sizeof(struct ovs_flow_stats)) /* OVS_FLOW_ATTR_STATS */ + nla_total_size(1) /* OVS_FLOW_ATTR_TCP_FLAGS */ + nla_total_size(8) /* OVS_FLOW_ATTR_USED */ - + nla_total_size(acts->actions_len); /* OVS_FLOW_ATTR_ACTIONS */ + + nla_total_size(acts->actions_len) /* OVS_FLOW_ATTR_ACTIONS */ + + nla_total_size(0); /* OVS_FLOW_ATTR_TIMESTAMP */ } /* Called with ovs_mutex. */ @@ -1165,6 +1181,10 @@ static int ovs_flow_cmd_fill_info(struct sw_flow *flow, struct datapath *dp, nla_put_u8(skb, OVS_FLOW_ATTR_TCP_FLAGS, tcp_flags)) goto nla_put_failure; + if (flow->enable_timestamp && + nla_put_flag(skb, OVS_FLOW_ATTR_TIMESTAMP)) + goto nla_put_failure; + /* If OVS_FLOW_ATTR_ACTIONS doesn't fit, skip dumping the actions if * this is the first flow to be dumped into 'skb'. This is unusual for * Netlink but individual action lists can be longer than @@ -1223,6 +1243,38 @@ static struct sk_buff *ovs_flow_cmd_build_info(struct sw_flow *flow, return skb; } +static void ovs_flow_set_enable_timestamp(struct sw_flow *flow, + struct datapath *dp, + bool enable_timestamp) +{ + if (flow->enable_timestamp) { + if (!enable_timestamp) { + net_disable_timestamp(); + dp->n_enable_timestamp--; + if (dp->n_enable_timestamp < 0) { + pr_err("disabled timestamps one too many times\n"); + } + } + } else { + if (enable_timestamp) { + net_enable_timestamp(); + dp->n_enable_timestamp++; + } + } + flow->enable_timestamp = enable_timestamp; +} + +static void ovs_dp_disable_timestamp(struct datapath *dp) +{ + int i, n_enable_timestamp; + + /* Disable packet timestamping for all remaining flows that enabled it. */ + n_enable_timestamp = dp->n_enable_timestamp; + for (i = 0; i < n_enable_timestamp; i++) { + net_disable_timestamp(); + } +} + static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) { struct nlattr **a = info->attrs; @@ -1235,6 +1287,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) struct sw_flow_actions *acts = NULL; int error; int key_len; + bool enable_timestamp; /* Extract key. */ error = -EINVAL; @@ -1259,6 +1312,8 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) goto error; } + enable_timestamp = a[OVS_FLOW_ATTR_TIMESTAMP]; + ovs_lock(); dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex); error = -ENODEV; @@ -1291,6 +1346,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) error = PTR_ERR(flow); goto err_unlock_ovs; } + ovs_flow_set_enable_timestamp(flow, dp, enable_timestamp); clear_stats(flow); rcu_assign_pointer(flow->sf_acts, acts); @@ -1321,6 +1377,8 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) rcu_assign_pointer(flow->sf_acts, acts); ovs_flow_deferred_free_acts(old_acts); + ovs_flow_set_enable_timestamp(flow, dp, enable_timestamp); + reply = ovs_flow_cmd_build_info(flow, dp, info->snd_portid, info->snd_seq, OVS_FLOW_CMD_NEW); @@ -1333,6 +1391,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info) } ovs_unlock(); + if (!IS_ERR(reply)) ovs_notify(reply, info, &ovs_dp_flow_multicast_group); else @@ -1440,6 +1499,8 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info) info->snd_seq, 0, OVS_FLOW_CMD_DEL); BUG_ON(err < 0); + ovs_flow_set_enable_timestamp(flow, dp, false); + ovs_flow_deferred_free(flow); ovs_unlock(); @@ -1735,6 +1796,8 @@ static void __dp_destroy(struct datapath *dp) */ ovs_dp_detach_port(ovs_vport_ovsl(dp, OVSP_LOCAL)); + ovs_dp_disable_timestamp(dp); + call_rcu(&dp->rcu, destroy_dp_rcu); } diff --git a/datapath/datapath.h b/datapath/datapath.h index ad59a3a..4baaa33 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -66,6 +66,7 @@ struct dp_stats_percpu { * ovs_mutex and RCU. * @stats_percpu: Per-CPU datapath statistics. * @net: Reference to net namespace. + * @n_enable_timestamp: Number of flows that enabled packet timestamping. * * Context: See the comment on locking at the top of datapath.c for additional * locking information. @@ -87,6 +88,8 @@ struct datapath { /* Network namespace ref. */ struct net *net; #endif + + int n_enable_timestamp; }; /** diff --git a/datapath/flow.h b/datapath/flow.h index dba66cf..6eba67e 100644 --- a/datapath/flow.h +++ b/datapath/flow.h @@ -127,6 +127,7 @@ struct sw_flow { u64 packet_count; /* Number of packets matched. */ u64 byte_count; /* Number of bytes matched. */ u8 tcp_flags; /* Union of seen TCP flags. */ + bool enable_timestamp; /* Packet timestamping enabled. */ }; struct arp_eth_header { diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h index bd2f05f..432ea90 100644 --- a/include/linux/openvswitch.h +++ b/include/linux/openvswitch.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007-2011 Nicira, Inc. + * Copyright (c) 2007-2011, 2013 Nicira, Inc. * * This file is offered under your choice of two licenses: Apache 2.0 or GNU * GPL 2.0 or later. The permission statements for each of these licenses is @@ -151,6 +151,8 @@ enum ovs_packet_cmd { * notification if the %OVS_ACTION_ATTR_USERSPACE action specified an * %OVS_USERSPACE_ATTR_USERDATA attribute, with the same length and content * specified there. + * @OVS_PACKET_ATTR_TIMESTAMP: Present for all notifications if the + * packet contains a non-zero timestamp. * * These attributes follow the &struct ovs_header within the Generic Netlink * payload for %OVS_PACKET_* commands. @@ -161,11 +163,17 @@ enum ovs_packet_attr { OVS_PACKET_ATTR_KEY, /* Nested OVS_KEY_ATTR_* attributes. */ OVS_PACKET_ATTR_ACTIONS, /* Nested OVS_ACTION_ATTR_* attributes. */ OVS_PACKET_ATTR_USERDATA, /* OVS_ACTION_ATTR_USERSPACE arg. */ + OVS_PACKET_ATTR_TIMESTAMP, /* struct ovs_packet_tstamp */ __OVS_PACKET_ATTR_MAX }; #define OVS_PACKET_ATTR_MAX (__OVS_PACKET_ATTR_MAX - 1) +struct ovs_packet_tstamp { + __u32 sec; + __u32 usec; +}; + /* Virtual ports. */ #define OVS_VPORT_FAMILY "ovs_vport" @@ -408,6 +416,10 @@ struct ovs_key_nd { * @OVS_FLOW_ATTR_CLEAR: If present in a %OVS_FLOW_CMD_SET request, clears the * last-used time, accumulated TCP flags, and statistics for this flow. * Otherwise ignored in requests. Never present in notifications. + * @OVS_FLOW_ATTR_TIMESTAMP: If present in a %OVS_FLOW_CMD_NEW or + * %OVS_FLOW_CMD_SET request, enables packet timestamping, for one or + * more userspace actions in this flow require timestamps. Otherwise + * ignored in requests. * * These attributes follow the &struct ovs_header within the Generic Netlink * payload for %OVS_FLOW_* commands. @@ -420,6 +432,7 @@ enum ovs_flow_attr { OVS_FLOW_ATTR_TCP_FLAGS, /* 8-bit OR'd TCP flags. */ OVS_FLOW_ATTR_USED, /* u64 msecs last used in monotonic time. */ OVS_FLOW_ATTR_CLEAR, /* Flag to clear stats, tcp_flags, used. */ + OVS_FLOW_ATTR_TIMESTAMP, /* Flag to enable packet timestamping. */ __OVS_FLOW_ATTR_MAX }; diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c index b863a2e..b20f00f 100644 --- a/lib/dpif-linux.c +++ b/lib/dpif-linux.c @@ -111,6 +111,7 @@ struct dpif_linux_flow { const uint8_t *tcp_flags; /* OVS_FLOW_ATTR_TCP_FLAGS. */ const ovs_32aligned_u64 *used; /* OVS_FLOW_ATTR_USED. */ bool clear; /* OVS_FLOW_ATTR_CLEAR. */ + bool enable_timestamp; /* OVS_FLOW_ATTR_TIMESTAMP. */ }; static void dpif_linux_flow_init(struct dpif_linux_flow *); @@ -768,7 +769,8 @@ dpif_linux_flow_get__(const struct dpif *dpif_, static int dpif_linux_flow_get(const struct dpif *dpif_, const struct nlattr *key, size_t key_len, - struct ofpbuf **actionsp, struct dpif_flow_stats *stats) + struct ofpbuf **actionsp, bool *enable_timestamp, + struct dpif_flow_stats *stats) { struct dpif_linux_flow reply; struct ofpbuf *buf; @@ -786,6 +788,9 @@ dpif_linux_flow_get(const struct dpif *dpif_, } else { ofpbuf_delete(buf); } + if (enable_timestamp) { + *enable_timestamp = reply.enable_timestamp; + } } return error; } @@ -810,6 +815,9 @@ dpif_linux_init_flow_put(struct dpif *dpif_, const struct dpif_flow_put *put, if (put->flags & DPIF_FP_ZERO_STATS) { request->clear = true; } + if (put->flags & DPIF_FP_TIMESTAMP) { + request->enable_timestamp = true; + } request->nlmsg_flags = put->flags & DPIF_FP_MODIFY ? 0 : NLM_F_CREATE; } @@ -897,6 +905,7 @@ static int dpif_linux_flow_dump_next(const struct dpif *dpif_ OVS_UNUSED, void *state_, const struct nlattr **key, size_t *key_len, const struct nlattr **actions, size_t *actions_len, + bool *enable_timestamp, const struct dpif_flow_stats **stats) { struct dpif_linux_flow_state *state = state_; @@ -936,6 +945,9 @@ dpif_linux_flow_dump_next(const struct dpif *dpif_ OVS_UNUSED, void *state_, *key = state->flow.key; *key_len = state->flow.key_len; } + if (enable_timestamp) { + *enable_timestamp = state->flow.enable_timestamp; + } if (stats) { dpif_linux_flow_get_stats(&state->flow, &state->stats); *stats = &state->stats; @@ -1236,6 +1248,9 @@ parse_odp_packet(struct ofpbuf *buf, struct dpif_upcall *upcall, .min_len = ETH_HEADER_LEN }, [OVS_PACKET_ATTR_KEY] = { .type = NL_A_NESTED }, + /* Present if timestamp available. */ + [OVS_PACKET_ATTR_TIMESTAMP] = { .type = NL_A_UNSPEC, .optional = true }, + /* OVS_PACKET_CMD_ACTION only. */ [OVS_PACKET_ATTR_USERDATA] = { .type = NL_A_UNSPEC, .optional = true }, }; @@ -1275,6 +1290,12 @@ parse_odp_packet(struct ofpbuf *buf, struct dpif_upcall *upcall, upcall->key = CONST_CAST(struct nlattr *, nl_attr_get(a[OVS_PACKET_ATTR_KEY])); upcall->key_len = nl_attr_get_size(a[OVS_PACKET_ATTR_KEY]); + if (a[OVS_PACKET_ATTR_TIMESTAMP]) { + upcall->tstamp = CONST_CAST( + struct ovs_packet_tstamp *, + nl_attr_get_unspec(a[OVS_PACKET_ATTR_TIMESTAMP], + sizeof *upcall->tstamp)); + } upcall->userdata = a[OVS_PACKET_ATTR_USERDATA]; *dp_ifindex = ovs_header->dp_ifindex; @@ -1833,6 +1854,7 @@ dpif_linux_flow_from_ofpbuf(struct dpif_linux_flow *flow, [OVS_FLOW_ATTR_TCP_FLAGS] = { .type = NL_A_U8, .optional = true }, [OVS_FLOW_ATTR_USED] = { .type = NL_A_U64, .optional = true }, /* The kernel never uses OVS_FLOW_ATTR_CLEAR. */ + [OVS_FLOW_ATTR_TIMESTAMP] = { .type = NL_A_FLAG, .optional = true }, }; struct nlattr *a[ARRAY_SIZE(ovs_flow_policy)]; @@ -1871,6 +1893,7 @@ dpif_linux_flow_from_ofpbuf(struct dpif_linux_flow *flow, if (a[OVS_FLOW_ATTR_USED]) { flow->used = nl_attr_get(a[OVS_FLOW_ATTR_USED]); } + flow->enable_timestamp = a[OVS_FLOW_ATTR_TIMESTAMP]; return 0; } @@ -1906,6 +1929,10 @@ dpif_linux_flow_to_ofpbuf(const struct dpif_linux_flow *flow, if (flow->clear) { nl_msg_put_flag(buf, OVS_FLOW_ATTR_CLEAR); } + + if (flow->enable_timestamp) { + nl_msg_put_flag(buf, OVS_FLOW_ATTR_TIMESTAMP); + } } /* Clears 'flow' to "empty" values. */ diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index e4a2f75..bc19d05 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -123,6 +123,8 @@ struct dp_netdev_flow { /* Actions. */ struct nlattr *actions; size_t actions_len; + + bool enable_timestamp; }; /* Interface to netdev-based datapath. */ @@ -708,7 +710,8 @@ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, uint32_t key_len, static int dpif_netdev_flow_get(const struct dpif *dpif, const struct nlattr *nl_key, size_t nl_key_len, - struct ofpbuf **actionsp, struct dpif_flow_stats *stats) + struct ofpbuf **actionsp, bool *enable_timestamp, + struct dpif_flow_stats *stats) { struct dp_netdev *dp = get_dp_netdev(dpif); struct dp_netdev_flow *flow; @@ -731,6 +734,9 @@ dpif_netdev_flow_get(const struct dpif *dpif, if (actionsp) { *actionsp = ofpbuf_clone_data(flow->actions, flow->actions_len); } + if (enable_timestamp) { + *enable_timestamp = flow->enable_timestamp; + } return 0; } @@ -746,7 +752,8 @@ set_flow_actions(struct dp_netdev_flow *flow, static int dp_netdev_flow_add(struct dp_netdev *dp, const struct flow *key, - const struct nlattr *actions, size_t actions_len) + const struct nlattr *actions, size_t actions_len, + bool enable_timestamp) { struct dp_netdev_flow *flow; int error; @@ -760,6 +767,8 @@ dp_netdev_flow_add(struct dp_netdev *dp, const struct flow *key, return error; } + flow->enable_timestamp = enable_timestamp; + hmap_insert(&dp->flow_table, &flow->node, flow_hash(&flow->key, 0)); return 0; } @@ -794,7 +803,8 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put) memset(put->stats, 0, sizeof *put->stats); } return dp_netdev_flow_add(dp, &key, put->actions, - put->actions_len); + put->actions_len, + put->flags & DPIF_FP_TIMESTAMP); } else { return EFBIG; } @@ -811,6 +821,7 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put) if (put->flags & DPIF_FP_ZERO_STATS) { clear_stats(flow); } + flow->enable_timestamp = put->flags & DPIF_FP_TIMESTAMP; } return error; } else { @@ -868,6 +879,7 @@ static int dpif_netdev_flow_dump_next(const struct dpif *dpif, void *state_, const struct nlattr **key, size_t *key_len, const struct nlattr **actions, size_t *actions_len, + bool *enable_timestamp, const struct dpif_flow_stats **stats) { struct dp_netdev_flow_state *state = state_; @@ -900,6 +912,10 @@ dpif_netdev_flow_dump_next(const struct dpif *dpif, void *state_, *actions_len = flow->actions_len; } + if (enable_timestamp) { + *enable_timestamp = flow->enable_timestamp; + } + if (stats) { get_dpif_flow_stats(flow, &state->stats); *stats = &state->stats; diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h index bea822f..58e9b19 100644 --- a/lib/dpif-provider.h +++ b/lib/dpif-provider.h @@ -221,13 +221,16 @@ struct dpif_class { * If 'actionsp' is nonnull, then on success '*actionsp' must be set to an * ofpbuf owned by the caller that contains the Netlink attributes for the * flow's actions. The caller must free the ofpbuf (with ofpbuf_delete()) - * when it is no longer needed. + * when it is no longer needed. If 'enable_timestamp' is nonnull + * then it should be set to true or false depending on whether + * packet timestamping is enable for the flow. * * If 'stats' is nonnull, then on success it must be updated with the * flow's statistics. */ int (*flow_get)(const struct dpif *dpif, const struct nlattr *key, size_t key_len, - struct ofpbuf **actionsp, struct dpif_flow_stats *stats); + struct ofpbuf **actionsp, bool *enable_timestamp, + struct dpif_flow_stats *stats); /* Adds or modifies a flow in 'dpif'. The flow is specified by the Netlink * attributes with types OVS_KEY_ATTR_* in the 'put->key_len' bytes @@ -283,8 +286,11 @@ struct dpif_class { * '*key_len' must be set to Netlink attributes with types OVS_KEY_ATTR_* * representing the dumped flow's key. If 'actions' and 'actions_len' are * nonnull then they should be set to Netlink attributes with types - * OVS_ACTION_ATTR_* representing the dumped flow's actions. If 'stats' - * is nonnull then it should be set to the dumped flow's statistics. + * OVS_ACTION_ATTR_* representing the dumped flow's actions. If + * 'enable_timestamp' is nonnull then it should be set to true or + * false depending on whether packet timestamping is enable for + * the flow. If 'stats' is nonnull then it should be set to the + * dumped flow's statistics. * * All of the returned data is owned by 'dpif', not by the caller, and the * caller must not modify or free it. 'dpif' must guarantee that it @@ -293,6 +299,7 @@ struct dpif_class { int (*flow_dump_next)(const struct dpif *dpif, void *state, const struct nlattr **key, size_t *key_len, const struct nlattr **actions, size_t *actions_len, + bool *enable_timestamp, const struct dpif_flow_stats **stats); /* Releases resources from 'dpif' for 'state', which was initialized by a diff --git a/lib/dpif.c b/lib/dpif.c index 6aa52d5..1483688 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -82,7 +82,8 @@ static void log_flow_message(const struct dpif *dpif, int error, const char *operation, const struct nlattr *key, size_t key_len, const struct dpif_flow_stats *stats, - const struct nlattr *actions, size_t actions_len); + const struct nlattr *actions, size_t actions_len, + bool enable_timestamp); static void log_operation(const struct dpif *, const char *operation, int error); static bool should_log_flow_message(int error); @@ -775,24 +776,31 @@ dpif_flow_flush(struct dpif *dpif) * If 'actionsp' is nonnull, then on success '*actionsp' will be set to an * ofpbuf owned by the caller that contains the Netlink attributes for the * flow's actions. The caller must free the ofpbuf (with ofpbuf_delete()) when - * it is no longer needed. + * it is no longer needed. If 'enable_timestamp' is nonnull, then on + * success '*enable_timestamp' will be set to true or false depending + * on whether packet timestamping is enabled for the flow. * * If 'stats' is nonnull, then on success it will be updated with the flow's * statistics. */ int dpif_flow_get(const struct dpif *dpif, const struct nlattr *key, size_t key_len, - struct ofpbuf **actionsp, struct dpif_flow_stats *stats) + struct ofpbuf **actionsp, bool *enable_timestamp, + struct dpif_flow_stats *stats) { int error; COVERAGE_INC(dpif_flow_get); - error = dpif->dpif_class->flow_get(dpif, key, key_len, actionsp, stats); + error = dpif->dpif_class->flow_get(dpif, key, key_len, actionsp, + enable_timestamp, stats); if (error) { if (actionsp) { *actionsp = NULL; } + if (enable_timestamp) { + *enable_timestamp = false; + } if (stats) { memset(stats, 0, sizeof *stats); } @@ -809,7 +817,8 @@ dpif_flow_get(const struct dpif *dpif, actions_len = 0; } log_flow_message(dpif, error, "flow_get", key, key_len, stats, - actions, actions_len); + actions, actions_len, + enable_timestamp ? *enable_timestamp : false); } return error; } @@ -821,7 +830,7 @@ dpif_flow_put__(struct dpif *dpif, const struct dpif_flow_put *put) COVERAGE_INC(dpif_flow_put); ovs_assert(!(put->flags & ~(DPIF_FP_CREATE | DPIF_FP_MODIFY - | DPIF_FP_ZERO_STATS))); + | DPIF_FP_ZERO_STATS | DPIF_FP_TIMESTAMP))); error = dpif->dpif_class->flow_put(dpif, put); if (error && put->stats) { @@ -835,6 +844,8 @@ dpif_flow_put__(struct dpif *dpif, const struct dpif_flow_put *put) * attributes with types OVS_KEY_ATTR_* in the 'key_len' bytes starting at * 'key'. The associated actions are specified by the Netlink attributes with * types OVS_ACTION_ATTR_* in the 'actions_len' bytes starting at 'actions'. + * Packet timestamping can be enabled for the flow by setting the + * DPIF_FP_TIMESTAMP flag in 'flags'. * * - If the flow's key does not exist in 'dpif', then the flow will be added if * 'flags' includes DPIF_FP_CREATE. Otherwise the operation will fail with @@ -927,8 +938,10 @@ dpif_flow_dump_start(struct dpif_flow_dump *dump, const struct dpif *dpif) * will be set to Netlink attributes with types OVS_KEY_ATTR_* representing the * dumped flow's key. If 'actions' and 'actions_len' are nonnull then they are * set to Netlink attributes with types OVS_ACTION_ATTR_* representing the - * dumped flow's actions. If 'stats' is nonnull then it will be set to the - * dumped flow's statistics. + * dumped flow's actions. If 'enable_timestamp' is nonnull then it is + * set to true or false depending on whether packet timestamping is + * enabled for the flow. If 'stats' is nonnull then it will be set to + * the dumped flow's statistics. * * All of the returned data is owned by 'dpif', not by the caller, and the * caller must not modify or free it. 'dpif' guarantees that it remains @@ -938,6 +951,7 @@ bool dpif_flow_dump_next(struct dpif_flow_dump *dump, const struct nlattr **key, size_t *key_len, const struct nlattr **actions, size_t *actions_len, + bool *enable_timestamp, const struct dpif_flow_stats **stats) { const struct dpif *dpif = dump->dpif; @@ -947,7 +961,7 @@ dpif_flow_dump_next(struct dpif_flow_dump *dump, error = dpif->dpif_class->flow_dump_next(dpif, dump->state, key, key_len, actions, actions_len, - stats); + enable_timestamp, stats); if (error) { dpif->dpif_class->flow_dump_done(dpif, dump->state); } @@ -961,6 +975,9 @@ dpif_flow_dump_next(struct dpif_flow_dump *dump, *actions = NULL; *actions_len = 0; } + if (enable_timestamp) { + *enable_timestamp = false; + } if (stats) { *stats = NULL; } @@ -972,7 +989,8 @@ dpif_flow_dump_next(struct dpif_flow_dump *dump, log_flow_message(dpif, error, "flow_dump", key ? *key : NULL, key ? *key_len : 0, stats ? *stats : NULL, actions ? *actions : NULL, - actions ? *actions_len : 0); + actions ? *actions_len : 0, + enable_timestamp ? *enable_timestamp : false); } } dump->error = error; @@ -1264,7 +1282,8 @@ static void log_flow_message(const struct dpif *dpif, int error, const char *operation, const struct nlattr *key, size_t key_len, const struct dpif_flow_stats *stats, - const struct nlattr *actions, size_t actions_len) + const struct nlattr *actions, size_t actions_len, + bool enable_timestamp) { struct ds ds = DS_EMPTY_INITIALIZER; ds_put_format(&ds, "%s: ", dpif_name(dpif)); @@ -1284,6 +1303,9 @@ log_flow_message(const struct dpif *dpif, int error, const char *operation, ds_put_cstr(&ds, ", actions:"); format_odp_actions(&ds, actions, actions_len); } + if (enable_timestamp) { + ds_put_cstr(&ds, ", enable_timestamp"); + } vlog(THIS_MODULE, flow_message_log_level(error), "%s", ds_cstr(&ds)); ds_destroy(&ds); } @@ -1308,7 +1330,8 @@ log_flow_put_message(struct dpif *dpif, const struct dpif_flow_put *put, } log_flow_message(dpif, error, ds_cstr(&s), put->key, put->key_len, put->stats, - put->actions, put->actions_len); + put->actions, put->actions_len, + put->flags & DPIF_FP_TIMESTAMP); ds_destroy(&s); } } @@ -1319,7 +1342,7 @@ log_flow_del_message(struct dpif *dpif, const struct dpif_flow_del *del, { if (should_log_flow_message(error)) { log_flow_message(dpif, error, "flow_del", del->key, del->key_len, - !error ? del->stats : NULL, NULL, 0); + !error ? del->stats : NULL, NULL, 0, false); } } diff --git a/lib/dpif.h b/lib/dpif.h index fd05b2f..c23a8d3 100644 --- a/lib/dpif.h +++ b/lib/dpif.h @@ -441,7 +441,8 @@ void dpif_flow_stats_format(const struct dpif_flow_stats *, struct ds *); enum dpif_flow_put_flags { DPIF_FP_CREATE = 1 << 0, /* Allow creating a new flow. */ DPIF_FP_MODIFY = 1 << 1, /* Allow modifying an existing flow. */ - DPIF_FP_ZERO_STATS = 1 << 2 /* Zero the stats of an existing flow. */ + DPIF_FP_ZERO_STATS = 1 << 2, /* Zero the stats of an existing flow. */ + DPIF_FP_TIMESTAMP = 1 << 3 /* Enable packet timestamping. */ }; int dpif_flow_flush(struct dpif *); @@ -454,7 +455,8 @@ int dpif_flow_del(struct dpif *, struct dpif_flow_stats *); int dpif_flow_get(const struct dpif *, const struct nlattr *key, size_t key_len, - struct ofpbuf **actionsp, struct dpif_flow_stats *); + struct ofpbuf **actionsp, bool *enable_timestamp, + struct dpif_flow_stats *); struct dpif_flow_dump { const struct dpif *dpif; @@ -465,6 +467,7 @@ void dpif_flow_dump_start(struct dpif_flow_dump *, const struct dpif *); bool dpif_flow_dump_next(struct dpif_flow_dump *, const struct nlattr **key, size_t *key_len, const struct nlattr **actions, size_t *actions_len, + bool *enable_timestamp, const struct dpif_flow_stats **); int dpif_flow_dump_done(struct dpif_flow_dump *); @@ -551,6 +554,7 @@ struct dpif_upcall { struct ofpbuf *packet; /* Packet data. */ struct nlattr *key; /* Flow key. */ size_t key_len; /* Length of 'key' in bytes. */ + struct ovs_packet_tstamp *tstamp; /* Optional packet timestamp. */ /* DPIF_UC_ACTION only. */ struct nlattr *userdata; /* Argument to OVS_ACTION_ATTR_USERSPACE. */ diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c index 9de8b1c..545405f 100644 --- a/ofproto/ofproto-dpif-ipfix.c +++ b/ofproto/ofproto-dpif-ipfix.c @@ -23,6 +23,7 @@ #include "hmap.h" #include "ofpbuf.h" #include "ofproto.h" +#include "linux/openvswitch.h" #include "packets.h" #include "sset.h" #include "util.h" @@ -139,6 +140,8 @@ BUILD_ASSERT_DECL(sizeof(struct ipfix_template_field_specifier) == 4); /* Part of data record for common metadata and Ethernet entities. */ struct ipfix_data_record_common { + ovs_be32 flow_start_delta_microseconds; /* FLOW_START_DELTA_MICROSECONDS */ + ovs_be32 flow_end_delta_microseconds; /* FLOW_END_DELTA_MICROSECONDS */ ovs_be32 observation_point_id; /* OBSERVATION_POINT_ID */ ovs_be64 packet_delta_count; /* PACKET_DELTA_COUNT */ ovs_be64 layer2_octet_delta_count; /* LAYER2_OCTET_DELTA_COUNT */ @@ -148,7 +151,7 @@ struct ipfix_data_record_common { ovs_be16 ethernet_total_length; /* ETHERNET_TOTAL_LENGTH */ uint8_t ethernet_header_length; /* ETHERNET_HEADER_LENGTH */ } __attribute__((packed)); -BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_common) == 37); +BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_common) == 45); /* Part of data record for VLAN entities. */ struct ipfix_data_record_vlan { @@ -498,15 +501,15 @@ dpif_ipfix_destroy(struct dpif_ipfix *di) } static void -ipfix_init_header(uint32_t seq_number, uint32_t obs_domain_id, - struct ofpbuf *msg) +ipfix_init_header(uint32_t export_time_sec, uint32_t seq_number, + uint32_t obs_domain_id, struct ofpbuf *msg) { struct ipfix_header *hdr; hdr = ofpbuf_put_zeros(msg, sizeof *hdr); hdr->version = htons(IPFIX_VERSION); hdr->length = htons(sizeof *hdr); /* Updated in ipfix_send_msg. */ - hdr->export_time = htonl(time_wall()); + hdr->export_time = htonl(export_time_sec); hdr->seq_number = htonl(seq_number); hdr->obs_domain_id = htonl(obs_domain_id); } @@ -559,6 +562,8 @@ ipfix_define_template_fields(enum ipfix_proto_l2 l2, enum ipfix_proto_l3 l3, count++; \ } + DEF(FLOW_START_DELTA_MICROSECONDS); + DEF(FLOW_END_DELTA_MICROSECONDS); DEF(OBSERVATION_POINT_ID); DEF(PACKET_DELTA_COUNT); DEF(LAYER2_OCTET_DELTA_COUNT); @@ -606,7 +611,7 @@ ipfix_define_template_fields(enum ipfix_proto_l2 l2, enum ipfix_proto_l3 l3, static void ipfix_send_template_msg(struct dpif_ipfix_exporter *exporter, - uint32_t obs_domain_id) + uint32_t export_time_sec, uint32_t obs_domain_id) { uint64_t msg_stub[DIV_ROUND_UP(1500, 8)]; struct ofpbuf msg; @@ -620,7 +625,8 @@ ipfix_send_template_msg(struct dpif_ipfix_exporter *exporter, ofpbuf_use_stub(&msg, msg_stub, sizeof msg_stub); - ipfix_init_header(exporter->seq_number, obs_domain_id, &msg); + ipfix_init_header(export_time_sec, exporter->seq_number, obs_domain_id, + &msg); set_hdr_offset = msg.size; /* Add a Template Set. */ @@ -661,6 +667,8 @@ ipfix_send_template_msg(struct dpif_ipfix_exporter *exporter, static void ipfix_send_data_msg(struct dpif_ipfix_exporter *exporter, struct ofpbuf *packet, + uint32_t export_time_sec, + uint32_t export_time_neg_delta_usec, const struct flow *flow, uint64_t packet_delta_count, uint32_t obs_domain_id, uint32_t obs_point_id) { @@ -674,7 +682,8 @@ ipfix_send_data_msg(struct dpif_ipfix_exporter *exporter, struct ofpbuf *packet, ofpbuf_use_stub(&msg, msg_stub, sizeof msg_stub); - ipfix_init_header(exporter->seq_number, obs_domain_id, &msg); + ipfix_init_header(export_time_sec, exporter->seq_number, obs_domain_id, + &msg); exporter->seq_number++; set_hdr_offset = msg.size; @@ -728,6 +737,12 @@ ipfix_send_data_msg(struct dpif_ipfix_exporter *exporter, struct ofpbuf *packet, layer2_octet_delta_count = packet_delta_count * ethernet_total_length; data_common = ofpbuf_put_zeros(&msg, sizeof *data_common); + /* Send a duration of 1usec for every flow, so that it's + * non-zero, which could be confusing to collectors. */ + data_common->flow_start_delta_microseconds = + htonl(export_time_neg_delta_usec + 1); + data_common->flow_end_delta_microseconds = + htonl(export_time_neg_delta_usec); data_common->observation_point_id = htonl(obs_point_id); data_common->packet_delta_count = htonll(packet_delta_count); data_common->layer2_octet_delta_count = @@ -798,29 +813,56 @@ ipfix_send_data_msg(struct dpif_ipfix_exporter *exporter, struct ofpbuf *packet, static void dpif_ipfix_sample(struct dpif_ipfix_exporter *exporter, - struct ofpbuf *packet, const struct flow *flow, - uint64_t packet_delta_count, uint32_t obs_domain_id, - uint32_t obs_point_id) + struct ofpbuf *packet, const struct ovs_packet_tstamp *tstamp, + const struct flow *flow, uint64_t packet_delta_count, + uint32_t obs_domain_id, uint32_t obs_point_id) { time_t now = time_wall(); + uint32_t sample_time_sec, sample_time_delta_usec; + uint32_t export_time_sec, export_time_neg_delta_usec; + + if (tstamp) { + sample_time_sec = tstamp->sec; + sample_time_delta_usec = tstamp->usec; + } else { + /* A packet in an upcall may not have a timestamp in a few + * cases, e.g. if the upcall was triggered by a packet sent + * from userspace in a dpif_execute() and not received on a + * Linux interface, or if the datapath doesn't support packet + * timestamping. In those cases, build a less precise + * timestamp now using the clock. */ + struct timeval tv; + xgettimeofday(&tv); + sample_time_sec = tv.tv_sec; + sample_time_delta_usec = tv.tv_usec; + } + + /* The IPFIX start and end deltas are negative deltas relative to + * the export time, so set the export time 1 second off to + * calculate those deltas. */ + export_time_sec = sample_time_sec + 1; + export_time_neg_delta_usec = 1000000 - sample_time_delta_usec; + if ((exporter->last_template_set_time + IPFIX_TEMPLATE_INTERVAL) <= now) { - ipfix_send_template_msg(exporter, obs_domain_id); + ipfix_send_template_msg(exporter, export_time_sec, obs_domain_id); exporter->last_template_set_time = now; } - ipfix_send_data_msg(exporter, packet, flow, packet_delta_count, + ipfix_send_data_msg(exporter, packet, export_time_sec, + export_time_neg_delta_usec, flow, packet_delta_count, obs_domain_id, obs_point_id); } void dpif_ipfix_bridge_sample(struct dpif_ipfix *di, struct ofpbuf *packet, + const struct ovs_packet_tstamp *tstamp, const struct flow *flow) { /* Use the sampling probability as an approximation of the number * of matched packets. */ uint64_t packet_delta_count = UINT32_MAX / di->bridge_exporter.probability; - dpif_ipfix_sample(&di->bridge_exporter.exporter, packet, flow, + dpif_ipfix_sample(&di->bridge_exporter.exporter, packet, tstamp, flow, packet_delta_count, di->bridge_exporter.options->obs_domain_id, di->bridge_exporter.options->obs_point_id); @@ -828,6 +870,7 @@ dpif_ipfix_bridge_sample(struct dpif_ipfix *di, struct ofpbuf *packet, void dpif_ipfix_flow_sample(struct dpif_ipfix *di, struct ofpbuf *packet, + const struct ovs_packet_tstamp *tstamp, const struct flow *flow, uint32_t collector_set_id, uint16_t probability, uint32_t obs_domain_id, uint32_t obs_point_id) @@ -843,6 +886,6 @@ dpif_ipfix_flow_sample(struct dpif_ipfix *di, struct ofpbuf *packet, return; } - dpif_ipfix_sample(&node->exporter.exporter, packet, flow, + dpif_ipfix_sample(&node->exporter.exporter, packet, tstamp, flow, packet_delta_count, obs_domain_id, obs_point_id); } diff --git a/ofproto/ofproto-dpif-ipfix.h b/ofproto/ofproto-dpif-ipfix.h index 26b02f1..6d8db94 100644 --- a/ofproto/ofproto-dpif-ipfix.h +++ b/ofproto/ofproto-dpif-ipfix.h @@ -24,6 +24,7 @@ struct flow; struct ofpbuf; struct ofproto_ipfix_bridge_exporter_options; struct ofproto_ipfix_flow_exporter_options; +struct ovs_packet_tstamp; struct dpif_ipfix *dpif_ipfix_create(void); uint32_t dpif_ipfix_get_bridge_exporter_probability(const struct dpif_ipfix *); @@ -34,8 +35,10 @@ void dpif_ipfix_set_options( const struct ofproto_ipfix_flow_exporter_options *, size_t); void dpif_ipfix_bridge_sample(struct dpif_ipfix *, struct ofpbuf *, + const struct ovs_packet_tstamp *, const struct flow *); void dpif_ipfix_flow_sample(struct dpif_ipfix *, struct ofpbuf *, + const struct ovs_packet_tstamp *, const struct flow *, uint32_t, uint16_t, uint32_t, uint32_t); diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 40e897f..027fc60 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -274,6 +274,7 @@ struct action_xlate_ctx { bool has_learn; /* Actions include NXAST_LEARN? */ bool has_normal; /* Actions output to OFPP_NORMAL? */ bool has_fin_timeout; /* Actions include NXAST_FIN_TIMEOUT? */ + bool enable_timestamp; /* Actions require packet timestamping? */ uint16_t nf_output_iface; /* Output interface index for NetFlow. */ mirror_mask_t mirrors; /* Bitmap of associated mirrors. */ @@ -485,6 +486,7 @@ struct facet { bool has_learn; /* Actions include NXAST_LEARN? */ bool has_normal; /* Actions output to OFPP_NORMAL? */ bool has_fin_timeout; /* Actions include NXAST_FIN_TIMEOUT? */ + bool enable_timestamp; /* Actions require packet timestamping? */ tag_type tags; /* Tags that would require revalidation. */ mirror_mask_t mirrors; /* Bitmap of dependent mirrors. */ @@ -825,7 +827,7 @@ static size_t compose_sflow_action(const struct ofproto_dpif *, const struct flow *, uint32_t odp_port); static void compose_ipfix_action(const struct ofproto_dpif *, struct ofpbuf *odp_actions, - const struct flow *); + const struct flow *, bool *); static void add_mirror_actions(struct action_xlate_ctx *ctx, const struct flow *flow); /* Global variables. */ @@ -3725,6 +3727,9 @@ handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet, op->garbage = NULL; op->dpif_op.type = DPIF_OP_FLOW_PUT; put->flags = DPIF_FP_CREATE | DPIF_FP_MODIFY; + if (facet->enable_timestamp) { + put->flags |= DPIF_FP_TIMESTAMP; + } put->key = miss->key; put->key_len = miss->key_len; if (want_path == SF_FAST_PATH) { @@ -4133,8 +4138,8 @@ handle_flow_sample_upcall(struct dpif_backer *backer, /* The flow reflects exactly the contents of the packet. Sample * the packet using it. */ - dpif_ipfix_flow_sample(ofproto->ipfix, upcall->packet, &flow, - cookie.flow_sample.collector_set_id, + dpif_ipfix_flow_sample(ofproto->ipfix, upcall->packet, upcall->tstamp, + &flow, cookie.flow_sample.collector_set_id, cookie.flow_sample.probability, cookie.flow_sample.obs_domain_id, cookie.flow_sample.obs_point_id); @@ -4155,7 +4160,8 @@ handle_ipfix_upcall(struct dpif_backer *backer, /* The flow reflects exactly the contents of the packet. Sample * the packet using it. */ - dpif_ipfix_bridge_sample(ofproto->ipfix, upcall->packet, &flow); + dpif_ipfix_bridge_sample(ofproto->ipfix, upcall->packet, upcall->tstamp, + &flow); } static int @@ -4365,7 +4371,8 @@ update_stats(struct dpif_backer *backer) size_t key_len; dpif_flow_dump_start(&dump, backer->dpif); - while (dpif_flow_dump_next(&dump, &key, &key_len, NULL, NULL, &stats)) { + while (dpif_flow_dump_next(&dump, &key, &key_len, NULL, NULL, NULL, + &stats)) { struct flow flow; struct subfacet *subfacet; struct ofport_dpif *ofport; @@ -5127,6 +5134,7 @@ facet_revalidate(struct facet *facet) facet->has_learn = ctx.has_learn; facet->has_normal = ctx.has_normal; facet->has_fin_timeout = ctx.has_fin_timeout; + facet->enable_timestamp = ctx.enable_timestamp; facet->mirrors = ctx.mirrors; i = 0; @@ -5421,6 +5429,7 @@ subfacet_make_actions(struct subfacet *subfacet, const struct ofpbuf *packet, facet->has_learn = ctx.has_learn; facet->has_normal = ctx.has_normal; facet->has_fin_timeout = ctx.has_fin_timeout; + facet->enable_timestamp = ctx.enable_timestamp; facet->nf_flow.output_iface = ctx.nf_output_iface; facet->mirrors = ctx.mirrors; @@ -5456,6 +5465,9 @@ subfacet_install(struct subfacet *subfacet, if (stats) { flags |= DPIF_FP_ZERO_STATS; } + if (facet->enable_timestamp) { + flags |= DPIF_FP_TIMESTAMP; + } if (path == SF_SLOW_PATH) { compose_slow_path(ofproto, &facet->flow, slow, @@ -5791,6 +5803,7 @@ send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet) uint32_t odp_port; struct flow flow; int error; + bool enable_timestamp = false; flow_extract(packet, 0, 0, NULL, OFPP_LOCAL, &flow); if (netdev_vport_is_patch(ofport->up.netdev)) { @@ -5844,7 +5857,7 @@ send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet) ofp_port_to_odp_port(ofproto, flow.in_port)); compose_sflow_action(ofproto, &odp_actions, &flow, odp_port); - compose_ipfix_action(ofproto, &odp_actions, &flow); + compose_ipfix_action(ofproto, &odp_actions, &flow, &enable_timestamp); nl_msg_put_u32(&odp_actions, OVS_ACTION_ATTR_OUTPUT, odp_port); error = dpif_execute(ofproto->backer->dpif, @@ -6022,7 +6035,7 @@ compose_ipfix_cookie(union user_action_cookie *cookie) static void compose_ipfix_action(const struct ofproto_dpif *ofproto, struct ofpbuf *odp_actions, - const struct flow *flow) + const struct flow *flow, bool *enable_timestamp) { uint32_t probability; union user_action_cookie cookie; @@ -6036,6 +6049,10 @@ compose_ipfix_action(const struct ofproto_dpif *ofproto, compose_sample_action(ofproto, odp_actions, flow, probability, &cookie, sizeof cookie.ipfix); + + if (enable_timestamp) { + *enable_timestamp = true; + } } /* SAMPLE action for sFlow must be first action in any given list of @@ -6056,7 +6073,8 @@ add_sflow_action(struct action_xlate_ctx *ctx) static void add_ipfix_action(struct action_xlate_ctx *ctx) { - compose_ipfix_action(ctx->ofproto, ctx->odp_actions, &ctx->flow); + compose_ipfix_action(ctx->ofproto, ctx->odp_actions, &ctx->flow, + &ctx->enable_timestamp); } /* Fix SAMPLE action according to data collected while composing ODP actions. @@ -7003,6 +7021,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, } case OFPACT_SAMPLE: + ctx->enable_timestamp = true; xlate_sample_action(ctx, ofpact_get_SAMPLE(a)); break; } @@ -7087,6 +7106,7 @@ xlate_actions(struct action_xlate_ctx *ctx, ctx->has_learn = false; ctx->has_normal = false; ctx->has_fin_timeout = false; + ctx->enable_timestamp = false; ctx->nf_output_iface = NF_OUT_DROP; ctx->mirrors = 0; ctx->recurse = 0; diff --git a/utilities/ovs-dpctl.8.in b/utilities/ovs-dpctl.8.in index 2b0036c..e28de66 100644 --- a/utilities/ovs-dpctl.8.in +++ b/utilities/ovs-dpctl.8.in @@ -122,11 +122,15 @@ required. Prints to the console all flow entries in datapath \fIdp\fR's flow table. . -.IP "\fBadd\-flow\fR [\fIdp\fR] \fIflow actions\fR" -.IQ "[\fB\-\-clear\fR] [\fB\-\-may-create\fR] [\fB\-s\fR | \fB\-\-statistics\fR] \fBmod\-flow\fR [\fIdp\fR] \fIflow actions\fR" +.IP "\fBadd\-flow\fR [\fIdp\fR] \fIflow actions\fR [\fBenable_timestamp\fR]" +.IQ "[\fB\-\-clear\fR] [\fB\-\-may-create\fR] [\fB\-s\fR | \fB\-\-statistics\fR] \fBmod\-flow\fR [\fIdp\fR] \fIflow actions\fR [\fBenable_timestamp\fR]" Adds or modifies a flow in \fIdp\fR's flow table that, when a packet matching \fIflow\fR arrives, causes \fIactions\fR to be executed. .IP +If \fBenable_timestamp\fR is specified, timestamping of packets sent +in upcalls is enabled until all flows that enable timestamping are +deleted. +.IP The \fBadd\-flow\fR command succeeds only if \fIflow\fR does not already exist in \fIdp\fR. Contrariwise, \fBmod\-flow\fR without \fB\-\-may\-create\fR only modifies the actions for an existing flow. diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c index 3b6e6a5..6fcb00c 100644 --- a/utilities/ovs-dpctl.c +++ b/utilities/ovs-dpctl.c @@ -748,6 +748,7 @@ dpctl_dump_flows(int argc, char *argv[]) size_t key_len; struct ds ds; char *name; + bool enable_timestamp; name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp(); run(parsed_dpif_open(name, false, &dpif), "opening datapath"); @@ -756,13 +757,17 @@ dpctl_dump_flows(int argc, char *argv[]) ds_init(&ds); dpif_flow_dump_start(&dump, dpif); while (dpif_flow_dump_next(&dump, &key, &key_len, - &actions, &actions_len, &stats)) { + &actions, &actions_len, &enable_timestamp, + &stats)) { ds_clear(&ds); odp_flow_key_format(key, key_len, &ds); ds_put_cstr(&ds, ", "); dpif_flow_stats_format(stats, &ds); ds_put_cstr(&ds, ", actions:"); format_odp_actions(&ds, actions, actions_len); + if (enable_timestamp) { + ds_put_cstr(&ds, ", enable_timestamp"); + } printf("%s\n", ds_cstr(&ds)); } dpif_flow_dump_done(&dump); @@ -773,21 +778,34 @@ dpctl_dump_flows(int argc, char *argv[]) static void dpctl_put_flow(int argc, char *argv[], enum dpif_flow_put_flags flags) { - const char *key_s = argv[argc - 2]; - const char *actions_s = argv[argc - 1]; + const char *key_s; + const char *actions_s; struct dpif_flow_stats stats; struct ofpbuf actions; struct ofpbuf key; struct dpif *dpif; + bool has_dp_name; char *dp_name; + if (argc > 2 && !strcmp(argv[argc - 1], "enable_timestamp")) { + flags |= DPIF_FP_TIMESTAMP; + + key_s = argv[argc - 3]; + actions_s = argv[argc - 2]; + has_dp_name = argc == 4; + } else { + key_s = argv[argc - 2]; + actions_s = argv[argc - 1]; + has_dp_name = argc == 3; + } + ofpbuf_init(&key, 0); run(odp_flow_key_from_string(key_s, NULL, &key), "parsing flow key"); ofpbuf_init(&actions, 0); run(odp_actions_from_string(actions_s, NULL, &actions), "parsing actions"); - dp_name = argc == 3 ? xstrdup(argv[1]) : get_one_dp(); + dp_name = has_dp_name ? xstrdup(argv[1]) : get_one_dp(); run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath"); free(dp_name); @@ -1121,8 +1139,8 @@ static const struct command all_commands[] = { { "dump-dps", 0, 0, dpctl_dump_dps }, { "show", 0, INT_MAX, dpctl_show }, { "dump-flows", 0, 1, dpctl_dump_flows }, - { "add-flow", 2, 3, dpctl_add_flow }, - { "mod-flow", 2, 3, dpctl_mod_flow }, + { "add-flow", 2, 4, dpctl_add_flow }, + { "mod-flow", 2, 4, dpctl_mod_flow }, { "del-flow", 1, 2, dpctl_del_flow }, { "del-flows", 0, 1, dpctl_del_flows }, { "help", 0, INT_MAX, dpctl_help }, -- 1.8.1.3 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev