Skip to content

Commit 47c0b1f

Browse files
socketpair1st1
authored andcommitted
bpo-32221: makeipaddr(): remove interface part + speedup (GH-4724)
1 parent ee72ac0 commit 47c0b1f

File tree

4 files changed

+94
-28
lines changed

4 files changed

+94
-28
lines changed

Doc/library/socket.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ created. Socket addresses are represented as follows:
7777
backward compatibility. Note, however, omission of *scopeid* can cause problems
7878
in manipulating scoped IPv6 addresses.
7979

80+
.. versionchanged:: 3.7
81+
For multicast addresses (with *scopeid* meaningful) *address* may not contain
82+
``%scope`` (or ``zone id``) part. This information is superfluous and may
83+
be safely omitted (recommended).
84+
8085
- :const:`AF_NETLINK` sockets are represented as pairs ``(pid, groups)``.
8186

8287
- Linux-only support for TIPC is available using the :const:`AF_TIPC`
@@ -630,6 +635,10 @@ The :mod:`socket` module also offers various network-related services:
630635
.. versionchanged:: 3.2
631636
parameters can now be passed using keyword arguments.
632637

638+
.. versionchanged:: 3.7
639+
for IPv6 multicast addresses, string representing an address will not
640+
contain ``%scope`` part.
641+
633642
.. function:: getfqdn([name])
634643

635644
Return a fully qualified domain name for *name*. If *name* is omitted or empty,
@@ -688,6 +697,8 @@ The :mod:`socket` module also offers various network-related services:
688697
or numeric address representation in *host*. Similarly, *port* can contain a
689698
string port name or a numeric port number.
690699

700+
For IPv6 addresses, ``%scope`` is appended to the host part if *sockaddr*
701+
contains meaningful *scopeid*. Usually this happens for multicast addresses.
691702

692703
.. function:: getprotobyname(protocolname)
693704

@@ -1178,6 +1189,10 @@ to sockets.
11781189
an exception, the method now retries the system call instead of raising
11791190
an :exc:`InterruptedError` exception (see :pep:`475` for the rationale).
11801191

1192+
.. versionchanged:: 3.7
1193+
For multicast IPv6 address, first item of *address* does not contain
1194+
``%scope`` part anymore. In order to get full IPv6 address use
1195+
:func:`getnameinfo`.
11811196

11821197
.. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]])
11831198

Lib/test/test_socket.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,50 @@ def test_flowinfo(self):
15861586
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
15871587
self.assertRaises(OverflowError, s.bind, (support.HOSTv6, 0, -10))
15881588

1589+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1590+
def test_getaddrinfo_ipv6_basic(self):
1591+
((*_, sockaddr),) = socket.getaddrinfo(
1592+
'ff02::1de:c0:face:8D', # Note capital letter `D`.
1593+
1234, socket.AF_INET6,
1594+
socket.SOCK_DGRAM,
1595+
socket.IPPROTO_UDP
1596+
)
1597+
self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, 0))
1598+
1599+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1600+
@unittest.skipUnless(
1601+
hasattr(socket, 'if_nameindex'),
1602+
'IPv6 scope id by interface name is not supported'
1603+
)
1604+
def test_getaddrinfo_ipv6_scopeid(self):
1605+
test_interface = 'lo'
1606+
ifindex = socket.if_nametoindex(test_interface)
1607+
((*_, sockaddr),) = socket.getaddrinfo(
1608+
'ff02::1de:c0:face:8D%' + test_interface,
1609+
1234, socket.AF_INET6,
1610+
socket.SOCK_DGRAM,
1611+
socket.IPPROTO_UDP
1612+
)
1613+
# Note missing interface name part in IPv6 address
1614+
self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, ifindex))
1615+
1616+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1617+
def test_getaddrinfo_ipv6_scopeid_numeric(self):
1618+
((*_, sockaddr),) = socket.getaddrinfo(
1619+
'ff02::1de:c0:face:8D%42',
1620+
1234, socket.AF_INET6,
1621+
socket.SOCK_DGRAM,
1622+
socket.IPPROTO_UDP
1623+
)
1624+
# Note missing interface name part in IPv6 address
1625+
self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, 42))
1626+
1627+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1628+
def test_getnameinfo_ipv6_scopeid(self):
1629+
sockaddr = ('ff02::1de:c0:face:8D', 1234, 0, 100500) # Note capital letter `D`.
1630+
nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
1631+
self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%100500', '1234'))
1632+
15891633
def test_str_for_enums(self):
15901634
# Make sure that the AF_* and SOCK_* constants have enum-like string
15911635
# reprs.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Various functions returning tuple containig IPv6 addresses now omit ``%scope``
2+
part since the same information is already encoded in *scopeid* tuple item.
3+
Especially this speeds up :func:`socket.recvfrom` when it receives multicast
4+
packet since useless resolving of network interface name is omitted.

Modules/socketmodule.c

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,25 +1094,33 @@ setipaddr(const char *name, struct sockaddr *addr_ret, size_t addr_ret_size, int
10941094
}
10951095

10961096

1097-
/* Create a string object representing an IP address.
1098-
This is always a string of the form 'dd.dd.dd.dd' (with variable
1099-
size numbers). */
1097+
/* Convert IPv4 sockaddr to a Python str. */
11001098

