/* * Copyright (c) 2025 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System; using System.Collections.Generic; using System.IO; using System.IO.Pipes; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using ProtonVPN.Common.Core.Networking; using ProtonVPN.Configurations.Contracts; namespace ProtonVPN.Vpn.WireGuard; public class WintunTrafficManager : IWintunTrafficManager { private readonly string _pipeName; private StreamReader? _reader; private NamedPipeClientStream? _stream; public WintunTrafficManager(IStaticConfiguration config) { _pipeName = config.WireGuard.PipeName; } public async IAsyncEnumerable WatchTrafficAsync([EnumeratorCancellation] CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { await using (IAsyncEnumerator enumerator = WatchOnceAsync(cancellationToken).GetAsyncEnumerator(cancellationToken)) { while (true) { bool hasNext; try { hasNext = await enumerator.MoveNextAsync(); } catch (OperationCanceledException) { throw; } catch { // ignored; retry outer loop break; } if (!hasNext) { break; } yield return enumerator.Current; } } try { await Task.Delay(1000, cancellationToken); } catch (OperationCanceledException) { yield break; } } } private async IAsyncEnumerable WatchOnceAsync([EnumeratorCancellation] CancellationToken cancellationToken) { await ConnectToPipeAsync(cancellationToken); try { while (_stream != null && _stream.IsConnected && !cancellationToken.IsCancellationRequested) { byte[] bytes = Encoding.UTF8.GetBytes("get=1\n\n"); await _stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); ulong rx = 0, tx = 0; while (true) { if (_reader == null) { break; } string? line = await _reader.ReadLineAsync(cancellationToken); if (line == null) { break; } line = line.Trim(); if (line.Length == 0) { break; } if (line.StartsWith("rx_bytes=")) { rx += ulong.Parse(line.Substring(9)); } else if (line.StartsWith("tx_bytes=")) { tx += ulong.Parse(line.Substring(9)); } yield return new NetworkTraffic(rx, tx); } await Task.Delay(1000, cancellationToken); } } finally { _reader?.Dispose(); _reader = null; _stream?.Dispose(); _stream = null; } } private async Task ConnectToPipeAsync(CancellationToken cancellationToken) { while (true) { try { _stream = new NamedPipeClientStream(_pipeName); await _stream.ConnectAsync(cancellationToken); _reader = new StreamReader(_stream); break; } catch (OperationCanceledException) { throw; } catch { // ignored } await Task.Delay(1000, cancellationToken); } } }