@@ -129,7 +129,7 @@ void ovpn_decrypt_post(void *data, int ret)
kfree(ovpn_skb_cb(skb)->crypto_tmp);
if (unlikely(ret < 0)) {
- atomic64_inc(&peer->estats.rx_decrypt_errors);
+ ovpn_estats_inc(peer, rx_decrypt_errors);
goto drop;
}
@@ -140,7 +140,7 @@ void ovpn_decrypt_post(void *data, int ret)
net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n",
netdev_name(peer->ovpn->dev), peer->id,
ret);
- atomic64_inc(&peer->estats.rx_replay_errors);
+ ovpn_estats_inc(peer, rx_replay_errors);
goto drop;
}
@@ -168,7 +168,7 @@ void ovpn_decrypt_post(void *data, int ret)
net_info_ratelimited("%s: NULL packet received from peer %u\n",
netdev_name(peer->ovpn->dev),
peer->id);
- atomic64_inc(&peer->estats.rx_unsupported_proto);
+ ovpn_estats_inc(peer, rx_unsupported_proto);
goto drop;
}
@@ -176,7 +176,7 @@ void ovpn_decrypt_post(void *data, int ret)
net_dbg_ratelimited("%s: ping received from peer %u\n",
netdev_name(peer->ovpn->dev),
peer->id);
- atomic64_inc(&peer->estats.keepalive_rx);
+ ovpn_estats_inc(peer, keepalive_rx);
/* we drop the packet, but this is not a failure */
consume_skb(skb);
goto drop_nocount;
@@ -184,7 +184,7 @@ void ovpn_decrypt_post(void *data, int ret)
net_info_ratelimited("%s: unsupported protocol received from peer %u\n",
netdev_name(peer->ovpn->dev), peer->id);
- atomic64_inc(&peer->estats.rx_unsupported_proto);
+ ovpn_estats_inc(peer, rx_unsupported_proto);
goto drop;
}
skb->protocol = proto;
@@ -199,7 +199,7 @@ void ovpn_decrypt_post(void *data, int ret)
net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI4\n",
netdev_name(peer->ovpn->dev),
peer->id, &ip_hdr(skb)->saddr);
- atomic64_inc(&peer->estats.rx_rpf_errors);
+ ovpn_estats_inc(peer, rx_rpf_errors);
goto drop;
}
@@ -232,7 +232,7 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n",
netdev_name(peer->ovpn->dev), peer->id,
key_id);
- atomic64_inc(&peer->estats.rx_unknown_keyid);
+ ovpn_estats_inc(peer, rx_unknown_keyid);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
ovpn_peer_put(peer);
@@ -274,12 +274,12 @@ void ovpn_encrypt_post(void *data, int ret)
/* let userspace know so that a new key must be negotiated */
ovpn_nl_key_swap_notify(peer, ks->key_id);
- atomic64_inc(&peer->estats.tx_iv_exhausted);
+ ovpn_estats_inc(peer, tx_iv_exhausted);
goto err;
}
if (unlikely(ret < 0)) {
- atomic64_inc(&peer->estats.tx_encrypt_errors);
+ ovpn_estats_inc(peer, tx_encrypt_errors);
goto err;
}
@@ -289,7 +289,7 @@ void ovpn_encrypt_post(void *data, int ret)
rcu_read_lock();
sock = rcu_dereference(peer->sock);
if (unlikely(!sock)) {
- atomic64_inc(&peer->estats.tx_no_transport);
+ ovpn_estats_inc(peer, tx_no_transport);
goto err_unlock;
}
@@ -302,7 +302,7 @@ void ovpn_encrypt_post(void *data, int ret)
break;
default:
/* no transport configured yet */
- atomic64_inc(&peer->estats.tx_no_transport);
+ ovpn_estats_inc(peer, tx_no_transport);
goto err_unlock;
}
@@ -330,7 +330,7 @@ static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
/* get primary key to be used for encrypting data */
ks = ovpn_crypto_key_slot_primary(&peer->crypto);
if (unlikely(!ks)) {
- atomic64_inc(&peer->estats.tx_no_key);
+ ovpn_estats_inc(peer, tx_no_key);
return false;
}
@@ -384,8 +384,11 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
/* verify IP header size in network packet */
proto = ovpn_ip_check_protocol(skb);
- if (unlikely(!proto || skb->protocol != proto))
+ if (unlikely(!proto || skb->protocol != proto)) {
+ ovpn_dev_estats_inc(ovpn->estats,
+ OVPN_DEV_ESTAT_TX_BAD_PROTO);
goto drop_no_peer;
+ }
/* retrieve peer serving the destination IP of this packet */
peer = ovpn_peer_get_by_dst(ovpn, skb);
@@ -402,6 +405,8 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
&ipv6_hdr(skb)->daddr);
break;
}
+ ovpn_dev_estats_inc(ovpn->estats,
+ OVPN_DEV_ESTAT_TX_NO_PEER);
goto drop_no_peer;
}
/* dst was needed for peer selection - it can now be dropped */
@@ -413,7 +418,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
ret = PTR_ERR(segments);
net_err_ratelimited("%s: cannot segment payload packet: %d\n",
netdev_name(dev), ret);
- atomic64_inc(&peer->estats.tx_gso_errors);
+ ovpn_estats_inc(peer, tx_gso_errors);
goto drop;
}
@@ -30,6 +30,7 @@ static void ovpn_priv_free(struct net_device *net)
{
struct ovpn_priv *ovpn = netdev_priv(net);
+ free_percpu(ovpn->estats);
kfree(ovpn->peers);
}
@@ -60,18 +61,29 @@ static int ovpn_mp_alloc(struct ovpn_priv *ovpn)
static int ovpn_net_init(struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
- int err = gro_cells_init(&ovpn->gro_cells, dev);
+ int err;
+
+ ovpn->estats = netdev_alloc_pcpu_stats(struct ovpn_dev_estats);
+ if (!ovpn->estats)
+ return -ENOMEM;
+
+ err = gro_cells_init(&ovpn->gro_cells, dev);
if (err < 0)
- return err;
+ goto err_free_estats;
err = ovpn_mp_alloc(ovpn);
if (err < 0) {
gro_cells_destroy(&ovpn->gro_cells);
- return err;
+ goto err_free_estats;
}
return 0;
+
+err_free_estats:
+ free_percpu(ovpn->estats);
+ ovpn->estats = NULL;
+ return err;
}
static void ovpn_net_uninit(struct net_device *dev)
@@ -145,10 +157,90 @@ static void ovpn_get_drvinfo(struct net_device *dev,
strscpy(info->bus_info, "ovpn", sizeof(info->bus_info));
}
+/**
+ * struct ovpn_ethtool_stat - descriptor for one ethtool counter
+ * @name: counter name, as shown by ethtool -S
+ * @index: index of the counter within struct ovpn_dev_estats
+ */
+struct ovpn_ethtool_stat {
+ const char *name;
+ unsigned int index;
+};
+
+#define OVPN_ETHTOOL_ESTAT(_counter) \
+ { #_counter, OVPN_PEER_ESTAT_IDX(_counter) }
+
+static const struct ovpn_ethtool_stat ovpn_ethtool_stats[] = {
+ OVPN_ETHTOOL_ESTAT(rx_decrypt_errors),
+ OVPN_ETHTOOL_ESTAT(rx_replay_errors),
+ OVPN_ETHTOOL_ESTAT(rx_unknown_keyid),
+ OVPN_ETHTOOL_ESTAT(rx_unsupported_proto),
+ OVPN_ETHTOOL_ESTAT(rx_rpf_errors),
+ OVPN_ETHTOOL_ESTAT(tx_encrypt_errors),
+ OVPN_ETHTOOL_ESTAT(tx_iv_exhausted),
+ OVPN_ETHTOOL_ESTAT(tx_no_key),
+ OVPN_ETHTOOL_ESTAT(tx_no_transport),
+ OVPN_ETHTOOL_ESTAT(tx_gso_errors),
+ OVPN_ETHTOOL_ESTAT(keepalive_rx),
+ OVPN_ETHTOOL_ESTAT(keepalive_tx),
+ OVPN_ETHTOOL_ESTAT(floats),
+ /* device-only counters */
+ { "rx_no_peer", OVPN_DEV_ESTAT_RX_NO_PEER },
+ { "tx_no_peer", OVPN_DEV_ESTAT_TX_NO_PEER },
+ { "tx_bad_proto", OVPN_DEV_ESTAT_TX_BAD_PROTO },
+};
+
+static void ovpn_get_strings(struct net_device *dev, u32 stringset, u8 *data)
+{
+ unsigned int i;
+
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(ovpn_ethtool_stats); i++)
+ ethtool_puts(&data, ovpn_ethtool_stats[i].name);
+}
+
+static int ovpn_get_sset_count(struct net_device *dev, int sset)
+{
+ if (sset == ETH_SS_STATS)
+ return ARRAY_SIZE(ovpn_ethtool_stats);
+
+ return -EOPNOTSUPP;
+}
+
+static void ovpn_get_ethtool_stats(struct net_device *dev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct ovpn_priv *ovpn = netdev_priv(dev);
+ struct ovpn_dev_estats *estats;
+ const u64_stats_t *counter;
+ unsigned int start;
+ unsigned int i;
+ u64 value;
+ int cpu;
+
+ for (i = 0; i < ARRAY_SIZE(ovpn_ethtool_stats); i++) {
+ data[i] = 0;
+ for_each_possible_cpu(cpu) {
+ estats = per_cpu_ptr(ovpn->estats, cpu);
+ counter = &estats->counters[ovpn_ethtool_stats[i].index];
+ do {
+ start = u64_stats_fetch_begin(&estats->syncp);
+ value = u64_stats_read(counter);
+ } while (u64_stats_fetch_retry(&estats->syncp, start));
+ data[i] += value;
+ }
+ }
+}
+
static const struct ethtool_ops ovpn_ethtool_ops = {
.get_drvinfo = ovpn_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_ts_info = ethtool_op_get_ts_info,
+ .get_strings = ovpn_get_strings,
+ .get_sset_count = ovpn_get_sset_count,
+ .get_ethtool_stats = ovpn_get_ethtool_stats,
};
static void ovpn_setup(struct net_device *dev)
@@ -15,6 +15,8 @@
#include <uapi/linux/if_link.h>
#include <uapi/linux/ovpn.h>
+#include "stats.h"
+
/**
* struct ovpn_peer_collection - container of peers for MultiPeer mode
* @by_id: table of peers index by ID
@@ -41,6 +43,7 @@ struct ovpn_peer_collection {
* @peer: in P2P mode, this is the only remote peer
* @gro_cells: pointer to the Generic Receive Offload cell
* @keepalive_work: struct used to schedule keepalive periodic job
+ * @estats: monotonic per-CPU device-wide drop/event counters
*/
struct ovpn_priv {
struct net_device *dev;
@@ -50,6 +53,7 @@ struct ovpn_priv {
struct ovpn_peer __rcu *peer;
struct gro_cells gro_cells;
struct delayed_work keepalive_work;
+ struct ovpn_dev_estats __percpu *estats;
};
#endif /* _NET_OVPN_OVPNSTRUCT_H_ */
@@ -80,7 +80,7 @@ static void ovpn_peer_keepalive_send(struct work_struct *work)
/* count attempted keepalives: if the TX path fails afterwards,
* keepalive_tx will include a transmission that was not sent
*/
- atomic64_inc(&peer->estats.keepalive_tx);
+ ovpn_estats_inc(peer, keepalive_tx);
local_bh_disable();
ovpn_xmit_special(peer, ovpn_keepalive_message,
sizeof(ovpn_keepalive_message));
@@ -313,7 +313,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb)
spin_unlock_bh(&peer->lock);
- atomic64_inc(&peer->estats.floats);
+ ovpn_estats_inc(peer, floats);
ovpn_nl_peer_float_notify(peer, &ss);
/* rehashing is required only in MP mode as P2P has one peer
@@ -118,6 +118,22 @@ struct ovpn_peer {
struct ovpn_peer_estats estats;
};
+/**
+ * ovpn_estats_inc - increment a counter in both the per-peer and the
+ * device-wide extended statistics
+ * @peer: peer whose counter should be incremented
+ * @counter: name of the counter member in struct ovpn_peer_estats
+ *
+ * Device-wide counters are monotonic, they do not decrease when a peer
+ * is deleted. For device only counters use ovpn_dev_estats_inc() instead.
+ */
+#define ovpn_estats_inc(peer, counter) \
+ do { \
+ atomic64_inc(&(peer)->estats.counter); \
+ ovpn_dev_estats_inc((peer)->ovpn->estats, \
+ OVPN_PEER_ESTAT_IDX(counter)); \
+ } while (0)
+
/**
* ovpn_peer_hold - increase reference counter
* @peer: the peer whose counter should be increased
@@ -58,6 +58,37 @@ struct ovpn_peer_estats {
atomic64_t floats;
};
+#define OVPN_PEER_ESTAT_COUNT \
+ (sizeof(struct ovpn_peer_estats) / sizeof(atomic64_t))
+#define OVPN_PEER_ESTAT_IDX(_counter) \
+ (offsetof(struct ovpn_peer_estats, _counter) / sizeof(atomic64_t))
+
+enum ovpn_dev_estat {
+ OVPN_DEV_ESTAT_RX_NO_PEER = OVPN_PEER_ESTAT_COUNT,
+ OVPN_DEV_ESTAT_TX_NO_PEER,
+ OVPN_DEV_ESTAT_TX_BAD_PROTO,
+ OVPN_DEV_ESTAT_COUNT,
+};
+
+struct ovpn_dev_estats {
+ u64_stats_t counters[OVPN_DEV_ESTAT_COUNT];
+ struct u64_stats_sync syncp;
+};
+
+static inline void
+ovpn_dev_estats_inc(struct ovpn_dev_estats __percpu *estats,
+ unsigned int index)
+{
+ struct ovpn_dev_estats *stats;
+
+ local_bh_disable();
+ stats = this_cpu_ptr(estats);
+ u64_stats_update_begin(&stats->syncp);
+ u64_stats_inc(&stats->counters[index]);
+ u64_stats_update_end(&stats->syncp);
+ local_bh_enable();
+}
+
void ovpn_peer_stats_init(struct ovpn_peer_stats *ps);
static inline void ovpn_peer_stats_increment(struct ovpn_peer_stat *stat,
@@ -116,8 +116,11 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
else
peer = ovpn_peer_get_by_id(ovpn, peer_id);
- if (unlikely(!peer))
+ if (unlikely(!peer)) {
+ ovpn_dev_estats_inc(ovpn->estats,
+ OVPN_DEV_ESTAT_RX_NO_PEER);
goto drop;
+ }
/* pop off outer UDP header */
__skb_pull(skb, sizeof(struct udphdr));