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 | ||||
| 
 | ||||
| - :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 | ||||
| program may show a nondeterministic behavior, as Python uses the first address | ||||
| returned from the DNS resolution.  The socket address will be resolved | ||||
|  | @ -589,6 +612,26 @@ Constants | |||
| 
 | ||||
|   .. 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 | ||||
| ^^^^^^^^^ | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ | |||
| import signal | ||||
| import math | ||||
| import pickle | ||||
| import re | ||||
| import struct | ||||
| import random | ||||
| import shutil | ||||
|  | @ -143,6 +144,17 @@ def _have_socket_bluetooth(): | |||
|     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 | ||||
| def socket_setdefaulttimeout(timeout): | ||||
|     old_timeout = socket.getdefaulttimeout() | ||||
|  | @ -171,6 +183,8 @@ def socket_setdefaulttimeout(timeout): | |||
| 
 | ||||
| HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() | ||||
| 
 | ||||
| HAVE_SOCKET_HYPERV = _have_socket_hyperv() | ||||
| 
 | ||||
| # Size in bytes of the int type | ||||
| SIZEOF_INT = array.array("i").itemsize | ||||
| 
 | ||||
|  | @ -2459,6 +2473,60 @@ def testCreateScoSocket(self): | |||
|             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): | ||||
| 
 | ||||
|     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> | ||||
| # endif | ||||
| 
 | ||||
| /* Helpers needed for AF_HYPERV */ | ||||
| # include <Rpc.h> | ||||
| 
 | ||||
| /* Macros based on the IPPROTO enum, see: https://bugs.python.org/issue29515 */ | ||||
| #ifdef MS_WINDOWS | ||||
| #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 */ | ||||
| 
 | ||||
| #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... */ | ||||
| 
 | ||||
|     default: | ||||
|  | @ -2375,6 +2407,76 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, | |||
|         return 1; | ||||
|     } | ||||
| #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... */ | ||||
| 
 | ||||
|  | @ -2524,6 +2626,13 @@ getsockaddrlen(PySocketSockObject *s, socklen_t *len_ret) | |||
|         return 1; | ||||
|     } | ||||
| #endif /* HAVE_SOCKADDR_ALG */ | ||||
| #ifdef AF_HYPERV | ||||
|     case AF_HYPERV: | ||||
|     { | ||||
|         *len_ret = sizeof (SOCKADDR_HV); | ||||
|         return 1; | ||||
|     } | ||||
| #endif /* AF_HYPERV */ | ||||
| 
 | ||||
|     /* More cases here... */ | ||||
| 
 | ||||
|  | @ -7351,6 +7460,28 @@ PyInit__socket(void) | |||
|     /* Linux LLC */ | ||||
|     PyModule_AddIntMacro(m, AF_LLC); | ||||
| #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 | ||||
|     PyModule_AddIntMacro(m, AF_BLUETOOTH); | ||||
|  |  | |||
|  | @ -76,6 +76,15 @@ struct SOCKADDR_BTH_REDEF { | |||
| # else | ||||
| typedef int socklen_t; | ||||
| # 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 */ | ||||
| 
 | ||||
| #ifdef HAVE_SYS_UN_H | ||||
|  | @ -288,6 +297,9 @@ typedef union sock_addr { | |||
| #ifdef HAVE_LINUX_TIPC_H | ||||
|     struct sockaddr_tipc tipc; | ||||
| #endif | ||||
| #ifdef AF_HYPERV | ||||
|     SOCKADDR_HV hv; | ||||
| #endif | ||||
| } sock_addr_t; | ||||
| 
 | ||||
| /* The object holding a socket.  It holds some extra information,
 | ||||
|  |  | |||
|  | @ -93,7 +93,7 @@ | |||
|   </PropertyGroup> | ||||
|   <ItemDefinitionGroup> | ||||
|     <Link> | ||||
|       <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||
|       <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||
|     </Link> | ||||
|   </ItemDefinitionGroup> | ||||
|   <ItemGroup> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jordan Borean
						Jordan Borean