From patchwork Fri Sep 4 21:12:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5316 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:2190:b0:892:1b45:3040 with SMTP id s16csp4171454mae; Fri, 4 Sep 2026 14:13:25 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBwEep3z1+ym1W3N8e39zr0QAEoZ9quXWb5L25AWXo7wLd/i4/YWKeuBZvwbsL2J4w8N+aluOYQ8Do4=@openvpn.net X-Received: by 2002:a05:6871:ac0b:b0:456:44dc:76a3 with SMTP id 586e51a60fabf-4755115b08dmr7154662fac.2.1788556405483; Fri, 04 Sep 2026 14:13:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788556405; cv=none; d=google.com; s=arc-20260327; b=PM9A+SKMOwUL1q5E+VnTl663SosQlS2e3+hUGld429c7LiQY+ZCG58wH6hRGbo9B+v dDX15vAMRKZlr3La27B96kckFZ3cYPCgPpCUaIUqYjqbuMhZ3kjNaKsoD9HL+m5aTAdx /1SRJK+KnwlO23FLk6Zfh8Qs972vmWeQ7yw55E5VeVyf2AL28AELMlMIerWEo9gccz0O WfWccwTOimN0+fAZ+WaMiJAhea2y3wpkphB81zw9q42z5WuYuZ/QXCfmf2EmTSjmCO3g pxNkK1LSH6BiaDZFRrlyv/HC148M5wDbvmmWw9miL51oLHz3Mo7tSJUIuhmaAYf/QUp+ ChjA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:cc:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:message-id:date:to:from:dkim-signature:dkim-signature :dkim-signature:dkim-signature; bh=1mYaSH/UlTU7Kihu/cFPLcXC4x14dNwIwzJv4LKoQcw=; fh=BsMg/B0Yb/hS/rzP5Npz4luh0IleZm8REk1XWiWRt2A=; b=RW2rWsriwXDa/uzp3OTTdoymX6I0FwuDd1CcrBnP2nHrfcN89KgskG2xAk3kL5lM4E mHrF//klhNzVdxScbQzkSkYXmHiywn04u2/GtSYIdaN3i4WQxyEkJbxknBUi77G/fovZ SXEqLoi6zzz+9kZbrroAD6GHalPKs1Z78qorHie7eZZqFJtaF1Xw+Smf0QFehRBOG++l y0JmcDQk3KgvgpZot+gC2Wo3KVwQJRSolc7M4IdOrgDAYaJlNDwTQ8vPsoF9hHQh6nwR iSZs/vrB5ebO3zL/2IqSzpPR4pX7aCQSjwXY4RHu8TQOG98bqojTNu37+aJEVab52/uG AV2A==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=c+FnXrHW; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="OK1/uV+F"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=gDdgcB4Q; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=sAjMP9RY; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-47555b20259si5507860fac.226.2026.09.04.14.13.25 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 04 Sep 2026 14:13:25 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=c+FnXrHW; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="OK1/uV+F"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=gDdgcB4Q; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=sAjMP9RY; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type:Cc: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:Message-ID:Date:To:From:Sender:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Owner; bh=1mYaSH/UlTU7Kihu/cFPLcXC4x14dNwIwzJv4LKoQcw=; b=c+FnXrHW9dlnScRNwdLhD7qCcr i4tvAU29OJ71jheHujJPS8niremN6f4JYrZEi7cG0RdaeqRD2L1QfRxN4Lm/KfOPyBg7tBEZf+oy6 4b24mjk4nRK9tvn6cN/Tlj7gr3o2PPtSuc3rLZVgb+dZL3BX+BtLgBe0LpqBnceMfgZw=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1x2bDr-0000Gi-1X; Fri, 04 Sep 2026 21:13:16 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1x2bDn-0000GZ-RN for openvpn-devel@lists.sourceforge.net; Fri, 04 Sep 2026 21:13:14 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=DwkEhWk/HPUIXDqmPogeKdUoeKj6PIy5vhG1iRp6ZOE=; b=OK1/uV+F52pp0lIaDz5V3/xQig 04El4tNqSKqiyZemqnytWo24MMTFWbNU1pmdGWykKs4vjdC904+uXg/chZeHqEk3EzrzQmempW2In WSPlRRwDya++S6cRUaawyVbEKDGcWKggTGs8l0X/b1xNF/zAdx7d/+TboW5PeqpybZkU=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject:Cc:To:From :Sender:Reply-To:Content-Type:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post: List-Owner:List-Archive; bh=DwkEhWk/HPUIXDqmPogeKdUoeKj6PIy5vhG1iRp6ZOE=; b=g DdgcB4QgE2QmPJaqf67CaNJYoD/TakfdCR/iNl+KO26HBxQCfi4VpKSd1z4/q2C030Gzz9k/RiumP Lr6BJPvHxSjFTgYraUxveb63oRxBp6sCla9bexfFZmL7N5YCULSD0WH9yXMeE5hQkTXRPMvfndEs7 L1So5AX7ZFIDPIKA=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1x2bDi-00021j-Ax for openvpn-devel@lists.sourceforge.net; Fri, 04 Sep 2026 21:13:13 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4hc8M22QV0zLm0j; Fri, 04 Sep 2026 23:13:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1788556382; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=DwkEhWk/HPUIXDqmPogeKdUoeKj6PIy5vhG1iRp6ZOE=; b=sAjMP9RYcT7tIPOLXILdSvp5qeHavcuEWwcEC/B8AGtRRph4VogPFMKMjeDXcFJCoeAgT8 SAz6J8ThqNhsB/CTMvDjxi7Uo3fT4lICUxrjz0QR7zy/oXYz9aVQRk88KiTioMLgKUU8MR 04DKvVxkwzgRO8YBF7z1OoH40hemtr9Z1sB1d8YSCRNOJ7oH69KaaXQybDxFs5Z3maB4F4 DSZXJIYXhP73cs/U6h5fJ+0Cz5DivH/YK+yEYwSDOtciDTOU44mY7/dqeX4TzLnw1iGl5J V4UUurDYC76v4N1AE8IN5tvIcXtySVK+i4rTNlGB91v4PyFUPHz+daDlXG2xOA== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Fri, 4 Sep 2026 23:12:55 +0200 Message-ID: <20260904211256.2889974-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4hc8M22QV0zLm0j X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Drops are currently accounted only via the undifferentiated netdev rx/tx_dropped counters, hiding the actual drop reason. Add per-peer atomic64_t counters (struct ovpn_peer_estats) for the significant [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1x2bDi-00021j-Ax Subject: [Openvpn-devel] [RFC ovpn net-next 1/2] ovpn: extend statistics with per-peer drop and event counters X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1875437321585150176 X-GMAIL-MSGID: 1875437321585150176 Drops are currently accounted only via the undifferentiated netdev rx/tx_dropped counters, hiding the actual drop reason. Add per-peer atomic64_t counters (struct ovpn_peer_estats) for the significant drop reasons and events in the data path: * RX: decrypt errors, replay errors, unknown key-id, unsupported inner protocol, RPF check failures * TX: encrypt errors, IV exhaustion, missing primary key, missing transport, GSO segmentation errors * events: keepalives received/sent, endpoint floats Only ovpn-specific drop reasons are covered. Generic ENOMEM failures and teardown races are not accounted. Export the counters in a new OVPN_A_PEER_ESTATS nest in the OVPN_CMD_PEER_GET reply and teach ovpn-cli to print them. Signed-off-by: Marco Baffo --- Documentation/netlink/specs/ovpn.yaml | 62 +++++++++++++++++++++ drivers/net/ovpn/io.c | 25 +++++++-- drivers/net/ovpn/netlink-gen.c | 19 ++++++- drivers/net/ovpn/netlink-gen.h | 3 +- drivers/net/ovpn/netlink.c | 37 +++++++++++- drivers/net/ovpn/peer.c | 5 ++ drivers/net/ovpn/peer.h | 2 + drivers/net/ovpn/stats.h | 33 +++++++++++ include/uapi/linux/ovpn.h | 20 +++++++ tools/testing/selftests/net/ovpn/ovpn-cli.c | 60 ++++++++++++++++++++ 10 files changed, 259 insertions(+), 7 deletions(-) diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml index ac50d1d7c00a..dd1bb96c312d 100644 --- a/Documentation/netlink/specs/ovpn.yaml +++ b/Documentation/netlink/specs/ovpn.yaml @@ -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 diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 9a66d693039a..f9379d8c9c08 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -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; } diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c index 92d2fdc17c2e..d427db6c563f 100644 --- a/drivers/net/ovpn/netlink-gen.c +++ b/drivers/net/ovpn/netlink-gen.c @@ -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, }, diff --git a/drivers/net/ovpn/netlink-gen.h b/drivers/net/ovpn/netlink-gen.h index 67cd85f86173..197e4a992d69 100644 --- a/drivers/net/ovpn/netlink-gen.h +++ b/drivers/net/ovpn/netlink-gen.h @@ -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]; diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4dad85294198..9e03b4cefcee 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -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); diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index b0519f9840d8..6235c3d8414a 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -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 diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index dfa5c0037e02..5667bad91c31 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -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; }; /** diff --git a/drivers/net/ovpn/stats.h b/drivers/net/ovpn/stats.h index 3a45b97c0056..8b900e0c1121 100644 --- a/drivers/net/ovpn/stats.h +++ b/drivers/net/ovpn/stats.h @@ -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, diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h index 06690090a1a9..e5ed635c0373 100644 --- a/include/uapi/linux/ovpn.h +++ b/include/uapi/linux/ovpn.h @@ -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, diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index f4effa7580c0..ecac71db769a 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -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; } From patchwork Fri Sep 4 21:12:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5317 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:2190:b0:892:1b45:3040 with SMTP id s16csp4171462mae; Fri, 4 Sep 2026 14:13:26 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBzCeZlC6fJnCkNqKDuXH06sLteeUsaTj+U094QshXTzwda9JJahn5fyiVtAn8e0buT0iaPF2l0EJmI=@openvpn.net X-Received: by 2002:a05:6820:211:b0:6b7:83c5:fdf2 with SMTP id 006d021491bc7-6b783c60319mr3982181eaf.51.1788556405853; Fri, 04 Sep 2026 14:13:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1788556405; cv=none; d=google.com; s=arc-20260327; b=ppPWSP3RK/sKgWeO1rL4dbCjt25PQfBZyxIWoS7IwnWdVrhdrahQHlekXuCqiVfYEg yYRgvP0e4fNdsci4kFxmZoT4+ScgzEYrV3PgZVS1VO/8kWnWkR55wYPn1Q2eYFE7tr+w /GYw0LlRpKNVUNdhP1fEz7S8722WlVCM+Xr/OQY5Ysy9M/ZrB87CqPEYTExndHT1+6iu kSlQcFnCvLs8msyZacWfxZmje0ajEUsKzH4uE8/vLGPod301Idk+VIC2oNT24iZkYQhm pKbByLNDkEVwdYuyHMDM0N6g6YMS2qkbjm7pCODM9vT01AY8k5LijUrWafrPnOj+o4Gm 54Zw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:cc:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=/SjzWWSRy49uu/xCr5MPOakk7Dh8O9Qa21kOww47uew=; fh=BsMg/B0Yb/hS/rzP5Npz4luh0IleZm8REk1XWiWRt2A=; b=EQhTmD5A/F5Wovvd/Mbd9Tm4vtevK14LkvuOy2udsjBAH2/A6pg34JmZZH3HVi9Ym4 LR2kb8w1iSQlLdqZI6PHhcNkyTNgqDKf8kQeFZ15Al42ZAkKqQhzcrAepE5l42ddkN2D 7K/JPITojfD7fALV+NJedVDSAbsPAmWlds4Dsv6nAzUCXe1g8hVDGLg0seOuBsgNJXBb /+so/tOsZczaVZ+ki+7LY0R5rNCc0R/S9OwwMiEHiR/GBvgTbIYZFGLLriespZ/MM4ge CukKVNimGVJ70QPGPWtXGcBioBekxPfzos2lvqoaHAMWuLhF9idC9aLe56E9+Gj34eC1 UXGw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=X9bwwaL6; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=NBqYha10; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=icLCF5vU; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=GZGGrnDd; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 006d021491bc7-6b6dab40d5esi6685143eaf.74.2026.09.04.14.13.25 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 04 Sep 2026 14:13:25 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=X9bwwaL6; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=NBqYha10; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=icLCF5vU; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=GZGGrnDd; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type:Cc: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=/SjzWWSRy49uu/xCr5MPOakk7Dh8O9Qa21kOww47uew=; b=X9bwwaL64osk5AxxtdTHQF/b1B L+vK58JHJUseRgIulXiQe1Bm6VbpJR7E1ot+pNf5YBZ7dd0msurSLRtVi7bUlfmAk5SFZLtmHSNAK ZBFI4GIslHQXtYr/O5yVdsyrWhj1xcgsUj6FACAUg+3x6LOadQhmVThmBkMkqVwcM4/c=; Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1x2bDn-00038Y-Lj; Fri, 04 Sep 2026 21:13:16 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1x2bDl-00038F-Gq for openvpn-devel@lists.sourceforge.net; Fri, 04 Sep 2026 21:13:14 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=BKEMYPnn6Ib64Do8/+QetYOGlwPJ/pqqnzN7w1Gu/oA=; b=NBqYha10WONkm3/WVm3F70Ic48 XP8f2hQlOEFPXsSafdeaH7jDzNPEBKDm/VP8xJJia8gKygmWJdY80V/0NSpL/CCznfWZwhMCXZZ9X zxjAzMRsM1IoCO7yMC2Oz6CZ7wtvOAHjpuvVxdjV9pjbSWOx51ZP5lZyAYAjlosXgN6M=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=BKEMYPnn6Ib64Do8/+QetYOGlwPJ/pqqnzN7w1Gu/oA=; b=icLCF5vU+IHnW5WfwoMg8D5Lh0 jGSgGi5UCMFLZrlyXkJA5CYhWQ74gvFzUwRkUQygqKblfl3J+ptI0ce38ARNINboOWmMKFQjWGdac dPzwujL9wAZFE2TgLsc6+6gyHDV9A2zLluNdyY2+E15hBnHQb8ObULDuoJczmz1kPW8E=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1x2bDl-00021l-0V for openvpn-devel@lists.sourceforge.net; Fri, 04 Sep 2026 21:13:14 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4hc8M53JDgzLm1Z; Fri, 04 Sep 2026 23:13:05 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1788556385; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BKEMYPnn6Ib64Do8/+QetYOGlwPJ/pqqnzN7w1Gu/oA=; b=GZGGrnDdbtVnLoIVICpdueDKSXY1YsLWEHcOYjr/4y9gKvTz09iVc/eUrUvjnrYQfVEqYm 0e7f2DgtqFdoNg7udzG7EgBJ95thSDZAQtXzFfAAg7BTgYfVu28VyWQ4bDH9kuwq2HK1Ff KN+98rhadwQLi1ETgkQ4MV0Yb9RQFxe4t1zc4e0h8P+ZVhDevqxfIPTAa8rL0QhGSMt7Vs DA39128zRtQfxU1nyxXt65QM0Nnaa023L6szHpCkVoftp3/QtB0xNnMzlL25P76E6VxzLc Atfs+3U2sg/eCGGRhzT17Wa7SVa5rvWTIw9qB8k5m1zM5A2+0D4fa4DMsOPwdw== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Fri, 4 Sep 2026 23:12:56 +0200 Message-ID: <20260904211256.2889974-2-marco@mandelbit.com> In-Reply-To: <20260904211256.2889974-1-marco@mandelbit.com> References: <20260904211256.2889974-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4hc8M53JDgzLm1Z X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Detailed per-peer counters are currently available only through netlink. Maintain device-wide copies of the same counters, incremented together with the per-peer ones, and expose them through ethtool. Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1x2bDl-00021l-0V Subject: [Openvpn-devel] [RFC ovpn net-next 2/2] ovpn: export detailed statistics via ethtool X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1875437321823362252 X-GMAIL-MSGID: 1875437321823362252 Detailed per-peer counters are currently available only through netlink. Maintain device-wide copies of the same counters, incremented together with the per-peer ones, and expose them through ethtool. Store the device-wide counters in per-CPU storage and aggregate them when queried, avoiding an additional device-wide atomic update in packet processing paths. The totals remain monotonic across peer deletion and interface down/up. Also count packets dropped because no peer matched the source (RX) or destination (TX) address, and locally generated packets with an invalid protocol. These cannot be attributed to an individual peer and are therefore exported via ethtool only. Signed-off-by: Marco Baffo --- drivers/net/ovpn/io.c | 33 +++++++------ drivers/net/ovpn/main.c | 98 +++++++++++++++++++++++++++++++++++-- drivers/net/ovpn/ovpnpriv.h | 4 ++ drivers/net/ovpn/peer.c | 4 +- drivers/net/ovpn/peer.h | 16 ++++++ drivers/net/ovpn/stats.h | 31 ++++++++++++ drivers/net/ovpn/udp.c | 5 +- 7 files changed, 171 insertions(+), 20 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index f9379d8c9c08..7f3a56d7d1a8 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -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; } diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 168cfe9b59a9..985bd2e56cf4 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -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) diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index 5898f6adada7..6e236880036e 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -15,6 +15,8 @@ #include #include +#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_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 6235c3d8414a..248849ef5c74 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -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 diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 5667bad91c31..027958001174 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -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 diff --git a/drivers/net/ovpn/stats.h b/drivers/net/ovpn/stats.h index 8b900e0c1121..763fe19a4938 100644 --- a/drivers/net/ovpn/stats.h +++ b/drivers/net/ovpn/stats.h @@ -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, diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 7f69e8890b5b..5ee196a8d430 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -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));