[ovs-dev] [PATCH] sFlow export: include standard tunnel structures (for GRE, VXLAN etc.)
Neil Mckee
neil.mckee at inmon.com
Wed Oct 9 05:09:29 UTC 2013
On Oct 7, 2013, at 2:57 PM, Ben Pfaff <blp at nicira.com> wrote:
> On Sun, Oct 06, 2013 at 10:11:11PM -0700, Neil Mckee wrote:
>> Please comment on this proposed patch. It adds the standard
>> sFlow-TUNNEL structures to the sFlow export, which will be helpful
>> for tracing tunneled traffic through a network fabric in real-time.
>>
>> As part of doing that, it switches over from using odp_port to ofp_port
>> as the port reference used in dpif-sflow, which allows some only-for-sflow
>> code to be removed elsewhere. It also completely removes the private
>> port-lookup hash table that dpif-sflow was maintaining and replaces
>> it with a simple callback to the parent dpif - presenting the parent with
>> a structure to fill in with just the necessary details about the port.
>> This makes it easier for the parent dpif to control what port state is
>> exposed to the sFlow module during the processing of a packet-sample.
>>
>> (This same mechanism can now be extended to expose some
>> bundle/LAG information, so the standard sFlow-LAG structures can
>> be exported too, but that can be in another patch).
>>
>> This patch also cleans up the way sFlow is treated in ofproto-xlate.c, so
>> that the upcall cookie just contains the essential details and the
>> sFlow-specific encoding is migrated into ofproto-dpif-sflow.c.
>>
>> Finally a new test for sFlow tunnel structures was added that exercises
>> the new code. So 'make check TESTSUITEFLAGS="-k sflow"' now runs
>> two separate tests.
>>
>> One very specific question is asked in an XXX comment in ofproto-dpif.c,
>> regarding which lock should be acquired during the new callback. A little
>> guidance there would be most appreciated.
>
> This commit increases the size of the sflow userdata (the 'sflow'
> member in struct user_action_cookie) from 8 bytes to 12. If the size
> increase can be avoided, then it is desirable to avoid it, because
> only recent versions of the Open vSwitch kernel module support
> userdata longer than 8 bytes.
>
> I'd like to see a full discussion of Jesse's comments.
>
> I see various other minor issues but nothing that will be much trouble.
It seemed like the easiest way to reduce the cookie to 8 bytes was to have
two sFlow cookie types:
USER_ACTION_COOKIE_SFLOW_SINGLEPORT
USER_ACTION_COOKIE_SFLOW_MULTIPORT
That way there will still be room in case ofp_port_t goes from 16 to 32 bits.
If cookie->type were uint8_t you would recover 8 more bits, if ever needed.
The revised patch is inlined below for your comments. It now also has the
LAG export (and a test for it). It goes out with the periodic counter-push
-- not with every packet-sample. This LAG information can be important for
synthesizing network-wide traffic matrices accurately, and for detecting
instability promptly:
http://sflow.org/sflow_lag.txt
Unresolved questions are marked with XXX comments.
Neil
Add sFlow Tunnel and LAG structures.
Signed-off-by: Neil McKee<neil.mckee at inmon.com>
---
lib/lacp.c | 64 +++++++++
lib/lacp.h | 23 +++
lib/odp-util.c | 51 +++++--
lib/odp-util.h | 19 ++-
lib/sflow.h | 38 ++++-
lib/sflow_agent.c | 15 ++
lib/sflow_api.h | 1 +
lib/sflow_receiver.c | 19 +++
manpages.mk | 8 +-
ofproto/ofproto-dpif-sflow.c | 326 ++++++++++++++++++++++++++----------------
ofproto/ofproto-dpif-sflow.h | 25 +++-
ofproto/ofproto-dpif-upcall.c | 3 +-
ofproto/ofproto-dpif-xlate.c | 61 ++++----
ofproto/ofproto-dpif.c | 42 ++++--
tests/odp.at | 2 +-
tests/ofproto-dpif.at | 138 ++++++++++++++++++
tests/test-sflow.c | 82 +++++++++++
17 files changed, 714 insertions(+), 203 deletions(-)
diff --git a/lib/lacp.c b/lib/lacp.c
index 5421e2a..ff83850 100644
--- a/lib/lacp.c
+++ b/lib/lacp.c
@@ -122,6 +122,11 @@ struct slave {
struct lacp_info ntt_actor; /* Used to decide if we Need To Transmit. */
struct timer tx; /* Next message transmission timer. */
struct timer rx; /* Expected message receive timer. */
+ struct {
+ uint32_t rx_pdus; /* dot3adAggPortStatsLACPDUsRx */
+ uint32_t rx_pdus_bad; /* dot3adAggPortStatsIllegalRx */
+ uint32_t tx_pdus; /* dot3adAggPortStatsLACPDUsTx */
+ } counters;
};
static struct ovs_mutex mutex;
@@ -316,9 +321,11 @@ lacp_process_packet(struct lacp *lacp, const void *slave_,
if (!slave) {
goto out;
}
+ slave->counters.rx_pdus++;
pdu = parse_lacp_packet(packet);
if (!pdu) {
+ slave->counters.rx_pdus_bad++;
VLOG_WARN_RL(&rl, "%s: received an unparsable LACP PDU.", lacp->name);
goto out;
}
@@ -529,6 +536,7 @@ lacp_run(struct lacp *lacp, lacp_send_pdu *send_pdu) OVS_EXCLUDED(mutex)
slave->ntt_actor = actor;
compose_lacp_pdu(&actor, &slave->partner, &pdu);
send_pdu(slave->aux, &pdu, sizeof pdu);
+ slave->counters.tx_pdus++;
duration = (slave->partner.state & LACP_STATE_TIME
? LACP_FAST_TIME_TX
@@ -954,3 +962,59 @@ lacp_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[],
out:
ovs_mutex_unlock(&mutex);
}
+
+/* Extract a snapshot of the current state and counters for a slave port.
+ Return false if the slave is not active. */
+bool
+lacp_get_slave_stats(const struct lacp *lacp, const void *slave_, struct lacp_slave_stats *stats)
+ OVS_EXCLUDED(mutex)
+{
+ struct slave *slave;
+ struct lacp_info actor;
+ bool ret;
+
+ ovs_mutex_lock(&mutex);
+
+ slave = slave_lookup(lacp, slave_);
+ if(slave) {
+ ret = true;
+ slave_get_actor(slave, &actor);
+ memcpy(&stats->dot3adAggPortActorSystemID,
+ actor.sys_id,
+ ETH_ADDR_LEN);
+ memcpy(&stats->dot3adAggPortPartnerOperSystemID,
+ slave->partner.sys_id,
+ ETH_ADDR_LEN);
+ stats->dot3adAggPortAttachedAggID = (lacp->key_slave->key ?
+ lacp->key_slave->key :
+ lacp->key_slave->port_id);
+
+ /* Construct my admin-state. Assume aggregation is configured on. */
+ stats->dot3adAggPortActorAdminState = LACP_STATE_AGG;
+ if(lacp->active) {
+ stats->dot3adAggPortActorAdminState |= LACP_STATE_ACT;
+ }
+ if(lacp->fast) {
+ stats->dot3adAggPortActorAdminState |= LACP_STATE_TIME;
+ }
+ /* XXX Not sure how to know the partner admin state. It
+ * might have to be captured and remembered during the
+ * negotiation phase.
+ */
+ stats->dot3adAggPortPartnerAdminState = 0;
+
+ stats->dot3adAggPortActorOperState = actor.state;
+ stats->dot3adAggPortPartnerOperState = slave->partner.state;
+
+ /* Read out the latest counters */
+ stats->dot3adAggPortStatsLACPDUsRx = slave->counters.rx_pdus;
+ stats->dot3adAggPortStatsIllegalRx = slave->counters.rx_pdus_bad;
+ stats->dot3adAggPortStatsLACPDUsTx = slave->counters.tx_pdus;
+ }
+ else {
+ ret = false;
+ }
+ ovs_mutex_unlock(&mutex);
+ return ret;
+
+}
diff --git a/lib/lacp.h b/lib/lacp.h
index 89b0e0a..f5fcc96 100644
--- a/lib/lacp.h
+++ b/lib/lacp.h
@@ -69,4 +69,27 @@ typedef void lacp_send_pdu(void *slave, const void *pdu, size_t pdu_size);
void lacp_run(struct lacp *, lacp_send_pdu *);
void lacp_wait(struct lacp *);
+struct lacp_slave_stats {
+ /* id */
+ uint8_t dot3adAggPortActorSystemID[ETH_ADDR_LEN];
+ uint8_t dot3adAggPortPartnerOperSystemID[ETH_ADDR_LEN];
+ uint32_t dot3adAggPortAttachedAggID;
+ /* state */
+ uint8_t dot3adAggPortActorAdminState;
+ uint8_t dot3adAggPortActorOperState;
+ uint8_t dot3adAggPortPartnerAdminState;
+ uint8_t dot3adAggPortPartnerOperState;
+ /* counters */
+ uint32_t dot3adAggPortStatsLACPDUsRx;
+ /* uint32_t dot3adAggPortStatsMarkerPDUsRx; */
+ /* uint32_t dot3adAggPortStatsMarkerResponsePDUsRx; */
+ /* uint32_t dot3adAggPortStatsUnknownRx; */
+ uint32_t dot3adAggPortStatsIllegalRx;
+ uint32_t dot3adAggPortStatsLACPDUsTx;
+ /* uint32_t dot3adAggPortStatsMarkerPDUsTx; */
+ /* uint32_t dot3adAggPortStatsMarkerResponsePDUsTx; */
+};
+
+bool lacp_get_slave_stats(const struct lacp *, const void *slave_, struct lacp_slave_stats *);
+
#endif /* lacp.h */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 5ca8baf..de66ed5 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -294,12 +294,21 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
userdata_unspec = false;
if (userdata_len == sizeof cookie.sflow
- && cookie.type == USER_ACTION_COOKIE_SFLOW) {
+ && cookie.type == USER_ACTION_COOKIE_SFLOW_SINGLEPORT) {
ds_put_format(ds, ",sFlow("
- "vid=%"PRIu16",pcp=%"PRIu8",output=%"PRIu32")",
- vlan_tci_to_vid(cookie.sflow.vlan_tci),
- vlan_tci_to_pcp(cookie.sflow.vlan_tci),
- cookie.sflow.output);
+ "vid=%"PRIu16",pcp=%"PRIu8
+ ",n_outputs=1,output_port=%"PRIu32")",
+ vlan_tci_to_vid(cookie.sflow.single.vlan_tci),
+ vlan_tci_to_pcp(cookie.sflow.single.vlan_tci),
+ cookie.sflow.single.output_port);
+ } else if (userdata_len == sizeof cookie.sflow
+ && cookie.type == USER_ACTION_COOKIE_SFLOW_MULTIPORT) {
+ ds_put_format(ds, ",sFlow("
+ "vid=%"PRIu16",pcp=%"PRIu8
+ ",n_outputs=%"PRIu32",output_port=0)",
+ vlan_tci_to_vid(cookie.sflow.multi.vlan_tci),
+ vlan_tci_to_pcp(cookie.sflow.multi.vlan_tci),
+ cookie.sflow.multi.n_outputs);
} else if (userdata_len == sizeof cookie.slow_path
&& cookie.type == USER_ACTION_COOKIE_SLOW_PATH) {
const char *reason;
@@ -325,8 +334,8 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
}
if (userdata_unspec) {
- size_t i;
- ds_put_format(ds, ",userdata(");
+ size_t i;
+ ds_put_format(ds, ",userdata(");
for (i = 0; i < userdata_len; i++) {
ds_put_format(ds, "%02x", userdata[i]);
}
@@ -507,7 +516,8 @@ parse_odp_action(const char *s, const struct simap *port_names,
{
unsigned long long int pid;
- unsigned long long int output;
+ unsigned long long int n_outputs;
+ unsigned long long int output_port;
unsigned long long int probability;
unsigned long long int collector_set_id;
unsigned long long int obs_domain_id;
@@ -519,8 +529,8 @@ parse_odp_action(const char *s, const struct simap *port_names,
odp_put_userspace_action(pid, NULL, 0, actions);
return n;
} else if (sscanf(s, "userspace(pid=%lli,sFlow(vid=%i,"
- "pcp=%i,output=%lli))%n",
- &pid, &vid, &pcp, &output, &n) > 0 && n > 0) {
+ "pcp=%i,n_outputs=%lli,output_port=%lli))%n",
+ &pid, &vid, &pcp, &n_outputs, &output_port, &n) > 0 && n > 0) {
union user_action_cookie cookie;
uint16_t tci;
@@ -529,11 +539,22 @@ parse_odp_action(const char *s, const struct simap *port_names,
tci |= VLAN_CFI;
}
- cookie.type = USER_ACTION_COOKIE_SFLOW;
- cookie.sflow.vlan_tci = htons(tci);
- cookie.sflow.output = output;
- odp_put_userspace_action(pid, &cookie, sizeof cookie.sflow,
- actions);
+ if(n_outputs == 1) {
+ cookie.type = USER_ACTION_COOKIE_SFLOW_SINGLEPORT;
+ cookie.sflow.single.vlan_tci = htons(tci);
+ cookie.sflow.single.output_port = output_port;
+ odp_put_userspace_action(pid, &cookie,
+ sizeof cookie.sflow.single,
+ actions);
+ }
+ else {
+ cookie.type = USER_ACTION_COOKIE_SFLOW_MULTIPORT;
+ cookie.sflow.multi.vlan_tci = htons(tci);
+ cookie.sflow.multi.n_outputs = n_outputs;
+ odp_put_userspace_action(pid, &cookie,
+ sizeof cookie.sflow.multi,
+ actions);
+ }
return n;
} else if (sscanf(s, "userspace(pid=%lli,slow_path(%n", &pid, &n) > 0
&& n > 0) {
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 192cfa0..9dc63bb 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -142,7 +142,8 @@ void commit_odp_actions(const struct flow *, struct flow *base,
enum user_action_cookie_type {
USER_ACTION_COOKIE_UNSPEC,
- USER_ACTION_COOKIE_SFLOW, /* Packet for per-bridge sFlow sampling. */
+ USER_ACTION_COOKIE_SFLOW_SINGLEPORT, /* sFlow sample (1 output port). */
+ USER_ACTION_COOKIE_SFLOW_MULTIPORT, /* sFlow sample (N output ports). */
USER_ACTION_COOKIE_SLOW_PATH, /* Userspace must process this flow. */
USER_ACTION_COOKIE_FLOW_SAMPLE, /* Packet for per-flow sampling. */
USER_ACTION_COOKIE_IPFIX, /* Packet for per-bridge IPFIX sampling. */
@@ -153,10 +154,18 @@ enum user_action_cookie_type {
union user_action_cookie {
uint16_t type; /* enum user_action_cookie_type. */
- struct {
- uint16_t type; /* USER_ACTION_COOKIE_SFLOW. */
- ovs_be16 vlan_tci; /* Destination VLAN TCI. */
- uint32_t output; /* SFL_FLOW_SAMPLE_TYPE 'output' value. */
+ union {
+ struct {
+ uint16_t type; /* USER_ACTION_COOKIE_SFLOW_SINGLEPORT. */
+ ovs_be16 vlan_tci; /* Destination VLAN TCI. */
+ ofp_port_t output_port; /* Output port */
+ /* XXX do we need to pad to 8 bytes -- while sizeof(ofp_port_t) is still 2 ?*/
+ } single;
+ struct {
+ uint16_t type; /* USER_ACTION_COOKIE_SFLOW_MULTIPORT. */
+ ovs_be16 vlan_tci; /* Destination VLAN TCI. */
+ uint32_t n_outputs; /* Number of output ports. */
+ } multi;
} sflow;
struct {
diff --git a/lib/sflow.h b/lib/sflow.h
index 0d1f2b9..0038c27 100644
--- a/lib/sflow.h
+++ b/lib/sflow.h
@@ -285,6 +285,8 @@ enum SFLFlow_type_tag {
SFLFLOW_EX_MPLS_FTN = 1010,
SFLFLOW_EX_MPLS_LDP_FEC = 1011,
SFLFLOW_EX_VLAN_TUNNEL = 1012, /* VLAN stack */
+ SFLFLOW_EX_IPV4_TUNNEL_EGRESS = 1023, /* http://sflow.org/sflow_tunnels.txt */
+ SFLFLOW_EX_IPV4_TUNNEL_INGRESS = 1024,
};
typedef union _SFLFlow_type {
@@ -382,6 +384,9 @@ typedef struct _SFLFlow_sample_expanded {
/* Counter types */
+#define SFL_UNDEF_COUNTER(c) c=(typeof(c))-1
+#define SFL_UNDEF_GAUGE(c) c=0
+
/* Generic interface counters - see RFC 1573, 2233 */
typedef struct _SFLIf_counters {
@@ -478,6 +483,35 @@ typedef struct _SFLVlan_counters {
u_int32_t discards;
} SFLVlan_counters;
+
+/* LAG Port Statistics - see http://sflow.org/sflow_lag.txt */
+/* opaque = counter_data; enterprise = 0; format = 7 */
+
+typedef union _SFLLACP_portState {
+ uint32_t all;
+ struct {
+ uint8_t actorAdmin;
+ uint8_t actorOper;
+ uint8_t partnerAdmin;
+ uint8_t partnerOper;
+ } v;
+} SFLLACP_portState;
+
+typedef struct _SFLLACP_counters {
+ uint8_t actorSystemID[8]; /* 6 bytes + 2 pad */
+ uint8_t partnerSystemID[8]; /* 6 bytes + 2 pad */
+ uint32_t attachedAggID;
+ SFLLACP_portState portState;
+ uint32_t LACPDUsRx;
+ uint32_t markerPDUsRx;
+ uint32_t markerResponsePDUsRx;
+ uint32_t unknownRx;
+ uint32_t illegalRx;
+ uint32_t LACPDUsTx;
+ uint32_t markerPDUsTx;
+ uint32_t markerResponsePDUsTx;
+} SFLLACP_counters;
+
/* Counters data */
enum SFLCounters_type_tag {
@@ -486,7 +520,8 @@ enum SFLCounters_type_tag {
SFLCOUNTERS_ETHERNET = 2,
SFLCOUNTERS_TOKENRING = 3,
SFLCOUNTERS_VG = 4,
- SFLCOUNTERS_VLAN = 5
+ SFLCOUNTERS_VLAN = 5,
+ SFLCOUNTERS_LACP = 7
};
typedef union _SFLCounters_type {
@@ -495,6 +530,7 @@ typedef union _SFLCounters_type {
SFLTokenring_counters tokenring;
SFLVg_counters vg;
SFLVlan_counters vlan;
+ SFLLACP_counters lacp;
} SFLCounters_type;
typedef struct _SFLCounters_sample_element {
diff --git a/lib/sflow_agent.c b/lib/sflow_agent.c
index 817420d..9c2e028 100644
--- a/lib/sflow_agent.c
+++ b/lib/sflow_agent.c
@@ -363,6 +363,21 @@ SFLPoller *sfl_agent_getPoller(SFLAgent *agent, SFLDataSource_instance *pdsi)
return NULL;
}
+/*_________________-----------------------------------__________________
+ _________________ sfl_agent_getPollerByBridgePort __________________
+ -----------------___________________________________------------------
+*/
+
+SFLPoller *sfl_agent_getPollerByBridgePort(SFLAgent *agent, uint32_t port_no)
+{
+ /* find it and return it */
+ SFLPoller *pl = agent->pollers;
+ for(; pl != NULL; pl = pl->nxt)
+ if(pl->bridgePort == port_no) return pl;
+ /* not found */
+ return NULL;
+}
+
/*_________________---------------------------__________________
_________________ sfl_agent_getReceiver __________________
-----------------___________________________------------------
diff --git a/lib/sflow_api.h b/lib/sflow_api.h
index 3cc060b..2730a4c 100644
--- a/lib/sflow_api.h
+++ b/lib/sflow_api.h
@@ -281,6 +281,7 @@ void sfl_agent_set_agentSubId(SFLAgent *agent, u_int32_t subId);
to get counters if it is not the same as the global ifIndex */
void sfl_poller_set_bridgePort(SFLPoller *poller, u_int32_t port_no);
u_int32_t sfl_poller_get_bridgePort(SFLPoller *poller);
+SFLPoller *sfl_agent_getPollerByBridgePort(SFLAgent *agent, u_int32_t port_no);
/* call this to indicate a discontinuity with a counter like samplePool so that the
sflow collector will ignore the next delta */
diff --git a/lib/sflow_receiver.c b/lib/sflow_receiver.c
index 3e5a67a..8b3ffad 100644
--- a/lib/sflow_receiver.c
+++ b/lib/sflow_receiver.c
@@ -460,6 +460,8 @@ static int computeFlowSampleSize(SFLReceiver *receiver, SFL_FLOW_SAMPLE_TYPE *fs
case SFLFLOW_EX_MPLS_FTN: elemSiz = mplsFtnEncodingLength(&elem->flowType.mpls_ftn); break;
case SFLFLOW_EX_MPLS_LDP_FEC: elemSiz = mplsLdpFecEncodingLength(&elem->flowType.mpls_ldp_fec); break;
case SFLFLOW_EX_VLAN_TUNNEL: elemSiz = vlanTunnelEncodingLength(&elem->flowType.vlan_tunnel); break;
+ case SFLFLOW_EX_IPV4_TUNNEL_EGRESS: elemSiz = sizeof(SFLSampled_ipv4); break;
+ case SFLFLOW_EX_IPV4_TUNNEL_INGRESS: elemSiz = sizeof(SFLSampled_ipv4); break;
default:
sflError(receiver, "unexpected packet_data_tag");
return -1;
@@ -556,6 +558,8 @@ int sfl_receiver_writeFlowSample(SFLReceiver *receiver, SFL_FLOW_SAMPLE_TYPE *fs
putNet32(receiver, elem->flowType.ethernet.eth_type);
break;
case SFLFLOW_IPV4:
+ case SFLFLOW_EX_IPV4_TUNNEL_EGRESS:
+ case SFLFLOW_EX_IPV4_TUNNEL_INGRESS:
putNet32(receiver, elem->flowType.ipv4.length);
putNet32(receiver, elem->flowType.ipv4.protocol);
put32(receiver, elem->flowType.ipv4.src_ip.addr);
@@ -630,6 +634,7 @@ static int computeCountersSampleSize(SFLReceiver *receiver, SFL_COUNTERS_SAMPLE_
case SFLCOUNTERS_TOKENRING: elemSiz = sizeof(elem->counterBlock.tokenring); break;
case SFLCOUNTERS_VG: elemSiz = sizeof(elem->counterBlock.vg); break;
case SFLCOUNTERS_VLAN: elemSiz = sizeof(elem->counterBlock.vlan); break;
+ case SFLCOUNTERS_LACP: elemSiz = sizeof(elem->counterBlock.lacp); break;
default:
sflError(receiver, "unexpected counters_tag");
return -1;
@@ -731,6 +736,20 @@ int sfl_receiver_writeCountersSample(SFLReceiver *receiver, SFL_COUNTERS_SAMPLE_
putNet32(receiver, elem->counterBlock.vlan.broadcastPkts);
putNet32(receiver, elem->counterBlock.vlan.discards);
break;
+ case SFLCOUNTERS_LACP:
+ putMACAddress(receiver, elem->counterBlock.lacp.actorSystemID);
+ putMACAddress(receiver, elem->counterBlock.lacp.partnerSystemID);
+ putNet32(receiver, elem->counterBlock.lacp.attachedAggID);
+ put32(receiver, elem->counterBlock.lacp.portState.all);
+ putNet32(receiver, elem->counterBlock.lacp.LACPDUsRx);
+ putNet32(receiver, elem->counterBlock.lacp.markerPDUsRx);
+ putNet32(receiver, elem->counterBlock.lacp.markerResponsePDUsRx);
+ putNet32(receiver, elem->counterBlock.lacp.unknownRx);
+ putNet32(receiver, elem->counterBlock.lacp.illegalRx);
+ putNet32(receiver, elem->counterBlock.lacp.LACPDUsTx);
+ putNet32(receiver, elem->counterBlock.lacp.markerPDUsTx);
+ putNet32(receiver, elem->counterBlock.lacp.markerResponsePDUsTx);
+ break;
default:
sflError(receiver, "unexpected counters_tag");
return -1;
diff --git a/manpages.mk b/manpages.mk
index 811d2f9..2a34f04 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -116,6 +116,10 @@ lib/vconn-active.man:
lib/vconn-passive.man:
lib/vlog.man:
+utilities/ovs-dpctl-top.8: \
+ utilities/ovs-dpctl-top.8.in
+utilities/ovs-dpctl-top.8.in:
+
utilities/ovs-dpctl.8: \
utilities/ovs-dpctl.8.in \
lib/common.man \
@@ -124,10 +128,6 @@ utilities/ovs-dpctl.8.in:
lib/common.man:
lib/vlog.man:
-utilities/ovs-dpctl-top.8: \
- utilities/ovs-dpctl-top.8.in
-utilities/ovs-dpctl-top.8.in:
-
utilities/ovs-l3ping.8: \
utilities/ovs-l3ping.8.in \
lib/common-syn.man \
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 158887f..1e90a29 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -24,8 +24,6 @@
#include "collectors.h"
#include "compiler.h"
#include "dpif.h"
-#include "hash.h"
-#include "hmap.h"
#include "netdev.h"
#include "netlink.h"
#include "ofpbuf.h"
@@ -39,32 +37,24 @@
#include "vlog.h"
#include "lib/odp-util.h"
#include "ofproto-provider.h"
+#include "lacp.h"
VLOG_DEFINE_THIS_MODULE(sflow);
static struct ovs_mutex mutex;
-struct dpif_sflow_port {
- struct hmap_node hmap_node; /* In struct dpif_sflow's "ports" hmap. */
- SFLDataSource_instance dsi; /* sFlow library's notion of port number. */
- struct ofport *ofport; /* To retrive port stats. */
- odp_port_t odp_port;
-};
-
struct dpif_sflow {
struct collectors *collectors;
SFLAgent *sflow_agent;
struct ofproto_sflow_options *options;
time_t next_tick;
size_t n_flood, n_all;
- struct hmap ports; /* Contains "struct dpif_sflow_port"s. */
uint32_t probability;
atomic_int ref_cnt;
+ dpif_sflow_callback port_callback;
+ void *port_callback_magic;
};
-static void dpif_sflow_del_port__(struct dpif_sflow *,
- struct dpif_sflow_port *);
-
#define RECEIVER_INDEX 1
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
@@ -144,21 +134,6 @@ sflow_agent_send_packet_cb(void *ds_, SFLAgent *agent OVS_UNUSED,
collectors_send(ds->collectors, pkt, pktLen);
}
-static struct dpif_sflow_port *
-dpif_sflow_find_port(const struct dpif_sflow *ds, odp_port_t odp_port)
- OVS_REQUIRES(mutex)
-{
- struct dpif_sflow_port *dsp;
-
- HMAP_FOR_EACH_IN_BUCKET (dsp, hmap_node, hash_odp_port(odp_port),
- &ds->ports) {
- if (dsp->odp_port == odp_port) {
- return dsp;
- }
- }
- return NULL;
-}
-
static void
sflow_agent_get_counters(void *ds_, SFLPoller *poller,
SFL_COUNTERS_SAMPLE_TYPE *cs)
@@ -167,21 +142,27 @@ sflow_agent_get_counters(void *ds_, SFLPoller *poller,
struct dpif_sflow *ds = ds_;
SFLCounters_sample_element elem;
enum netdev_features current;
- struct dpif_sflow_port *dsp;
SFLIf_counters *counters;
struct netdev_stats stats;
enum netdev_flags flags;
-
- dsp = dpif_sflow_find_port(ds, u32_to_odp(poller->bridgePort));
- if (!dsp) {
- return;
+ struct dpif_sflow_port_lookup port_lookup;
+ SFLCounters_sample_element lacpElem;
+ struct lacp_slave_stats lacp_stats;
+ SFLLACP_counters *lacp;
+
+ memset(&port_lookup, 0, sizeof port_lookup);
+ if(!(*ds->port_callback)(ds->port_callback_magic,
+ (ofp_port_t)(poller->bridgePort),
+ &port_lookup)) {
+ return;
}
-
+
+ memset(&elem, 0, sizeof elem);
elem.tag = SFLCOUNTERS_GENERIC;
counters = &elem.counterBlock.generic;
counters->ifIndex = SFL_DS_INDEX(poller->dsi);
counters->ifType = 6;
- if (!netdev_get_features(dsp->ofport->netdev, ¤t, NULL, NULL, NULL)) {
+ if (!netdev_get_features(port_lookup.ofport->netdev, ¤t, NULL, NULL, NULL)) {
/* The values of ifDirection come from MAU MIB (RFC 2668): 0 = unknown,
1 = full-duplex, 2 = half-duplex, 3 = in, 4=out */
counters->ifSpeed = netdev_features_to_bps(current, 0);
@@ -191,9 +172,9 @@ sflow_agent_get_counters(void *ds_, SFLPoller *poller,
counters->ifSpeed = 100000000;
counters->ifDirection = 0;
}
- if (!netdev_get_flags(dsp->ofport->netdev, &flags) && flags & NETDEV_UP) {
+ if (!netdev_get_flags(port_lookup.ofport->netdev, &flags) && flags & NETDEV_UP) {
counters->ifStatus = 1; /* ifAdminStatus up. */
- if (netdev_get_carrier(dsp->ofport->netdev)) {
+ if (netdev_get_carrier(port_lookup.ofport->netdev)) {
counters->ifStatus |= 2; /* ifOperStatus us. */
}
} else {
@@ -205,23 +186,57 @@ sflow_agent_get_counters(void *ds_, SFLPoller *poller,
2. Does the multicast counter include broadcasts?
3. Does the rx_packets counter include multicasts/broadcasts?
*/
- ofproto_port_get_stats(dsp->ofport, &stats);
+ ofproto_port_get_stats(port_lookup.ofport, &stats);
counters->ifInOctets = stats.rx_bytes;
counters->ifInUcastPkts = stats.rx_packets;
counters->ifInMulticastPkts = stats.multicast;
- counters->ifInBroadcastPkts = -1;
+ SFL_UNDEF_COUNTER(counters->ifInBroadcastPkts);
counters->ifInDiscards = stats.rx_dropped;
counters->ifInErrors = stats.rx_errors;
- counters->ifInUnknownProtos = -1;
+ SFL_UNDEF_COUNTER(counters->ifInUnknownProtos);
counters->ifOutOctets = stats.tx_bytes;
counters->ifOutUcastPkts = stats.tx_packets;
- counters->ifOutMulticastPkts = -1;
- counters->ifOutBroadcastPkts = -1;
+ SFL_UNDEF_COUNTER(counters->ifOutMulticastPkts);
+ SFL_UNDEF_COUNTER(counters->ifOutBroadcastPkts);
counters->ifOutDiscards = stats.tx_dropped;
counters->ifOutErrors = stats.tx_errors;
counters->ifPromiscuousMode = 0;
SFLADD_ELEMENT(cs, &elem);
+
+ if(port_lookup.lacp) {
+ memset(&lacp_stats, 0, sizeof lacp_stats);
+ /* Implements sFlow standard: http://sflow.org/sflow_lag.txt */
+ if(lacp_get_slave_stats(port_lookup.lacp,
+ port_lookup.lacp_slave,
+ &lacp_stats)) {
+ memset(&lacpElem, 0, sizeof lacpElem);
+ lacpElem.tag = SFLCOUNTERS_LACP;
+ lacp = &lacpElem.counterBlock.lacp;
+ memcpy(lacp->actorSystemID,
+ lacp_stats.dot3adAggPortActorSystemID,
+ ETH_ADDR_LEN);
+ memcpy(lacp->partnerSystemID,
+ lacp_stats.dot3adAggPortPartnerOperSystemID,
+ ETH_ADDR_LEN);
+ lacp->attachedAggID = lacp_stats.dot3adAggPortAttachedAggID;
+ lacp->portState.v.actorAdmin = lacp_stats.dot3adAggPortActorAdminState;
+ lacp->portState.v.actorOper = lacp_stats.dot3adAggPortActorOperState;
+ lacp->portState.v.partnerAdmin = lacp_stats.dot3adAggPortPartnerAdminState;
+ lacp->portState.v.partnerOper = lacp_stats.dot3adAggPortPartnerOperState;
+ lacp->LACPDUsRx = lacp_stats.dot3adAggPortStatsLACPDUsRx;
+ SFL_UNDEF_COUNTER(lacp->markerPDUsRx);
+ SFL_UNDEF_COUNTER(lacp->markerResponsePDUsRx);
+ SFL_UNDEF_COUNTER(lacp->unknownRx);
+ lacp->illegalRx = lacp_stats.dot3adAggPortStatsIllegalRx;
+ lacp->LACPDUsTx = lacp_stats.dot3adAggPortStatsLACPDUsTx;
+ SFL_UNDEF_COUNTER(lacp->markerPDUsTx);
+ SFL_UNDEF_COUNTER(lacp->markerResponsePDUsTx);
+
+ SFLADD_ELEMENT(cs, &lacpElem);
+ }
+ }
+
sfl_poller_writeCountersSample(poller, cs);
}
@@ -324,7 +339,6 @@ dpif_sflow_create(void)
ds = xcalloc(1, sizeof *ds);
ds->next_tick = time_now() + 1;
- hmap_init(&ds->ports);
ds->probability = 0;
route_table_register();
atomic_init(&ds->ref_cnt, 1);
@@ -369,84 +383,54 @@ dpif_sflow_unref(struct dpif_sflow *ds) OVS_EXCLUDED(mutex)
atomic_sub(&ds->ref_cnt, 1, &orig);
ovs_assert(orig > 0);
if (orig == 1) {
- struct dpif_sflow_port *dsp, *next;
-
route_table_unregister();
dpif_sflow_clear(ds);
- HMAP_FOR_EACH_SAFE (dsp, next, hmap_node, &ds->ports) {
- dpif_sflow_del_port__(ds, dsp);
- }
- hmap_destroy(&ds->ports);
free(ds);
}
}
static void
-dpif_sflow_add_poller(struct dpif_sflow *ds, struct dpif_sflow_port *dsp)
+dpif_sflow_add_poller(struct dpif_sflow *ds, uint32_t ifindex, ofp_port_t ofp_port)
OVS_REQUIRES(mutex)
{
- SFLPoller *poller = sfl_agent_addPoller(ds->sflow_agent, &dsp->dsi, ds,
- sflow_agent_get_counters);
- sfl_poller_set_sFlowCpInterval(poller, ds->options->polling_interval);
- sfl_poller_set_sFlowCpReceiver(poller, RECEIVER_INDEX);
- sfl_poller_set_bridgePort(poller, odp_to_u32(dsp->odp_port));
+ SFLDataSource_instance dsi;
+ SFLPoller *poller;
+ if(ds->sflow_agent) {
+ SFL_DS_SET(dsi, SFL_DSCLASS_IFINDEX, ifindex, 0);
+ poller = sfl_agent_addPoller(ds->sflow_agent, &dsi, ds,
+ sflow_agent_get_counters);
+ sfl_poller_set_sFlowCpInterval(poller, ds->options->polling_interval);
+ sfl_poller_set_sFlowCpReceiver(poller, RECEIVER_INDEX);
+ sfl_poller_set_bridgePort(poller, (uint32_t)ofp_port);
+ }
}
void
-dpif_sflow_add_port(struct dpif_sflow *ds, struct ofport *ofport,
- odp_port_t odp_port) OVS_EXCLUDED(mutex)
+dpif_sflow_add_port(struct dpif_sflow *ds, struct ofport *ofport) OVS_EXCLUDED(mutex)
{
- struct dpif_sflow_port *dsp;
int ifindex;
ovs_mutex_lock(&mutex);
- dpif_sflow_del_port(ds, odp_port);
-
+ dpif_sflow_del_port(ds, ofport->ofp_port);
ifindex = netdev_get_ifindex(ofport->netdev);
-
- if (ifindex <= 0) {
- /* Not an ifindex port, so do not add a cross-reference to it here */
- goto out;
+ if (ifindex > 0) {
+ /* ifindex port, so add a poller for it here */
+ dpif_sflow_add_poller(ds, ifindex, ofport->ofp_port);
}
-
- /* Add to table of ports. */
- dsp = xmalloc(sizeof *dsp);
- dsp->ofport = ofport;
- dsp->odp_port = odp_port;
- SFL_DS_SET(dsp->dsi, SFL_DSCLASS_IFINDEX, ifindex, 0);
- hmap_insert(&ds->ports, &dsp->hmap_node, hash_odp_port(odp_port));
-
- /* Add poller. */
- if (ds->sflow_agent) {
- dpif_sflow_add_poller(ds, dsp);
- }
-
-out:
ovs_mutex_unlock(&mutex);
}
-static void
-dpif_sflow_del_port__(struct dpif_sflow *ds, struct dpif_sflow_port *dsp)
- OVS_REQUIRES(mutex)
-{
- if (ds->sflow_agent) {
- sfl_agent_removePoller(ds->sflow_agent, &dsp->dsi);
- sfl_agent_removeSampler(ds->sflow_agent, &dsp->dsi);
- }
- hmap_remove(&ds->ports, &dsp->hmap_node);
- free(dsp);
-}
-
void
-dpif_sflow_del_port(struct dpif_sflow *ds, odp_port_t odp_port)
+dpif_sflow_del_port(struct dpif_sflow *ds, ofp_port_t ofp_port)
OVS_EXCLUDED(mutex)
{
- struct dpif_sflow_port *dsp;
-
+ SFLPoller *poller;
ovs_mutex_lock(&mutex);
- dsp = dpif_sflow_find_port(ds, odp_port);
- if (dsp) {
- dpif_sflow_del_port__(ds, dsp);
+ if(ds->sflow_agent) {
+ poller = sfl_agent_getPollerByBridgePort(ds->sflow_agent, (uint32_t)ofp_port);
+ if(poller) {
+ sfl_agent_removePoller(ds->sflow_agent, &poller->dsi);
+ }
}
ovs_mutex_unlock(&mutex);
}
@@ -456,7 +440,6 @@ dpif_sflow_set_options(struct dpif_sflow *ds,
const struct ofproto_sflow_options *options)
OVS_EXCLUDED(mutex)
{
- struct dpif_sflow_port *dsp;
bool options_changed;
SFLReceiver *receiver;
SFLAddress agentIP;
@@ -543,33 +526,65 @@ dpif_sflow_set_options(struct dpif_sflow *ds,
sfl_sampler_set_sFlowFsMaximumHeaderSize(sampler, ds->options->header_len);
sfl_sampler_set_sFlowFsReceiver(sampler, RECEIVER_INDEX);
- /* Add pollers for the currently known ifindex-ports */
- HMAP_FOR_EACH (dsp, hmap_node, &ds->ports) {
- dpif_sflow_add_poller(ds, dsp);
- }
-
-
out:
ovs_mutex_unlock(&mutex);
}
-int
-dpif_sflow_odp_port_to_ifindex(const struct dpif_sflow *ds,
- odp_port_t odp_port) OVS_EXCLUDED(mutex)
+void
+dpif_sflow_set_port_lookup_callback(struct dpif_sflow *ds,
+ dpif_sflow_callback callback,
+ void *magic)
+ OVS_EXCLUDED(mutex)
{
- struct dpif_sflow_port *dsp;
- int ret;
-
ovs_mutex_lock(&mutex);
- dsp = dpif_sflow_find_port(ds, odp_port);
- ret = dsp ? SFL_DS_INDEX(dsp->dsi) : 0;
+ ds->port_callback = callback;
+ ds->port_callback_magic = magic;
ovs_mutex_unlock(&mutex);
- return ret;
+}
+
+/* Implements sFlow standard: http://sflow.org/sflow_tunnels.txt */
+static bool
+dpif_sflow_ipv4_tunnel_encode(SFLSampled_ipv4 *tunnel4,
+ struct dpif_sflow_port_lookup *port_lookup)
+{
+ const char *netdev_type;
+ const struct netdev_tunnel_config *tunnel_config;
+
+ /* XXX will need the struct flow here too - for those tunnels that
+ get their config dynamically from the flow. */
+
+ tunnel_config = netdev_get_tunnel_config(port_lookup->ofport->netdev);
+ if(tunnel_config == NULL) {
+ return false;
+ }
+ netdev_type = netdev_get_type(port_lookup->ofport->netdev);
+ tunnel4->src_ip.addr = tunnel_config->ip_src;
+ tunnel4->dst_ip.addr = tunnel_config->ip_dst;
+ /* Indicate 0==unknown for the src_port. It may be set to a random
+ number on a flow-by-flow basis to increase entropy for ECMP fabrics.
+ The assumption being made here is that it is not so important to
+ report this. At least not important enough to justify the effort
+ of making it accessible here. */
+ tunnel4->src_port = 0;
+ tunnel4->dst_port = tunnel_config->dst_port;
+ tunnel4->tos = tunnel_config->tos;
+ /* Use the netdev_type to determine the IP protocol
+ that was (or will be) seen on the wire. */
+ if(!strncmp(netdev_type, "gre", strlen("gre"))) {
+ tunnel4->protocol = IPPROTO_GRE;
+ }
+ else if(!strncmp(netdev_type, "vxlan", strlen("vxlan"))) {
+ tunnel4->protocol = IPPROTO_UDP;
+ }
+ else if(!strncmp(netdev_type, "ipsec", strlen("ipsec"))) {
+ tunnel4->protocol = IPPROTO_ESP;
+ }
+ return true;
}
void
dpif_sflow_received(struct dpif_sflow *ds, struct ofpbuf *packet,
- const struct flow *flow, odp_port_t odp_in_port,
+ const struct flow *flow,
const union user_action_cookie *cookie)
OVS_EXCLUDED(mutex)
{
@@ -578,10 +593,13 @@ dpif_sflow_received(struct dpif_sflow *ds, struct ofpbuf *packet,
SFLSampled_header *header;
SFLFlow_sample_element switchElem;
SFLSampler *sampler;
- struct dpif_sflow_port *in_dsp;
ovs_be16 vlan_tci;
+ struct dpif_sflow_port_lookup in_lookup, out_lookup;
+ SFLFlow_sample_element in_tunnelElem, out_tunnelElem;
+ int in_ifindex, out_ifindex;
ovs_mutex_lock(&mutex);
+
sampler = ds->sflow_agent->samplers;
if (!sampler) {
goto out;
@@ -590,11 +608,26 @@ dpif_sflow_received(struct dpif_sflow *ds, struct ofpbuf *packet,
/* Build a flow sample. */
memset(&fs, 0, sizeof fs);
- /* Look up the input ifIndex if this port has one. Otherwise just
- * leave it as 0 (meaning 'unknown') and continue. */
- in_dsp = dpif_sflow_find_port(ds, odp_in_port);
- if (in_dsp) {
- fs.input = SFL_DS_INDEX(in_dsp->dsi);
+ memset(&in_lookup, 0, sizeof in_lookup);
+ if((*ds->port_callback)(ds->port_callback_magic,
+ flow->in_port.ofp_port,
+ &in_lookup)) {
+ if(in_lookup.ofport->netdev) {
+ /* Look up the input ifIndex if this port has one. Otherwise just
+ * leave it as 0 (meaning 'unknown') and continue. */
+ in_ifindex = netdev_get_ifindex(in_lookup.ofport->netdev);
+ if(in_ifindex > 0) {
+ fs.input = in_ifindex;
+ }
+ }
+ if(in_lookup.is_tunnel) {
+ memset(&in_tunnelElem, 0, sizeof in_tunnelElem);
+ in_tunnelElem.tag = SFLFLOW_EX_IPV4_TUNNEL_INGRESS;
+ if(dpif_sflow_ipv4_tunnel_encode(&in_tunnelElem.flowType.ipv4,
+ &in_lookup)) {
+ SFLADD_ELEMENT(&fs, &in_tunnelElem);
+ }
+ }
}
/* Make the assumption that the random number generator in the datapath converges
@@ -623,15 +656,62 @@ dpif_sflow_received(struct dpif_sflow *ds, struct ofpbuf *packet,
switchElem.flowType.sw.src_priority = vlan_tci_to_pcp(flow->vlan_tci);
/* Retrieve data from user_action_cookie. */
- vlan_tci = cookie->sflow.vlan_tci;
- switchElem.flowType.sw.dst_vlan = vlan_tci_to_vid(vlan_tci);
- switchElem.flowType.sw.dst_priority = vlan_tci_to_pcp(vlan_tci);
+ if(cookie->type == USER_ACTION_COOKIE_SFLOW_MULTIPORT) {
+ VLOG_INFO("sFlow cookie multiport");
+ vlan_tci = cookie->sflow.multi.vlan_tci;
+ switchElem.flowType.sw.dst_vlan = vlan_tci_to_vid(vlan_tci);
+ switchElem.flowType.sw.dst_priority = vlan_tci_to_pcp(vlan_tci);
+ /* See http://www.sflow.org/sflow_version_5.txt (search for "Input/output
+ * port information") */
+ if(cookie->sflow.multi.n_outputs == 0) {
+ /* 0x40000000 | 256 means "packet dropped for unknown reason". */
+ fs.output = 0x40000000 | 256;
+ }
+ else {
+ /* 0x80000000 means "multiple output ports. */
+ fs.output = 0x80000000 | cookie->sflow.multi.n_outputs;
+ }
+ }
+ else if(cookie->type == USER_ACTION_COOKIE_SFLOW_SINGLEPORT) {
+ VLOG_INFO("sFlow cookie singleport");
+ vlan_tci = cookie->sflow.single.vlan_tci;
+ switchElem.flowType.sw.dst_vlan = vlan_tci_to_vid(vlan_tci);
+ switchElem.flowType.sw.dst_priority = vlan_tci_to_pcp(vlan_tci);
+
+ if(cookie->sflow.single.output_port) {
+ memset(&out_lookup, 0, sizeof out_lookup);
+ if((*ds->port_callback)(ds->port_callback_magic,
+ cookie->sflow.single.output_port,
+ &out_lookup)) {
+ if(out_lookup.ofport->netdev) {
+ /* Look up the output ifIndex if this port has one. Otherwise just
+ * leave it as 0 (meaning 'unknown') and continue. */
+ out_ifindex = netdev_get_ifindex(out_lookup.ofport->netdev);
+ if(out_ifindex > 0) {
+ fs.output = out_ifindex;
+ }
+ }
+ if(out_lookup.is_tunnel) {
+ memset(&out_tunnelElem, 0, sizeof out_tunnelElem);
+ out_tunnelElem.tag = SFLFLOW_EX_IPV4_TUNNEL_EGRESS;
+ if(dpif_sflow_ipv4_tunnel_encode(&out_tunnelElem.flowType.ipv4,
+ &out_lookup)) {
+ VLOG_INFO("sFlow add tunnel element");
+ SFLADD_ELEMENT(&fs, &out_tunnelElem);
+ }
+ }
+ }
+ }
+ }
+ else {
+ VLOG_WARN("unknown upcall cookie type: %"PRIu16, cookie->type);
+ }
- fs.output = cookie->sflow.output;
/* Submit the flow sample to be encoded into the next datagram. */
SFLADD_ELEMENT(&fs, &hdrElem);
SFLADD_ELEMENT(&fs, &switchElem);
+ VLOG_INFO("sFlow writeFlowSample");
sfl_sampler_writeFlowSample(sampler, &fs);
out:
diff --git a/ofproto/ofproto-dpif-sflow.h b/ofproto/ofproto-dpif-sflow.h
index d53c95c..736256a 100644
--- a/ofproto/ofproto-dpif-sflow.h
+++ b/ofproto/ofproto-dpif-sflow.h
@@ -21,6 +21,7 @@
#include <stdint.h>
#include "svec.h"
#include "lib/odp-util.h"
+#include "lacp.h"
struct dpif;
struct dpif_upcall;
@@ -34,14 +35,28 @@ void dpif_sflow_unref(struct dpif_sflow *);
uint32_t dpif_sflow_get_probability(const struct dpif_sflow *);
+struct dpif_sflow_port_lookup {
+ struct ofport *ofport;
+ /* indicate tunnel */
+ bool is_tunnel;
+ /* LAG/bundle details */
+ struct lacp *lacp;
+ void *lacp_slave;
+};
+
+typedef bool (*dpif_sflow_callback)(void *, ofp_port_t, struct dpif_sflow_port_lookup *);
+
+void dpif_sflow_set_port_lookup_callback(struct dpif_sflow *, dpif_sflow_callback, void *);
+
void dpif_sflow_set_options(struct dpif_sflow *,
const struct ofproto_sflow_options *);
+
void dpif_sflow_clear(struct dpif_sflow *);
bool dpif_sflow_is_enabled(const struct dpif_sflow *);
-void dpif_sflow_add_port(struct dpif_sflow *ds, struct ofport *ofport,
- odp_port_t odp_port);
-void dpif_sflow_del_port(struct dpif_sflow *, odp_port_t odp_port);
+void dpif_sflow_add_port(struct dpif_sflow *ds, struct ofport *ofport);
+
+void dpif_sflow_del_port(struct dpif_sflow *, ofp_port_t ofp_port);
void dpif_sflow_run(struct dpif_sflow *);
void dpif_sflow_wait(struct dpif_sflow *);
@@ -49,10 +64,6 @@ void dpif_sflow_wait(struct dpif_sflow *);
void dpif_sflow_received(struct dpif_sflow *,
struct ofpbuf *,
const struct flow *,
- odp_port_t odp_port,
const union user_action_cookie *);
-int dpif_sflow_odp_port_to_ifindex(const struct dpif_sflow *,
- odp_port_t odp_port);
-
#endif /* ofproto/ofproto-dpif-sflow.h */
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index d75c61b..668d5f9 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -456,7 +456,8 @@ classify_upcall(const struct upcall *upcall)
memset(&cookie, 0, sizeof cookie);
memcpy(&cookie, nl_attr_get(dpif_upcall->userdata), userdata_len);
if (userdata_len == sizeof cookie.sflow
- && cookie.type == USER_ACTION_COOKIE_SFLOW) {
+ && (cookie.type == USER_ACTION_COOKIE_SFLOW_SINGLEPORT
+ || cookie.type == USER_ACTION_COOKIE_SFLOW_MULTIPORT)) {
return SFLOW_UPCALL;
} else if (userdata_len == sizeof cookie.slow_path
&& cookie.type == USER_ACTION_COOKIE_SLOW_PATH) {
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index a5b6814..b3898e4 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -162,7 +162,7 @@ struct xlate_ctx {
uint32_t orig_skb_priority; /* Priority when packet arrived. */
uint8_t table_id; /* OpenFlow table ID where flow was found. */
uint32_t sflow_n_outputs; /* Number of output ports. */
- odp_port_t sflow_odp_port; /* Output port for composing sFlow action. */
+ odp_port_t sflow_ofp_port; /* Output port for composing sFlow action. */
uint16_t user_cookie_offset;/* Used for user_action_cookie fixup. */
bool exit; /* No further actions should be processed. */
};
@@ -1350,34 +1350,19 @@ compose_sample_action(const struct xbridge *xbridge,
}
static void
-compose_sflow_cookie(const struct xbridge *xbridge, ovs_be16 vlan_tci,
- odp_port_t odp_port, unsigned int n_outputs,
+compose_sflow_cookie(ovs_be16 vlan_tci,
+ ofp_port_t ofp_port, unsigned int n_outputs,
union user_action_cookie *cookie)
{
- int ifindex;
-
- cookie->type = USER_ACTION_COOKIE_SFLOW;
- cookie->sflow.vlan_tci = vlan_tci;
-
- /* See http://www.sflow.org/sflow_version_5.txt (search for "Input/output
- * port information") for the interpretation of cookie->output. */
- switch (n_outputs) {
- case 0:
- /* 0x40000000 | 256 means "packet dropped for unknown reason". */
- cookie->sflow.output = 0x40000000 | 256;
- break;
-
- case 1:
- ifindex = dpif_sflow_odp_port_to_ifindex(xbridge->sflow, odp_port);
- if (ifindex) {
- cookie->sflow.output = ifindex;
- break;
- }
- /* Fall through. */
- default:
- /* 0x80000000 means "multiple output ports. */
- cookie->sflow.output = 0x80000000 | n_outputs;
- break;
+ if(n_outputs == 1) {
+ cookie->type = USER_ACTION_COOKIE_SFLOW_SINGLEPORT;
+ cookie->sflow.single.vlan_tci = vlan_tci;
+ cookie->sflow.single.output_port = ofp_port;
+ }
+ else {
+ cookie->type = USER_ACTION_COOKIE_SFLOW_MULTIPORT;
+ cookie->sflow.multi.vlan_tci = vlan_tci;
+ cookie->sflow.multi.n_outputs = (uint32_t)n_outputs;
}
}
@@ -1386,7 +1371,7 @@ static size_t
compose_sflow_action(const struct xbridge *xbridge,
struct ofpbuf *odp_actions,
const struct flow *flow,
- odp_port_t odp_port)
+ ofp_port_t ofp_port)
{
uint32_t probability;
union user_action_cookie cookie;
@@ -1396,8 +1381,11 @@ compose_sflow_action(const struct xbridge *xbridge,
}
probability = dpif_sflow_get_probability(xbridge->sflow);
- compose_sflow_cookie(xbridge, htons(0), odp_port,
- odp_port == ODPP_NONE ? 0 : 1, &cookie);
+ // maybe we should be putting something like flow->out_port.ofp_port in here?
+ // -- and flow->in_port.ofp_port too if that's the easiest way to get the tunnel
+ // ids to the sFlow module?
+ compose_sflow_cookie(htons(0), ofp_port,
+ ofp_port == OFPP_NONE ? 0 : 1, &cookie);
return compose_sample_action(xbridge, odp_actions, flow, probability,
&cookie, sizeof cookie.sflow);
@@ -1449,8 +1437,8 @@ add_sflow_action(struct xlate_ctx *ctx)
{
ctx->user_cookie_offset = compose_sflow_action(ctx->xbridge,
&ctx->xout->odp_actions,
- &ctx->xin->flow, ODPP_NONE);
- ctx->sflow_odp_port = 0;
+ &ctx->xin->flow, OFPP_NONE);
+ ctx->sflow_ofp_port = 0;
ctx->sflow_n_outputs = 0;
}
@@ -1478,10 +1466,11 @@ fix_sflow_action(struct xlate_ctx *ctx)
cookie = ofpbuf_at(&ctx->xout->odp_actions, ctx->user_cookie_offset,
sizeof cookie->sflow);
- ovs_assert(cookie->type == USER_ACTION_COOKIE_SFLOW);
+ ovs_assert(cookie->type == USER_ACTION_COOKIE_SFLOW_SINGLEPORT
+ || cookie->type == USER_ACTION_COOKIE_SFLOW_MULTIPORT);
- compose_sflow_cookie(ctx->xbridge, base->vlan_tci,
- ctx->sflow_odp_port, ctx->sflow_n_outputs, cookie);
+ compose_sflow_cookie(base->vlan_tci,
+ ctx->sflow_ofp_port, ctx->sflow_n_outputs, cookie);
}
static enum slow_path_reason
@@ -1649,7 +1638,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
nl_msg_put_odp_port(&ctx->xout->odp_actions, OVS_ACTION_ATTR_OUTPUT,
out_port);
- ctx->sflow_odp_port = odp_port;
+ ctx->sflow_ofp_port = ofp_port;
ctx->sflow_n_outputs++;
ctx->xout->nf_output_iface = ofp_port;
}
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 80874b8..3bacd56 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1763,7 +1763,7 @@ port_construct(struct ofport *port_)
dpif_port_destroy(&dpif_port);
if (ofproto->sflow) {
- dpif_sflow_add_port(ofproto->sflow, port_, port->odp_port);
+ dpif_sflow_add_port(ofproto->sflow, port_);
}
return 0;
@@ -1863,24 +1863,47 @@ port_reconfigured(struct ofport *port_, enum ofputil_port_config old_config)
}
}
+static bool
+dpif_sflow_port_lookup_callback(void *magic, ofp_port_t ofp_port, struct dpif_sflow_port_lookup *port_lookup)
+{
+ /* XXX do we need to acquire ofproto_mutex here? Or should it be acquired in handle_sflow_upcall() below,
+ * so that it will not be released until after the sFlow-module has followed the pointers we are giving
+ * it here and the sflow sample has been fully processed? */
+
+ struct ofproto_dpif *ofproto = (struct ofproto_dpif *)magic;
+ struct ofport_dpif *port = get_ofp_port(ofproto, ofp_port);
+ if(port) {
+ port_lookup->ofport = &port->up;
+ port_lookup->is_tunnel = port->is_tunnel;
+ if(port->bundle
+ && port->bundle->lacp) {
+ port_lookup->lacp = port->bundle->lacp;
+ port_lookup->lacp_slave = port;
+ }
+ return true;
+ }
+ return false;
+}
+
+
static int
set_sflow(struct ofproto *ofproto_,
const struct ofproto_sflow_options *sflow_options)
{
struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
struct dpif_sflow *ds = ofproto->sflow;
+ struct ofport_dpif *ofport;
if (sflow_options) {
if (!ds) {
- struct ofport_dpif *ofport;
-
- ds = ofproto->sflow = dpif_sflow_create();
- HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
- dpif_sflow_add_port(ds, &ofport->up, ofport->odp_port);
- }
- ofproto->backer->need_revalidate = REV_RECONFIGURE;
+ ds = ofproto->sflow = dpif_sflow_create();
+ ofproto->backer->need_revalidate = REV_RECONFIGURE;
}
+ dpif_sflow_set_port_lookup_callback(ds, dpif_sflow_port_lookup_callback, (void *)ofproto);
dpif_sflow_set_options(ds, sflow_options);
+ HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
+ dpif_sflow_add_port(ds, &ofport->up);
+ }
} else {
if (ds) {
dpif_sflow_unref(ds);
@@ -3444,8 +3467,7 @@ handle_sflow_upcall(struct dpif_backer *backer,
memset(&cookie, 0, sizeof cookie);
memcpy(&cookie, nl_attr_get(upcall->userdata), sizeof cookie.sflow);
- dpif_sflow_received(ofproto->sflow, upcall->packet, &flow,
- odp_in_port, &cookie);
+ dpif_sflow_received(ofproto->sflow, upcall->packet, &flow, &cookie);
}
static void
diff --git a/tests/odp.at b/tests/odp.at
index 469e120..7b5fef1 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -155,7 +155,7 @@ AT_SETUP([OVS datapath actions parsing and formatting - valid forms])
AT_DATA([actions.txt], [dnl
1,2,3
userspace(pid=555666777)
-userspace(pid=6633,sFlow(vid=9,pcp=7,output=10))
+userspace(pid=6633,sFlow(vid=9,pcp=7,n_outputs=1,output_port=10))
userspace(pid=9765,slow_path())
userspace(pid=9765,slow_path(cfm))
userspace(pid=1234567,userdata(0102030405060708090a0b0c0d0e0f))
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index f67c3ab..77bd6b7 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -1831,6 +1831,144 @@ IFCOUNTERS
AT_CLEANUP
+dnl Test sFlow tunnel structures.
+AT_SETUP([ofproto-dpif - sFlow tunnel structures])
+OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \
+ options:remote_ip=1.1.1.1 options:local_ip=2.2.2.2 \
+ options:key=5 ofport_request=1\
+ -- add-port br0 p2 -- set Interface p2 type=dummy \
+ options:ifindex=1002 ofport_request=2])
+AT_DATA([flows.txt], [dnl
+actions=output:1
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+AT_CHECK([ovs-appctl dpif/show | tail -n +5], [0], [dnl
+ br0 65534/100: (dummy)
+ p1 1/1: (gre: key=5, local_ip=2.2.2.2, remote_ip=1.1.1.1)
+ p2 2/2: (dummy: ifindex=1002)
+])
+
+dnl set up sFlow logging
+dnl ON_EXIT([kill `cat test-sflow.pid`])
+AT_CHECK([test-sflow --log-file --detach --no-chdir --pidfile 0:127.0.0.1 > sflow.log], [0], [], [ignore])
+AT_CAPTURE_FILE([sflow.log])
+SFLOW_PORT=`parse_listening_port < test-sflow.log`
+ovs-appctl time/stop
+ovs-vsctl \
+ set Bridge br0 sflow=@sf -- \
+ --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \
+ header=128 sampling=1 polling=0
+
+dnl use ofproto/trace to check the actions
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=4,ttl=128,frag=no),tcp(src=8,dst=9)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+ [Datapath actions: sample(sample=100.0%,actions(userspace(pid=0,sFlow(vid=0,pcp=0,n_outputs=1,output_port=1)))),set(tunnel(tun_id=0x5,src=2.2.2.2,dst=1.1.1.1,tos=0x0,ttl=64,flags(df,key))),1
+])
+
+dnl use netdev-dummy/receive to send a packet that will be sampled
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 'in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=4,ttl=128,frag=no),tcp(src=8,dst=9)'], [0], [stdout])
+
+dnl sleep long enough to get the sFlow datagram flushed out (may be delayed for up to 1 second)
+for i in `seq 1 30`; do
+ ovs-appctl time/warp 100
+done
+OVS_VSWITCHD_STOP
+ovs-appctl -t test-sflow exit
+
+AT_CHECK([[sort sflow.log | $EGREP 'HEADER|ERROR' | sed 's/ /\
+ /g']], [0], [dnl
+HEADER
+ dgramSeqNo=1
+ ds=127.0.0.1>2:1000
+ fsSeqNo=1
+ tunnel4_out_length=0
+ tunnel4_out_protocol=47
+ tunnel4_out_src=2.2.2.2
+ tunnel4_out_dst=1.1.1.1
+ tunnel4_out_src_port=0
+ tunnel4_out_dst_port=0
+ tunnel4_out_tcp_flags=0
+ tunnel4_out_tos=0
+ in_vlan=0
+ in_priority=0
+ out_vlan=0
+ out_priority=0
+ meanSkip=1
+ samplePool=1
+ dropEvents=0
+ in_ifindex=1002
+ in_format=0
+ out_ifindex=0
+ out_format=0
+ hdr_prot=1
+ pkt_len=64
+ stripped=4
+ hdr_len=60
+ hdr=50-54-00-00-00-07-50-54-00-00-00-05-08-00-45-04-00-28-00-00-00-00-80-06-B9-78-C0-A8-00-01-C0-A8-00-02-00-08-00-09-00-00-00-00-00-00-00-00-50-00-00-00-00-00-00-00-00-00-00-00-00-00
+])
+AT_CLEANUP
+
+dnl Test sFlow LAG structures
+AT_SETUP([ofproto-dpif - sFlow LACP structures])
+
+OVS_VSWITCHD_START([dnl
+ add-bond br0 bond p1 p2 --\
+ set Port bond lacp=active bond-mode=active-backup \
+ other_config:lacp-time="fast" \
+ other_config:lacp-system-id=11:22:33:44:55:66 \
+ other_config:lacp-system-priority=54321 --\
+ set Interface p1 type=dummy \
+ other_config:lacp-port-id=11 \
+ other_config:lacp-port-priority=111 \
+ other_config:lacp-aggregation-key=3333 --\
+ set Interface p2 type=dummy \
+ other_config:lacp-port-id=22 \
+ other_config:lacp-port-priority=222 \
+ other_config:lacp-aggregation-key=3333 ])
+
+ON_EXIT([kill `cat test-sflow.pid`])
+AT_CHECK([test-sflow --log-file --detach --no-chdir --pidfile 0:127.0.0.1 > sflow.log], [0], [], [ignore])
+AT_CAPTURE_FILE([sflow.log])
+SFLOW_PORT=`parse_listening_port < test-sflow.log`
+
+ovs-appctl time/stop
+
+ovs-vsctl \
+ set Interface p1 options:ifindex=1003 -- \
+ set Bridge br0 sflow=@sf -- \
+ --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" \
+ header=128 sampling=1 polling=1
+
+dnl sleep long enough to get the sFlow datagram flushed out (may be delayed for up to 1 second)
+for i in `seq 1 30`; do
+ ovs-appctl time/warp 100
+done
+OVS_VSWITCHD_STOP
+ovs-appctl -t test-sflow exit
+
+AT_CHECK([[sort sflow.log | $EGREP 'LACPCOUNTERS|ERROR' | head -n 1 | sed 's/ /\
+ /g']], [0], [dnl
+LACPCOUNTERS
+ sysID=11:22:33:44:55:66
+ partnerID=00:00:00:00:00:00
+ aggID=3333
+ actorAdmin=0x7
+ actorOper=0xbf
+ partnerAdmin=0x0
+ partnerOper=0x2
+ LACPUDsRx=0
+ markerPDUsRx=4294967295
+ markerRespPDUsRx=4294967295
+ unknownRx=4294967295
+ illegalRx=0
+ LACPUDsTx=1
+ markerPDUsTx=4294967295
+ markerRespPDUsTx=4294967295
+])
+
+AT_CLEANUP
dnl Test that basic NetFlow reports flow statistics correctly:
dnl - The initial packet of a flow are correctly accounted.
diff --git a/tests/test-sflow.c b/tests/test-sflow.c
index cba01b9..c53e5ab 100644
--- a/tests/test-sflow.c
+++ b/tests/test-sflow.c
@@ -54,8 +54,11 @@ static unixctl_cb_func test_sflow_exit;
/* Structure element tag numbers. */
#define SFLOW_TAG_CTR_IFCOUNTERS 1
+#define SFLOW_TAG_CTR_LACPCOUNTERS 7
#define SFLOW_TAG_PKT_HEADER 1
#define SFLOW_TAG_PKT_SWITCH 1001
+#define SFLOW_TAG_PKT_TUNNEL4_OUT 1023
+#define SFLOW_TAG_PKT_TUNNEL4_IN 1024
struct sflow_addr {
enum {
@@ -99,7 +102,10 @@ struct sflow_xdr {
struct {
uint32_t HEADER;
uint32_t SWITCH;
+ uint32_t TUNNEL4_OUT;
+ uint32_t TUNNEL4_IN;
uint32_t IFCOUNTERS;
+ uint32_t LACPCOUNTERS;
} offset;
/* Flow sample fields. */
@@ -221,6 +227,42 @@ process_counter_sample(struct sflow_xdr *x)
printf(" promiscuous=%"PRIu32, sflowxdr_next(x));
printf("\n");
}
+ if(x->offset.LACPCOUNTERS) {
+ uint8_t *mac;
+ union {
+ uint32_t all;
+ struct {
+ uint8_t actorAdmin;
+ uint8_t actorOper;
+ uint8_t partnerAdmin;
+ uint8_t partnerOper;
+ } v;
+ } state;
+
+ sflowxdr_setc(x, x->offset.LACPCOUNTERS);
+ printf("LACPCOUNTERS");
+ mac = (uint8_t *)sflowxdr_str(x);
+ printf(" sysID="ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
+ sflowxdr_skip(x, 2);
+ mac = (uint8_t *)sflowxdr_str(x);
+ printf(" partnerID="ETH_ADDR_FMT, ETH_ADDR_ARGS(mac));
+ sflowxdr_skip(x, 2);
+ printf(" aggID=%"PRIu32, sflowxdr_next(x));
+ state.all = sflowxdr_next_n(x);
+ printf(" actorAdmin=0x%"PRIx32, state.v.actorAdmin);
+ printf(" actorOper=0x%"PRIx32, state.v.actorOper);
+ printf(" partnerAdmin=0x%"PRIx32, state.v.partnerAdmin);
+ printf(" partnerOper=0x%"PRIx32, state.v.partnerOper);
+ printf(" LACPUDsRx=%"PRIu32, sflowxdr_next(x));
+ printf(" markerPDUsRx=%"PRIu32, sflowxdr_next(x));
+ printf(" markerRespPDUsRx=%"PRIu32, sflowxdr_next(x));
+ printf(" unknownRx=%"PRIu32, sflowxdr_next(x));
+ printf(" illegalRx=%"PRIu32, sflowxdr_next(x));
+ printf(" LACPUDsTx=%"PRIu32, sflowxdr_next(x));
+ printf(" markerPDUsTx=%"PRIu32, sflowxdr_next(x));
+ printf(" markerRespPDUsTx=%"PRIu32, sflowxdr_next(x));
+ printf("\n");
+ }
}
static char
@@ -251,6 +293,25 @@ print_hex(const char *a, int len, char *buf, int bufLen)
return b;
}
+static void
+print_struct_ipv4(struct sflow_xdr *x, const char *prefix)
+{
+ uint32_t src, dst;
+
+ printf(" %s_length=%"PRIu32, prefix, sflowxdr_next(x));
+ printf(" %s_protocol=%"PRIu32, prefix, sflowxdr_next(x));
+
+ src = sflowxdr_next_n(x);
+ dst = sflowxdr_next_n(x);
+ printf(" %s_src="IP_FMT, prefix, IP_ARGS(src));
+ printf(" %s_dst="IP_FMT, prefix, IP_ARGS(dst));
+
+ printf(" %s_src_port=%"PRIu32, prefix, sflowxdr_next(x));
+ printf(" %s_dst_port=%"PRIu32, prefix, sflowxdr_next(x));
+ printf(" %s_tcp_flags=%"PRIu32, prefix, sflowxdr_next(x));
+ printf(" %s_tos=%"PRIu32, prefix, sflowxdr_next(x));
+}
+
#define SFLOW_HEX_SCRATCH 1024
static void
@@ -266,6 +327,16 @@ process_flow_sample(struct sflow_xdr *x)
x->agentIPStr, x->dsClass, x->dsIndex);
printf(" fsSeqNo=%"PRIu32, x->fsSeqNo);
+ if (x->offset.TUNNEL4_IN) {
+ sflowxdr_setc(x, x->offset.TUNNEL4_IN);
+ print_struct_ipv4(x, "tunnel4_in");
+ }
+
+ if (x->offset.TUNNEL4_OUT) {
+ sflowxdr_setc(x, x->offset.TUNNEL4_OUT);
+ print_struct_ipv4(x, "tunnel4_out");
+ }
+
if (x->offset.SWITCH) {
sflowxdr_setc(x, x->offset.SWITCH);
printf(" in_vlan=%"PRIu32, sflowxdr_next(x));
@@ -374,6 +445,9 @@ process_datagram(struct sflow_xdr *x)
case SFLOW_TAG_CTR_IFCOUNTERS:
sflowxdr_mark_unique(x, &x->offset.IFCOUNTERS);
break;
+ case SFLOW_TAG_CTR_LACPCOUNTERS:
+ sflowxdr_mark_unique(x, &x->offset.LACPCOUNTERS);
+ break;
/* Add others here... */
}
@@ -442,6 +516,14 @@ process_datagram(struct sflow_xdr *x)
sflowxdr_mark_unique(x, &x->offset.SWITCH);
break;
+ case SFLOW_TAG_PKT_TUNNEL4_OUT:
+ sflowxdr_mark_unique(x, &x->offset.TUNNEL4_OUT);
+ break;
+
+ case SFLOW_TAG_PKT_TUNNEL4_IN:
+ sflowxdr_mark_unique(x, &x->offset.TUNNEL4_IN);
+ break;
+
/* Add others here... */
}
--
1.8.1.4
More information about the dev
mailing list