[Python-checkins] gh-92658: Add Hyper-V socket support (GH-92755)

zooba webhook-mailer at python.org
Tue May 24 16:37:16 EDT 2022


https://github.com/python/cpython/commit/fbd11f3edd6d2034774d802e048261e613ffcbf5
commit: fbd11f3edd6d2034774d802e048261e613ffcbf5
branch: main
author: Jordan Borean <jborean93 at gmail.com>
committer: zooba <steve.dower at microsoft.com>
date: 2022-05-24T21:37:06+01:00
summary:

gh-92658: Add Hyper-V socket support (GH-92755)

files:
A Misc/NEWS.d/next/Core and Builtins/2022-05-13-00-57-18.gh-issue-92658.YdhFE2.rst
M Doc/library/socket.rst
M Lib/test/test_socket.py
M Modules/socketmodule.c
M Modules/socketmodule.h
M PCbuild/_socket.vcxproj

diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index 2d25646134a3d..1b801955dff17 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 1aaa9e44f90c6..c98190382416c 100755
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -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
 
 
+ at 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 0000000000000..887b3d6159660
--- /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 f376513fead1b..0bc99011b2c4e 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 1b35b11cdee6a..66d9ccf24e3fd 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 8fd75f90e7ee1..78fa4d6729abb 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>



More information about the Python-checkins mailing list