Skip to content

Commit 4863cb9

Browse files
nadeaudisgolemon-corp
authored and
MongoDB Bot
committed
SERVER-85804 Support Proxy Protocol on Mongod (#31814)
Co-authored-by: Sara Golemon <[email protected]> GitOrigin-RevId: ab2539a
1 parent a37a628 commit 4863cb9

File tree

8 files changed

+150
-22
lines changed

8 files changed

+150
-22
lines changed

buildscripts/resmokeconfig/fully_disabled_feature_flags.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
- featureFlagEgressGrpcForSearch
1818
- featureFlagTrackUnshardedCollectionsUponCreation
1919
- featureFlagTSBucketingParametersUnchanged
20-
- featureFlagMongogProxyProcolSupport
20+
- featureFlagMongodProxyProcolSupport
2121
- featureFlagShardAuthoritativeDbMetadata
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Verify mongod support proxy protocol connections.
3+
* @tags: [
4+
* requires_fcv_81,
5+
* # TODO (SERVER-97257): Re-enable this test or add an explanation why it is incompatible.
6+
* embedded_router_incompatible,
7+
* grpc_incompatible,
8+
* ]
9+
*/
10+
11+
if (_isWindows()) {
12+
quit();
13+
}
14+
import {ProxyProtocolServer} from "jstests/sharding/libs/proxy_protocol.js";
15+
import {ReplSetTest} from "jstests/libs/replsettest.js";
16+
17+
function runHello(port, loadBalanced) {
18+
let uri = `mongodb://127.0.0.1:${port}`;
19+
if (typeof loadBalanced != 'undefined') {
20+
uri += `/?loadBalanced=${loadBalanced}`;
21+
}
22+
const conn = new Mongo(uri);
23+
assert.neq(null, conn, 'Client was unable to connect to the load balancer port');
24+
assert.commandWorked(conn.getDB('admin').runCommand({hello: 1}));
25+
}
26+
27+
function failInvalidProtocol(node, port, id, attrs, loadBalanced, count) {
28+
let uri = `mongodb://127.0.0.1:${port}`;
29+
if (typeof loadBalanced != 'undefined') {
30+
uri += `/?loadBalanced=${loadBalanced}`;
31+
}
32+
try {
33+
new Mongo(uri);
34+
assert(false, 'Client was unable to connect to the load balancer port');
35+
} catch (err) {
36+
assert(checkLog.checkContainsWithCountJson(node, id, attrs, count, undefined, true),
37+
`Did not find log id ${tojson(id)} with attr ${tojson(attrs)} ${
38+
tojson(id)} times in the log`);
39+
}
40+
}
41+
42+
// Test that you can connect to the load balancer port over a proxy.
43+
function testProxyProtocolReplicaSet(ingressPort, egressPort, version) {
44+
let proxy_server = new ProxyProtocolServer(ingressPort, egressPort, version);
45+
proxy_server.start();
46+
47+
let rs = new ReplSetTest({nodes: 1, nodeOptions: {"proxyPort": egressPort}});
48+
rs.startSet({setParameter: {featureFlagMongodProxyProcolSupport: true}});
49+
rs.initiate();
50+
51+
// Connecting to the to the proxy port succeeds.
52+
runHello(ingressPort, undefined);
53+
runHello(ingressPort, false);
54+
55+
// Connecting to the to the proxy port with {loadBalanced: true} fails.
56+
const lbmismatch = {
57+
"error": "LoadBalancerSupportMismatch: Mongod does not support load-balanced connections"
58+
};
59+
60+
const kCmdExecAssertion = 21962;
61+
const node = rs.getPrimary();
62+
failInvalidProtocol(node, ingressPort, kCmdExecAssertion, lbmismatch, "true", 1);
63+
64+
// Connecting to the standard port without proxy header succeeds.
65+
const port = node.port;
66+
runHello(port, undefined);
67+
runHello(port, false);
68+
69+
// Connecting to the standard port without and with {loadBalanced:true} proxy header fails.
70+
failInvalidProtocol(node, port, kCmdExecAssertion, lbmismatch, "true", 2);
71+
72+
// Connecting to the proxy port without proxy header fails.
73+
const kProxyProtocolParseError = 6067900;
74+
failInvalidProtocol(node, egressPort, kProxyProtocolParseError, undefined, "true", 1);
75+
failInvalidProtocol(node, egressPort, kProxyProtocolParseError, undefined, "false", 2);
76+
failInvalidProtocol(node, egressPort, kProxyProtocolParseError, undefined, undefined, 3);
77+
78+
proxy_server.stop();
79+
80+
// Connecting to the standard port with proxy header fails.
81+
proxy_server = new ProxyProtocolServer(ingressPort, port, version);
82+
proxy_server.start();
83+
const attrs = {
84+
"error": {
85+
"code": ErrorCodes.OperationFailed,
86+
"codeName": "OperationFailed",
87+
"errmsg": "ProxyProtocol message detected on mongorpc port",
88+
}
89+
};
90+
failInvalidProtocol(node, ingressPort, 22988, attrs, "true", 1);
91+
failInvalidProtocol(node, ingressPort, 22988, attrs, "false", 2);
92+
failInvalidProtocol(node, ingressPort, 22988, attrs, undefined, 3);
93+
proxy_server.stop();
94+
95+
rs.stopSet();
96+
}
97+
98+
const ingressPort = allocatePort();
99+
const egressPort = allocatePort();
100+
101+
testProxyProtocolReplicaSet(ingressPort, egressPort, 1);
102+
testProxyProtocolReplicaSet(ingressPort, egressPort, 2);

src/mongo/db/mongod_main.cpp

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
#include "mongo/db/s/sharding_initialization_mongod.h"
189189
#include "mongo/db/s/sharding_ready.h"
190190
#include "mongo/db/s/transaction_coordinator_service.h"
191+
#include "mongo/db/server_feature_flags_gen.h"
191192
#include "mongo/db/server_lifecycle_monitor.h"
192193
#include "mongo/db/server_options.h"
193194
#include "mongo/db/service_context.h"
@@ -322,7 +323,6 @@ const ntservice::NtServiceDefaultStrings defaultServiceStrings = {
322323

323324
auto makeTransportLayer(ServiceContext* svcCtx) {
324325
boost::optional<int> routerPort;
325-
boost::optional<int> loadBalancerPort;
326326
boost::optional<int> proxyPort;
327327

328328
if (serverGlobalParams.routerPort) {
@@ -336,20 +336,23 @@ auto makeTransportLayer(ServiceContext* svcCtx) {
336336
// TODO SERVER-78730: add support for load-balanced connections.
337337
}
338338

339-
if (serverGlobalParams.proxyPort) {
340-
proxyPort = *serverGlobalParams.proxyPort;
341-
if (*proxyPort == serverGlobalParams.port) {
342-
LOGV2_ERROR(9967800,
343-
"The proxy port must be different from the public listening port.",
344-
"port"_attr = serverGlobalParams.port);
345-
quickExit(ExitCode::badOptions);
346-
}
339+
// (Ignore FCV check): The proxy port needs to be open before the FCV is set.
340+
if (gFeatureFlagMongodProxyProcolSupport.isEnabledAndIgnoreFCVUnsafe()) {
341+
if (serverGlobalParams.proxyPort) {
342+
proxyPort = *serverGlobalParams.proxyPort;
343+
if (*proxyPort == serverGlobalParams.port) {
344+
LOGV2_ERROR(9967800,
345+
"The proxy port must be different from the public listening port.",
346+
"port"_attr = serverGlobalParams.port);
347+
quickExit(ExitCode::badOptions);
348+
}
347349

348-
if (routerPort && *proxyPort == *routerPort) {
349-
LOGV2_ERROR(9967801,
350-
"The proxy port must be different from the public router port.",
351-
"port"_attr = *routerPort);
352-
quickExit(ExitCode::badOptions);
350+
if (routerPort && *proxyPort == *routerPort) {
351+
LOGV2_ERROR(9967801,
352+
"The proxy port must be different from the public router port.",
353+
"port"_attr = *routerPort);
354+
quickExit(ExitCode::badOptions);
355+
}
353356
}
354357
}
355358

@@ -369,11 +372,8 @@ auto makeTransportLayer(ServiceContext* svcCtx) {
369372
}
370373
#endif
371374

372-
return transport::TransportLayerManagerImpl::createWithConfig(&serverGlobalParams,
373-
svcCtx,
374-
useEgressGRPC,
375-
std::move(loadBalancerPort),
376-
std::move(routerPort));
375+
return transport::TransportLayerManagerImpl::createWithConfig(
376+
&serverGlobalParams, svcCtx, useEgressGRPC, std::move(proxyPort), std::move(routerPort));
377377
}
378378

379379
ExitCode initializeTransportLayer(ServiceContext* serviceContext, BSONObjBuilder* timerReport) {

src/mongo/db/repl/replication_info.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ class CmdHello : public BasicCommandWithReplyBuilderInterface {
413413
auto cmd = idl::parseCommandDocument<HelloCommand>(
414414
IDLParserContext("hello", vts, dbName.tenantId(), sc), cmdObj);
415415

416+
uassert(ErrorCodes::LoadBalancerSupportMismatch,
417+
"Mongod does not support load-balanced connections",
418+
!cmd.getLoadBalanced().value_or(false));
419+
416420
shardWaitInHello.execute(
417421
[&](const BSONObj& customArgs) { _handleHelloFailPoint(customArgs, opCtx, cmdObj); });
418422

src/mongo/db/server_feature_flags.idl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ feature_flags:
134134
cpp_varname: gFeatureFlagExposeClientIpInAuditLogs
135135
default: false
136136
shouldBeFCVGated: true
137-
featureFlagMongogProxyProcolSupport:
137+
featureFlagMongodProxyProcolSupport:
138138
description: "Enables non-OCS proxy protocol connections on Mongos and Mongod"
139-
cpp_varname: gFeatureFlagMongogProxyProcolSupport
139+
cpp_varname: gFeatureFlagMongodProxyProcolSupport
140140
default: false
141141
shouldBeFCVGated: true
142142
featureFlagRawDataCrudOperations:

src/mongo/transport/asio/asio_session_impl.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,15 @@ Future<bool> CommonAsioSession::maybeHandshakeSSLForIngress(const MutableBufferS
747747
if (checkForHTTPRequest(buffer)) {
748748
return Future<bool>::makeReady(false);
749749
}
750+
751+
if (maybeProxyProtocolHeader(
752+
StringData(asio::buffer_cast<const char*>(buffer), asio::buffer_size(buffer)))) {
753+
// Protocol requirements mean that neither raw mongorpc nor TLS client hello will look like
754+
// Proxy.
755+
return Future<bool>::makeReady(
756+
Status(ErrorCodes::OperationFailed, "ProxyProtocol message detected on mongorpc port"));
757+
}
758+
750759
// This logic was taken from the old mongo/util/net/sock.cpp.
751760
//
752761
// It lets us run both TLS and unencrypted mongo over the same port.

src/mongo/transport/proxy_protocol_header_parser.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,5 +455,8 @@ boost::optional<ParserResults> parseProxyProtocolHeader(StringData buffer) {
455455
}
456456
}
457457

458+
bool maybeProxyProtocolHeader(StringData buffer) {
459+
return buffer.startsWith(kV1Start) || buffer.startsWith(kV2Start);
460+
}
458461

459462
} // namespace mongo::transport

src/mongo/transport/proxy_protocol_header_parser.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ struct ParserResults {
8686
*/
8787
boost::optional<ParserResults> parseProxyProtocolHeader(StringData buffer);
8888

89+
/**
90+
* Peek a buffer fo at least 12 bytes to determine if it may be a proxy protocol header.
91+
*
92+
* Note that this does not definitively identify the initial packet as proxy protocol,
93+
* it only establishes that it is possible that it is such.
94+
* To be used in determining appropriate error messages during otherwise failed
95+
* initial handshakes only.
96+
*/
97+
bool maybeProxyProtocolHeader(StringData buffer);
98+
8999
namespace proxy_protocol_details {
90100
static constexpr size_t kMaxUnixPathLength = 108;
91101

0 commit comments

Comments
 (0)