[Python-checkins] bpo-32221: makeipaddr(): remove interface part + speedup (GH-5449) (#5449)

Yury Selivanov webhook-mailer at python.org
Mon Feb 12 14:47:51 EST 2018


https://github.com/python/cpython/commit/7766b96ab80b04509bbac708ee5ecf3c1c5934fc
commit: 7766b96ab80b04509bbac708ee5ecf3c1c5934fc
branch: master
author: Коренберг Марк <socketpair at gmail.com>
committer: Yury Selivanov <yury at magic.io>
date: 2018年02月12日T14:47:42-05:00
summary:
bpo-32221: makeipaddr(): remove interface part + speedup (GH-5449) (#5449)
files:
A Misc/NEWS.d/next/Library/2017-12-06-10-10-10.bpo-32221.ideco_.rst
M Doc/library/socket.rst
M Lib/test/test_socket.py
M Modules/socketmodule.c
diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index 7f0d4ede7ccf..04042ffdf1ac 100644
--- a/Doc/library/socket.rst
+++ b/Doc/library/socket.rst
@@ -77,6 +77,11 @@ created. Socket addresses are represented as follows:
 backward compatibility. Note, however, omission of *scopeid* can cause problems
 in manipulating scoped IPv6 addresses.
 
+ .. versionchanged:: 3.7
+ For multicast addresses (with *scopeid* meaningful) *address* may not contain
+ ``%scope`` (or ``zone id``) part. This information is superfluous and may
+ be safely omitted (recommended).
+
 - :const:`AF_NETLINK` sockets are represented as pairs ``(pid, groups)``.
 
 - Linux-only support for TIPC is available using the :const:`AF_TIPC`
@@ -635,6 +640,10 @@ The :mod:`socket` module also offers various network-related services:
 .. versionchanged:: 3.2
 parameters can now be passed using keyword arguments.
 
+ .. versionchanged:: 3.7
+ for IPv6 multicast addresses, string representing an address will not
+ contain ``%scope`` part.
+
 .. function:: getfqdn([name])
 
 Return a fully qualified domain name for *name*. If *name* is omitted or empty,
@@ -693,6 +702,8 @@ The :mod:`socket` module also offers various network-related services:
 or numeric address representation in *host*. Similarly, *port* can contain a
 string port name or a numeric port number.
 
+ For IPv6 addresses, ``%scope`` is appended to the host part if *sockaddr*
+ contains meaningful *scopeid*. Usually this happens for multicast addresses.
 
 .. function:: getprotobyname(protocolname)
 
@@ -1193,6 +1204,10 @@ to sockets.
 an exception, the method now retries the system call instead of raising
 an :exc:`InterruptedError` exception (see :pep:`475` for the rationale).
 
+ .. versionchanged:: 3.7
+ For multicast IPv6 address, first item of *address* does not contain
+ ``%scope`` part anymore. In order to get full IPv6 address use
+ :func:`getnameinfo`.
 
 .. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]])
 
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 2851922bd0c5..b0e1b7471ee2 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -1594,6 +1594,72 @@ def test_flowinfo(self):
 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
 self.assertRaises(OverflowError, s.bind, (support.HOSTv6, 0, -10))
 
+ @unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
+ def test_getaddrinfo_ipv6_basic(self):
+ ((*_, sockaddr),) = socket.getaddrinfo(
+ 'ff02::1de:c0:face:8D', # Note capital letter `D`.
+ 1234, socket.AF_INET6,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP
+ )
+ 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')
+ def test_getaddrinfo_ipv6_scopeid_symbolic(self):
+ # Just pick up any network interface (Linux, Mac OS X)
+ (ifindex, test_interface) = socket.if_nameindex()[0]
+ ((*_, sockaddr),) = socket.getaddrinfo(
+ 'ff02::1de:c0:face:8D%' + test_interface,
+ 1234, socket.AF_INET6,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP
+ )
+ # Note missing interface name part in IPv6 address
+ self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, ifindex))
+
+ @unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
+ @unittest.skipUnless(
+ sys.platform == 'win32',
+ 'Numeric scope id does not work or undocumented')
+ def test_getaddrinfo_ipv6_scopeid_numeric(self):
+ # Also works on Linux and Mac OS X, but is not documented (?)
+ # Windows, Linux and Max OS X allow nonexistent interface numbers here.
+ ifindex = 42
+ ((*_, sockaddr),) = socket.getaddrinfo(
+ 'ff02::1de:c0:face:8D%' + str(ifindex),
+ 1234, socket.AF_INET6,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP
+ )
+ # Note missing interface name part in IPv6 address
+ 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')
+ def test_getnameinfo_ipv6_scopeid_symbolic(self):
+ # Just pick up any network interface.
+ (ifindex, test_interface) = socket.if_nameindex()[0]
+ sockaddr = ('ff02::1de:c0:face:8D', 1234, 0, ifindex) # Note capital letter `D`.
+ nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
+ self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%' + test_interface, '1234'))
+
+ @unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
+ @unittest.skipUnless(
+ sys.platform == 'win32',
+ 'Numeric scope id does not work or undocumented')
+ def test_getnameinfo_ipv6_scopeid_numeric(self):
+ # Also works on Linux (undocumented), but does not work on Mac OS X
+ # Windows and Linux allow nonexistent interface numbers here.
+ ifindex = 42
+ sockaddr = ('ff02::1de:c0:face:8D', 1234, 0, ifindex) # Note capital letter `D`.
+ nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
+ self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%' + str(ifindex), '1234'))
+
 def test_str_for_enums(self):
 # Make sure that the AF_* and SOCK_* constants have enum-like string
 # reprs.
