Skip to content

Commit fbd11f3

Browse files
authored
gh-92658: Add Hyper-V socket support (GH-92755)
1 parent 5115a16 commit fbd11f3

File tree

6 files changed

+256
-1
lines changed

6 files changed

+256
-1
lines changed

Doc/library/socket.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,29 @@ created. Socket addresses are represented as follows:
225225

226226
.. versionadded:: 3.9
227227

228+
- :const:`AF_HYPERV` is a Windows-only socket based interface for communicating
229+
with Hyper-V hosts and guests. The address family is represented as a
230+
``(vm_id, service_id)`` tuple where the ``vm_id`` and ``service_id`` are
231+
UUID strings.
232+
233+
The ``vm_id`` is the virtual machine identifier or a set of known VMID values
234+
if the target is not a specific virtual machine. Known VMID constants
235+
defined on ``socket`` are:
236+
237+
- ``HV_GUID_ZERO``
238+
- ``HV_GUID_BROADCAST``
239+
- ``HV_GUID_WILDCARD`` - Used to bind on itself and accept connections from
240+
all partitions.
241+
- ``HV_GUID_CHILDREN`` - Used to bind on itself and accept connection from
242+
child partitions.
243+
- ``HV_GUID_LOOPBACK`` - Used as a target to itself.
244+
- ``HV_GUID_PARENT`` - When used as a bind accepts connection from the parent
245+
partition. When used as an address target it will connect to the parent parition.
246+
247+
The ``service_id`` is the service identifier of the registered service.
248+
249+
.. versionadded:: 3.12
250+
228251
If you use a hostname in the *host* portion of IPv4/v6 socket address, the
229252
program may show a nondeterministic behavior, as Python uses the first address
230253
returned from the DNS resolution. The socket address will be resolved
@@ -589,6 +612,26 @@ Constants
589612

590613
.. availability:: Linux >= 3.9
591614

615+
.. data:: AF_HYPERV
616+
HV_PROTOCOL_RAW
617+
HVSOCKET_CONNECT_TIMEOUT
618+
HVSOCKET_CONNECT_TIMEOUT_MAX
619+
HVSOCKET_CONTAINER_PASSTHRU
620+
HVSOCKET_CONNECTED_SUSPEND
621+
HVSOCKET_ADDRESS_FLAG_PASSTHRU
622+
HV_GUID_ZERO
623+
HV_GUID_WILDCARD
624+
HV_GUID_BROADCAST
625+
HV_GUID_CHILDREN
626+
HV_GUID_LOOPBACK
627+
HV_GUID_LOOPBACK
628+
629+
Constants for Windows Hyper-V sockets for host/guest communications.
630+
631+
.. availability:: Windows.
632+
633+
.. versionadded:: 3.12
634+
592635
Functions
593636
^^^^^^^^^
594637

Lib/test/test_socket.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import signal
2323
import math
2424
import pickle
25+
import re
2526
import struct
2627
import random
2728
import shutil
@@ -143,6 +144,17 @@ def _have_socket_bluetooth():
143144
return True
144145

145146

147+
def _have_socket_hyperv():
148+
"""Check whether AF_HYPERV sockets are supported on this host."""
149+
try:
150+
s = socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW)
151+
except (AttributeError, OSError):
152+
return False
153+
else:
154+
s.close()
155+
return True
156+
157+
146158
@contextlib.contextmanager
147159
def socket_setdefaulttimeout(timeout):
148160
old_timeout = socket.getdefaulttimeout()
@@ -171,6 +183,8 @@ def socket_setdefaulttimeout(timeout):
171183

172184
HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth()
173185

186+
HAVE_SOCKET_HYPERV = _have_socket_hyperv()
187+
174188
# Size in bytes of the int type
175189
SIZEOF_INT = array.array("i").itemsize
176190

@@ -2459,6 +2473,60 @@ def testCreateScoSocket(self):
24592473
pass
24602474

24612475

2476+
@unittest.skipUnless(HAVE_SOCKET_HYPERV,
2477+
'Hyper-V sockets required for this test.')
2478+
class BasicHyperVTest(unittest.TestCase):
2479+
2480+
def testHyperVConstants(self):
2481+
socket.HVSOCKET_CONNECT_TIMEOUT
2482+
socket.HVSOCKET_CONNECT_TIMEOUT_MAX
2483+
socket.HVSOCKET_CONTAINER_PASSTHRU
2484+
socket.HVSOCKET_CONNECTED_SUSPEND
2485+
socket.HVSOCKET_ADDRESS_FLAG_PASSTHRU
2486+
socket.HV_GUID_ZERO
2487+
socket.HV_GUID_WILDCARD
2488+
socket.HV_GUID_BROADCAST
2489+
socket.HV_GUID_CHILDREN
2490+
socket.HV_GUID_LOOPBACK
2491+
socket.HV_GUID_LOOPBACK
2492+
2493+
def testCreateHyperVSocketWithUnknownProtoFailure(self):
2494+
expected = "A protocol was specified in the socket function call " \
2495+
"that does not support the semantics of the socket type requested"
2496+
with self.assertRaisesRegex(OSError, expected):
2497+
socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM)
2498+
2499+
def testCreateHyperVSocketAddrNotTupleFailure(self):
2500+
expected = "connect(): AF_HYPERV address must be tuple, not str"
2501+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2502+
with self.assertRaisesRegex(TypeError, re.escape(expected)):
2503+
s.connect(socket.HV_GUID_ZERO)
2504+
2505+
def testCreateHyperVSocketAddrNotTupleOf2StrsFailure(self):
2506+
expected = "AF_HYPERV address must be a str tuple (vm_id, service_id)"
2507+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2508+
with self.assertRaisesRegex(TypeError, re.escape(expected)):
2509+
s.connect((socket.HV_GUID_ZERO,))
2510+
2511+
def testCreateHyperVSocketAddrNotTupleOfStrsFailure(self):
2512+
expected = "AF_HYPERV address must be a str tuple (vm_id, service_id)"
2513+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2514+
with self.assertRaisesRegex(TypeError, re.escape(expected)):
2515+
s.connect((1, 2))
2516+
2517+
def testCreateHyperVSocketAddrVmIdNotValidUUIDFailure(self):
2518+
expected = "connect(): AF_HYPERV address vm_id is not a valid UUID string"
2519+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2520+
with self.assertRaisesRegex(ValueError, re.escape(expected)):
2521+
s.connect(("00", socket.HV_GUID_ZERO))
2522+
2523+
def testCreateHyperVSocketAddrServiceIdNotValidUUIDFailure(self):
2524+
expected = "connect(): AF_HYPERV address service_id is not a valid UUID string"
2525+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2526+
with self.assertRaisesRegex(ValueError, re.escape(expected)):
2527+
s.connect((socket.HV_GUID_ZERO, "00"))
2528+
2529+
24622530
class BasicTCPTest(SocketConnectedTest):
24632531

24642532
def __init__(self, methodName='runTest'):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for connecting and binding to Hyper-V sockets on Windows Hyper-V hosts and guests.

Modules/socketmodule.c

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ shutdown(how) -- shut down traffic in one or both directions\n\
271271
# include <fcntl.h>
272272
# endif
273273

274+
/* Helpers needed for AF_HYPERV */
275+
# include <Rpc.h>
276+
274277
/* Macros based on the IPPROTO enum, see: https://bugs.python.org/issue29515 */
275278
#ifdef MS_WINDOWS
276279
#define IPPROTO_ICMP IPPROTO_ICMP
@@ -1579,6 +1582,35 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
15791582
}
15801583
#endif /* HAVE_SOCKADDR_ALG */
15811584

1585+
#ifdef AF_HYPERV
1586+
case AF_HYPERV:
1587+
{
1588+
SOCKADDR_HV *a = (SOCKADDR_HV *) addr;
1589+
1590+
wchar_t *guidStr;
1591+
RPC_STATUS res = UuidToStringW(&a->VmId, &guidStr);
1592+
if (res != RPC_S_OK) {
1593+
PyErr_SetFromWindowsErr(res);
1594+
return 0;
1595+
}
1596+
PyObject *vmId = PyUnicode_FromWideChar(guidStr, -1);
1597+
res = RpcStringFreeW(&guidStr);
1598+
assert(res == RPC_S_OK);
1599+
1600+
res = UuidToStringW(&a->ServiceId, &guidStr);
1601+
if (res != RPC_S_OK) {
1602+
Py_DECREF(vmId);
1603+
PyErr_SetFromWindowsErr(res);
1604+
return 0;
1605+
}
1606+
PyObject *serviceId = PyUnicode_FromWideChar(guidStr, -1);
1607+
res = RpcStringFreeW(&guidStr);
1608+
assert(res == RPC_S_OK);
1609+
1610+
return Py_BuildValue("NN", vmId, serviceId);
1611+
}
1612+
#endif /* AF_HYPERV */
1613+
15821614
/* More cases here... */
15831615

15841616
default:
@@ -2375,6 +2407,76 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
23752407
return 1;
23762408
}
23772409
#endif /* HAVE_SOCKADDR_ALG */
2410+
#ifdef AF_HYPERV
2411+
case AF_HYPERV:
2412+
{
2413+
switch (s->sock_proto) {
2414+
case HV_PROTOCOL_RAW:
2415+
{
2416+
PyObject *vm_id_obj = NULL;
2417+
PyObject *service_id_obj = NULL;
2418+
2419+
SOCKADDR_HV *addr = &addrbuf->hv;
2420+
2421+
memset(addr, 0, sizeof(*addr));
2422+
addr->Family = AF_HYPERV;
2423+
2424+
if (!PyTuple_Check(args)) {
2425+
PyErr_Format(PyExc_TypeError,
2426+
"%s(): AF_HYPERV address must be tuple, not %.500s",
2427+
caller, Py_TYPE(args)->tp_name);
2428+
return 0;
2429+
}
2430+
if (!PyArg_ParseTuple(args,
2431+
"UU;AF_HYPERV address must be a str tuple (vm_id, service_id)",
2432+
&vm_id_obj, &service_id_obj))
2433+
{
2434+
return 0;
2435+
}
2436+
2437+
wchar_t *guid_str = PyUnicode_AsWideCharString(vm_id_obj, NULL);
2438+
if (guid_str == NULL) {
2439+
PyErr_Format(PyExc_ValueError,
2440+
"%s(): AF_HYPERV address vm_id is not a valid UUID string",
2441+
caller);
2442+
return 0;
2443+
}
2444+
RPC_STATUS rc = UuidFromStringW(guid_str, &addr->VmId);
2445+
PyMem_Free(guid_str);
2446+
if (rc != RPC_S_OK) {
2447+
PyErr_Format(PyExc_ValueError,
2448+
"%s(): AF_HYPERV address vm_id is not a valid UUID string",
2449+
caller);
2450+
return 0;
2451+
}
2452+
2453+
guid_str = PyUnicode_AsWideCharString(service_id_obj, NULL);
2454+
if (guid_str == NULL) {
2455+
PyErr_Format(PyExc_ValueError,
2456+
"%s(): AF_HYPERV address service_id is not a valid UUID string",
2457+
caller);
2458+
return 0;
2459+
}
2460+
rc = UuidFromStringW(guid_str, &addr->ServiceId);
2461+
PyMem_Free(guid_str);
2462+
if (rc != RPC_S_OK) {
2463+
PyErr_Format(PyExc_ValueError,
2464+
"%s(): AF_HYPERV address service_id is not a valid UUID string",
2465+
caller);
2466+
return 0;
2467+
}
2468+
2469+
*len_ret = sizeof(*addr);
2470+
return 1;
2471+
}
2472+
default:
2473+
PyErr_Format(PyExc_OSError,
2474+
"%s(): unsupported AF_HYPERV protocol: %d",
2475+
caller, s->sock_proto);
2476+
return 0;
2477+
}
2478+
}
2479+
#endif /* AF_HYPERV */
23782480

23792481
/* More cases here... */
23802482

