Skip to content

Commit 85e99d6

Browse files
committed
Lib now dynamically accepts HTTPS clients and HTTP clients on a single port
1 parent 8a3244c commit 85e99d6

File tree

4 files changed

+43
-64
lines changed

4 files changed

+43
-64
lines changed

CitadelCore/CitadelCore.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
<PackageIconUrl />
1313
<RepositoryUrl>https://github.com/TechnikEmpire/CitadelCore</RepositoryUrl>
1414
<PackageTags>proxy filtering content-filtering transparent-proxy</PackageTags>
15-
<PackageReleaseNotes>Fixes an issue where accessing content-type header could cause a null-ref exception.</PackageReleaseNotes>
15+
<PackageReleaseNotes>Now dynamically handles HTTPS and HTTP streams on a single bound port. Clients no longer need to guess if a non-standard port connection is HTTP or HTTPS, the engine will figure this out itself.</PackageReleaseNotes>
1616
<Title>CitadelCore</Title>
1717
<Summary>Transparent filtering HTTP/S and Websocket/WebsocketSecure proxy.</Summary>
1818
<Description>Transparent filtering HTTP/S and Websocket/WebsocketSecure proxy.</Description>
19-
<Version>3.6.2</Version>
20-
<AssemblyVersion>3.6.2.0</AssemblyVersion>
21-
<FileVersion>3.6.2.0</FileVersion>
19+
<Version>3.7.0</Version>
20+
<AssemblyVersion>3.7.0.0</AssemblyVersion>
21+
<FileVersion>3.7.0.0</FileVersion>
2222
</PropertyGroup>
2323

2424
<ItemGroup Label="dotnet pack instructions">

