MacOS Swift 应用程序中不正确的 VPN 数据统计信息

Incorrect VPN Data Statistics in MacOS Swift App

提问人:narner 提问时间:11/16/2023 最后编辑:narner 更新时间:11/19/2023 访问量:123

问:

我正在尝试在使用 TunnelKit 创建与 VPN 的 WireGuard 连接的 MacOS Swift 应用程序中获取 Rx(接收的数据包字节数)和 Tx(传输的数据包字节数)。

我已经确认我已连接到我的 VPN 并正在获取数据。 但是,我根本没有取回 Rx 和 Tx 值 - 这是我的代码;我正在使用 SystemConfiguration 框架尝试获取连接的 VPN 的数据。

这是我尝试通过框架获取VPN统计数据的地方SystemConfiguration

#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>

NS_ASSUME_NONNULL_BEGIN

@interface VPNConnectionStatisticsManager : NSObject

- (SCNetworkConnectionRef _Nullable)initializeVPNConnectionWithServiceID:(NSString *)serviceID;

@end

NS_ASSUME_NONNULL_END


#import <Foundation/Foundation.h>
#import "VPNConnectionStatisticsManager.h"

@implementation VPNConnectionStatisticsManager

- (SCNetworkConnectionRef _Nullable)initializeVPNConnectionWithServiceID:(NSString *)serviceID {
    SCNetworkConnectionContext context = {0, NULL, NULL, NULL, NULL};
    SCNetworkConnectionRef connection = SCNetworkConnectionCreateWithServiceID(NULL, (__bridge CFStringRef)serviceID, ConnectionStatusChanged, &context);
    return connection;
}


static void ConnectionStatusChanged(SCNetworkConnectionRef connection, SCNetworkConnectionStatus status, void *info) {
    NSLog(@"VPN status changed: %d", (int)status);
}

@end

我更改了我的代码,以确保不会在每次调用时重新创建它。还包括我的视图代码,该代码在上下文中显示了这一点:VPNConnectionStatisticsManager

class VPNStatusViewModel: ObservableObject {
    var vpnStatusRefreshTimer: Timer?
    var vpnConnection: SCNetworkConnection?
    private let vpnConnectionStatisticsManager = VPNConnectionStatisticsManager()

    func initializeVPNConnection() {
        if let serviceID = findMeterVPNServiceID(),
           let vpnConnectionRef = vpnConnectionStatisticsManager.initializeVPNConnection(withServiceID: serviceID) {
            vpnConnection = vpnConnectionRef.takeRetainedValue()
        }
    }

    func getVPNData() -> (rx: String, tx: String) {
        var rx: String = "N/A"
        var tx: String = "N/A"
        
        if let vpnConnection = vpnConnection,
           let statsDict = SCNetworkConnectionCopyStatistics(vpnConnection) as? [String: Any],
           let vpnDict = statsDict["VPN"] as? [String: Any] {
            if let rxInt = vpnDict["BytesIn"] as? Int, let txInt = vpnDict["BytesOut"] as? Int {
                let formatter = ByteCountFormatter()
                formatter.allowedUnits = [.useBytes, .useKB, .useMB]
                rx = formatter.string(fromByteCount: Int64(rxInt))
                tx = formatter.string(fromByteCount: Int64(txInt))
            }
        }
        
        return (rx, tx)
    }

    func stopVPNStatusTimer() {
        vpnStatusRefreshTimer?.invalidate()
        vpnStatusRefreshTimer = nil
    }

    func releaseVPNConnection() {
        vpnConnection = nil
    }
}

struct VPNStatusPane: View {
    @ObservedObject var appDelegate: AppDelegate
    @StateObject private var viewModel = VPNStatusViewModel()
    @State var connectedSince: String
    @State var rxBytes: String = "0"
    @State var txBytes: String = "0"

    init(appDelegate: AppDelegate) {
        self.appDelegate = appDelegate
        self._connectedSince = State(initialValue: appDelegate.vpnConnectedSince)
    }

    var body: some View {
        VStack(alignment: .leading) {
            VStack(alignment: .leading, spacing: 10) {
                HypeneatedText(label: "Rx bytes", value: rxBytes)
                HypeneatedText(label: "Tx bytes", value: txBytes)
                HypeneatedText(label: "Connected for", value: connectedSince)
            }
            .padding(.top, 15)

            Spacer()
        }
        .onAppear {
            viewModel.initializeVPNConnection()
            updateVPNData()
            startVPNStatusTimer()
        }
        .onDisappear {
            viewModel.stopVPNStatusTimer()
            viewModel.releaseVPNConnection()
        }
        .frame(maxWidth: .infinity, alignment: .topLeading)
    }

