forked from vpnhood/VpnHood
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Http01ChallengeService.cs
115 lines (98 loc) · 3.87 KB
/
Http01ChallengeService.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
using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Logging;
using VpnHood.Common.Logging;
using VpnHood.Common.Utils;
using VpnHood.Tunneling;
using VpnHood.Tunneling.Utils;
namespace VpnHood.Server;
public class Http01ChallengeService(IPAddress[] ipAddresses, string token, string keyAuthorization, TimeSpan timeout) : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new(timeout);
private readonly List<TcpListener> _tcpListeners = [];
private bool _disposed;
public bool IsStarted { get; private set; }
public void Start()
{
if (IsStarted) throw new InvalidOperationException("The HTTP-01 Challenge Service has already been started.");
if (_disposed) throw new ObjectDisposedException(nameof(Http01ChallengeService));
try
{
IsStarted = true;
foreach (var ipAddress in ipAddresses)
{
var ipEndPoint = new IPEndPoint(ipAddress, 80);
VhLogger.Instance.LogInformation("HTTP-01 Challenge Listener starting on {EndPoint}", ipEndPoint);
var listener = new TcpListener(ipEndPoint);
listener.Start();
_tcpListeners.Add(listener);
_ = AcceptTcpClient(listener, _cancellationTokenSource.Token);
}
}
catch
{
Stop();
throw;
}
}
private async Task AcceptTcpClient(TcpListener tcpListener, CancellationToken cancellationToken)
{
while (IsStarted && !cancellationToken.IsCancellationRequested)
{
using var client = await tcpListener.AcceptTcpClientAsync().VhConfigureAwait();
try
{
await HandleRequest(client, token, keyAuthorization, cancellationToken).VhConfigureAwait();
}
catch (Exception ex)
{
VhLogger.Instance.LogError(GeneralEventId.DnsChallenge, ex, "Could not process the HTTP-01 challenge.");
}
}
}
private static async Task HandleRequest(TcpClient client, string token, string keyAuthorization, CancellationToken cancellationToken)
{
await using var stream = client.GetStream();
var headers = await HttpUtil.ParseHeadersAsync(stream, cancellationToken).VhConfigureAwait()
?? throw new Exception("Connection has been closed before receiving any request.");
if (!headers.Any()) return;
var request = headers[HttpUtil.HttpRequestKey];
var requestParts = request.Split(' ');
var expectedUrl = $"/.well-known/acme-challenge/{token}";
var isMatched = requestParts.Length > 1 && requestParts[0] == "GET" && requestParts[1] == expectedUrl;
VhLogger.Instance.LogInformation(GeneralEventId.DnsChallenge, "HTTP Challenge. Request: {request}, IsMatched: {isMatched}", request, isMatched);
var response = (isMatched)
? HttpResponseBuilder.Http01(keyAuthorization)
: HttpResponseBuilder.NotFound();
await stream.WriteAsync(response, 0, response.Length, cancellationToken).VhConfigureAwait();
await stream.FlushAsync(cancellationToken).VhConfigureAwait();
}
// use dispose
private void Stop()
{
if (!IsStarted || _disposed)
return;
foreach (var listener in _tcpListeners)
{
try
{
listener.Stop();
}
catch (Exception ex)
{
VhLogger.Instance.LogError(ex, "Could not stop HTTP-01 Challenge Listener. {EndPoint}", listener.LocalEndpoint);
}
}
_tcpListeners.Clear();
_cancellationTokenSource.Cancel();
IsStarted = false;
}
public void Dispose()
{
if (_disposed)
return;
if (IsStarted)
Stop();
_disposed = true;
}
}