-
Notifications
You must be signed in to change notification settings - Fork 145
/
AndroidPacketCapture.cs
358 lines (302 loc) · 11.2 KB
/
AndroidPacketCapture.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
using System.Net;
using System.Net.Sockets;
using Android;
using Android.Content;
using Android.Content.PM;
using Android.Net;
using Android.OS;
using Android.Runtime;
using Java.IO;
using Java.Net;
using Microsoft.Extensions.Logging;
using PacketDotNet;
using VpnHood.Common.Logging;
using VpnHood.Common.Net;
using VpnHood.Common.Utils;
using ProtocolType = PacketDotNet.ProtocolType;
using Socket = System.Net.Sockets.Socket;
namespace VpnHood.Client.Device.Droid;
[Service(
Permission = Manifest.Permission.BindVpnService,
Exported = true,
ForegroundServiceType = ForegroundService.TypeSystemExempted)]
[IntentFilter(["android.net.VpnService"])]
public class AndroidPacketCapture : VpnService, IPacketCapture
{
public const string VpnServiceName = "VhSession";
private IPAddress[]? _dnsServers;
private FileInputStream? _inStream; // Packets to be sent are queued in this input stream.
private ParcelFileDescriptor? _mInterface;
private int _mtu;
private FileOutputStream? _outStream; // Packets received need to be written to this output stream.
private readonly ConnectivityManager? _connectivityManager = ConnectivityManager.FromContext(Application.Context);
internal static TaskCompletionSource<AndroidPacketCapture>? StartServiceTaskCompletionSource { get; set; }
public event EventHandler<PacketReceivedEventArgs>? PacketReceivedFromInbound;
public event EventHandler? Stopped;
public bool Started => _mInterface != null;
private bool _isServiceStarted;
public IpNetwork[]? IncludeNetworks { get; set; }
public bool CanSendPacketToOutbound => false;
public bool CanExcludeApps => true;
public bool CanIncludeApps => true;
public string[]? ExcludeApps { get; set; }
public string[]? IncludeApps { get; set; }
public bool IsMtuSupported => true;
public int Mtu {
get => _mtu;
set {
if (Started)
throw new InvalidOperationException("Could not set MTU while PacketCapture is started.");
_mtu = value;
}
}
public bool IsAddIpV6AddressSupported => true;
public bool AddIpV6Address { get; set; }
public bool IsDnsServersSupported => true;
public IPAddress[]? DnsServers {
get => _dnsServers;
set {
if (Started)
throw new InvalidOperationException(
$"Could not set {nameof(DnsServers)} while {nameof(IPacketCapture)} is started.");
_dnsServers = value;
}
}
public void StartCapture()
{
var builder = new Builder(this)
.SetBlocking(true)
.SetSession(VpnServiceName)
.AddAddress("192.168.199.188", 24);
if (OperatingSystem.IsAndroidVersionAtLeast(29))
builder.SetMetered(false);
if (AddIpV6Address)
builder.AddAddress("fd00::1000", 64);
// MTU
if (Mtu != 0)
builder.SetMtu(Mtu);
// DNS Servers
AddDnsServers(builder);
// Routes
AddRoutes(builder);
// AppFilter
AddAppFilter(builder);
// try to establish the connection
_mInterface = builder.Establish() ?? throw new Exception("Could not establish VpnService.");
//Packets to be sent are queued in this input stream.
_inStream = new FileInputStream(_mInterface.FileDescriptor);
//Packets received need to be written to this output stream.
_outStream = new FileOutputStream(_mInterface.FileDescriptor);
Task.Run(ReadingPacketTask);
}
public void SendPacketToInbound(IPPacket ipPacket)
{
_outStream?.Write(ipPacket.Bytes);
}
public void SendPacketToInbound(IList<IPPacket> ipPackets)
{
// ReSharper disable once ForCanBeConvertedToForeach
for (var i = 0; i < ipPackets.Count; i++) {
var ipPacket = ipPackets[i];
_outStream?.Write(ipPacket.Bytes);
}
}
public void SendPacketToOutbound(IList<IPPacket> ipPackets)
{
throw new NotSupportedException();
}
public void SendPacketToOutbound(IPPacket ipPacket)
{
throw new NotSupportedException();
}
public bool CanProtectSocket => true;
public void ProtectSocket(Socket socket)
{
if (!Protect(socket.Handle.ToInt32()))
throw new Exception("Could not protect socket!");
}
public void StopCapture()
{
VhLogger.Instance.LogTrace("Stopping VPN Service...");
StopVpnService();
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent? intent,
[GeneratedEnum] StartCommandFlags flags, int startId)
{
// close Vpn if it is already started
CloseVpn();
// post vpn service start command
try {
AndroidDevice.Instance.OnServiceStartCommand(this, intent);
}
catch (Exception ex) {
StartServiceTaskCompletionSource?.TrySetException(ex);
StopVpnService();
return StartCommandResult.NotSticky;
}
// signal start command
if (intent?.Action == "connect")
StartServiceTaskCompletionSource?.TrySetResult(this);
_isServiceStarted = true;
return StartCommandResult.Sticky;
}
private void AddRoutes(Builder builder)
{
var includeNetworks = IncludeNetworks ?? IpNetwork.All;
foreach (var network in includeNetworks)
builder.AddRoute(network.Prefix.ToString(), network.PrefixLength);
}
private void AddDnsServers(Builder builder)
{
var dnsServers = VhUtil.IsNullOrEmpty(DnsServers) ? IPAddressUtil.GoogleDnsServers : DnsServers;
if (!AddIpV6Address)
dnsServers = dnsServers.Where(x => x.AddressFamily == AddressFamily.InterNetwork).ToArray();
foreach (var dnsServer in dnsServers)
builder.AddDnsServer(dnsServer.ToString());
}
private void AddAppFilter(Builder builder)
{
// Applications Filter
if (IncludeApps != null) {
// make sure to add current app if an allowed app exists
var packageName = ApplicationContext?.PackageName ??
throw new Exception("Could not get the app PackageName!");
builder.AddAllowedApplication(packageName);
// add user apps
foreach (var app in IncludeApps.Where(x => x != packageName))
try {
builder.AddAllowedApplication(app);
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Could not add an allowed app. App: {app}", app);
}
}
if (ExcludeApps != null) {
var packageName = ApplicationContext?.PackageName ??
throw new Exception("Could not get the app PackageName!");
foreach (var app in ExcludeApps.Where(x => x != packageName))
try {
builder.AddDisallowedApplication(app);
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Could not add a disallowed app. App: {app}", app);
}
}
}
private Task ReadingPacketTask()
{
if (_inStream == null)
throw new ArgumentNullException(nameof(_inStream));
try {
var buf = new byte[short.MaxValue];
int read;
while (!_isClosing && (read = _inStream.Read(buf)) > 0) {
var packetBuffer = buf[..read]; // copy buffer for packet
var ipPacket = Packet.ParsePacket(LinkLayers.Raw, packetBuffer)?.Extract<IPPacket>();
if (ipPacket != null)
ProcessPacket(ipPacket);
}
}
catch (ObjectDisposedException) {
}
catch (Exception ex) {
if (!VhUtil.IsSocketClosedException(ex))
VhLogger.Instance.LogError(ex, "Error occurred in Android ReadingPacketTask.");
}
StopVpnService();
return Task.FromResult(0);
}
public bool? IsInProcessPacket(ProtocolType protocol, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
{
var localAddress = new InetSocketAddress(InetAddress.GetByAddress(localEndPoint.Address.GetAddressBytes()),
localEndPoint.Port);
var remoteAddress = new InetSocketAddress(InetAddress.GetByAddress(remoteEndPoint.Address.GetAddressBytes()),
remoteEndPoint.Port);
// Android 9 and below
if (!OperatingSystem.IsAndroidVersionAtLeast(29))
return false; //not supported
// Android 10 and above
var uid = _connectivityManager?.GetConnectionOwnerUid((int)protocol, localAddress, remoteAddress);
return uid == Process.MyUid();
}
protected virtual void ProcessPacket(IPPacket ipPacket)
{
try {
PacketReceivedFromInbound?.Invoke(this,
new PacketReceivedEventArgs([ipPacket], this));
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Error in processing packet. Packet: {Packet}",
VhLogger.FormatIpPacket(ipPacket.ToString()!));
}
}
public override void OnDestroy()
{
VhLogger.Instance.LogTrace("VpnService has been destroyed!");
CloseVpn();
base.OnDestroy();
}
private bool _isClosing;
private void CloseVpn()
{
if (_mInterface == null || _isClosing) return;
_isClosing = true;
VhLogger.Instance.LogTrace("Closing VpnService...");
// close streams
try {
_inStream?.Dispose();
_outStream?.Dispose();
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Error while closing the VpnService streams.");
}
// close VpnService
try {
_mInterface?.Close(); //required to close the vpn. dispose is not enough
_mInterface?.Dispose();
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Error while closing the VpnService.");
}
finally {
_mInterface = null;
}
try {
Stopped?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Error while invoking Stopped event.");
}
_isClosing = false;
}
private void StopVpnService()
{
// make sure to close vpn; it has self check
CloseVpn();
// close the service
if (!_isServiceStarted) return;
_isServiceStarted = false;
try {
// it must be after _mInterface.Close
if (OperatingSystem.IsAndroidVersionAtLeast(24))
StopForeground(StopForegroundFlags.Remove);
else
StopForeground(true);
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Error in StopForeground of VpnService.");
}
try {
StopSelf();
}
catch (Exception ex) {
VhLogger.Instance.LogError(ex, "Error in StopSelf of VpnService.");
}
}
void IDisposable.Dispose()
{
// The parent should not be disposed, never call parent dispose
StopVpnService();
}
}