forked from vpnhood/VpnHood
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPingProxy.cs
109 lines (88 loc) · 4.2 KB
/
PingProxy.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
using System.Net.NetworkInformation;
using PacketDotNet;
using PacketDotNet.Utils;
using VpnHood.Common.Collections;
using VpnHood.Common.Utils;
namespace VpnHood.Tunneling;
public class PingProxy : ITimeoutItem
{
private readonly Ping _ping = new();
private readonly SemaphoreSlim _finishSemaphore = new(1, 1);
public DateTime LastUsedTime { get; set; } = DateTime.MinValue;
public bool IsBusy { get; private set; }
public void Cancel() => _ping.SendAsyncCancel();
public TimeSpan IcmpTimeout { get; set; } = TimeSpan.FromSeconds(30);
public bool Disposed { get; private set; }
public async Task<IPPacket> Send(IPPacket ipPacket)
{
if (ipPacket is null) throw new ArgumentNullException(nameof(ipPacket));
try
{
await _finishSemaphore.WaitAsync();
IsBusy = true;
LastUsedTime = FastDateTime.Now;
return ipPacket.Version == IPVersion.IPv4
? await SendIpV4(ipPacket.Extract<IPv4Packet>())
: await SendIpV6(ipPacket.Extract<IPv6Packet>());
}
finally
{
IsBusy = false;
_finishSemaphore.Release();
}
}
private async Task<IPPacket> SendIpV4(IPv4Packet ipPacket)
{
if (ipPacket is null) throw new ArgumentNullException(nameof(ipPacket));
if (ipPacket.Protocol != ProtocolType.Icmp)
throw new InvalidOperationException($"Packet is not {ProtocolType.Icmp}! Packet: {PacketUtil.Format(ipPacket)}");
var icmpPacket = PacketUtil.ExtractIcmp(ipPacket);
if (icmpPacket.TypeCode != IcmpV4TypeCode.EchoRequest)
throw new InvalidOperationException($"The icmp is not {IcmpV4TypeCode.EchoRequest}! Packet: {PacketUtil.Format(ipPacket)}");
var noFragment = (ipPacket.FragmentFlags & 0x2) != 0;
var pingOptions = new PingOptions(ipPacket.TimeToLive - 1, noFragment);
var pingReply = await _ping.SendPingAsync(ipPacket.DestinationAddress, (int)IcmpTimeout.TotalMilliseconds, icmpPacket.Data, pingOptions);
if (pingReply.Status != IPStatus.Success)
throw new Exception($"Ping Reply has been failed! Status: {pingReply.Status}");
icmpPacket.TypeCode = IcmpV4TypeCode.EchoReply;
icmpPacket.Data = pingReply.Buffer;
ipPacket.DestinationAddress = ipPacket.SourceAddress;
ipPacket.SourceAddress = pingReply.Address;
ipPacket.ParentPacket = icmpPacket;
PacketUtil.UpdateIpPacket(ipPacket);
return ipPacket;
}
private async Task<IPPacket> SendIpV6(IPv6Packet ipPacket)
{
if (ipPacket is null) throw new ArgumentNullException(nameof(ipPacket));
if (ipPacket.Protocol != ProtocolType.IcmpV6)
throw new InvalidOperationException($"Packet is not {ProtocolType.IcmpV6}!");
// We should not use Task due its stack usage, this method is called by many session each many times!
var icmpPacket = PacketUtil.ExtractIcmpV6(ipPacket);
if (icmpPacket.Type != IcmpV6Type.EchoRequest)
throw new InvalidOperationException($"The icmp is not {IcmpV6Type.EchoRequest}! Packet: {PacketUtil.Format(ipPacket)}");
var pingOptions = new PingOptions(ipPacket.TimeToLive - 1, true);
var pingData = icmpPacket.Bytes[8..];
var pingReply = await _ping.SendPingAsync(ipPacket.DestinationAddress, (int)IcmpTimeout.TotalMilliseconds, pingData, pingOptions);
// IcmpV6 packet generation is not fully implemented by packetNet
// So create all packet in buffer
icmpPacket.Type = IcmpV6Type.EchoReply;
icmpPacket.Code = 0;
var buffer = new byte[pingReply.Buffer.Length + 8];
Array.Copy(icmpPacket.Bytes, 0, buffer, 0, 8);
Array.Copy(pingReply.Buffer, 0, buffer, 8, pingReply.Buffer.Length);
icmpPacket = new IcmpV6Packet(new ByteArraySegment(buffer));
ipPacket.DestinationAddress = ipPacket.SourceAddress;
ipPacket.SourceAddress = pingReply.Address;
ipPacket.ParentPacket = icmpPacket;
PacketUtil.UpdateIpPacket(ipPacket);
return ipPacket;
}
public void Dispose()
{
if (!Disposed)
return;
Disposed = true;
_ping.Dispose();
}
}