@@ -2524,6 +2626,13 @@ getsockaddrlen(PySocketSockObject *s, socklen_t *len_ret)
25242626
return 1;
25252627
}
25262628
#endif /* HAVE_SOCKADDR_ALG */
2629+
#ifdef AF_HYPERV
2630+
case AF_HYPERV:
2631+
{
2632+
*len_ret = sizeof (SOCKADDR_HV);
2633+
return 1;
2634+
}
2635+
#endif /* AF_HYPERV */
25272636

25282637
/* More cases here... */
25292638

@@ -7351,6 +7460,28 @@ PyInit__socket(void)
73517460
/* Linux LLC */
73527461
PyModule_AddIntMacro(m, AF_LLC);
73537462
#endif
7463+
#ifdef AF_HYPERV
7464+
/* Hyper-V sockets */
7465+
PyModule_AddIntMacro(m, AF_HYPERV);
7466+
7467+
/* for proto */
7468+
PyModule_AddIntMacro(m, HV_PROTOCOL_RAW);
7469+
7470+
/* for setsockopt() */
7471+
PyModule_AddIntMacro(m, HVSOCKET_CONNECT_TIMEOUT);
7472+
PyModule_AddIntMacro(m, HVSOCKET_CONNECT_TIMEOUT_MAX);
7473+
PyModule_AddIntMacro(m, HVSOCKET_CONTAINER_PASSTHRU);
7474+
PyModule_AddIntMacro(m, HVSOCKET_CONNECTED_SUSPEND);
7475+
PyModule_AddIntMacro(m, HVSOCKET_ADDRESS_FLAG_PASSTHRU);
7476+
7477+
/* for bind() or connect() */
7478+
PyModule_AddStringConstant(m, "HV_GUID_ZERO", "00000000-0000-0000-0000-000000000000");
7479+
PyModule_AddStringConstant(m, "HV_GUID_WILDCARD", "00000000-0000-0000-0000-000000000000");
7480+
PyModule_AddStringConstant(m, "HV_GUID_BROADCAST", "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
7481+
PyModule_AddStringConstant(m, "HV_GUID_CHILDREN", "90DB8B89-0D35-4F79-8CE9-49EA0AC8B7CD");
7482+
PyModule_AddStringConstant(m, "HV_GUID_LOOPBACK", "E0E16197-DD56-4A10-9195-5EE7A155A838");
7483+
PyModule_AddStringConstant(m, "HV_GUID_PARENT", "A42E7CDA-D03F-480C-9CC2-A4DE20ABB878");
7484+
#endif /* AF_HYPERV */
73547485

73557486
#ifdef USE_BLUETOOTH
73567487
PyModule_AddIntMacro(m, AF_BLUETOOTH);

Modules/socketmodule.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ struct SOCKADDR_BTH_REDEF {
7676
# else
7777
typedef int socklen_t;
7878
# endif /* IPPROTO_IPV6 */
79+
80+
/* Remove ifdef once Py_WINVER >= 0x0604
81+
* socket.h only defines AF_HYPERV if _WIN32_WINNT is at that level or higher
82+
* so for now it's just manually defined.
83+
*/
84+
# ifndef AF_HYPERV
85+
# define AF_HYPERV 34
86+
# endif
87+
# include <hvsocket.h>
7988
#endif /* MS_WINDOWS */
8089

8190
#ifdef HAVE_SYS_UN_H
@@ -288,6 +297,9 @@ typedef union sock_addr {
288297
#ifdef HAVE_LINUX_TIPC_H
289298
struct sockaddr_tipc tipc;
290299
#endif
300+
#ifdef AF_HYPERV
301+
SOCKADDR_HV hv;
302+
#endif
291303
} sock_addr_t;
292304

293305
/* The object holding a socket. It holds some extra information,

PCbuild/_socket.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
</PropertyGroup>
9494
<ItemDefinitionGroup>
9595
<Link>
96-
<AdditionalDependencies>ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
96+
<AdditionalDependencies>ws2_32.lib;iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
9797
</Link>
9898
</ItemDefinitionGroup>
9999
<ItemGroup>

0 commit comments

Comments
 (0)