11011099
static PyObject *
1102-
makeipaddr(struct sockaddr *addr, int addrlen)
1100+
make_ipv4_addr(const struct sockaddr_in *addr)
11031101
{
1104-
char buf[NI_MAXHOST];
1105-
int error;
1106-
1107-
error = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0,
1108-
NI_NUMERICHOST);
1109-
if (error) {
1110-
set_gaierror(error);
1102+
char buf[INET_ADDRSTRLEN];
1103+
if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) == NULL) {
1104+
PyErr_SetFromErrno(PyExc_OSError);
11111105
return NULL;
11121106
}
11131107
return PyUnicode_FromString(buf);
11141108
}
11151109

1110+
#ifdef ENABLE_IPV6
1111+
/* Convert IPv6 sockaddr to a Python str. */
1112+
1113+
static PyObject *
1114+
make_ipv6_addr(const struct sockaddr_in6 *addr)
1115+
{
1116+
char buf[INET6_ADDRSTRLEN];
1117+
if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) == NULL) {
1118+
PyErr_SetFromErrno(PyExc_OSError);
1119+
return NULL;
1120+
}
1121+
return PyUnicode_FromString(buf);
1122+
}
1123+
#endif
11161124

11171125
#ifdef USE_BLUETOOTH
11181126
/* Convert a string representation of a Bluetooth address into a numeric
@@ -1177,11 +1185,10 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
11771185

11781186
case AF_INET:
11791187
{
1180-
struct sockaddr_in *a;
1181-
PyObject *addrobj = makeipaddr(addr, sizeof(*a));
1188+
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
1189+
PyObject *addrobj = make_ipv4_addr(a);
11821190
PyObject *ret = NULL;
11831191
if (addrobj) {
1184-
a = (struct sockaddr_in *)addr;
11851192
ret = Py_BuildValue("Oi", addrobj, ntohs(a->sin_port));
11861193
Py_DECREF(addrobj);
11871194
}
@@ -1225,11 +1232,10 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
12251232
#ifdef ENABLE_IPV6
12261233
case AF_INET6:
12271234
{
1228-
struct sockaddr_in6 *a;
1229-
PyObject *addrobj = makeipaddr(addr, sizeof(*a));
1235+
const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
1236+
PyObject *addrobj = make_ipv6_addr(a);
12301237
PyObject *ret = NULL;
12311238
if (addrobj) {
1232-
a = (struct sockaddr_in6 *)addr;
12331239
ret = Py_BuildValue("OiII",
12341240
addrobj,
12351241
ntohs(a->sin6_port),
@@ -5036,14 +5042,14 @@ static PyObject *
50365042
socket_gethostbyname(PyObject *self, PyObject *args)
50375043
{
50385044
char *name;
5039-
sock_addr_t addrbuf;
5045+
struct sockaddr_in addrbuf;
50405046
PyObject *ret = NULL;
50415047

50425048
if (!PyArg_ParseTuple(args, "et:gethostbyname", "idna", &name))
50435049
return NULL;
5044-
if (setipaddr(name, SAS2SA(&addrbuf), sizeof(addrbuf), AF_INET) < 0)
5050+
if (setipaddr(name, (struct sockaddr *)&addrbuf, sizeof(addrbuf), AF_INET) < 0)
50455051
goto finally;
5046-
ret = makeipaddr(SAS2SA(&addrbuf), sizeof(struct sockaddr_in));
5052+
ret = make_ipv4_addr(&addrbuf);
50475053
finally:
50485054
PyMem_Free(name);
50495055
return ret;
@@ -5145,7 +5151,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
51455151
sin.sin_len = sizeof(sin);
51465152
#endif
51475153
memcpy(&sin.sin_addr, *pch, sizeof(sin.sin_addr));
5148-
tmp = makeipaddr((struct sockaddr *)&sin, sizeof(sin));
5154+
tmp = make_ipv4_addr(&sin);
51495155

51505156
if (pch == h->h_addr_list && alen >= sizeof(sin))
51515157
memcpy((char *) addr, &sin, sizeof(sin));
@@ -5162,8 +5168,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
51625168
sin6.sin6_len = sizeof(sin6);
51635169
#endif
51645170
memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr));
5165-
tmp = makeipaddr((struct sockaddr *)&sin6,
5166-
sizeof(sin6));
5171+
tmp = make_ipv6_addr(&sin6);
51675172

51685173
if (pch == h->h_addr_list && alen >= sizeof(sin6))
51695174
memcpy((char *) addr, &sin6, sizeof(sin6));
@@ -5934,14 +5939,11 @@ socket_inet_ntop(PyObject *self, PyObject *args)
59345939
Py_buffer packed_ip;
59355940
const char* retval;
59365941
#ifdef ENABLE_IPV6
5937-
char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
5942+
char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
59385943
#else
5939-
char ip[INET_ADDRSTRLEN + 1];
5944+
char ip[INET_ADDRSTRLEN];
59405945
#endif
59415946

5942-
/* Guarantee NUL-termination for PyUnicode_FromString() below */
5943-
memset((void *) &ip[0], '\0', sizeof(ip));
5944-
59455947
if (!PyArg_ParseTuple(args, "iy*:inet_ntop", &af, &packed_ip)) {
59465948
return NULL;
59475949
}
@@ -5969,6 +5971,7 @@ socket_inet_ntop(PyObject *self, PyObject *args)
59695971
return NULL;
59705972
}
59715973

5974+
/* inet_ntop guarantee NUL-termination of resulting string. */
59725975
retval = inet_ntop(af, packed_ip.buf, ip, sizeof(ip));
59735976
PyBuffer_Release(&packed_ip);
59745977
if (!retval) {

0 commit comments

Comments
 (0)