-
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
base: backpressure
Are you sure you want to change the base?
Changes from 20 commits
ac1c120
b33dca9
fa82369
ffe5242
44541fc
c380b2e
d083e1b
bd888c9
394f7b1
0eb04a0
f2bcce5
28a074d
c2ca4fd
801127f
61a1c5e
49e58f0
db26d92
fd39744
7416dd3
4e3249b
98483bb
78d6b01
dee79f0
b3da503
2c5be54
effa09a
095f524
582169c
d7a9b15
43e478c
7971f9a
27417eb
b2bb401
2517b69
df8430c
0aafd71
11a5866
4023a0b
4cac195
4a44d38
4306d2e
c5abbce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| /** | ||
| * Thrown when an error occurs while establishing a connection to a SOCKS5 proxy. | ||
| * | ||
| * <p>Per the CMAP specification, errors of this type are excluded from backpressure | ||
| * error labels ({@link MongoException#SYSTEM_OVERLOADED_ERROR_LABEL}, | ||
| * {@link MongoException#RETRYABLE_ERROR_LABEL}). | ||
| * | ||
| * <p>The {@link #getHandshakePhase()} identifies which phase of the SOCKS5 handshake failed. | ||
| * For {@link HandshakePhase#CONNECT_RELAY} failures, {@link #getProxyReplyCode()} returns | ||
| * the RFC 1928 reply code sent by the proxy; for all other phases it returns {@code null}. | ||
|
nhachicha marked this conversation as resolved.
Outdated
|
||
| * | ||
| * <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 { | ||
|
nhachicha marked this conversation as resolved.
|
||
| 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, | ||
|
|
||
| /** | ||
| * SOCKS5 method-selection exchange failed: the proxy version is incompatible, | ||
| * no common authentication method was found, or the proxy returned an | ||
| * unrecognised method. This is always a configuration error. | ||
|
nhachicha marked this conversation as resolved.
Outdated
|
||
| */ | ||
| NEGOTIATION, | ||
|
|
||
| /** | ||
| * Credential verification with the proxy failed. This is always a | ||
| * configuration error (wrong username or password). | ||
|
nhachicha marked this conversation as resolved.
Outdated
|
||
| */ | ||
| AUTHENTICATION, | ||
|
|
||
| /** | ||
| * The proxy processed the CONNECT command for the target host and returned | ||
| * a non-success reply code. See {@link MongoSocksProxyException#getProxyReplyCode()} | ||
| * for the specific RFC 1928 reply code. | ||
|
nhachicha marked this conversation as resolved.
Outdated
|
||
| */ | ||
| CONNECT_RELAY | ||
| } | ||
|
|
||
| private final HandshakePhase handshakePhase; | ||
|
|
||
| @Nullable | ||
| private final Integer proxyReplyCode; | ||
|
|
||
| /** | ||
| * Construct an instance for failures that have no RFC 1928 reply code and no cause | ||
| * ({@link HandshakePhase#PROXY_TCP_CONNECT}, {@link HandshakePhase#NEGOTIATION}, | ||
| * {@link HandshakePhase#AUTHENTICATION}). | ||
| * | ||
| * @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, handshakePhase, null); | ||
| } | ||
|
|
||
| /** | ||
| * Construct an instance for failures that have no RFC 1928 reply code | ||
| * ({@link HandshakePhase#PROXY_TCP_CONNECT}, {@link HandshakePhase#NEGOTIATION}, | ||
| * {@link HandshakePhase#AUTHENTICATION}). | ||
| * | ||
| * @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, handshakePhase, null); | ||
| } | ||
|
|
||
| /** | ||
| * Construct an instance with an optional RFC 1928 reply code. | ||
| * Use {@code null} for phases that do not carry a reply code | ||
| * ({@link HandshakePhase#PROXY_TCP_CONNECT}, {@link HandshakePhase#NEGOTIATION}, | ||
| * {@link HandshakePhase#AUTHENTICATION}). | ||
| * | ||
| * @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 = handshakePhase; | ||
| this.proxyReplyCode = proxyReplyCode; | ||
|
nhachicha marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Construct an instance with an optional RFC 1928 reply code. | ||
| * Use {@code null} for phases that do not carry a reply code | ||
| * ({@link HandshakePhase#PROXY_TCP_CONNECT}, {@link HandshakePhase#NEGOTIATION}, | ||
| * {@link HandshakePhase#AUTHENTICATION}). | ||
| * | ||
| * @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 = handshakePhase; | ||
| this.proxyReplyCode = proxyReplyCode; | ||
|
nhachicha marked this conversation as resolved.
|
||
| } | ||
|
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 in response to a CONNECT request, | ||
| * or {@code null} if the failure occurred before the proxy sent a CONNECT response | ||
| * (i.e. phase is not {@link HandshakePhase#CONNECT_RELAY}). | ||
|
nhachicha marked this conversation as resolved.
Outdated
|
||
| * | ||
| * @return the RFC 1928 proxy reply code, or {@code null} | ||
| */ | ||
| @Nullable | ||
| public Integer getProxyReplyCode() { | ||
| return proxyReplyCode; | ||
| } | ||
| } | ||
| 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(); | ||
| 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.
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) { | ||
|
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. |
||
| try { | ||
| toClose.close(); | ||
| } catch (IOException closeException) { | ||
| e.addSuppressed(closeException); | ||
| } | ||
| throw e; | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -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; | ||
|
Comment on lines
+194
to
+201
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. I think Could we remove this |
||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.