From 8f96c9f8ed2a4795e34b333411451e24f28f74d2 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Wed, 29 May 2019 15:02:37 -0600 Subject: [PATCH] bpo-37007: Implement socket.if_nametoindex(), if_indextoname() and if_nameindex() on Windows (GH-13522) --- Doc/library/socket.rst | 15 ++++- Doc/whatsnew/3.8.rst | 4 ++ Lib/test/test_socket.py | 24 ++++---- .../2019-05-23-04-19-13.bpo-37007.d1SOtF.rst | 2 + Modules/socketmodule.c | 60 +++++++++++++++---- PCbuild/_socket.vcxproj | 2 +- 6 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-05-23-04-19-13.bpo-37007.d1SOtF.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 5be2b76113e..e0dbbb4c0d3 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1034,10 +1034,13 @@ The :mod:`socket` module also offers various network-related services: (index int, name string) tuples. :exc:`OSError` if the system call fails. - .. availability:: Unix. + .. availability:: Unix, Windows. .. versionadded:: 3.3 + .. versionchanged:: 3.8 + Windows support was added. + .. function:: if_nametoindex(if_name) @@ -1045,10 +1048,13 @@ The :mod:`socket` module also offers various network-related services: interface name. :exc:`OSError` if no interface with the given name exists. - .. availability:: Unix. + .. availability:: Unix, Windows. .. versionadded:: 3.3 + .. versionchanged:: 3.8 + Windows support was added. + .. function:: if_indextoname(if_index) @@ -1056,10 +1062,13 @@ The :mod:`socket` module also offers various network-related services: interface index number. :exc:`OSError` if no interface with the given index exists. - .. availability:: Unix. + .. availability:: Unix, Windows. .. versionadded:: 3.3 + .. versionchanged:: 3.8 + Windows support was added. + .. _socket-objects: diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 5cd9a8edc79..5ee9cf07eba 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -556,6 +556,10 @@ convenience functions to automate the necessary tasks usually involved when creating a server socket, including accepting both IPv4 and IPv6 connections on the same socket. (Contributed by Giampaolo Rodola in :issue:`17561`.) +The :func:`socket.if_nameindex()`, :func:`socket.if_nametoindex()`, and +:func:`socket.if_indextoname()` functions have been implemented on Windows. +(Contributed by Zackery Spytz in :issue:`37007`.) + shlex ---------- diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 0094cecb79c..74662cfeb32 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -973,16 +973,18 @@ def testInterfaceNameIndex(self): self.assertIsInstance(_name, str) self.assertEqual(name, _name) - @unittest.skipUnless(hasattr(socket, 'if_nameindex'), - 'socket.if_nameindex() not available.') - def testInvalidInterfaceNameIndex(self): - # test nonexistent interface index/name + @unittest.skipUnless(hasattr(socket, 'if_indextoname'), + 'socket.if_indextoname() not available.') + def testInvalidInterfaceIndexToName(self): self.assertRaises(OSError, socket.if_indextoname, 0) - self.assertRaises(OSError, socket.if_nametoindex, '_DEADBEEF') - # test with invalid values - self.assertRaises(TypeError, socket.if_nametoindex, 0) self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF') + @unittest.skipUnless(hasattr(socket, 'if_nametoindex'), + 'socket.if_nametoindex() not available.') + def testInvalidInterfaceNameToIndex(self): + self.assertRaises(TypeError, socket.if_nametoindex, 0) + self.assertRaises(OSError, socket.if_nametoindex, '_DEADBEEF') + @unittest.skipUnless(hasattr(sys, 'getrefcount'), 'test needs sys.getrefcount()') def testRefCountGetNameInfo(self): @@ -1638,9 +1640,7 @@ def test_getaddrinfo_ipv6_basic(self): self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, 0)) @unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') - @unittest.skipUnless( - hasattr(socket, 'if_nameindex'), - 'if_nameindex is not supported') + @unittest.skipIf(sys.platform == 'win32', 'does not work on Windows') @unittest.skipIf(AIX, 'Symbolic scope id does not work') def test_getaddrinfo_ipv6_scopeid_symbolic(self): # Just pick up any network interface (Linux, Mac OS X) @@ -1672,9 +1672,7 @@ def test_getaddrinfo_ipv6_scopeid_numeric(self): self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, ifindex)) @unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') - @unittest.skipUnless( - hasattr(socket, 'if_nameindex'), - 'if_nameindex is not supported') + @unittest.skipIf(sys.platform == 'win32', 'does not work on Windows') @unittest.skipIf(AIX, 'Symbolic scope id does not work') def test_getnameinfo_ipv6_scopeid_symbolic(self): # Just pick up any network interface. diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-23-04-19-13.bpo-37007.d1SOtF.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-23-04-19-13.bpo-37007.d1SOtF.rst new file mode 100644 index 00000000000..ac344a57c83 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-23-04-19-13.bpo-37007.d1SOtF.rst @@ -0,0 +1,2 @@ +Implement :func:`socket.if_nameindex()`, :func:`socket.if_nametoindex()`, and +:func:`socket.if_indextoname()` on Windows. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index ca4d760f8cb..ac1698c6c76 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -345,6 +345,8 @@ if_indextoname(index) -- return the corresponding interface name\n\ /* Provides the IsWindows7SP1OrGreater() function */ #include +// For if_nametoindex() and if_indextoname() +#include /* remove some flags on older version Windows during run-time. https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596.aspx */ @@ -6667,28 +6669,56 @@ Set the default timeout in seconds (float) for new socket objects.\n\ A value of None indicates that new socket objects have no timeout.\n\ When the socket module is first imported, the default is None."); -#ifdef HAVE_IF_NAMEINDEX +#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) /* Python API for getting interface indices and names */ static PyObject * socket_if_nameindex(PyObject *self, PyObject *arg) { - PyObject *list; + PyObject *list = PyList_New(0); + if (list == NULL) { + return NULL; + } +#ifdef MS_WINDOWS + PMIB_IF_TABLE2 tbl; + int ret; + if ((ret = GetIfTable2Ex(MibIfTableRaw, &tbl)) != NO_ERROR) { + Py_DECREF(list); + // ret is used instead of GetLastError() + return PyErr_SetFromWindowsErr(ret); + } + for (ULONG i = 0; i < tbl->NumEntries; ++i) { + MIB_IF_ROW2 r = tbl->Table[i]; + WCHAR buf[NDIS_IF_MAX_STRING_SIZE + 1]; + if ((ret = ConvertInterfaceLuidToNameW(&r.InterfaceLuid, buf, + Py_ARRAY_LENGTH(buf)))) { + Py_DECREF(list); + FreeMibTable(tbl); + // ret is used instead of GetLastError() + return PyErr_SetFromWindowsErr(ret); + } + PyObject *tuple = Py_BuildValue("Iu", r.InterfaceIndex, buf); + if (tuple == NULL || PyList_Append(list, tuple) == -1) { + Py_XDECREF(tuple); + Py_DECREF(list); + FreeMibTable(tbl); + return NULL; + } + Py_DECREF(tuple); + } + FreeMibTable(tbl); + return list; +#else int i; struct if_nameindex *ni; ni = if_nameindex(); if (ni == NULL) { + Py_DECREF(list); PyErr_SetFromErrno(PyExc_OSError); return NULL; } - list = PyList_New(0); - if (list == NULL) { - if_freenameindex(ni); - return NULL; - } - #ifdef _Py_MEMORY_SANITIZER __msan_unpoison(ni, sizeof(ni)); __msan_unpoison(&ni[0], sizeof(ni[0])); @@ -6720,6 +6750,7 @@ socket_if_nameindex(PyObject *self, PyObject *arg) if_freenameindex(ni); return list; +#endif } PyDoc_STRVAR(if_nameindex_doc, @@ -6731,8 +6762,11 @@ static PyObject * socket_if_nametoindex(PyObject *self, PyObject *args) { PyObject *oname; +#ifdef MS_WINDOWS + NET_IFINDEX index; +#else unsigned long index; - +#endif if (!PyArg_ParseTuple(args, "O&:if_nametoindex", PyUnicode_FSConverter, &oname)) return NULL; @@ -6756,7 +6790,11 @@ Returns the interface index corresponding to the interface name if_name."); static PyObject * socket_if_indextoname(PyObject *self, PyObject *arg) { +#ifdef MS_WINDOWS + NET_IFINDEX index; +#else unsigned long index; +#endif char name[IF_NAMESIZE + 1]; index = PyLong_AsUnsignedLong(arg); @@ -6776,7 +6814,7 @@ PyDoc_STRVAR(if_indextoname_doc, \n\ Returns the interface name corresponding to the interface index if_index."); -#endif /* HAVE_IF_NAMEINDEX */ +#endif // defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) #ifdef CMSG_LEN @@ -6898,7 +6936,7 @@ static PyMethodDef socket_methods[] = { METH_NOARGS, getdefaulttimeout_doc}, {"setdefaulttimeout", socket_setdefaulttimeout, METH_O, setdefaulttimeout_doc}, -#ifdef HAVE_IF_NAMEINDEX +#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) {"if_nameindex", socket_if_nameindex, METH_NOARGS, if_nameindex_doc}, {"if_nametoindex", socket_if_nametoindex, diff --git a/PCbuild/_socket.vcxproj b/PCbuild/_socket.vcxproj index 9498abf8fb5..8fd75f90e7e 100644 --- a/PCbuild/_socket.vcxproj +++ b/PCbuild/_socket.vcxproj @@ -93,7 +93,7 @@ - ws2_32.lib;%(AdditionalDependencies) + ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)