Skip to content

Commit abf5b6f

Browse files
authored
gh-61460: Add a comment describing the multiprocessing.connection protocol (gh-99623)
Describe the multiprocessing connection protocol. It isn't a good protocol, but it is what it is. This way we can more easily reason about making changes to it in a backwards compatible way.
1 parent 9c4232a commit abf5b6f

File tree

1 file changed

+68
-0
lines changed

1 file changed

+68
-0
lines changed

Lib/multiprocessing/connection.py

+68
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,74 @@ def PipeClient(address):
728728
WELCOME = b'#WELCOME#'
729729
FAILURE = b'#FAILURE#'
730730

731+
# multiprocessing.connection Authentication Handshake Protocol Description
732+
# (as documented for reference after reading the existing code)
733+
# =============================================================================
734+
#
735+
# On Windows: native pipes with "overlapped IO" are used to send the bytes,
736+
# instead of the length prefix SIZE scheme described below. (ie: the OS deals
737+
# with message sizes for us)
738+
#
739+
# Protocol error behaviors:
740+
#
741+
# On POSIX, any failure to receive the length prefix into SIZE, for SIZE greater
742+
# than the requested maxsize to receive, or receiving fewer than SIZE bytes
743+
# results in the connection being closed and auth to fail.
744+
#
745+
# On Windows, receiving too few bytes is never a low level _recv_bytes read
746+
# error, receiving too many will trigger an error only if receive maxsize
747+
# value was larger than 128 OR the if the data arrived in smaller pieces.
748+
#
749+
# Serving side Client side
750+
# ------------------------------ ---------------------------------------
751+
# 0. Open a connection on the pipe.
752+
# 1. Accept connection.
753+
# 2. New random 20 bytes -> MESSAGE
754+
# 3. send 4 byte length (net order)
755+
# prefix followed by:
756+
# b'#CHALLENGE#' + MESSAGE
757+
# 4. Receive 4 bytes, parse as network byte
758+
# order integer. If it is -1, receive an
759+
# additional 8 bytes, parse that as network
760+
# byte order. The result is the length of
761+
# the data that follows -> SIZE.
762+
# 5. Receive min(SIZE, 256) bytes -> M1
763+
# 6. Assert that M1 starts with:
764+
# b'#CHALLENGE#'
765+
# 7. Strip that prefix from M1 into -> M2
766+
# 8. Compute HMAC-MD5 of AUTHKEY, M2 -> C_DIGEST
767+
# 9. Send 4 byte length prefix (net order)
768+
# followed by C_DIGEST bytes.
769+
# 10. Compute HMAC-MD5 of AUTHKEY,
770+
# MESSAGE into -> M_DIGEST.
771+
# 11. Receive 4 or 4+8 byte length
772+
# prefix (#4 dance) -> SIZE.
773+
# 12. Receive min(SIZE, 256) -> C_D.
774+
# 13. Compare M_DIGEST == C_D:
775+
# 14a: Match? Send length prefix &
776+
# b'#WELCOME#'
777+
# <- RETURN
778+
# 14b: Mismatch? Send len prefix &
779+
# b'#FAILURE#'
780+
# <- CLOSE & AuthenticationError
781+
# 15. Receive 4 or 4+8 byte length prefix (net
782+
# order) again as in #4 into -> SIZE.
783+
# 16. Receive min(SIZE, 256) bytes -> M3.
784+
# 17. Compare M3 == b'#WELCOME#':
785+
# 17a. Match? <- RETURN
786+
# 17b. Mismatch? <- CLOSE & AuthenticationError
787+
#
788+
# If this RETURNed, the connection remains open: it has been authenticated.
789+
#
790+
# Length prefixes are used consistently even though every step so far has
791+
# always been a singular specific fixed length. This may help us evolve
792+
# the protocol in the future without breaking backwards compatibility.
793+
#
794+
# Similarly the initial challenge message from the serving side has always
795+
# been 20 bytes, but clients can accept a 100+ so using the length of the
796+
# opening challenge message as an indicator of protocol version may work.
797+
798+
731799
def deliver_challenge(connection, authkey):
732800
import hmac
733801
if not isinstance(authkey, bytes):

0 commit comments

Comments
 (0)