-
Notifications
You must be signed in to change notification settings - Fork 1.5k
JAVA-6194 Add MongoSocksProxyException for CMAP backpressure labeling #1968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nhachicha
wants to merge
55
commits into
mongodb:backpressure
Choose a base branch
from
nhachicha:nh/backpressure/socks5_exception
base: backpressure
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 30 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
ac1c120
JAVA-6035: Add backpressure flag to connection handshake (#1906)
nhachicha b33dca9
Add `MongoException.SYSTEM_OVERLOADED_ERROR_LABEL`/`RETRYABLE_ERROR_L…
stIncMale fa82369
JAVA-6055 Implement prose backpressure retryable writes tests (#1929)
stIncMale ffe5242
Add `maxAdaptiveRetries` API (#1944)
stIncMale 44541fc
Add support for server selection's deprioritized servers (#1860)
vbabanin c380b2e
Implement prose backpressure tests (#1946)
stIncMale d083e1b
Add `enableOverloadRetargeting` API (#1943)
vbabanin bd888c9
Add handshake prose Test 9: backpressure: true in handshake documents…
nhachicha 394f7b1
JAVA-5950 Update Transactions Convenient API with exponential backoff…
nhachicha 0eb04a0
JAVA-6194 Add MongoSocksProxyException for CMAP backpressure labeling
nhachicha f2bcce5
Merge remote-tracking branch 'origin/backpressure' into nh/backpressu…
nhachicha 28a074d
update submodule
nhachicha c2ca4fd
Address review nits in MongoSocksProxyException
nhachicha 801127f
Close proxy socket on MongoSocksProxyException in SocksSocket.connect
nhachicha 61a1c5e
Use getHostString in SocksSocket exception reporting path
nhachicha 49e58f0
Fix socket leak in SOCKS5 initializer methods and DRY open()
nhachicha db26d92
Replace Thread.sleep(300) with input drain in SocksSocketTest
nhachicha fd39744
Use ephemeral closed port instead of port 1 in SocksSocketTest
nhachicha 7416dd3
Use Java 8-compatible drain in SocksSocketTest mini-server
nhachicha 4e3249b
Add Scala type alias for MongoSocksProxyException
nhachicha 98483bb
Phase-aware MongoSocksProxyException handling in BackpressureErrorLab…
nhachicha 78d6b01
Tag handshake-phase IOExceptions with the correct HandshakePhase
nhachicha dee79f0
Validate non-null HandshakePhase in MongoSocksProxyException
nhachicha b3da503
Align MongoSocksProxyException class-level Javadoc with phase-aware b…
nhachicha 2c5be54
Broaden HandshakePhase enum Javadoc to cover I/O-failure path
nhachicha effa09a
Rename misleading eofDuring* tests to ioFailureDuring*
nhachicha 095f524
Narrow tcpConnectFailure test to IOException, not Throwable
nhachicha 582169c
Drive real EOF in ioFailureDuring* tests via half-close
nhachicha d7a9b15
Align constructor Javadoc with phase/replyCode semantics post round 2
nhachicha 43e478c
Potential fix for pull request finding
nhachicha 7971f9a
Widen outer catch in SocksSocket.connect to IOException
nhachicha 27417eb
Drop redundant MongoSocksProxyException re-throw branches
nhachicha b2bb401
Drop redundant null-phase guard in BackpressureErrorLabeler
nhachicha 2517b69
Backpressure-label SOCKS5 failures by mongod-attribution
nhachicha df8430c
Include proxy host:port in PROXY_TCP_CONNECT exception message
nhachicha 0aafd71
Add SOCKS5/code context to CONNECT non-success reply message
nhachicha 11a5866
Realign comments on the two outer catches in SocksSocket.connect
nhachicha 4023a0b
Cleanups
nhachicha 4cac195
Stop swallowing unexpected exceptions in connectWithMiniServer
nhachicha 4a44d38
Document constructor parameter ordering convention
nhachicha 4306d2e
Review feedback
nhachicha c5abbce
Fixing SOCKS5 failing prose tests
nhachicha 9efad2b
Dropped the HandshakePhase enum
nhachicha a838766
PR feedback
nhachicha a1f0cb8
Wrapping IOException into MongoSocksProxyException to simplify logic …
nhachicha c188e24
Potential fix for pull request finding
nhachicha 91d3d09
Copilot doc nits
nhachicha a239532
Update driver-core/src/test/unit/com/mongodb/internal/connection/Sock…
nhachicha 9e09f96
Update driver-core/src/test/unit/com/mongodb/internal/connection/Sock…
nhachicha eb6a716
Update driver-core/src/test/unit/com/mongodb/internal/connection/Sock…
nhachicha 196ddf1
Update driver-core/src/test/unit/com/mongodb/internal/connection/Sock…
nhachicha 882a416
Review feedback
nhachicha 2fe56ff
Copilot nit
nhachicha befdfff
Translate interrupt-flavored proxy-connect failures to MongoInterrupt…
nhachicha 7975b3b
Merge remote-tracking branch 'origin/backpressure' into nh/backpressu…
nhachicha File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
184 changes: 184 additions & 0 deletions
184
driver-core/src/main/com/mongodb/MongoSocksProxyException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| /* | ||
| * Copyright 2008-present MongoDB, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package com.mongodb; | ||
|
|
||
| import com.mongodb.lang.Nullable; | ||
|
|
||
| import static com.mongodb.assertions.Assertions.notNull; | ||
|
|
||
| /** | ||
| * Thrown when an error occurs while establishing a connection to a SOCKS5 proxy. | ||
| * | ||
| * <p>Per the CMAP specification, post-TCP SOCKS5 failures | ||
| * ({@link HandshakePhase#NEGOTIATION}, {@link HandshakePhase#AUTHENTICATION}, | ||
| * {@link HandshakePhase#CONNECT_RELAY}) are excluded from backpressure error labels | ||
| * ({@link MongoException#SYSTEM_OVERLOADED_ERROR_LABEL}, | ||
| * {@link MongoException#RETRYABLE_ERROR_LABEL}). Failures in the | ||
| * {@link HandshakePhase#PROXY_TCP_CONNECT} phase are plain TCP-level reach failures | ||
| * to the proxy host and continue to receive these labels like any other | ||
| * socket-open failure. | ||
| * | ||
| * <p>The {@link #getHandshakePhase()} identifies which phase of the SOCKS5 handshake failed. | ||
| * {@link #getProxyReplyCode()} returns the RFC 1928 reply code sent by the proxy when a | ||
| * non-success CONNECT reply was successfully parsed; it returns {@code null} otherwise | ||
| * (including for {@link HandshakePhase#CONNECT_RELAY} failures caused by an I/O error or | ||
| * an unrecognised reply field). | ||
| * | ||
| * <p>RFC 1928 reply codes: 1=general failure, 2=connection not allowed by ruleset, | ||
| * 3=network unreachable, 4=host unreachable, 5=connection refused, 6=TTL expired, | ||
| * 7=command not supported, 8=address type not supported. | ||
| * | ||
| * @since 5.8 | ||
| */ | ||
| public class MongoSocksProxyException extends MongoSocketOpenException { | ||
| private static final long serialVersionUID = 1L; | ||
|
|
||
| /** | ||
| * The phase of the SOCKS5 handshake at which the failure occurred. | ||
| * | ||
| * @since 5.8 | ||
| */ | ||
| public enum HandshakePhase { | ||
| /** | ||
| * TCP connection to the proxy host itself failed before any SOCKS5 exchange. | ||
| * The proxy may be temporarily unreachable. | ||
| */ | ||
| PROXY_TCP_CONNECT, | ||
|
|
||
| /** | ||
| * The SOCKS5 method-selection exchange failed. Causes include: incompatible | ||
| * proxy version, no common authentication method, an unrecognised method, or | ||
| * an I/O failure (EOF, timeout, broken pipe) while sending the method-selection | ||
| * request or reading its reply. | ||
| */ | ||
| NEGOTIATION, | ||
|
|
||
| /** | ||
| * Username/password sub-negotiation with the proxy failed. Causes include: | ||
| * the proxy rejecting the credentials (typically wrong username/password), | ||
| * or an I/O failure (EOF, timeout, broken pipe) while sending credentials | ||
| * or reading the auth result. | ||
| */ | ||
| AUTHENTICATION, | ||
|
|
||
| /** | ||
| * A failure occurred while sending the CONNECT request to the proxy or | ||
| * reading/parsing its reply. Causes include: a parsed non-success RFC 1928 | ||
| * reply (in which case {@link MongoSocksProxyException#getProxyReplyCode()} | ||
| * carries the code), an unrecognised reply field or address type, or an | ||
| * I/O failure (EOF, timeout, broken pipe) on the CONNECT exchange. | ||
| */ | ||
| CONNECT_RELAY | ||
| } | ||
|
|
||
| private final HandshakePhase handshakePhase; | ||
|
|
||
| @Nullable | ||
| private final Integer proxyReplyCode; | ||
|
|
||
| /** | ||
| * Construct an instance with no RFC 1928 reply code and no cause. Suitable for any phase | ||
| * whose failure does not carry a parsed reply code: {@link HandshakePhase#PROXY_TCP_CONNECT}, | ||
| * {@link HandshakePhase#NEGOTIATION}, {@link HandshakePhase#AUTHENTICATION}, and the | ||
| * {@link HandshakePhase#CONNECT_RELAY} sub-cases driven by an I/O failure or an unrecognised | ||
| * reply field. | ||
| * | ||
| * @param message the message | ||
| * @param serverAddress the server address | ||
| * @param handshakePhase the phase at which the failure occurred | ||
| */ | ||
| public MongoSocksProxyException(final String message, final ServerAddress serverAddress, final HandshakePhase handshakePhase) { | ||
| this(message, serverAddress, notNull("handshakePhase", handshakePhase), null); | ||
| } | ||
|
|
||
| /** | ||
| * Construct an instance with no RFC 1928 reply code. Suitable for any phase whose failure | ||
| * does not carry a parsed reply code: {@link HandshakePhase#PROXY_TCP_CONNECT}, | ||
| * {@link HandshakePhase#NEGOTIATION}, {@link HandshakePhase#AUTHENTICATION}, and the | ||
| * {@link HandshakePhase#CONNECT_RELAY} sub-cases driven by an I/O failure or an unrecognised | ||
| * reply field. | ||
| * | ||
| * @param message the message | ||
| * @param address the server address | ||
| * @param cause the cause | ||
| * @param handshakePhase the phase at which the failure occurred | ||
| */ | ||
| public MongoSocksProxyException(final String message, final ServerAddress address, | ||
| final Throwable cause, final HandshakePhase handshakePhase) { | ||
| this(message, address, cause, notNull("handshakePhase", handshakePhase), null); | ||
| } | ||
|
|
||
| /** | ||
| * Construct an instance with an optional RFC 1928 reply code. A non-{@code null} | ||
| * {@code proxyReplyCode} should only accompany {@link HandshakePhase#CONNECT_RELAY} and | ||
| * indicates a successfully parsed non-success reply from the proxy. Use {@code null} in | ||
| * all other cases — including {@link HandshakePhase#CONNECT_RELAY} failures caused by an | ||
| * I/O error or an unrecognised reply field. | ||
| * | ||
| * @param message the message | ||
| * @param address the server address | ||
| * @param handshakePhase the phase at which the failure occurred | ||
| * @param proxyReplyCode the RFC 1928 reply code, or {@code null} | ||
| */ | ||
| public MongoSocksProxyException(final String message, final ServerAddress address, final HandshakePhase handshakePhase, | ||
| @Nullable final Integer proxyReplyCode) { | ||
| super(message, address); | ||
| this.handshakePhase = notNull("handshakePhase", handshakePhase); | ||
| this.proxyReplyCode = proxyReplyCode; | ||
| } | ||
|
|
||
| /** | ||
| * Construct an instance with an optional RFC 1928 reply code. A non-{@code null} | ||
| * {@code proxyReplyCode} should only accompany {@link HandshakePhase#CONNECT_RELAY} and | ||
| * indicates a successfully parsed non-success reply from the proxy. Use {@code null} in | ||
| * all other cases — including {@link HandshakePhase#CONNECT_RELAY} failures caused by an | ||
| * I/O error or an unrecognised reply field. | ||
| * | ||
| * @param message the message | ||
| * @param address the server address | ||
| * @param cause the cause | ||
| * @param handshakePhase the phase at which the failure occurred | ||
| * @param proxyReplyCode the RFC 1928 reply code, or {@code null} | ||
| */ | ||
| public MongoSocksProxyException(final String message, final ServerAddress address, | ||
| final Throwable cause, final HandshakePhase handshakePhase, | ||
| @Nullable final Integer proxyReplyCode) { | ||
| super(message, address, cause); | ||
| this.handshakePhase = notNull("handshakePhase", handshakePhase); | ||
| this.proxyReplyCode = proxyReplyCode; | ||
| } | ||
|
nhachicha marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Returns the phase of the SOCKS5 handshake at which the failure occurred. | ||
| * | ||
| * @return the handshake phase, never {@code null} | ||
| */ | ||
| public HandshakePhase getHandshakePhase() { | ||
| return handshakePhase; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the RFC 1928 reply code sent by the SOCKS5 proxy when a non-success CONNECT | ||
| * reply was successfully parsed, or {@code null} otherwise. | ||
| * | ||
| * @return the RFC 1928 proxy reply code, or {@code null} | ||
| */ | ||
| @Nullable | ||
| public Integer getProxyReplyCode() { | ||
| return proxyReplyCode; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,9 +16,11 @@ | |
|
|
||
| package com.mongodb.internal.connection; | ||
|
|
||
| import com.mongodb.MongoInterruptedException; | ||
| import com.mongodb.MongoSocketException; | ||
| import com.mongodb.MongoSocketOpenException; | ||
| import com.mongodb.MongoSocketReadException; | ||
| import com.mongodb.MongoSocksProxyException; | ||
| import com.mongodb.ServerAddress; | ||
| import com.mongodb.connection.AsyncCompletionHandler; | ||
| import com.mongodb.connection.ProxySettings; | ||
|
|
@@ -38,6 +40,7 @@ | |
| import java.net.SocketTimeoutException; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| import static com.mongodb.assertions.Assertions.assertTrue; | ||
| import static com.mongodb.assertions.Assertions.notNull; | ||
|
|
@@ -79,10 +82,21 @@ public void open(final OperationContext operationContext) { | |
| socket = initializeSocket(operationContext); | ||
| outputStream = socket.getOutputStream(); | ||
| inputStream = socket.getInputStream(); | ||
| } catch (MongoSocksProxyException e) { | ||
| close(); | ||
|
nhachicha marked this conversation as resolved.
|
||
| throw e; | ||
| } catch (IOException e) { | ||
| close(); | ||
| throw translateInterruptedException(e, "Interrupted while connecting") | ||
| .orElseThrow(() -> new MongoSocketOpenException("Exception opening socket", getAddress(), e)); | ||
| Optional<MongoInterruptedException> interrupted = translateInterruptedException(e, "Interrupted while connecting"); | ||
| if (interrupted.isPresent()) { | ||
| throw interrupted.get(); | ||
| } | ||
| if (settings.getProxySettings().isProxyEnabled()) { | ||
| throw new MongoSocksProxyException( | ||
| "Exception connecting to SOCKS5 proxy", getAddress(), e, | ||
| MongoSocksProxyException.HandshakePhase.PROXY_TCP_CONNECT); | ||
| } | ||
|
nhachicha marked this conversation as resolved.
Outdated
nhachicha marked this conversation as resolved.
Outdated
nhachicha marked this conversation as resolved.
Outdated
|
||
| throw new MongoSocketOpenException("Exception opening socket", getAddress(), e); | ||
| } | ||
| } | ||
|
|
||
|
nhachicha marked this conversation as resolved.
Outdated
|
||
|
|
@@ -119,15 +133,28 @@ private SSLSocket initializeSslSocketOverSocksProxy(final OperationContext opera | |
| final int serverPort = address.getPort(); | ||
|
|
||
| SocksSocket socksProxy = new SocksSocket(settings.getProxySettings()); | ||
| configureSocket(socksProxy, operationContext, settings); | ||
| InetSocketAddress inetSocketAddress = toSocketAddress(serverHost, serverPort); | ||
| socksProxy.connect(inetSocketAddress, operationContext.getTimeoutContext().getConnectTimeoutMs()); | ||
|
|
||
| SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socksProxy, serverHost, serverPort, true); | ||
| //Even though Socks proxy connection is already established, TLS handshake has not been performed yet. | ||
| //So it is possible to set SSL parameters before handshake is done. | ||
| configureSslSocket(sslSocket, sslSettings, inetSocketAddress); | ||
| return sslSocket; | ||
| // Track the outermost socket layer to close on failure. Initially this is socksProxy; | ||
| // once we wrap it into an SSLSocket, that becomes the outermost layer and closing it | ||
| // tears down the underlying socksProxy as well. | ||
| Socket toClose = socksProxy; | ||
| try { | ||
| configureSocket(socksProxy, operationContext, settings); | ||
| InetSocketAddress inetSocketAddress = toSocketAddress(serverHost, serverPort); | ||
| socksProxy.connect(inetSocketAddress, operationContext.getTimeoutContext().getConnectTimeoutMs()); | ||
| SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socksProxy, serverHost, serverPort, true); | ||
| toClose = sslSocket; | ||
| //Even though Socks proxy connection is already established, TLS handshake has not been performed yet. | ||
| //So it is possible to set SSL parameters before handshake is done. | ||
| configureSslSocket(sslSocket, sslSettings, inetSocketAddress); | ||
| return sslSocket; | ||
| } catch (IOException | RuntimeException e) { | ||
|
nhachicha marked this conversation as resolved.
|
||
| try { | ||
| toClose.close(); | ||
| } catch (IOException closeException) { | ||
| e.addSuppressed(closeException); | ||
| } | ||
| throw e; | ||
|
Comment on lines
+154
to
+160
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! |
||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -141,17 +168,30 @@ private static InetSocketAddress toSocketAddress(final String serverHost, final | |
|
|
||
| private Socket initializeSocketOverSocksProxy(final OperationContext operationContext) throws IOException { | ||
| Socket createdSocket = socketFactory.createSocket(); | ||
| configureSocket(createdSocket, operationContext, settings); | ||
| /* | ||
| Wrap the configured socket with SocksSocket to add extra functionality. | ||
| Reason for separate steps: We can't directly extend Java 11 methods within 'SocksSocket' | ||
| to configure itself. | ||
| */ | ||
| SocksSocket socksProxy = new SocksSocket(createdSocket, settings.getProxySettings()); | ||
|
|
||
| socksProxy.connect(toSocketAddress(address.getHost(), address.getPort()), | ||
| operationContext.getTimeoutContext().getConnectTimeoutMs()); | ||
| return socksProxy; | ||
| try { | ||
| configureSocket(createdSocket, operationContext, settings); | ||
| /* | ||
| Wrap the configured socket with SocksSocket to add extra functionality. | ||
| Reason for separate steps: We can't directly extend Java 11 methods within 'SocksSocket' | ||
| to configure itself. | ||
| */ | ||
| SocksSocket socksProxy = new SocksSocket(createdSocket, settings.getProxySettings()); | ||
| socksProxy.connect(toSocketAddress(address.getHost(), address.getPort()), | ||
| operationContext.getTimeoutContext().getConnectTimeoutMs()); | ||
| return socksProxy; | ||
| } catch (IOException | RuntimeException e) { | ||
| // SocksSocket.connect() now closes itself on failure, but createdSocket may not yet | ||
| // be owned by a SocksSocket (e.g. configureSocket threw). Close defensively; on success | ||
| // path SocksSocket holds the reference and this catch is not entered. | ||
| // Note: when SocksSocket.connect() has already closed the inner socket, this is a | ||
| // no-op (java.net.Socket.close() is idempotent per the JDK contract). | ||
| try { | ||
| createdSocket.close(); | ||
| } catch (IOException closeException) { | ||
| e.addSuppressed(closeException); | ||
| } | ||
| throw e; | ||
|
nhachicha marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.