@@ -175,6 +175,68 @@ attribute-sets:
will advertise the tx-id to be used on the link.
checks:
max: 0xFFFFFF
+ -
+ name: estats
+ type: nest
+ doc: Extended statistics, per-peer drop/event counters
+ nested-attributes: peer-estats
+ -
+ name: peer-estats
+ attributes:
+ -
+ name: rx-decrypt-errors
+ type: uint
+ doc: Number of packets dropped due to decryption failure
+ -
+ name: rx-replay-errors
+ type: uint
+ doc: Number of packets dropped by the replay protection check
+ -
+ name: rx-unknown-keyid
+ type: uint
+ doc: Number of packets dropped due to unknown key ID
+ -
+ name: rx-unsupported-proto
+ type: uint
+ doc: >-
+ Number of packets dropped due to unsupported or malformed inner
+ protocol
+ -
+ name: rx-rpf-errors
+ type: uint
+ doc: Number of packets dropped by the reverse path filtering check
+ -
+ name: tx-encrypt-errors
+ type: uint
+ doc: Number of packets dropped due to encryption failure
+ -
+ name: tx-iv-exhausted
+ type: uint
+ doc: Number of packets dropped due to packet ID (IV) exhaustion
+ -
+ name: tx-no-key
+ type: uint
+ doc: Number of packets dropped due to missing primary key
+ -
+ name: tx-no-transport
+ type: uint
+ doc: Number of packets dropped due to missing transport socket
+ -
+ name: tx-gso-errors
+ type: uint
+ doc: Number of packets dropped due to GSO segmentation failure
+ -
+ name: keepalive-rx
+ type: uint
+ doc: Number of keepalive packets received from this peer
+ -
+ name: keepalive-tx
+ type: uint
+ doc: Number of keepalive packets sent to this peer
+ -
+ name: float-count
+ type: uint
+ doc: Number of times the peer endpoint floated
-
name: peer-new-input
subset-of: peer
@@ -128,8 +128,10 @@ void ovpn_decrypt_post(void *data, int ret)
/* crypto is done, cleanup skb CB and its members */
kfree(ovpn_skb_cb(skb)->crypto_tmp);
- if (unlikely(ret < 0))
+ if (unlikely(ret < 0)) {
+ atomic64_inc(&peer->estats.rx_decrypt_errors);
goto drop;
+ }
/* PID sits after the op */
pid = (__force __be32 *)(skb->data + OVPN_OPCODE_SIZE);
@@ -138,6 +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);
goto drop;
}
@@ -165,6 +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);
goto drop;
}
@@ -172,6 +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);
/* we drop the packet, but this is not a failure */
consume_skb(skb);
goto drop_nocount;
@@ -179,6 +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);
goto drop;
}
skb->protocol = proto;
@@ -193,6 +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);
goto drop;
}
@@ -225,6 +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_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
ovpn_peer_put(peer);
@@ -266,19 +274,24 @@ 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);
goto err;
}
- if (unlikely(ret < 0))
+ if (unlikely(ret < 0)) {
+ atomic64_inc(&peer->estats.tx_encrypt_errors);
goto err;
+ }
skb_mark_not_on_list(skb);
orig_len = skb->len;
rcu_read_lock();
sock = rcu_dereference(peer->sock);
- if (unlikely(!sock))
+ if (unlikely(!sock)) {
+ atomic64_inc(&peer->estats.tx_no_transport);
goto err_unlock;
+ }
switch (sock->sk->sk_protocol) {
case IPPROTO_UDP:
@@ -289,6 +302,7 @@ void ovpn_encrypt_post(void *data, int ret)
break;
default:
/* no transport configured yet */
+ atomic64_inc(&peer->estats.tx_no_transport);
goto err_unlock;
}
@@ -315,8 +329,10 @@ 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))
+ if (unlikely(!ks)) {
+ atomic64_inc(&peer->estats.tx_no_key);
return false;
+ }
/* take a reference to the peer because the crypto code may run async.
* ovpn_encrypt_post() will release it upon completion
@@ -397,6 +413,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);
goto drop;
}
@@ -63,7 +63,7 @@ const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1] = {
[OVPN_A_KEYDIR_NONCE_TAIL] = NLA_POLICY_EXACT_LEN(OVPN_NONCE_TAIL_SIZE),
};
-const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
+const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_ESTATS + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -88,12 +88,29 @@ const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_LINK_RX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
+ [OVPN_A_PEER_ESTATS] = NLA_POLICY_NESTED(ovpn_peer_estats_nl_policy),
};
const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
};
+const struct nla_policy ovpn_peer_estats_nl_policy[OVPN_A_PEER_ESTATS_FLOAT_COUNT + 1] = {
+ [OVPN_A_PEER_ESTATS_RX_DECRYPT_ERRORS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_RX_REPLAY_ERRORS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_RX_UNKNOWN_KEYID] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_RX_UNSUPPORTED_PROTO] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_RX_RPF_ERRORS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_TX_ENCRYPT_ERRORS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_TX_IV_EXHAUSTED] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_TX_NO_KEY] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_TX_NO_TRANSPORT] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_TX_GSO_ERRORS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_KEEPALIVE_RX] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_KEEPALIVE_TX] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_ESTATS_FLOAT_COUNT] = { .type = NLA_UINT, },
+};
+
const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
@@ -18,8 +18,9 @@ extern const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_S
extern const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1];
extern const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1];
extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1];
-extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1];
+extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_ESTATS + 1];
extern const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1];
+extern const struct nla_policy ovpn_peer_estats_nl_policy[OVPN_A_PEER_ESTATS_FLOAT_COUNT + 1];
extern const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
extern const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
@@ -551,9 +551,9 @@ static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info,
int flags)
{
const struct ovpn_bind *bind;
+ struct nlattr *attr, *estats;
struct ovpn_socket *sock;
int ret = -EMSGSIZE;
- struct nlattr *attr;
__be16 local_port;
void *hdr;
int id;
@@ -654,6 +654,41 @@ static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info,
atomic64_read(&peer->link_stats.tx.packets)))
goto err;
+ estats = nla_nest_start(skb, OVPN_A_PEER_ESTATS);
+ if (!estats)
+ goto err;
+
+ if (/* drop/event counters */
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_RX_DECRYPT_ERRORS,
+ atomic64_read(&peer->estats.rx_decrypt_errors)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_RX_REPLAY_ERRORS,
+ atomic64_read(&peer->estats.rx_replay_errors)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_RX_UNKNOWN_KEYID,
+ atomic64_read(&peer->estats.rx_unknown_keyid)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_RX_UNSUPPORTED_PROTO,
+ atomic64_read(&peer->estats.rx_unsupported_proto)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_RX_RPF_ERRORS,
+ atomic64_read(&peer->estats.rx_rpf_errors)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_TX_ENCRYPT_ERRORS,
+ atomic64_read(&peer->estats.tx_encrypt_errors)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_TX_IV_EXHAUSTED,
+ atomic64_read(&peer->estats.tx_iv_exhausted)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_TX_NO_KEY,
+ atomic64_read(&peer->estats.tx_no_key)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_TX_NO_TRANSPORT,
+ atomic64_read(&peer->estats.tx_no_transport)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_TX_GSO_ERRORS,
+ atomic64_read(&peer->estats.tx_gso_errors)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_KEEPALIVE_RX,
+ atomic64_read(&peer->estats.keepalive_rx)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_KEEPALIVE_TX,
+ atomic64_read(&peer->estats.keepalive_tx)) ||
+ nla_put_uint(skb, OVPN_A_PEER_ESTATS_FLOAT_COUNT,
+ atomic64_read(&peer->estats.floats)))
+ goto err;
+
+ nla_nest_end(skb, estats);
+
nla_nest_end(skb, attr);
genlmsg_end(skb, hdr);
@@ -77,6 +77,10 @@ static void ovpn_peer_keepalive_send(struct work_struct *work)
struct ovpn_peer *peer = container_of(work, struct ovpn_peer,
keepalive_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);
local_bh_disable();
ovpn_xmit_special(peer, ovpn_keepalive_message,
sizeof(ovpn_keepalive_message));
@@ -309,6 +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_nl_peer_float_notify(peer, &ss);
/* rehashing is required only in MP mode as P2P has one peer
@@ -60,6 +60,7 @@
* @rcu: used to free peer in an RCU safe way
* @release_entry: entry for the socket release list
* @keepalive_work: used to schedule keepalive sending
+ * @estats: per-peer drop/event counters
*/
struct ovpn_peer {
struct ovpn_priv *ovpn;
@@ -114,6 +115,7 @@ struct ovpn_peer {
struct rcu_head rcu;
struct llist_node release_entry;
struct work_struct keepalive_work;
+ struct ovpn_peer_estats estats;
};
/**
@@ -25,6 +25,39 @@ struct ovpn_peer_stats {
struct ovpn_peer_stat tx;
};
+/**
+ * struct ovpn_peer_estats - per-peer drop/event counters
+ * @rx_decrypt_errors: packets dropped due to decryption/auth failure
+ * @rx_replay_errors: packets dropped by the replay protection check
+ * @rx_unknown_keyid: packets dropped due to unknown key ID
+ * @rx_unsupported_proto: packets dropped due to unsupported or malformed
+ * inner protocol
+ * @rx_rpf_errors: packets dropped by the reverse path filtering check
+ * @tx_encrypt_errors: packets dropped due to encryption failure
+ * @tx_iv_exhausted: packets dropped due to packet ID (IV) exhaustion
+ * @tx_no_key: packets dropped due to missing primary key
+ * @tx_no_transport: packets dropped due to missing transport socket
+ * @tx_gso_errors: packets dropped due to GSO segmentation failure
+ * @keepalive_rx: keepalive packets received from this peer
+ * @keepalive_tx: keepalive packets sent to this peer
+ * @floats: number of times the peer endpoint floated
+ */
+struct ovpn_peer_estats {
+ atomic64_t rx_decrypt_errors;
+ atomic64_t rx_replay_errors;
+ atomic64_t rx_unknown_keyid;
+ atomic64_t rx_unsupported_proto;
+ atomic64_t rx_rpf_errors;
+ atomic64_t tx_encrypt_errors;
+ atomic64_t tx_iv_exhausted;
+ atomic64_t tx_no_key;
+ atomic64_t tx_no_transport;
+ atomic64_t tx_gso_errors;
+ atomic64_t keepalive_rx;
+ atomic64_t keepalive_tx;
+ atomic64_t floats;
+};
+
void ovpn_peer_stats_init(struct ovpn_peer_stats *ps);
static inline void ovpn_peer_stats_increment(struct ovpn_peer_stat *stat,
@@ -56,11 +56,31 @@ enum {
OVPN_A_PEER_LINK_RX_PACKETS,
OVPN_A_PEER_LINK_TX_PACKETS,
OVPN_A_PEER_TX_ID,
+ OVPN_A_PEER_ESTATS,
__OVPN_A_PEER_MAX,
OVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)
};
+enum {
+ OVPN_A_PEER_ESTATS_RX_DECRYPT_ERRORS = 1,
+ OVPN_A_PEER_ESTATS_RX_REPLAY_ERRORS,
+ OVPN_A_PEER_ESTATS_RX_UNKNOWN_KEYID,
+ OVPN_A_PEER_ESTATS_RX_UNSUPPORTED_PROTO,
+ OVPN_A_PEER_ESTATS_RX_RPF_ERRORS,
+ OVPN_A_PEER_ESTATS_TX_ENCRYPT_ERRORS,
+ OVPN_A_PEER_ESTATS_TX_IV_EXHAUSTED,
+ OVPN_A_PEER_ESTATS_TX_NO_KEY,
+ OVPN_A_PEER_ESTATS_TX_NO_TRANSPORT,
+ OVPN_A_PEER_ESTATS_TX_GSO_ERRORS,
+ OVPN_A_PEER_ESTATS_KEEPALIVE_RX,
+ OVPN_A_PEER_ESTATS_KEEPALIVE_TX,
+ OVPN_A_PEER_ESTATS_FLOAT_COUNT,
+
+ __OVPN_A_PEER_ESTATS_MAX,
+ OVPN_A_PEER_ESTATS_MAX = (__OVPN_A_PEER_ESTATS_MAX - 1)
+};
+
enum {
OVPN_A_KEYCONF_PEER_ID = 1,
OVPN_A_KEYCONF_SLOT,
@@ -893,6 +893,66 @@ static int ovpn_handle_peer(struct nl_msg *msg, void (*arg)__always_unused)
fprintf(stderr, "\tLINK TX packets: %" PRIu64 "\n",
ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_PACKETS]));
+ if (pattrs[OVPN_A_PEER_ESTATS]) {
+ struct nlattr *eattrs[OVPN_A_PEER_ESTATS_MAX + 1];
+
+ nla_parse(eattrs, OVPN_A_PEER_ESTATS_MAX,
+ nla_data(pattrs[OVPN_A_PEER_ESTATS]),
+ nla_len(pattrs[OVPN_A_PEER_ESTATS]), NULL);
+
+ if (eattrs[OVPN_A_PEER_ESTATS_RX_DECRYPT_ERRORS])
+ fprintf(stderr, "\tRX decrypt errors: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_RX_DECRYPT_ERRORS]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_RX_REPLAY_ERRORS])
+ fprintf(stderr, "\tRX replay errors: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_RX_REPLAY_ERRORS]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_RX_UNKNOWN_KEYID])
+ fprintf(stderr, "\tRX unknown key-id: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_RX_UNKNOWN_KEYID]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_RX_UNSUPPORTED_PROTO])
+ fprintf(stderr, "\tRX unsupported/malformed proto: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_RX_UNSUPPORTED_PROTO]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_RX_RPF_ERRORS])
+ fprintf(stderr, "\tRX RPF errors: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_RX_RPF_ERRORS]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_TX_ENCRYPT_ERRORS])
+ fprintf(stderr, "\tTX encrypt errors: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_TX_ENCRYPT_ERRORS]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_TX_IV_EXHAUSTED])
+ fprintf(stderr, "\tTX IV exhausted: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_TX_IV_EXHAUSTED]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_TX_NO_KEY])
+ fprintf(stderr, "\tTX no key: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_TX_NO_KEY]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_TX_NO_TRANSPORT])
+ fprintf(stderr, "\tTX no transport: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_TX_NO_TRANSPORT]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_TX_GSO_ERRORS])
+ fprintf(stderr, "\tTX GSO errors: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_TX_GSO_ERRORS]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_KEEPALIVE_RX])
+ fprintf(stderr, "\tKeepalive RX: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_KEEPALIVE_RX]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_KEEPALIVE_TX])
+ fprintf(stderr, "\tKeepalive TX: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_KEEPALIVE_TX]));
+
+ if (eattrs[OVPN_A_PEER_ESTATS_FLOAT_COUNT])
+ fprintf(stderr, "\tFloat count: %" PRIu64 "\n",
+ ovpn_nla_get_uint(eattrs[OVPN_A_PEER_ESTATS_FLOAT_COUNT]));
+ }
+
return NL_SKIP;
}