From d8da22597e1e164156518d1fac8ba9f557324cba Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Thu, 23 May 2019 04:21:36 -0600 Subject: [PATCH 1/4] Implement socket.if_nametoindex() and socket.if_indextoname() on Windows --- Doc/library/socket.rst | 10 ++++++++-- Doc/whatsnew/3.8.rst | 4 ++++ Lib/test/test_socket.py | 16 +++++++++------- .../2019-05-23-04-19-13.bpo-37007.d1SOtF.rst | 2 ++ Modules/socketmodule.c | 17 +++++++++++++++-- PCbuild/_socket.vcxproj | 2 +- 6 files changed, 39 insertions(+), 12 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 379633a3b6052b..2d47ddea433b07 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1025,10 +1025,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) @@ -1036,10 +1039,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 b91f7bca63c9f2..5f1129481fa2d6 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -450,6 +450,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_nametoindex()` and :func:`socket.if_indextoname()` +functions have been implemented on Windows. +(Contributed by Zackery Spytz in :issue:`37007`.) + shutil ------ diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 0094cecb79cca6..42f4d09e77fead 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): 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 00000000000000..8939f0c9aad209 --- /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_nametoindex()` and +:func:`socket.if_indextoname()` on Windows. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index c024542fe70923..47b9cc52a177fe 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 */ @@ -6647,13 +6649,18 @@ PyDoc_STRVAR(if_nameindex_doc, "if_nameindex()\n\ \n\ Returns a list of network interface information (index, name) tuples."); +#endif /* HAVE_IF_NAMEINDEX */ +#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) 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; @@ -6677,7 +6684,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); @@ -6697,7 +6708,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 @@ -6822,6 +6833,8 @@ static PyMethodDef socket_methods[] = { #ifdef HAVE_IF_NAMEINDEX {"if_nameindex", socket_if_nameindex, METH_NOARGS, if_nameindex_doc}, +#endif +#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) {"if_nametoindex", socket_if_nametoindex, METH_VARARGS, if_nametoindex_doc}, {"if_indextoname", socket_if_indextoname, diff --git a/PCbuild/_socket.vcxproj b/PCbuild/_socket.vcxproj index 9498abf8fb5e2a..8fd75f90e7ee1e 100644 --- a/PCbuild/_socket.vcxproj +++ b/PCbuild/_socket.vcxproj @@ -93,7 +93,7 @@ - ws2_32.lib;%(AdditionalDependencies) + ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies) From 09eaf0bdb72b54a2cbebcacc215d9b13c5e8f7f9 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Fri, 24 May 2019 05:23:17 -0600 Subject: [PATCH 2/4] Implement socket.if_nameindex() on Windows testInterfaceNameIndex() in test_socket.py is now run on Windows (as hasattr(socket, 'if_nameindex') is now True). Two other tests that aren't directly related to if_nameindex() have been disabled on Windows. These tests only use if_nameindex() for the purpose of picking a random network interface. --- Doc/library/socket.rst | 5 +- Doc/whatsnew/3.8.rst | 4 +- Lib/test/test_socket.py | 8 +-- .../2019-05-23-04-19-13.bpo-37007.d1SOtF.rst | 2 +- Modules/socketmodule.c | 49 ++++++++++++++----- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 2d47ddea433b07..f2cd6a29d14188 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1014,10 +1014,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) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 5f1129481fa2d6..3b61188860cc8e 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -450,8 +450,8 @@ 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_nametoindex()` and :func:`socket.if_indextoname()` -functions have been implemented on Windows. +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`.) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 42f4d09e77fead..74662cfeb327af 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1640,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) @@ -1674,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 index 8939f0c9aad209..ac344a57c83d93 100644 --- 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 @@ -1,2 +1,2 @@ -Implement :func:`socket.if_nametoindex()` and +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 47b9cc52a177fe..508fa3cd2f1cd8 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -6590,28 +6590,54 @@ 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; + if (GetIfTable2Ex(MibIfTableRaw, &tbl) != NO_ERROR) { + Py_DECREF(list); + return PyErr_SetFromErrno(PyExc_OSError); + } + for (ULONG i = 0; i < tbl->NumEntries; ++i) { + MIB_IF_ROW2 r = tbl->Table[i]; + WCHAR buf[NDIS_IF_MAX_STRING_SIZE + 1]; + if (ConvertInterfaceLuidToNameW(&r.InterfaceLuid, buf, + NDIS_IF_MAX_STRING_SIZE + 1)) { + Py_DECREF(list); + FreeMibTable(tbl); + return PyErr_SetFromErrno(PyExc_OSError); + } + PyObject *tuple = Py_BuildValue("IN", r.InterfaceIndex, + PyUnicode_FromWideChar(buf, -1)); + 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])); @@ -6643,15 +6669,14 @@ socket_if_nameindex(PyObject *self, PyObject *arg) if_freenameindex(ni); return list; +#endif } PyDoc_STRVAR(if_nameindex_doc, "if_nameindex()\n\ \n\ Returns a list of network interface information (index, name) tuples."); -#endif /* HAVE_IF_NAMEINDEX */ -#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) static PyObject * socket_if_nametoindex(PyObject *self, PyObject *args) { @@ -6830,11 +6855,9 @@ 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}, -#endif -#if defined(HAVE_IF_NAMEINDEX) || defined(MS_WINDOWS) {"if_nametoindex", socket_if_nametoindex, METH_VARARGS, if_nametoindex_doc}, {"if_indextoname", socket_if_indextoname, From a012058829df1b4d5c246ecfdf2d80e4c2cb57da Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Wed, 29 May 2019 12:54:28 -0600 Subject: [PATCH 3/4] Fix the error handling in if_nameindex(). Use "u" with Py_BuildValue(). --- Modules/socketmodule.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 508fa3cd2f1cd8..da5f4daf57c189 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -6602,21 +6602,21 @@ socket_if_nameindex(PyObject *self, PyObject *arg) } #ifdef MS_WINDOWS PMIB_IF_TABLE2 tbl; - if (GetIfTable2Ex(MibIfTableRaw, &tbl) != NO_ERROR) { + int ret; + if ((ret = GetIfTable2Ex(MibIfTableRaw, &tbl)) != NO_ERROR) { Py_DECREF(list); - return PyErr_SetFromErrno(PyExc_OSError); + 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 (ConvertInterfaceLuidToNameW(&r.InterfaceLuid, buf, - NDIS_IF_MAX_STRING_SIZE + 1)) { + if ((ret = ConvertInterfaceLuidToNameW(&r.InterfaceLuid, buf, + Py_ARRAY_LENGTH(buf)))) { Py_DECREF(list); FreeMibTable(tbl); - return PyErr_SetFromErrno(PyExc_OSError); + return PyErr_SetFromWindowsErr(ret); } - PyObject *tuple = Py_BuildValue("IN", r.InterfaceIndex, - PyUnicode_FromWideChar(buf, -1)); + PyObject *tuple = Py_BuildValue("Iu", r.InterfaceIndex, buf); if (tuple == NULL || PyList_Append(list, tuple) == -1) { Py_XDECREF(tuple); Py_DECREF(list); From d398be0f50458d7b41cb15b463f30c39156a842f Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Wed, 29 May 2019 14:34:58 -0600 Subject: [PATCH 4/4] Apply suggestions from code review Co-Authored-By: Steve Dower --- Modules/socketmodule.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 6b31197845f105..28ed4a457bb1bd 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -6685,6 +6685,7 @@ socket_if_nameindex(PyObject *self, PyObject *arg) 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) { @@ -6694,6 +6695,7 @@ socket_if_nameindex(PyObject *self, PyObject *arg) 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);