bpo-32221: makeipaddr(): remove interface part + speedup (GH-5449) (#5449)
This commit is contained in:
parent
3c34aad4e7
commit
7766b96ab8
|
@ -77,6 +77,11 @@ created. Socket addresses are represented as follows:
|
||||||
backward compatibility. Note, however, omission of *scopeid* can cause problems
|
backward compatibility. Note, however, omission of *scopeid* can cause problems
|
||||||
in manipulating scoped IPv6 addresses.
|
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)``.
|
- :const:`AF_NETLINK` sockets are represented as pairs ``(pid, groups)``.
|
||||||
|
|
||||||
- Linux-only support for TIPC is available using the :const:`AF_TIPC`
|
- 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
|
.. versionchanged:: 3.2
|
||||||
parameters can now be passed using keyword arguments.
|
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])
|
.. function:: getfqdn([name])
|
||||||
|
|
||||||
Return a fully qualified domain name for *name*. If *name* is omitted or empty,
|
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
|
or numeric address representation in *host*. Similarly, *port* can contain a
|
||||||
string port name or a numeric port number.
|
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)
|
.. function:: getprotobyname(protocolname)
|
||||||
|
|
||||||
|
@ -1193,6 +1204,10 @@ to sockets.
|
||||||
an exception, the method now retries the system call instead of raising
|
an exception, the method now retries the system call instead of raising
|
||||||
an :exc:`InterruptedError` exception (see :pep:`475` for the rationale).
|
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]])
|
.. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]])
|
||||||
|
|
||||||
|
|
|
@ -1594,6 +1594,72 @@ class GeneralModuleTests(unittest.TestCase):
|
||||||
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
|
||||||
self.assertRaises(OverflowError, s.bind, (support.HOSTv6, 0, -10))
|
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):
|
def test_str_for_enums(self):
|
||||||
# Make sure that the AF_* and SOCK_* constants have enum-like string
|
# Make sure that the AF_* and SOCK_* constants have enum-like string
|
||||||
# reprs.
|
# reprs.
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
/* Convert IPv4 sockaddr to a Python str. */
|
||||||
This is always a string of the form 'dd.dd.dd.dd' (with variable
|
|
||||||
size numbers). */
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
makeipaddr(struct sockaddr *addr, int addrlen)
|
make_ipv4_addr(const struct sockaddr_in *addr)
|
||||||
{
|
{
|
||||||
char buf[NI_MAXHOST];
|
char buf[INET_ADDRSTRLEN];
|
||||||
int error;
|
if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) == NULL) {
|
||||||
|
PyErr_SetFromErrno(PyExc_OSError);
|
||||||
error = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0,
|
|
||||||
NI_NUMERICHOST);
|
|
||||||
if (error) {
|
|
||||||
set_gaierror(error);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return PyUnicode_FromString(buf);
|
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
|
#ifdef USE_BLUETOOTH
|
||||||
/* Convert a string representation of a Bluetooth address into a numeric
|
/* 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:
|
case AF_INET:
|
||||||
{
|
{
|
||||||
struct sockaddr_in *a;
|
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
|
||||||
PyObject *addrobj = makeipaddr(addr, sizeof(*a));
|
PyObject *addrobj = make_ipv4_addr(a);
|
||||||
PyObject *ret = NULL;
|
PyObject *ret = NULL;
|
||||||
if (addrobj) {
|
if (addrobj) {
|
||||||
a = (struct sockaddr_in *)addr;
|
|
||||||
ret = Py_BuildValue("Oi", addrobj, ntohs(a->sin_port));
|
ret = Py_BuildValue("Oi", addrobj, ntohs(a->sin_port));
|
||||||
Py_DECREF(addrobj);
|
Py_DECREF(addrobj);
|
||||||
}
|
}
|
||||||
|
@ -1230,11 +1237,10 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
|
||||||
#ifdef ENABLE_IPV6
|
#ifdef ENABLE_IPV6
|
||||||
case AF_INET6:
|
case AF_INET6:
|
||||||
{
|
{
|
||||||
struct sockaddr_in6 *a;
|
const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
|
||||||
PyObject *addrobj = makeipaddr(addr, sizeof(*a));
|
PyObject *addrobj = make_ipv6_addr(a);
|
||||||
PyObject *ret = NULL;
|
PyObject *ret = NULL;
|
||||||
if (addrobj) {
|
if (addrobj) {
|
||||||
a = (struct sockaddr_in6 *)addr;
|
|
||||||
ret = Py_BuildValue("OiII",
|
ret = Py_BuildValue("OiII",
|
||||||
addrobj,
|
addrobj,
|
||||||
ntohs(a->sin6_port),
|
ntohs(a->sin6_port),
|
||||||
|
@ -5154,14 +5160,14 @@ static PyObject *
|
||||||
socket_gethostbyname(PyObject *self, PyObject *args)
|
socket_gethostbyname(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
char *name;
|
char *name;
|
||||||
sock_addr_t addrbuf;
|
struct sockaddr_in addrbuf;
|
||||||
PyObject *ret = NULL;
|
PyObject *ret = NULL;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "et:gethostbyname", "idna", &name))
|
if (!PyArg_ParseTuple(args, "et:gethostbyname", "idna", &name))
|
||||||
return NULL;
|
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;
|
goto finally;
|
||||||
ret = makeipaddr(SAS2SA(&addrbuf), sizeof(struct sockaddr_in));
|
ret = make_ipv4_addr(&addrbuf);
|
||||||
finally:
|
finally:
|
||||||
PyMem_Free(name);
|
PyMem_Free(name);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -5263,7 +5269,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
|
||||||
sin.sin_len = sizeof(sin);
|
sin.sin_len = sizeof(sin);
|
||||||
#endif
|
#endif
|
||||||
memcpy(&sin.sin_addr, *pch, sizeof(sin.sin_addr));
|
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))
|
if (pch == h->h_addr_list && alen >= sizeof(sin))
|
||||||
memcpy((char *) addr, &sin, 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);
|
sin6.sin6_len = sizeof(sin6);
|
||||||
#endif
|
#endif
|
||||||
memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr));
|
memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr));
|
||||||
tmp = makeipaddr((struct sockaddr *)&sin6,
|
tmp = make_ipv6_addr(&sin6);
|
||||||
sizeof(sin6));
|
|
||||||
|
|
||||||
if (pch == h->h_addr_list && alen >= sizeof(sin6))
|
if (pch == h->h_addr_list && alen >= sizeof(sin6))
|
||||||
memcpy((char *) addr, &sin6, sizeof(sin6));
|
memcpy((char *) addr, &sin6, sizeof(sin6));
|
||||||
|
@ -6052,14 +6057,11 @@ socket_inet_ntop(PyObject *self, PyObject *args)
|
||||||
Py_buffer packed_ip;
|
Py_buffer packed_ip;
|
||||||
const char* retval;
|
const char* retval;
|
||||||
#ifdef ENABLE_IPV6
|
#ifdef ENABLE_IPV6
|
||||||
char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
|
char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
|
||||||
#else
|
#else
|
||||||
char ip[INET_ADDRSTRLEN + 1];
|
char ip[INET_ADDRSTRLEN];
|
||||||
#endif
|
#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)) {
|
if (!PyArg_ParseTuple(args, "iy*:inet_ntop", &af, &packed_ip)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -6087,6 +6089,7 @@ socket_inet_ntop(PyObject *self, PyObject *args)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* inet_ntop guarantee NUL-termination of resulting string. */
|
||||||
retval = inet_ntop(af, packed_ip.buf, ip, sizeof(ip));
|
retval = inet_ntop(af, packed_ip.buf, ip, sizeof(ip));
|
||||||
PyBuffer_Release(&packed_ip);
|
PyBuffer_Release(&packed_ip);
|
||||||
if (!retval) {
|
if (!retval) {
|
||||||
|
|
Loading…
Reference in New Issue