diff --git a/Misc/NEWS.d/next/Library/2017-12-06-10-10-10.bpo-32221.ideco_.rst b/Misc/NEWS.d/next/Library/2017-12-06-10-10-10.bpo-32221.ideco_.rst
new file mode 100644
index 000000000000..a88dcf48e02b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-12-06-10-10-10.bpo-32221.ideco_.rst
@@ -0,0 +1,4 @@
+Various functions returning tuple containig IPv6 addresses now omit ``%scope``
+part since the same information is already encoded in *scopeid* tuple item.
+Especially this speeds up :func:`socket.recvfrom` when it receives multicast
+packet since useless resolving of network interface name is omitted.
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index 23061bec5b7e..aa715bf95d76 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -1099,25 +1099,33 @@ setipaddr(const char *name, struct sockaddr *addr_ret, size_t addr_ret_size, int
 }
 
 
-/* Create a string object representing an IP address.
- This is always a string of the form 'dd.dd.dd.dd' (with variable
- size numbers). */
+/* Convert IPv4 sockaddr to a Python str. */
 
 static PyObject *
-makeipaddr(struct sockaddr *addr, int addrlen)
+make_ipv4_addr(const struct sockaddr_in *addr)
 {
- char buf[NI_MAXHOST];
- int error;
-
- error = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0,
- NI_NUMERICHOST);
- if (error) {
- set_gaierror(error);
+ char buf[INET_ADDRSTRLEN];
+ if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) == NULL) {
+ PyErr_SetFromErrno(PyExc_OSError);
 return NULL;
 }
 return PyUnicode_FromString(buf);
 }
 
+#ifdef ENABLE_IPV6
+/* Convert IPv6 sockaddr to a Python str. */
+
+static PyObject *
+make_ipv6_addr(const struct sockaddr_in6 *addr)
+{
+ char buf[INET6_ADDRSTRLEN];
+ if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) == NULL) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ return PyUnicode_FromString(buf);
+}
+#endif
 
 #ifdef USE_BLUETOOTH
 /* Convert a string representation of a Bluetooth address into a numeric
@@ -1182,11 +1190,10 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
 
 case AF_INET:
 {
- struct sockaddr_in *a;
- PyObject *addrobj = makeipaddr(addr, sizeof(*a));
+ const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
+ PyObject *addrobj = make_ipv4_addr(a);
 PyObject *ret = NULL;
 if (addrobj) {
- a = (struct sockaddr_in *)addr;
 ret = Py_BuildValue("Oi", addrobj, ntohs(a->sin_port));
 Py_DECREF(addrobj);
 }
@@ -1230,11 +1237,10 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
 #ifdef ENABLE_IPV6
 case AF_INET6:
 {
- struct sockaddr_in6 *a;
- PyObject *addrobj = makeipaddr(addr, sizeof(*a));
+ const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
+ PyObject *addrobj = make_ipv6_addr(a);
 PyObject *ret = NULL;
 if (addrobj) {
- a = (struct sockaddr_in6 *)addr;
 ret = Py_BuildValue("OiII",
 addrobj,
 ntohs(a->sin6_port),
@@ -5154,14 +5160,14 @@ static PyObject *
 socket_gethostbyname(PyObject *self, PyObject *args)
 {
 char *name;
- sock_addr_t addrbuf;
+ struct sockaddr_in addrbuf;
 PyObject *ret = NULL;
 
 if (!PyArg_ParseTuple(args, "et:gethostbyname", "idna", &name))
 return NULL;
- if (setipaddr(name, SAS2SA(&addrbuf), sizeof(addrbuf), AF_INET) < 0)
+ if (setipaddr(name, (struct sockaddr *)&addrbuf, sizeof(addrbuf), AF_INET) < 0)
 goto finally;
- ret = makeipaddr(SAS2SA(&addrbuf), sizeof(struct sockaddr_in));
+ ret = make_ipv4_addr(&addrbuf);
 finally:
 PyMem_Free(name);
 return ret;
@@ -5263,7 +5269,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
 sin.sin_len = sizeof(sin);
 #endif
 memcpy(&sin.sin_addr, *pch, sizeof(sin.sin_addr));
- tmp = makeipaddr((struct sockaddr *)&sin, sizeof(sin));
+ tmp = make_ipv4_addr(&sin);
 
 if (pch == h->h_addr_list && alen >= sizeof(sin))
 memcpy((char *) addr, &sin, sizeof(sin));
@@ -5280,8 +5286,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
 sin6.sin6_len = sizeof(sin6);
 #endif
 memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr));
- tmp = makeipaddr((struct sockaddr *)&sin6,
- sizeof(sin6));
+ tmp = make_ipv6_addr(&sin6);
 
 if (pch == h->h_addr_list && alen >= sizeof(sin6))
 memcpy((char *) addr, &sin6, sizeof(sin6));
@@ -6052,14 +6057,11 @@ socket_inet_ntop(PyObject *self, PyObject *args)
 Py_buffer packed_ip;
 const char* retval;
 #ifdef ENABLE_IPV6
- char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
+ char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
 #else
- char ip[INET_ADDRSTRLEN + 1];
+ char ip[INET_ADDRSTRLEN];
 #endif
 
- /* Guarantee NUL-termination for PyUnicode_FromString() below */
- memset((void *) &ip[0], '0円', sizeof(ip));
-
 if (!PyArg_ParseTuple(args, "iy*:inet_ntop", &af, &packed_ip)) {
 return NULL;
 }
@@ -6087,6 +6089,7 @@ socket_inet_ntop(PyObject *self, PyObject *args)
 return NULL;
 }
 
+ /* inet_ntop guarantee NUL-termination of resulting string. */
 retval = inet_ntop(af, packed_ip.buf, ip, sizeof(ip));
 PyBuffer_Release(&packed_ip);
 if (!retval) {


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /