mirror of https://github.com/python/cpython
gh-92658: Add Hyper-V socket support (GH-92755)
This commit is contained in:
parent
5115a16831
commit
fbd11f3edd
|
@ -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 @@ from weakref import proxy
|
||||||
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 @@ HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE")
|
||||||
|
|
||||||
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 @@ class BasicBluetoothTest(unittest.TestCase):
|
||||||
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…
Reference in New Issue