summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJordan Borean <jborean93@gmail.com>2022-05-24 20:37:06 (GMT)
committerGitHub <noreply@github.com>2022-05-24 20:37:06 (GMT)
commitfbd11f3edd6d2034774d802e048261e613ffcbf5 (patch)
tree23a08f9441e5a173240ec392c92be14dda2b0251
parent5115a1683154a1f2093aa7f2c6834e47d326cb7d (diff)
downloadcpython-fbd11f3edd6d2034774d802e048261e613ffcbf5.zip
cpython-fbd11f3edd6d2034774d802e048261e613ffcbf5.tar.gz
cpython-fbd11f3edd6d2034774d802e048261e613ffcbf5.tar.bz2
gh-92658: Add Hyper-V socket support (GH-92755)
-rwxr-xr-xDoc/library/socket.rst43
-rwxr-xr-xLib/test/test_socket.py68
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-05-13-00-57-18.gh-issue-92658.YdhFE2.rst1
-rw-r--r--Modules/socketmodule.c131
-rw-r--r--Modules/socketmodule.h12
-rw-r--r--PCbuild/_socket.vcxproj2
6 files changed, 256 insertions, 1 deletions
diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index 2d25646..1b80195 100755
--- a/Doc/library/socket.rst
+++ b/Doc/library/socket.rst
@@ -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
^^^^^^^^^
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 1aaa9e4..c981903 100755
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -22,6 +22,7 @@ from weakref import proxy
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 @@ HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE")
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 @@ class BasicBluetoothTest(unittest.TestCase):
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'):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-13-00-57-18.gh-issue-92658.YdhFE2.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-13-00-57-18.gh-issue-92658.YdhFE2.rst
new file mode 100644
index 0000000..887b3d6
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-13-00-57-18.gh-issue-92658.YdhFE2.rst
@@ -0,0 +1 @@
+Add support for connecting and binding to Hyper-V sockets on Windows Hyper-V hosts and guests.
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index f376513..0bc9901 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -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);
diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h
index 1b35b11..66d9ccf 100644
--- a/Modules/socketmodule.h
+++ b/Modules/socketmodule.h
@@ -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,
diff --git a/PCbuild/_socket.vcxproj b/PCbuild/_socket.vcxproj
index 8fd75f9..78fa4d6 100644
--- a/PCbuild/_socket.vcxproj
+++ b/PCbuild/_socket.vcxproj
@@ -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>