Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); wrap exceptions from callbacks in QuicError.CallbackError by wfurt · Pull Request #88614 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line numberDiff line numberDiff line change
Expand Up@@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All@@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All@@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
bool wrapException = false;

X509Chain? chain = null;
X509Certificate2? result = null;
Expand DownExpand Up@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
Expand All@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
catch
catch (Exception ex)
{
result?.Dispose();
if (wrapException)
{
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
}

throw;
}
finally
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
Expand DownExpand Up@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}

await connection.DisposeAsync().ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(ex))
if (!_acceptQueue.Writer.TryWrite(
wrapException ?
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
Assert.Equal(expectedMessage, exception.Message);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.NotNull(exception.InnerException);
Assert.Equal(expectedMessage, exception.InnerException.Message);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
}

[Fact]
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
{
bool firstRun = true;

QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
// Throw an exception, which should throw the same from accept.
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
{
if (firstRun)
{
firstRun = false;
throw new ObjectDisposedException("failed");
}

return ValueTask.FromResult(CreateQuicServerOptions());
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());

// Throwing ODE in callback should keep Listener running
connectTask = CreateQuicConnection(listener.LocalEndPoint);
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
await using QuicConnection clientConnection = await connectTask;
}

[Theory]
Expand Down