mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	gh-92658: Add Hyper-V socket support (GH-92755)
This commit is contained in:
		
							parent
							
								
									5115a16831
								
							
						
					
					
						commit
						fbd11f3edd
					
				
					 6 changed files with 256 additions and 1 deletions
				
			
		|  | @ -225,6 +225,29 @@ created.  Socket addresses are represented as follows: | ||||||
| 
 | 
 | ||||||
|   .. versionadded:: 3.9 |   .. versionadded:: 3.9 | ||||||
| 
 | 
 | ||||||
|  | - :const:`AF_HYPERV` is a Windows-only socket based interface for communicating | ||||||
|  |   with Hyper-V hosts and guests. The address family is represented as a | ||||||
|  |   ``(vm_id, service_id)`` tuple where the ``vm_id`` and ``service_id`` are | ||||||
|  |   UUID strings. | ||||||
|  | 
 | ||||||
|  |   The ``vm_id`` is the virtual machine identifier or a set of known VMID values | ||||||
|  |   if the target is not a specific virtual machine. Known VMID constants | ||||||
|  |   defined on ``socket`` are: | ||||||
|  | 
 | ||||||
|  |   - ``HV_GUID_ZERO`` | ||||||
|  |   - ``HV_GUID_BROADCAST`` | ||||||
|  |   - ``HV_GUID_WILDCARD`` - Used to bind on itself and accept connections from | ||||||
|  |     all partitions. | ||||||
|  |   - ``HV_GUID_CHILDREN`` - Used to bind on itself and accept connection from | ||||||
|  |     child partitions. | ||||||
|  |   - ``HV_GUID_LOOPBACK`` - Used as a target to itself. | ||||||
|  |   - ``HV_GUID_PARENT`` - When used as a bind accepts connection from the parent | ||||||
|  |     partition. When used as an address target it will connect to the parent parition. | ||||||
|  | 
 | ||||||
|  |   The ``service_id`` is the service identifier of the registered service. | ||||||
|  | 
 | ||||||
|  |   .. versionadded:: 3.12 | ||||||
|  | 
 | ||||||
| If you use a hostname in the *host* portion of IPv4/v6 socket address, the | If you use a hostname in the *host* portion of IPv4/v6 socket address, the | ||||||
| program may show a nondeterministic behavior, as Python uses the first address | program may show a nondeterministic behavior, as Python uses the first address | ||||||
| returned from the DNS resolution.  The socket address will be resolved | returned from the DNS resolution.  The socket address will be resolved | ||||||
|  | @ -589,6 +612,26 @@ Constants | ||||||
| 
 | 
 | ||||||
|   .. availability:: Linux >= 3.9 |   .. availability:: Linux >= 3.9 | ||||||
| 
 | 
 | ||||||
|  | .. data:: AF_HYPERV | ||||||
|  |           HV_PROTOCOL_RAW | ||||||
|  |           HVSOCKET_CONNECT_TIMEOUT | ||||||
|  |           HVSOCKET_CONNECT_TIMEOUT_MAX | ||||||
|  |           HVSOCKET_CONTAINER_PASSTHRU | ||||||
|  |           HVSOCKET_CONNECTED_SUSPEND | ||||||
|  |           HVSOCKET_ADDRESS_FLAG_PASSTHRU | ||||||
|  |           HV_GUID_ZERO | ||||||
|  |           HV_GUID_WILDCARD | ||||||
|  |           HV_GUID_BROADCAST | ||||||
|  |           HV_GUID_CHILDREN | ||||||
|  |           HV_GUID_LOOPBACK | ||||||
|  |           HV_GUID_LOOPBACK | ||||||
|  | 
 | ||||||
|  |    Constants for Windows Hyper-V sockets for host/guest communications. | ||||||
|  | 
 | ||||||
|  |    .. availability:: Windows. | ||||||
|  | 
 | ||||||
|  |    .. versionadded:: 3.12 | ||||||
|  | 
 | ||||||
