Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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]])

Expand Down
66 changes: 66 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
59 changes: 31 additions & 28 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down