CitadelCore/Net/ConnectionAdapters/TlsSniConnectionAdapter.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ namespace CitadelCore.Net.ConnectionAdapters
3030
/// </summary>
3131
internal class TlsSniConnectionAdapter : IConnectionAdapter
3232
{
33-
public bool IsHttps => true;
33+
public bool IsHttps
34+
{
35+
get;
36+
private set;
37+
}
3438

3539
/// <summary>
3640
/// Holds our certificate store. This is responsible for spoofing, storing and retrieving TLS certificates.
@@ -78,15 +82,17 @@ private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterC
7882
{
7983
// We start off by handing the connection stream off to a library that can do a peek
8084
// read (which is really just doing buffering tricks, not an actual peek read).
81-
var yourClientStream = new CustomBufferedStream(context.ConnectionStream, 4096);
85+
var clientStream = new CustomBufferedStream(context.ConnectionStream, 4096);
8286

8387
// We then use the same lib to parse the "peeked" data and extract the SNI hostname.
84-
var clientSslHelloInfo = await SslTools.PeekClientHello(yourClientStream);
88+
var clientSslHelloInfo = await SslTools.PeekClientHello(clientStream);
8589

8690
switch (clientSslHelloInfo != null)
8791
{
8892
case true:
8993
{
94+
IsHttps = true;
95+
9096
string sniHost = clientSslHelloInfo.Extensions?.FirstOrDefault(x => x.Name == "server_name")?.Data;
9197

9298
if (string.IsNullOrEmpty(sniHost) || string.IsNullOrWhiteSpace(sniHost))
@@ -96,7 +102,7 @@ private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterC
96102

97103
try
98104
{
99-
var sslStream = new SslStream(yourClientStream, true,
105+
var sslStream = new SslStream(clientStream, true,
100106
(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) =>
101107
{
102108
// TODO - Handle client certificates. They should be pushed
@@ -142,7 +148,7 @@ private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterC
142148
ClientCertificate = sslStream.RemoteCertificate?.ToV2Certificate()
143149
});
144150

145-
return new HttpsAdaptedConnection(sslStream);
151+
return new HttpsConnection(sslStream);
146152
}
147153
catch (Exception err)
148154
{
@@ -155,8 +161,8 @@ private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterC
155161

156162
default:
157163
{
158-
LoggerProxy.Default.Error("No client hello!");
159-
return s_closedConnection;
164+
IsHttps = false;
165+
return new HttpConnection(clientStream);
160166
}
161167
}
162168
}
@@ -168,11 +174,28 @@ private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterC
168174
}
169175
}
170176

171-
private class HttpsAdaptedConnection : IAdaptedConnection
177+
private class HttpConnection : IAdaptedConnection
178+
{
179+
private readonly Stream _plainHttpStream;
180+
181+
public HttpConnection(Stream stream)
182+
{
183+
_plainHttpStream = stream;
184+
}
185+
186+
public Stream ConnectionStream => _plainHttpStream;
187+
188+
public void Dispose()
189+
{
190+
_plainHttpStream.Dispose();
191+
}
192+
}
193+
194+
private class HttpsConnection : IAdaptedConnection
172195
{
173196
private readonly SslStream _sslStream;
174197

175-
public HttpsAdaptedConnection(SslStream sslStream)
198+
public HttpsConnection(SslStream sslStream)
176199
{
177200
_sslStream = sslStream;
178201
}

CitadelCore/Net/Handlers/FilterResponseHandlerFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ static FilterResponseHandlerFactory()
2727
// If this isn't set, we'll have a massive bottlenet on our upstream flow. The
2828
// performance gains here extreme. This must be set.
2929
ServicePointManager.DefaultConnectionLimit = ushort.MaxValue;
30-
30+
3131
ServicePointManager.Expect100Continue = false;
3232
ServicePointManager.CheckCertificateRevocationList = true;
3333
ServicePointManager.ReusePort = true;

CitadelCore/Net/Proxy/ProxyServer.cs

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,6 @@ public IPEndPoint V4HttpEndpoint
5555
get;
5656
}
5757

58-
/// <summary>
59-
/// Gets the IPV4 endpoint where HTTPS connections are being received. This will be ANY:0
60-
/// until Start has been called.
61-
/// </summary>
62-
public IPEndPoint V4HttpsEndpoint
63-
{
64-
private set;
65-
get;
66-
}
67-
6858
/// <summary>
6959
/// Gets the IPV6 endpoint where HTTP connections are being received. This will be ANY:0
7060
/// until Start has been called.
@@ -75,16 +65,6 @@ public IPEndPoint V6HttpEndpoint
7565
get;
7666
}
7767

78-
/// <summary>
79-
/// Gets the IPV6 endpoint where HTTPS connections are being received. This will be ANY:0
80-
/// until Start has been called.
81-
/// </summary>
82-
public IPEndPoint V6HttpsEndpoint
83-
{
84-
private set;
85-
get;
86-
}
87-
8868
/// <summary>
8969
/// Gets whether or not the server is currently running.
9070
/// </summary>
@@ -194,30 +174,26 @@ public void Start(int numThreads = 0)
194174

195175
// Create the public, v4 proxy.
196176
IPEndPoint v4HttpEndpoint = null;
197-
IPEndPoint v4HttpsEndpoint = null;
198177

199178
var publicV4Startup = new PublicServerStartup(null, _httpResponseFactory);
200-
var publicV4Host = CreateHost<PublicServerStartup>(false, false, out v4HttpEndpoint, out v4HttpsEndpoint, publicV4Startup);
179+
var publicV4Host = CreateHost<PublicServerStartup>(false, false, out v4HttpEndpoint, publicV4Startup);
201180

202181
V4HttpEndpoint = v4HttpEndpoint;
203-
V4HttpsEndpoint = v4HttpsEndpoint;
204182

205183
// Create the public, v6 proxy.
206184
IPEndPoint v6HttpEndpoint = null;
207-
IPEndPoint v6HttpsEndpoint = null;
208185

209186
var publicV6Startup = new PublicServerStartup(null, _httpResponseFactory);
210-
var publicV6Host = CreateHost<PublicServerStartup>(false, true, out v6HttpEndpoint, out v6HttpsEndpoint, publicV6Startup);
187+
var publicV6Host = CreateHost<PublicServerStartup>(false, true, out v6HttpEndpoint, publicV6Startup);
211188

212189
V6HttpEndpoint = v6HttpEndpoint;
213-
V6HttpsEndpoint = v6HttpsEndpoint;
214190

215191
// Create the private, v4 replay proxy
216192
IPEndPoint privateV4HttpEndpoint = null;
217193
IPEndPoint privateV4HttpsEndpoint = null;
218194

219195
var privateV4Startup = new PrivateServerStartup(null, _replayResponseFactory);
220-
var privateV4Host = CreateHost<PrivateServerStartup>(true, false, out privateV4HttpEndpoint, out privateV4HttpsEndpoint, privateV4Startup);
196+
var privateV4Host = CreateHost<PrivateServerStartup>(true, false, out privateV4HttpEndpoint, privateV4Startup);
221197

222198
_replayResponseFactory.V4HttpEndpoint = privateV4HttpEndpoint;
223199
_replayResponseFactory.V4HttpsEndpoint = privateV4HttpsEndpoint;
@@ -231,9 +207,9 @@ public void Start(int numThreads = 0)
231207

232208
_diverter = CreateDiverter(
233209
V4HttpEndpoint,
234-
V4HttpsEndpoint,
210+
V4HttpEndpoint,
235211
V6HttpEndpoint,
236-
V6HttpsEndpoint
212+
V6HttpEndpoint
237213
);
238214

239215
_diverter.ConfirmDenyFirewallAccess = (procPath) =>
@@ -369,9 +345,6 @@ protected virtual bool CertificateVerificationHandler(object sender, X509Certifi
369345
/// <param name="boundHttpEndpoint">
370346
/// The endpoint that the HTTP host was bound to.
371347
/// </param>
372-
/// <param name="boundHttpsEndpoint">
373-
/// The endpoint that the HTTPS host was bound to.
374-
/// </param>
375348
/// <param name="startupInsance">
376349
/// The startup instance to use for the server.
377350
/// </param>
@@ -382,12 +355,11 @@ protected virtual bool CertificateVerificationHandler(object sender, X509Certifi
382355
/// In the event that the internal kestrel engine doesn't properly initialize, this method
383356
/// will throw.
384357
/// </exception>
385-
private IWebHost CreateHost<T>(bool isPrivate, bool isV6, out IPEndPoint boundHttpEndpoint, out IPEndPoint boundHttpsEndpoint, IStartup startupInsance) where T : class
358+
private IWebHost CreateHost<T>(bool isPrivate, bool isV6, out IPEndPoint boundHttpEndpoint, IStartup startupInsance) where T : class
386359
{
387360
WebHostBuilder ipWebhostBuilder = new WebHostBuilder();
388361

389362
ListenOptions httpListenOptions = null;
390-
ListenOptions httpsListenOptions = null;
391363

392364
ipWebhostBuilder.UseSockets(opts =>
393365
{
@@ -418,21 +390,6 @@ private IWebHost CreateHost<T>(bool isPrivate, bool isV6, out IPEndPoint boundHt
418390
// https://github.com/aspnet/Docs/issues/5242#issuecomment-380863456
419391
// listenOpts.Protocols = HttpProtocols.Http1;
420392

421-
httpsListenOptions = listenOpts;
422-
});
423-
424-
// Listen for HTTP connections. Keep a reference to the options object so we can get
425-
// the chosen port number after we call start.
426-
opts.Listen(isV6 ? IPAddress.IPv6Any : IPAddress.Any, 0, listenOpts =>
427-
{
428-
// Who doesn't love to kick that old Nagle to the curb?
429-
listenOpts.NoDelay = true;
430-
431-
// HTTP 2 got cut last minute from 2.1 and MS speculates that it may take several
432-
// releases to get it properly included.
433-
// https://github.com/aspnet/Docs/issues/5242#issuecomment-380863456
434-
// listenOpts.Protocols = HttpProtocols.Http1;
435-
436393
httpListenOptions = listenOpts;
437394
});
438395
});
@@ -465,7 +422,6 @@ private IWebHost CreateHost<T>(bool isPrivate, bool isV6, out IPEndPoint boundHt
465422
if (httpListenOptions != null)
466423
{
467424
boundHttpEndpoint = httpListenOptions.IPEndPoint;
468-
boundHttpsEndpoint = httpsListenOptions.IPEndPoint;
469425
}
470426
else
471427
{

0 commit comments

Comments
 (0)