| Functions | Functions | ||||||
| ^^^^^^^^^ | ^^^^^^^^^ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ | ||||||
| import signal | import signal | ||||||
| import math | import math | ||||||
| import pickle | import pickle | ||||||
|  | import re | ||||||
| import struct | import struct | ||||||
| import random | import random | ||||||
| import shutil | import shutil | ||||||
|  | @ -143,6 +144,17 @@ def _have_socket_bluetooth(): | ||||||
|     return True |     return True | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def _have_socket_hyperv(): | ||||||
|  |     """Check whether AF_HYPERV sockets are supported on this host.""" | ||||||
|  |     try: | ||||||
|  |         s = socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) | ||||||
|  |     except (AttributeError, OSError): | ||||||
|  |         return False | ||||||
|  |     else: | ||||||
|  |         s.close() | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @contextlib.contextmanager | @contextlib.contextmanager | ||||||
| def socket_setdefaulttimeout(timeout): | def socket_setdefaulttimeout(timeout): | ||||||
|     old_timeout = socket.getdefaulttimeout() |     old_timeout = socket.getdefaulttimeout() | ||||||
|  | @ -171,6 +183,8 @@ def socket_setdefaulttimeout(timeout): | ||||||
| 
 | 
 | ||||||
| HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() | HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() | ||||||
| 
 | 
 | ||||||
|  | HAVE_SOCKET_HYPERV = _have_socket_hyperv() | ||||||
|  | 
 | ||||||
| # Size in bytes of the int type | # Size in bytes of the int type | ||||||
| SIZEOF_INT = array.array("i").itemsize | SIZEOF_INT = array.array("i").itemsize | ||||||
| 
 | 
 | ||||||
|  | @ -2459,6 +2473,60 @@ def testCreateScoSocket(self): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @unittest.skipUnless(HAVE_SOCKET_HYPERV, | ||||||
|  |                      'Hyper-V sockets required for this test.') | ||||||
|  | class BasicHyperVTest(unittest.TestCase): | ||||||
|  | 
 | ||||||
|  |     def testHyperVConstants(self): | ||||||
|  |         socket.HVSOCKET_CONNECT_TIMEOUT | ||||||
|  |         socket.HVSOCKET_CONNECT_TIMEOUT_MAX | ||||||
|  |         socket.HVSOCKET_CONTAINER_PASSTHRU | ||||||
|  |         socket.HVSOCKET_CONNECTED_SUSPEND | ||||||
|  |         socket.HVSOCKET_ADDRESS_FLAG_PASSTHRU | ||||||
|  |         socket.HV_GUID_ZERO | ||||||
|  |         socket.HV_GUID_WILDCARD | ||||||
|  |         socket.HV_GUID_BROADCAST | ||||||
|  |         socket.HV_GUID_CHILDREN | ||||||
|  |         socket.HV_GUID_LOOPBACK | ||||||
|  |         socket.HV_GUID_LOOPBACK | ||||||
|  | 
 | ||||||
|  |     def testCreateHyperVSocketWithUnknownProtoFailure(self): | ||||||
|  |         expected = "A protocol was specified in the socket function call " \ | ||||||
|  |             "that does not support the semantics of the socket type requested" | ||||||
|  |         with self.assertRaisesRegex(OSError, expected): | ||||||
|  |             socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM) | ||||||
|  | 
 | ||||||
|  |     def testCreateHyperVSocketAddrNotTupleFailure(self): | ||||||
|  |         expected = "connect(): AF_HYPERV address must be tuple, not str" | ||||||
|  |         with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s: | ||||||
|  |             with self.assertRaisesRegex(TypeError, re.escape(expected)): | ||||||
|  |                 s.connect(socket.HV_GUID_ZERO) | ||||||
|  | 
 | ||||||
|  |     def testCreateHyperVSocketAddrNotTupleOf2StrsFailure(self): | ||||||
|  |         expected = "AF_HYPERV address must be a str tuple (vm_id, service_id)" | ||||||
|  |         with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s: | ||||||
|  |             with self.assertRaisesRegex(TypeError, re.escape(expected)): | ||||||
|  |                 s.connect((socket.HV_GUID_ZERO,)) | ||||||
|  | 
 | ||||||
|  |     def testCreateHyperVSocketAddrNotTupleOfStrsFailure(self): | ||||||
|  |         expected = "AF_HYPERV address must be a str tuple (vm_id, service_id)" | ||||||
|  |         with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s: | ||||||
|  |             with self.assertRaisesRegex(TypeError, re.escape(expected)): | ||||||
|  |                 s.connect((1, 2)) | ||||||
|  | 
 | ||||||
