Use the new driver API to fetch per-peer link and VPN byte counters
in both client and server modes.
Two usage modes are supported:
- Single peer: pass the peer ID and a fixed-size output buffer. If the
IOCTL is not supported (old driver), fall back to the legacy API.
- All peers: first call the IOCTL with a small output buffer to get
the required size, then allocate a buffer and call again to fetch
stats for all peers.
Change-Id: I525d7300e49f9a5a18e7146ee35ccc2af8184b8a
Signed-off-by: Lev Stipakov <lev@openvpn.net>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20250902122542.31023-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32744.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
| ... | ... |
@@ -30,6 +30,7 @@ |
| 30 | 30 |
#include "forward.h" |
| 31 | 31 |
#include "tun.h" |
| 32 | 32 |
#include "crypto.h" |
| 33 |
+#include "multi.h" |
|
| 33 | 34 |
#include "ssl_common.h" |
| 34 | 35 |
#include "openvpn.h" |
| 35 | 36 |
|
| ... | ... |
@@ -190,6 +191,8 @@ ovpn_dco_init(struct context *c) |
| 190 | 190 |
{
|
| 191 | 191 |
dco_context_t *dco = &c->c1.tuntap->dco; |
| 192 | 192 |
|
| 193 |
+ dco->c = c; |
|
| 194 |
+ |
|
| 193 | 195 |
switch (c->mode) |
| 194 | 196 |
{
|
| 195 | 197 |
case MODE_POINT_TO_POINT: |
| ... | ... |
@@ -714,12 +717,132 @@ dco_do_read(dco_context_t *dco) |
| 714 | 714 |
int |
| 715 | 715 |
dco_get_peer_stats_multi(dco_context_t *dco, const bool raise_sigusr1_on_err) |
| 716 | 716 |
{
|
| 717 |
- /* Not implemented. */ |
|
| 718 |
- return 0; |
|
| 717 |
+ struct gc_arena gc = gc_new(); |
|
| 718 |
+ |
|
| 719 |
+ int ret = 0; |
|
| 720 |
+ struct tuntap *tt = dco->tt; |
|
| 721 |
+ |
|
| 722 |
+ if (!tuntap_defined(tt)) |
|
| 723 |
+ {
|
|
| 724 |
+ ret = -1; |
|
| 725 |
+ goto done; |
|
| 726 |
+ } |
|
| 727 |
+ |
|
| 728 |
+ OVPN_GET_PEER_STATS ps = {
|
|
| 729 |
+ .PeerId = -1 |
|
| 730 |
+ }; |
|
| 731 |
+ |
|
| 732 |
+ DWORD required_size = 0, bytes_returned = 0; |
|
| 733 |
+ /* first, figure out buffer size */ |
|
| 734 |
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), &required_size, sizeof(DWORD), &bytes_returned, NULL)) |
|
| 735 |
+ {
|
|
| 736 |
+ if (GetLastError() == ERROR_MORE_DATA) |
|
| 737 |
+ {
|
|
| 738 |
+ if (bytes_returned != sizeof(DWORD)) |
|
| 739 |
+ {
|
|
| 740 |
+ msg(M_WARN, "%s: invalid bytes returned for size query (%lu, expected %zu)", __func__, bytes_returned, sizeof(DWORD)); |
|
| 741 |
+ ret = -1; |
|
| 742 |
+ goto done; |
|
| 743 |
+ } |
|
| 744 |
+ /* required_size now contains the size written by the driver */ |
|
| 745 |
+ if (required_size == 0) |
|
| 746 |
+ {
|
|
| 747 |
+ ret = 0; /* no peers to process */ |
|
| 748 |
+ goto done; |
|
| 749 |
+ } |
|
| 750 |
+ if (required_size < sizeof(OVPN_PEER_STATS)) |
|
| 751 |
+ {
|
|
| 752 |
+ msg(M_WARN, "%s: invalid required size %lu (minimum %zu)", __func__, required_size, sizeof(OVPN_PEER_STATS)); |
|
| 753 |
+ ret = -1; |
|
| 754 |
+ goto done; |
|
| 755 |
+ } |
|
| 756 |
+ } |
|
| 757 |
+ else |
|
| 758 |
+ {
|
|
| 759 |
+ msg(M_WARN | M_ERRNO, "%s: failed to fetch required buffer size", __func__); |
|
| 760 |
+ ret = -1; |
|
| 761 |
+ goto done; |
|
| 762 |
+ } |
|
| 763 |
+ } |
|
| 764 |
+ else |
|
| 765 |
+ {
|
|
| 766 |
+ /* unexpected success? */ |
|
| 767 |
+ if (bytes_returned == 0) |
|
| 768 |
+ {
|
|
| 769 |
+ ret = 0; /* no peers to process */ |
|
| 770 |
+ goto done; |
|
| 771 |
+ } |
|
| 772 |
+ |
|
| 773 |
+ msg(M_WARN, "%s: first DeviceIoControl call succeeded unexpectedly (%lu bytes returned)", __func__, bytes_returned); |
|
| 774 |
+ ret = -1; |
|
| 775 |
+ goto done; |
|
| 776 |
+ } |
|
| 777 |
+ |
|
| 778 |
+ |
|
| 779 |
+ /* allocate the buffer and fetch stats */ |
|
| 780 |
+ OVPN_PEER_STATS *peer_stats = gc_malloc(required_size, true, &gc); |
|
| 781 |
+ if (!peer_stats) |
|
| 782 |
+ {
|
|
| 783 |
+ msg(M_WARN, "%s: failed to allocate buffer of size %lu", __func__, required_size); |
|
| 784 |
+ ret = -1; |
|
| 785 |
+ goto done; |
|
| 786 |
+ } |
|
| 787 |
+ |
|
| 788 |
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), peer_stats, required_size, &bytes_returned, NULL)) |
|
| 789 |
+ {
|
|
| 790 |
+ /* unlikely case when a peer has been added since fetching buffer size, not an error! */ |
|
| 791 |
+ if (GetLastError() == ERROR_MORE_DATA) |
|
| 792 |
+ {
|
|
| 793 |
+ msg(M_WARN, "%s: peer has been added, skip fetching stats", __func__); |
|
| 794 |
+ ret = 0; |
|
| 795 |
+ goto done; |
|
| 796 |
+ } |
|
| 797 |
+ |
|
| 798 |
+ msg(M_WARN | M_ERRNO, "%s: failed to fetch multipeer stats", __func__); |
|
| 799 |
+ ret = -1; |
|
| 800 |
+ goto done; |
|
| 801 |
+ } |
|
| 802 |
+ |
|
| 803 |
+ /* iterate over stats and update peers */ |
|
| 804 |
+ for (int i = 0; i < bytes_returned / sizeof(OVPN_PEER_STATS); ++i) |
|
| 805 |
+ {
|
|
| 806 |
+ OVPN_PEER_STATS *stat = &peer_stats[i]; |
|
| 807 |
+ |
|
| 808 |
+ if (stat->PeerId >= dco->c->multi->max_clients) |
|
| 809 |
+ {
|
|
| 810 |
+ msg(M_WARN, "%s: received out of bound peer_id %u (max=%u)", __func__, stat->PeerId, |
|
| 811 |
+ dco->c->multi->max_clients); |
|
| 812 |
+ continue; |
|
| 813 |
+ } |
|
| 814 |
+ |
|
| 815 |
+ struct multi_instance *mi = dco->c->multi->instances[stat->PeerId]; |
|
| 816 |
+ if (!mi) |
|
| 817 |
+ {
|
|
| 818 |
+ msg(M_WARN, "%s: received data for a non-existing peer %u", __func__, stat->PeerId); |
|
| 819 |
+ continue; |
|
| 820 |
+ } |
|
| 821 |
+ |
|
| 822 |
+ /* update peer stats */ |
|
| 823 |
+ struct context_2 *c2 = &mi->context.c2; |
|
| 824 |
+ c2->dco_read_bytes = stat->LinkRxBytes; |
|
| 825 |
+ c2->dco_write_bytes = stat->LinkTxBytes; |
|
| 826 |
+ c2->tun_read_bytes = stat->VpnRxBytes; |
|
| 827 |
+ c2->tun_write_bytes = stat->VpnTxBytes; |
|
| 828 |
+ } |
|
| 829 |
+ |
|
| 830 |
+done: |
|
| 831 |
+ gc_free(&gc); |
|
| 832 |
+ |
|
| 833 |
+ if (raise_sigusr1_on_err && ret < 0) |
|
| 834 |
+ {
|
|
| 835 |
+ register_signal(dco->c->sig, SIGUSR1, "dco peer stats error"); |
|
| 836 |
+ } |
|
| 837 |
+ |
|
| 838 |
+ return ret; |
|
| 719 | 839 |
} |
| 720 | 840 |
|
| 721 | 841 |
int |
| 722 |
-dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) |
|
| 842 |
+dco_get_peer_stats_fallback(struct context *c, const bool raise_sigusr1_on_err) |
|
| 723 | 843 |
{
|
| 724 | 844 |
struct tuntap *tt = c->c1.tuntap; |
| 725 | 845 |
|
| ... | ... |
@@ -747,6 +870,48 @@ dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) |
| 747 | 747 |
return 0; |
| 748 | 748 |
} |
| 749 | 749 |
|
| 750 |
+int |
|
| 751 |
+dco_get_peer_stats(struct context *c, const bool raise_sigusr1_on_err) |
|
| 752 |
+{
|
|
| 753 |
+ struct tuntap *tt = c->c1.tuntap; |
|
| 754 |
+ |
|
| 755 |
+ if (!tuntap_defined(tt)) |
|
| 756 |
+ {
|
|
| 757 |
+ return -1; |
|
| 758 |
+ } |
|
| 759 |
+ |
|
| 760 |
+ /* first, try a new ioctl */ |
|
| 761 |
+ OVPN_GET_PEER_STATS ps = { .PeerId = c->c2.tls_multi->dco_peer_id };
|
|
| 762 |
+ |
|
| 763 |
+ OVPN_PEER_STATS peer_stats = { 0 };
|
|
| 764 |
+ DWORD bytes_returned = 0; |
|
| 765 |
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_GET_PEER_STATS, &ps, sizeof(ps), &peer_stats, sizeof(peer_stats), |
|
| 766 |
+ &bytes_returned, NULL)) |
|
| 767 |
+ {
|
|
| 768 |
+ if (GetLastError() == ERROR_INVALID_FUNCTION) |
|
| 769 |
+ {
|
|
| 770 |
+ /* are we using the old driver? */ |
|
| 771 |
+ return dco_get_peer_stats_fallback(c, raise_sigusr1_on_err); |
|
| 772 |
+ } |
|
| 773 |
+ |
|
| 774 |
+ msg(M_WARN | M_ERRNO, "%s: DeviceIoControl(OVPN_IOCTL_GET_PEER_STATS) failed", __func__); |
|
| 775 |
+ return -1; |
|
| 776 |
+ } |
|
| 777 |
+ |
|
| 778 |
+ if (bytes_returned != sizeof(OVPN_PEER_STATS)) |
|
| 779 |
+ {
|
|
| 780 |
+ msg(M_WARN | M_ERRNO, "%s: DeviceIoControl(OVPN_IOCTL_GET_PEER_STATS) returned invalid size", __func__); |
|
| 781 |
+ return -1; |
|
| 782 |
+ } |
|
| 783 |
+ |
|
| 784 |
+ c->c2.dco_read_bytes = peer_stats.LinkRxBytes; |
|
| 785 |
+ c->c2.dco_write_bytes = peer_stats.LinkTxBytes; |
|
| 786 |
+ c->c2.tun_read_bytes = peer_stats.VpnRxBytes; |
|
| 787 |
+ c->c2.tun_write_bytes = peer_stats.VpnTxBytes; |
|
| 788 |
+ |
|
| 789 |
+ return 0; |
|
| 790 |
+} |
|
| 791 |
+ |
|
| 750 | 792 |
void |
| 751 | 793 |
dco_event_set(dco_context_t *dco, struct event_set *es, void *arg) |
| 752 | 794 |
{
|
| ... | ... |
@@ -83,6 +83,14 @@ typedef struct _OVPN_STATS {
|
| 83 | 83 |
LONG64 TunBytesReceived; |
| 84 | 84 |
} OVPN_STATS, * POVPN_STATS; |
| 85 | 85 |
|
| 86 |
+typedef struct _OVPN_PEER_STATS {
|
|
| 87 |
+ int PeerId; |
|
| 88 |
+ LONG64 LinkRxBytes; |
|
| 89 |
+ LONG64 LinkTxBytes; |
|
| 90 |
+ LONG64 VpnRxBytes; |
|
| 91 |
+ LONG64 VpnTxBytes; |
|
| 92 |
+} OVPN_PEER_STATS, * POVPN_PEER_STATS; |
|
| 93 |
+ |
|
| 86 | 94 |
typedef enum _OVPN_KEY_SLOT {
|
| 87 | 95 |
OVPN_KEY_SLOT_PRIMARY, |
| 88 | 96 |
OVPN_KEY_SLOT_SECONDARY |
| ... | ... |
@@ -185,6 +193,10 @@ typedef struct _OVPN_MP_IROUTE {
|
| 185 | 185 |
int IPv6; |
| 186 | 186 |
} OVPN_MP_IROUTE, * POVPN_MP_IROUTE; |
| 187 | 187 |
|
| 188 |
+typedef struct _OVPN_GET_PEER_STATS {
|
|
| 189 |
+ int PeerId; // -1 for all peers stats |
|
| 190 |
+} OVPN_GET_PEER_STATS, * POVPN_GET_PEER_STATS; |
|
| 191 |
+ |
|
| 188 | 192 |
#define OVPN_IOCTL_NEW_PEER CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, FILE_ANY_ACCESS) |
| 189 | 193 |
#define OVPN_IOCTL_GET_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) |
| 190 | 194 |
#define OVPN_IOCTL_NEW_KEY CTL_CODE(FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_ANY_ACCESS) |
| ... | ... |
@@ -207,3 +219,5 @@ typedef struct _OVPN_MP_IROUTE {
|
| 207 | 207 |
|
| 208 | 208 |
#define OVPN_IOCTL_MP_ADD_IROUTE CTL_CODE(FILE_DEVICE_UNKNOWN, 17, METHOD_BUFFERED, FILE_ANY_ACCESS) |
| 209 | 209 |
#define OVPN_IOCTL_MP_DEL_IROUTE CTL_CODE(FILE_DEVICE_UNKNOWN, 18, METHOD_BUFFERED, FILE_ANY_ACCESS) |
| 210 |
+ |
|
| 211 |
+#define OVPN_IOCTL_GET_PEER_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 19, METHOD_BUFFERED, FILE_ANY_ACCESS) |