    private func startVPNStatusTimer() {
        viewModel.vpnStatusRefreshTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [self] _ in
            if self.appDelegate.vpnIsConnected {
                self.appDelegate.updateConnectionTime()
            }
            self.connectedSince = self.appDelegate.vpnConnectedSince
            self.updateVPNData()
        }
    }

    private func updateVPNData() {
        let vpnData = viewModel.getVPNData()
        rxBytes = vpnData.rx
        txBytes = vpnData.tx
    }
}
Swift macOS WireGuard 系统配置

评论

1赞 Alexander 11/16/2023
看起来您在每次调用 时都会创建一个新的。这可能是问题的一部分。难道您不需要一个长时间运行的对象来记录连接整个生命周期的统计信息吗?VPNConnectionStatisticsManagergetVPNData()
0赞 narner 11/16/2023
哦,天哪,你介意写一个答案吗?这可能是问题的一部分
0赞 narner 11/16/2023
啊;我刚刚试过 - 将按问题更新
1赞 Alexander 11/16/2023
你能把你所有的代码调到一个最终的Q中吗?编辑历史记录已单独跟踪,您无需在其正文中列出问题的历史记录。另外,请用保护条款翻转那些厄运金字塔 matteomanferdini.com/swift-guard
0赞 narner 11/16/2023
@Alexander刚刚合并;谢谢

答:

-1赞 Stephen de Jager 11/18/2023 #1

其目的似乎是讨论或审查一段与 VPNConnectionStatisticsManager 类相关的 Objective-C 代码;但是,您的消息中尚未提供实际代码。为了以一种有用的方式进行,我将根据您的描述描述如何实现此类和方法。

在 Objective-C 中,一个名为 VPNConnectionStatisticsManager 的类可能会在头文件 (.h) 中声明,如下所示:

VPNConnectionStatisticsManager.h

#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>

@interface VPNConnectionStatisticsManager : NSObject

- (SCNetworkConnectionRef)initializeVPNConnectionWithServiceID:(NSString \*)serviceID;

@end

实现文件 (.m) 中的实现可能如下所示:

VPNConnectionStatisticsManager.m(VPNConnectionStatisticsManager.m)

#import "VPNConnectionStatisticsManager.h"

@implementation VPNConnectionStatisticsManager

- (SCNetworkConnectionRef)initializeVPNConnectionWithServiceID:(NSString \*)serviceID {
    // Assume that serviceID is already a valid and properly formatted string.

    CFStringRef serviceIDRef = (\__bridge CFStringRef)(serviceID);

    SCNetworkConnectionContext context = {0, (\__bridge void \*)(self), NULL, NULL, NULL};

    SCNetworkConnectionRef connectionRef = SCNetworkConnectionCreateWithServiceID(NULL, serviceIDRef, ConnectionStatusChanged, \&context);

    // Here, you might want to add additional setup if necessary
    // ...

    return connectionRef;
}

// It is important to define the correct signature for the callback function.
// The actual implementation may log the status change or act upon it.

void ConnectionStatusChanged(SCNetworkConnectionRef connection, SCNetworkConnectionStatus status, void \*info) {
    NSLog(@"VPN Connection Status Changed: %d", status);

    // Additional logic to handle status change can be implemented here
    // ...
}

@end

此基本实现仅演示如何使用 SCNetworkConnectionCreateWithServiceID 函数创建类方法来初始化 VPN 连接,以及如何使用回调函数记录连接状态更改。

请记住:

  • 错误检查至关重要,但为了简洁起见,此处省略了错误检查。
  • 需要管理返回的 SCNetworkConnectionRef 的生命周期(例如,完成后调用 CFRelease)。
  • 根据要求,VPN 状态更改的实际日志记录和处理应该更加可靠。
  • 若要在实际应用程序中使用它,应遵循内存管理的最佳实践,尤其是在未使用自动引用计数 (ARC) 的情况下。

如果您有实际代码或有关实现的具体疑虑或问题,请分享以获得更准确和详细的回复。