|  |     def testCreateHyperVSocketAddrVmIdNotValidUUIDFailure(self): | ||||||
|  |         expected = "connect(): AF_HYPERV address vm_id is not a valid UUID string" | ||||||
|  |         with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s: | ||||||
|  |             with self.assertRaisesRegex(ValueError, re.escape(expected)): | ||||||
|  |                 s.connect(("00", socket.HV_GUID_ZERO)) | ||||||
|  | 
 | ||||||
|  |     def testCreateHyperVSocketAddrServiceIdNotValidUUIDFailure(self): | ||||||
|  |         expected = "connect(): AF_HYPERV address service_id is not a valid UUID string" | ||||||
|  |         with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s: | ||||||
|  |             with self.assertRaisesRegex(ValueError, re.escape(expected)): | ||||||
|  |                 s.connect((socket.HV_GUID_ZERO, "00")) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class BasicTCPTest(SocketConnectedTest): | class BasicTCPTest(SocketConnectedTest): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, methodName='runTest'): |     def __init__(self, methodName='runTest'): | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Add support for connecting and binding to Hyper-V sockets on Windows Hyper-V hosts and guests. | ||||||
|  | @ -271,6 +271,9 @@ shutdown(how) -- shut down traffic in one or both directions\n\ | ||||||
| #  include <fcntl.h> | #  include <fcntl.h> | ||||||
| # endif | # endif | ||||||
| 
 | 
 | ||||||
|  | /* Helpers needed for AF_HYPERV */ | ||||||
|  | # include <Rpc.h> | ||||||
|  | 
 | ||||||
| /* Macros based on the IPPROTO enum, see: https://bugs.python.org/issue29515 */ | /* Macros based on the IPPROTO enum, see: https://bugs.python.org/issue29515 */ | ||||||
| #ifdef MS_WINDOWS | #ifdef MS_WINDOWS | ||||||
| #define IPPROTO_ICMP IPPROTO_ICMP | #define IPPROTO_ICMP IPPROTO_ICMP | ||||||
|  | @ -1579,6 +1582,35 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto) | ||||||
|     } |     } | ||||||
| #endif /* HAVE_SOCKADDR_ALG */ | #endif /* HAVE_SOCKADDR_ALG */ | ||||||
| 
 | 
 | ||||||
