Skip to content

Commit 47699cd

Browse files
committed
Add Hyper-V socket support
1 parent 4e28377 commit 47699cd

File tree

5 files changed

+190
-0
lines changed

5 files changed

+190
-0
lines changed

Doc/library/socket.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,33 @@ 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 the
231+
little endian byte representation of a ``uuid.UUID`` object.
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 values are:
235+
236+
- ``HV_GUID_ZERO 00000000-0000-0000-0000-000000000000`` - Used to bind on
237+
itself and accept connections from all partitions.
238+
- ``HV_GUID_BROADCAST FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF``
239+
- ``HV_GUID_CHILDREN 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd`` - Used to bind on
240+
itself and accept connection from child partitions.
241+
- ``HV_GUID_LOOPBACK e0e16197-dd56-4a10-9195-5ee7a155a838`` - Used as a
242+
target to itself.
243+
- ``HV_GUID_PARENT a42e7cda-d03f-480c-9cc2-a4de20abb878`` - When used as a
244+
bind accepts connection from the parent partition. When used as an address
245+
target it will connect to the parent parition.
246+
247+
The ``service_id`` is the registered service identifier of the registered
248+
service.
249+
250+
The easily get the byte value do
251+
``uuid.UUID("eee5f691-5210-47e8-bbc9-7198bed79b77").bytes_le``.
252+
253+
.. versionadded:: 3.12
254+
228255
If you use a hostname in the *host* portion of IPv4/v6 socket address, the
229256
program may show a nondeterministic behavior, as Python uses the first address
230257
returned from the DNS resolution. The socket address will be resolved
@@ -589,6 +616,16 @@ Constants
589616

590617
.. availability:: Linux >= 3.9
591618

619+
.. data:: AF_HYPERV
620+
HV_PROTOCOL_RAW
621+
HVSOCKET_*
622+
623+
Constants for Windows Hyper-V sockets for host/guest communications.
624+
625+
.. availability:: Windows.
626+
627+
.. versionadded:: 3.12
628+
592629
Functions
593630
^^^^^^^^^
594631

Lib/test/test_socket.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ def _have_socket_bluetooth():
143143
return True
144144

145145

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

172183
HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth()
173184

185+
HAVE_SOCKET_HYPERV = _have_socket_hyperv()
186+
174187
# Size in bytes of the int type
175188
SIZEOF_INT = array.array("i").itemsize
176189

@@ -2459,6 +2472,41 @@ def testCreateScoSocket(self):
24592472
pass
24602473

24612474

2475+
@unittest.skipUnless(HAVE_SOCKET_HYPERV,
2476+
'Hyper-V sockets required for this test.')
2477+
class BasicHyperVTest(unittest.TestCase):
2478+
2479+
def testHyperVConstants(self):
2480+
socket.HVSOCKET_CONNECT_TIMEOUT
2481+
socket.HVSOCKET_CONNECT_TIMEOUT_MAX
2482+
socket.HVSOCKET_CONTAINER_PASSTHRU
2483+
socket.HVSOCKET_CONNECTED_SUSPEND
2484+
socket.HVSOCKET_ADDRESS_FLAG_PASSTHRU
2485+
2486+
def testCreateHyperVSocketWithUnknownProtoFailure(self):
2487+
self.assertRaises(OSError, socket.socket, socket.AF_HYPERV, socket.SOCK_STREAM)
2488+
2489+
def testCreateHyperVSocketAddrNotTupleFailure(self):
2490+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2491+
self.assertRaises(TypeError, s.connect, b"\x00" * 16)
2492+
2493+
def testCreateHyperVSocketAddrNotTupleOf2BytesFailure(self):
2494+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2495+
self.assertRaises(TypeError, s.connect, (b"\x00" * 16,))
2496+
2497+
def testCreateHyperVSocketAddrNotTupleOfBytesFailure(self):
2498+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2499+
self.assertRaises(TypeError, s.connect, (1, 2))
2500+
2501+
def testCreateHyperVSocketAddrVmIdNotCorrectLengthFailure(self):
2502+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2503+
self.assertRaises(TypeError, s.connect, (b"\x00", b"\x00" * 16))
2504+
2505+
def testCreateHyperVSocketAddrServiceIdNotCorrectLengthFailure(self):
2506+
with socket.socket(socket.AF_HYPERV, socket.SOCK_STREAM, socket.HV_PROTOCOL_RAW) as s:
2507+
self.assertRaises(TypeError, s.connect, (b"\x00" * 16, b"\x00"))
2508+
2509+
24622510
class BasicTCPTest(SocketConnectedTest):
24632511

24642512
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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,18 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
15791579
}
15801580
#endif /* HAVE_SOCKADDR_ALG */
15811581

1582+
#ifdef AF_HYPERV
1583+
case AF_HYPERV:
1584+
{
1585+
SOCKADDR_HV *a = (SOCKADDR_HV *) addr;
1586+
return Py_BuildValue("y#y#",
1587+
a->VmId,
1588+
sizeof(GUID),
1589+
a->ServiceId,
1590+
sizeof(GUID));
1591+
}
1592+
#endif /* AF_HYPERV */
1593+
15821594
/* More cases here... */
15831595

15841596
default:
@@ -2375,6 +2387,66 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
23752387
return 1;
23762388
}
23772389
#endif /* HAVE_SOCKADDR_ALG */
2390+
#ifdef AF_HYPERV
2391+
case AF_HYPERV:
2392+
{
2393+
switch (s->sock_proto) {
2394+
case HV_PROTOCOL_RAW:
2395+
{
2396+
GUID *vm_id;
2397+
Py_ssize_t vm_id_len = 0;
2398+
2399+
GUID *service_id;
2400+
Py_ssize_t service_id_len = 0;
2401+
2402+
SOCKADDR_HV *addr = &addrbuf->hv;
2403+
2404+
memset(addr, 0, sizeof(*addr));
2405+
addr->Family = AF_HYPERV;
2406+
2407+
if (!PyTuple_Check(args)) {
2408+
PyErr_Format(PyExc_TypeError,
2409+
"%s(): AF_HYPERV address must be tuple, "
2410+
"not %.500s",
2411+
caller, Py_TYPE(args)->tp_name);
2412+
return 0;
2413+
}
2414+
if (!PyArg_ParseTuple(args,
2415+
"y#y#;AF_HYPERV address must be a tuple "
2416+
"(vm_id, service_id)",
2417+
&vm_id, &vm_id_len, &service_id,
2418+
&service_id_len))
2419+
{
2420+
return 0;
2421+
}
2422+
if (vm_id_len != sizeof(GUID)) {
2423+
PyErr_Format(PyExc_TypeError,
2424+
"%s(): AF_HYPERV address vm_id must have a "
2425+
"length of %d",
2426+
caller, sizeof(GUID));
2427+
return 0;
2428+
}
2429+
if (service_id_len != sizeof(GUID)) {
2430+
PyErr_Format(PyExc_TypeError,
2431+
"%s(): AF_HYPERV address service_id must have a "
2432+
"length of %d",
2433+
caller, sizeof(GUID));
2434+
return 0;
2435+
}
2436+
2437+
addr->VmId = *vm_id;
2438+
addr->ServiceId = *service_id;
2439+
2440+
*len_ret = sizeof(*addr);
2441+
return 1;
2442+
}
2443+
default:
2444+
PyErr_Format(PyExc_OSError,
2445+
"%s(): unsupported AF_HYPERV protocol", caller);
2446+
return 0;
2447+
}
2448+
}
2449+
#endif /* AF_HYPERV */
23782450

23792451
/* More cases here... */
23802452

@@ -2524,6 +2596,13 @@ getsockaddrlen(PySocketSockObject *s, socklen_t *len_ret)
25242596
return 1;
25252597
}
25262598
#endif /* HAVE_SOCKADDR_ALG */
2599+
#ifdef AF_HYPERV
2600+
case AF_HYPERV:
2601+
{
2602+
*len_ret = sizeof (SOCKADDR_HV);
2603+
return 1;
2604+
}
2605+
#endif /* AF_HYPERV
25272606
25282607
/* More cases here... */
25292608

@@ -7351,6 +7430,20 @@ PyInit__socket(void)
73517430
/* Linux LLC */
73527431
PyModule_AddIntMacro(m, AF_LLC);
73537432
#endif
7433+
#ifdef AF_HYPERV
7434+
/* Hyper-V sockets */
7435+
PyModule_AddIntMacro(m, AF_HYPERV);
7436+
7437+
/* for proto */
7438+
PyModule_AddIntMacro(m, HV_PROTOCOL_RAW);
7439+
7440+
/* for setsockopt() */
7441+
PyModule_AddIntMacro(m, HVSOCKET_CONNECT_TIMEOUT);
7442+
PyModule_AddIntMacro(m, HVSOCKET_CONNECT_TIMEOUT_MAX);
7443+
PyModule_AddIntMacro(m, HVSOCKET_CONTAINER_PASSTHRU);
7444+
PyModule_AddIntMacro(m, HVSOCKET_CONNECTED_SUSPEND);
7445+
PyModule_AddIntMacro(m, HVSOCKET_ADDRESS_FLAG_PASSTHRU);
7446+
#endif /* AF_HYPERV */
73547447

73557448
#ifdef USE_BLUETOOTH
73567449
PyModule_AddIntMacro(m, AF_BLUETOOTH);

Modules/socketmodule.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ struct SOCKADDR_BTH_REDEF {
7676
# else
7777
typedef int socklen_t;
7878
# endif /* IPPROTO_IPV6 */
79+
80+
/* Future remove once Py_WINVER has been bumped to >=0x0604 */
81+
# ifndef AF_HYPERV
82+
# define AF_HYPERV 34
83+
# endif
84+
85+
/* FIXME: Should this have some sort of safe guard? */
86+
# include <hvsocket.h>
7987
#endif /* MS_WINDOWS */
8088

8189
#ifdef HAVE_SYS_UN_H
@@ -288,6 +296,9 @@ typedef union sock_addr {
288296
#ifdef HAVE_LINUX_TIPC_H
289297
struct sockaddr_tipc tipc;
290298
#endif
299+
#ifdef AF_HYPERV
300+
SOCKADDR_HV hv;
301+
#endif
291302
} sock_addr_t;
292303

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

0 commit comments

Comments
 (0)