Post

Round Trips to Authenticate a MongoDB Client Connection

When MongoDB Drivers establish a connection with a MongoDB cluster a number of network round trips are performed. This can result in increased latency when measuring the time to response of an operation following a cold start, so it’s worth understanding what the anatomy of an authenticated connection is - as well as what can be done to improve an initial operations round trip time (RTT).

Current State

Connection Protocol

Typically a MongoDB connection string will contain a standard seed list, which is represented by the mongodb:// protocol followed by a list of servers (ex: mongodb://localhost:27017,localhost:27018....).

Starting with MongoDB 3.6 instead of having to provide the seed list in the connection string manually a DNS-constructed seed list could be used as well. With this configuration the mongodb+srv:// protocol is used to communicate both the seed list as well as any options by performing two (2) DNS queries to resolve the following DNS records: SRV and TXT.

See “MongoDB 3.6: Here to SRV you with easier replica set connections” for more information regarding this topic.

Additionally there may be an RTT for A/AAAA/CNAME resolution, however these may be done in parallel and may also be cached. DNS Caching will likely improve the performance of these queries, but it’s worth noting their presence within the connection establishment and authentication lifecycle.

1
2
/* Network Round Trips */
  [0 , 3] // Protocol

Note that if the SRV record returns multiple hosts, those A/AAAA/CNAME records will be resolved in parallel. And DNS servers typically will optimize the traversal returning any intermediate CNAMEs followed by the A/AAAA in the same request.

This also assumes UDP-based DNS resolution. If you exceed the UDP packet size, you might first try UDP, receive an error, and retry using TCP (and possibly requiring a TCP handshake to the DNS server as well).

TCP Handshake

Source: makeuseof.com

Once a host is known from the seed list, next we need to connect to it. This is done using a standard TCP 3-way Handshake, which constitutes 1 RTT. Note that as there is an ACK sent following the SYN/ACK this handshake is sometimes considered to be 1.5 RTT, however most TCP stacks will send the first data packet with the ACK.

1
2
3
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP

TLS Handshake

Source: cloudflare.com

To ensure all connections to MongoDB Atlas are secure, Transport Layer Security (TLS) is enabled by default. Following a successful TCP handshake, a TLS handshake will be performed.

1
2
3
4
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP
+  2      // TLS

MongoDB Handshake

Now that we have established a TLS secured TCP socket connection to a MongoDB host (mongos or mongod), the MongoDB Driver will send a hello command to perform the initial handshake.

This step is required to determine that the host at the other end of the socket is actually a MongoDB server. Assuming the version of the MongoDB Driver supports MongoDB 4.4+ the handshake will also include a speculativeAuthenticate argument. Specifying this argument to hello will speculatively include the first command of an authentication handshake, thus eliminating one round trip as the saslStart command doesn’t need to be sent during the authentication handshake.

1
2
3
4
5
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP
+  2      // TLS
+  1      // MongoDB

Authentication Handshake

MongoDB supports a number of SASL (Simple Authentication and Security Layer) mechanisms. By default the SASL mechanism that will be used will be a SCRAM mechanism (either SCRAM-SHA-1 or SCRAM-SHA-256), which effectively means “username and password”. Note that this is a “challenge response” mechanism, so these credentials aren’t broadcast in the clear.

As outlined in the MongoDB Authentication specification, a SCRAM-SHA-256 conversation will be made up of 2 round trips as follows:

1
2
3
4
>> {saslStart: 1, mechanism:"SCRAM-SHA-256", options: {skipEmptyExchange: true}, payload: BinData(0, "...=")}
<< {conversationId: 1, payload: BinData(0, "...="), done: false, ok: 1}
>> {saslContinue: 1, conversationId: 1, payload: BinData(0, "...==")}
<< {conversationId: 1, payload: BinData(0, "...=="), done: true, ok: 1}

For backwards compatibility with MongoDB 4.2 or earlier, MongoDB Drivers support a longer SCRAM conversation which includes an additional saslContinue command being sent as follows:

1
2
3
4
5
6
>> {saslStart: 1, mechanism: "SCRAM-SHA-1", payload: BinData(0, "..."), options: {skipEmptyExchange: true}}
<< {conversationId : 1, payload: BinData(0,"..."), done: false, ok: 1}
>> {saslContinue: 1, conversationId: 1, payload: BinData(0, "...")}
<< {conversationId: 1, payload: BinData(0,"..."), done: false, ok: 1}
>> {saslContinue: 1, conversationId: 1, payload: BinData(0, "")}
<< {conversationId: 1, payload: BinData(0,""), done: true, ok: 1}

RTT was improved with MongoDB 4.4+ as 2 round trips can potentially be avoided:

  1. when speculativeAuthenticate is used the saslStart command is incorporated into the initial MongoDB handshake
  2. when the saslStart command contains the skipEmptyExchange: true option, the second saslContinue command can be skipped
1
2
3
4
5
6
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP
+  2      // TLS
+  1      // MongoDB
+ [2 , 3] // Authentication

Reducing Round Trips

As outlined above there are a number of network round trips required to authenticate a client connection to a MongoDB host using a username and password:

1
2
3
4
5
6
7
8
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP
+  2      // TLS
+  1      // MongoDB
+ [2 , 3] // Authentication
---------------------------
[6 , 10]

MongoDB 4.4 has been out since at least September 2020, so chances are most applications are connecting to at least this version or newer. This would put the average round trip count for authenticating a connection between 6 and 9 (depending on what protocol is being used and if DNS results were previously cached).

Next let’s review what can be done to reduce these round trips where possible.

Use x.509 Authentication

When using x.509 certificates to authenticate clients, the conversation with the server does not require a saslContinue. Assuming this speculativeAuthenticate of the initial handshake succeeds (which it should), two full round trip can be removed!

1
2
3
4
5
6
7
8
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP
+  2      // TLS
+  1      // MongoDB
+  0      // Authentication
---------------------------
[4 , 7]

Note there’s no authentication round trips as the speculativeAuthenticate succeeding contains the information typically sent via the saslStart command.

Use TLS 1.3+

Source: thesslstore.com

TLS 1.3 (RFC 8446) can authenticate a connection approximately twice as fast as TLS 1.2 (RFC 5246) by eliminating a full round trip.

Though not necessarily supported (at time of writing) by the MongoDB Server, additional non-standard TLS 1.3 configurations can further help speed up encrypted connections - such as TLS false start and Zero Round Trip Time (0-RTT). See “TLS 1.3: Everything you need to know” for more information.

1
2
3
4
5
6
7
8
/* Network Round Trips */
  [0 , 3] // Protocol
+  1      // TCP
+  1      // TLS
+  1      // MongoDB
+ [0 , 3] // Authentication
---------------------------
[3 , 9]

Conclusion

In some environments (such as Function as a Service) the cold start time of an application is critically important. The time to authenticate a connection to a MongoDB host and how this can be improved can be useful in improving operational latency of applications.

Out of the box there may be upwards of 9 network round trips (SRV+TCP+TLS+MONGODB+AUTH), however this can potentially be cut in half (or more) by understanding what configuration and authentication options exist and how they can be applied.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.