|  | #ifdef AF_HYPERV | ||||||
|  |     case AF_HYPERV: | ||||||
|  |     { | ||||||
|  |         SOCKADDR_HV *a = (SOCKADDR_HV *) addr; | ||||||
|  | 
 | ||||||
|  |         wchar_t *guidStr; | ||||||
|  |         RPC_STATUS res = UuidToStringW(&a->VmId, &guidStr); | ||||||
|  |         if (res != RPC_S_OK) { | ||||||
|  |             PyErr_SetFromWindowsErr(res); | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |         PyObject *vmId = PyUnicode_FromWideChar(guidStr, -1); | ||||||
|  |         res = RpcStringFreeW(&guidStr); | ||||||
|  |         assert(res == RPC_S_OK); | ||||||
|  | 
 | ||||||
|  |         res = UuidToStringW(&a->ServiceId, &guidStr); | ||||||
|  |         if (res != RPC_S_OK) { | ||||||
|  |             Py_DECREF(vmId); | ||||||
|  |             PyErr_SetFromWindowsErr(res); | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |         PyObject *serviceId = PyUnicode_FromWideChar(guidStr, -1); | ||||||
|  |         res = RpcStringFreeW(&guidStr); | ||||||
|  |         assert(res == RPC_S_OK); | ||||||
|  | 
 | ||||||
|  |         return Py_BuildValue("NN", vmId, serviceId); | ||||||
|  |     } | ||||||
|  | #endif /* AF_HYPERV */ | ||||||
|  | 
 | ||||||
|     /* More cases here... */ |     /* More cases here... */ | ||||||
| 
 | 
 | ||||||
|     default: |     default: | ||||||
|  | @ -2375,6 +2407,76 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, | ||||||
|         return 1; |         return 1; | ||||||
|     } |     } | ||||||
| #endif /* HAVE_SOCKADDR_ALG */ | #endif /* HAVE_SOCKADDR_ALG */ | ||||||
|  | #ifdef AF_HYPERV | ||||||
|  |     case AF_HYPERV: | ||||||
|  |     { | ||||||
|  |         switch (s->sock_proto) { | ||||||
|  |         case HV_PROTOCOL_RAW: | ||||||
|  |         { | ||||||
|  |             PyObject *vm_id_obj = NULL; | ||||||
|  |             PyObject *service_id_obj = NULL; | ||||||
|  | 
 | ||||||
|  |             SOCKADDR_HV *addr = &addrbuf->hv; | ||||||
|  | 
 | ||||||
|  |             memset(addr, 0, sizeof(*addr)); | ||||||
|  |             addr->Family = AF_HYPERV; | ||||||
|  | 
 | ||||||
|  |             if (!PyTuple_Check(args)) { | ||||||
|  |                 PyErr_Format(PyExc_TypeError, | ||||||
|  |                     "%s(): AF_HYPERV address must be tuple, not %.500s", | ||||||
|  |                     caller, Py_TYPE(args)->tp_name); | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  |             if (!PyArg_ParseTuple(args, | ||||||
|  |                 "UU;AF_HYPERV address must be a str tuple (vm_id, service_id)", | ||||||
|  |                 &vm_id_obj, &service_id_obj)) | ||||||
|  |             { | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             wchar_t *guid_str = PyUnicode_AsWideCharString(vm_id_obj, NULL); | ||||||
|  |             if (guid_str == NULL) { | ||||||
|  |                 PyErr_Format(PyExc_ValueError, | ||||||
|  |                     "%s(): AF_HYPERV address vm_id is not a valid UUID string", | ||||||
|  |                     caller); | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  |             RPC_STATUS rc = UuidFromStringW(guid_str, &addr->VmId); | ||||||
|  |             PyMem_Free(guid_str); | ||||||
|  |             if (rc != RPC_S_OK) { | ||||||
|  |                 PyErr_Format(PyExc_ValueError, | ||||||
|  |                     "%s(): AF_HYPERV address vm_id is not a valid UUID string", | ||||||
|  |                     caller); | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             guid_str = PyUnicode_AsWideCharString(service_id_obj, NULL); | ||||||
|  |             if (guid_str == NULL) { | ||||||
|  |                 PyErr_Format(PyExc_ValueError, | ||||||
|  |                     "%s(): AF_HYPERV address service_id is not a valid UUID string", | ||||||
|  |                     caller); | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  |             rc = UuidFromStringW(guid_str, &addr->ServiceId); | ||||||
|  |             PyMem_Free(guid_str); | ||||||
|  |             if (rc != RPC_S_OK) { | ||||||
|  |                 PyErr_Format(PyExc_ValueError, | ||||||
|  |                     "%s(): AF_HYPERV address service_id is not a valid UUID string", | ||||||
|  |                     caller); | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             *len_ret = sizeof(*addr); | ||||||
|  |             return 1; | ||||||
|  |         } | ||||||
|  |         default: | ||||||
|  |             PyErr_Format(PyExc_OSError, | ||||||
|  |                 "%s(): unsupported AF_HYPERV protocol: %d", | ||||||
|  |                 caller, s->sock_proto); | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | #endif /* AF_HYPERV */ | ||||||
| 
 | 
 | ||||||
|     /* More cases here... */ |     /* More cases here... */ | ||||||
| 
 | 
 | ||||||
|  | @ -2524,6 +2626,13 @@ getsockaddrlen(PySocketSockObject *s, socklen_t *len_ret) | ||||||
|         return 1; |         return 1; | ||||||
|     } |     } | ||||||
| #endif /* HAVE_SOCKADDR_ALG */ | #endif /* HAVE_SOCKADDR_ALG */ | ||||||
|  | #ifdef AF_HYPERV | ||||||
|  |     case AF_HYPERV: | ||||||
|  |     { | ||||||
|  |         *len_ret = sizeof (SOCKADDR_HV); | ||||||
|  |         return 1; | ||||||
|  |     } | ||||||
|  | #endif /* AF_HYPERV */ | ||||||
| 
 | 
 | ||||||
|     /* More cases here... */ |     /* More cases here... */ | ||||||
| 
 | 
 | ||||||
|  | @ -7351,6 +7460,28 @@ PyInit__socket(void) | ||||||
|     /* Linux LLC */ |     /* Linux LLC */ | ||||||
|     PyModule_AddIntMacro(m, AF_LLC); |     PyModule_AddIntMacro(m, AF_LLC); | ||||||
| #endif | #endif | ||||||
|  | #ifdef AF_HYPERV | ||||||
|  |     /* Hyper-V sockets */ | ||||||
|  |     PyModule_AddIntMacro(m, AF_HYPERV); | ||||||
|  | 
 | ||||||
|  |     /* for proto */ | ||||||
|  |     PyModule_AddIntMacro(m, HV_PROTOCOL_RAW); | ||||||
|  | 
 | ||||||
|  |     /* for setsockopt() */ | ||||||
|  |     PyModule_AddIntMacro(m, HVSOCKET_CONNECT_TIMEOUT); | ||||||
|  |     PyModule_AddIntMacro(m, HVSOCKET_CONNECT_TIMEOUT_MAX); | ||||||
|  |     PyModule_AddIntMacro(m, HVSOCKET_CONTAINER_PASSTHRU); | ||||||
|  |     PyModule_AddIntMacro(m, HVSOCKET_CONNECTED_SUSPEND); | ||||||
|  |     PyModule_AddIntMacro(m, HVSOCKET_ADDRESS_FLAG_PASSTHRU); | ||||||
|  | 
 | ||||||
|  |     /* for bind() or connect() */ | ||||||
|  |     PyModule_AddStringConstant(m, "HV_GUID_ZERO", "00000000-0000-0000-0000-000000000000"); | ||||||
|  |     PyModule_AddStringConstant(m, "HV_GUID_WILDCARD", "00000000-0000-0000-0000-000000000000"); | ||||||
|  |     PyModule_AddStringConstant(m, "HV_GUID_BROADCAST", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"); | ||||||
|  |     PyModule_AddStringConstant(m, "HV_GUID_CHILDREN", "90DB8B89-0D35-4F79-8CE9-49EA0AC8B7CD"); | ||||||
|  |     PyModule_AddStringConstant(m, "HV_GUID_LOOPBACK", "E0E16197-DD56-4A10-9195-5EE7A155A838"); | ||||||
|  |     PyModule_AddStringConstant(m, "HV_GUID_PARENT", "A42E7CDA-D03F-480C-9CC2-A4DE20ABB878"); | ||||||
|  | #endif /* AF_HYPERV */ | ||||||
| 
 | 
 | ||||||
| #ifdef USE_BLUETOOTH | #ifdef USE_BLUETOOTH | ||||||
|     PyModule_AddIntMacro(m, AF_BLUETOOTH); |     PyModule_AddIntMacro(m, AF_BLUETOOTH); | ||||||
|  |  | ||||||
|  | @ -76,6 +76,15 @@ struct SOCKADDR_BTH_REDEF { | ||||||
| # else | # else | ||||||
| typedef int socklen_t; | typedef int socklen_t; | ||||||
| # endif /* IPPROTO_IPV6 */ | # endif /* IPPROTO_IPV6 */ | ||||||
|  | 
 | ||||||
|  | /* Remove ifdef once Py_WINVER >= 0x0604
 | ||||||
|  |  * socket.h only defines AF_HYPERV if _WIN32_WINNT is at that level or higher | ||||||
|  |  * so for now it's just manually defined. | ||||||
|  |  */ | ||||||
|  | # ifndef AF_HYPERV | ||||||
|  | #  define AF_HYPERV 34 | ||||||
|  | # endif | ||||||
|  | # include <hvsocket.h> | ||||||
| #endif /* MS_WINDOWS */ | #endif /* MS_WINDOWS */ | ||||||
| 
 | 
 | ||||||
| #ifdef HAVE_SYS_UN_H | #ifdef HAVE_SYS_UN_H | ||||||
|  | @ -288,6 +297,9 @@ typedef union sock_addr { | ||||||
| #ifdef HAVE_LINUX_TIPC_H | #ifdef HAVE_LINUX_TIPC_H | ||||||
|     struct sockaddr_tipc tipc; |     struct sockaddr_tipc tipc; | ||||||
| #endif | #endif | ||||||
|  | #ifdef AF_HYPERV | ||||||
|  |     SOCKADDR_HV hv; | ||||||
|  | #endif | ||||||
| } sock_addr_t; | } sock_addr_t; | ||||||
| 
 | 
 | ||||||
| /* The object holding a socket.  It holds some extra information,
 | /* The object holding a socket.  It holds some extra information,
 | ||||||
|  |  | ||||||
|  | @ -93,7 +93,7 @@ | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|   <ItemDefinitionGroup> |   <ItemDefinitionGroup> | ||||||
|     <Link> |     <Link> | ||||||
|       <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies> |       <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||||
|     </Link> |     </Link> | ||||||
|   </ItemDefinitionGroup> |   </ItemDefinitionGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jordan Borean
						Jordan Borean