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

Skip to content

Commit 7766b96

Browse files
socketpair1st1
authored andcommitted
bpo-32221: makeipaddr(): remove interface part + speedup (GH-5449) (#5449)
1 parent 3c34aad commit 7766b96

File tree

4 files changed

+116
-28
lines changed

4 files changed

+116
-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`
@@ -635,6 +640,10 @@ The :mod:`socket` module also offers various network-related services:
635640
.. versionchanged:: 3.2
636641
parameters can now be passed using keyword arguments.
637642

643+
.. versionchanged:: 3.7
644+
for IPv6 multicast addresses, string representing an address will not
645+
contain ``%scope`` part.
646+
638647
.. function:: getfqdn([name])
639648

640649
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:
693702
or numeric address representation in *host*. Similarly, *port* can contain a
694703
string port name or a numeric port number.
695704

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

697708
.. function:: getprotobyname(protocolname)
698709

@@ -1193,6 +1204,10 @@ to sockets.
11931204
an exception, the method now retries the system call instead of raising
11941205
an :exc:`InterruptedError` exception (see :pep:`475` for the rationale).
11951206

1207+
.. versionchanged:: 3.7
1208+
For multicast IPv6 address, first item of *address* does not contain
1209+
``%scope`` part anymore. In order to get full IPv6 address use
1210+
:func:`getnameinfo`.
11961211

11971212
.. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]])
11981213

Lib/test/test_socket.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,72 @@ def test_flowinfo(self):
15941594
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
15951595
self.assertRaises(OverflowError, s.bind, (support.HOSTv6, 0, -10))
15961596

1597+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1598+
def test_getaddrinfo_ipv6_basic(self):
1599+
((*_, sockaddr),) = socket.getaddrinfo(
1600+
'ff02::1de:c0:face:8D', # Note capital letter `D`.
1601+
1234, socket.AF_INET6,
1602+
socket.SOCK_DGRAM,
1603+
socket.IPPROTO_UDP
1604+
)
1605+
self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, 0))
1606+
1607+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1608+
@unittest.skipUnless(
1609+
hasattr(socket, 'if_nameindex'),
1610+
'if_nameindex is not supported')
1611+
def test_getaddrinfo_ipv6_scopeid_symbolic(self):
1612+
# Just pick up any network interface (Linux, Mac OS X)
1613+
(ifindex, test_interface) = socket.if_nameindex()[0]
1614+
((*_, sockaddr),) = socket.getaddrinfo(
1615+
'ff02::1de:c0:face:8D%' + test_interface,
1616+
1234, socket.AF_INET6,
1617+
socket.SOCK_DGRAM,
1618+
socket.IPPROTO_UDP
1619+
)
1620+
# Note missing interface name part in IPv6 address
1621+
self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, ifindex))
1622+
1623+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1624+
@unittest.skipUnless(
1625+
sys.platform == 'win32',
1626+
'Numeric scope id does not work or undocumented')
1627+
def test_getaddrinfo_ipv6_scopeid_numeric(self):
1628+
# Also works on Linux and Mac OS X, but is not documented (?)
1629+
# Windows, Linux and Max OS X allow nonexistent interface numbers here.
1630+
ifindex = 42
1631+
((*_, sockaddr),) = socket.getaddrinfo(
1632+
'ff02::1de:c0:face:8D%' + str(ifindex),
1633+
1234, socket.AF_INET6,
1634+
socket.SOCK_DGRAM,
1635+
socket.IPPROTO_UDP
1636+
)
1637+
# Note missing interface name part in IPv6 address
1638+
self.assertEqual(sockaddr, ('ff02::1de:c0:face:8d', 1234, 0, ifindex))
1639+
1640+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1641+
@unittest.skipUnless(
1642+
hasattr(socket, 'if_nameindex'),
1643+
'if_nameindex is not supported')
1644+
def test_getnameinfo_ipv6_scopeid_symbolic(self):
1645+
# Just pick up any network interface.
1646+
(ifindex, test_interface) = socket.if_nameindex()[0]
1647+
sockaddr = ('ff02::1de:c0:face:8D', 1234, 0, ifindex) # Note capital letter `D`.
1648+
nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
1649+
self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%' + test_interface, '1234'))
1650+
1651+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
1652+
@unittest.skipUnless(
1653+
sys.platform == 'win32',
1654+
'Numeric scope id does not work or undocumented')
1655+
def test_getnameinfo_ipv6_scopeid_numeric(self):
1656+
# Also works on Linux (undocumented), but does not work on Mac OS X
1657+
# Windows and Linux allow nonexistent interface numbers here.
1658+
ifindex = 42
1659+
sockaddr = ('ff02::1de:c0:face:8D', 1234, 0, ifindex) # Note capital letter `D`.
1660+
nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
1661+
self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%' + str(ifindex), '1234'))
1662+
15971663
def test_str_for_enums(self):
15981664
# Make sure that the AF_* and SOCK_* constants have enum-like string
15991665
# 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
@@ -1099,25 +1099,33 @@ setipaddr(const char *name, struct sockaddr *addr_ret, size_t addr_ret_size, int
10991099
}
11001100

11011101

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

11061104
static PyObject *
1107-
makeipaddr(struct sockaddr *addr, int addrlen)
1105+
make_ipv4_addr(const struct sockaddr_in *addr)
11081106
{
1109-
char buf[NI_MAXHOST];
1110-
int error;
1111-
1112-
error = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0,
1113-
NI_NUMERICHOST);
1114-
if (error) {
1115-
set_gaierror(error);
1107+
char buf[INET_ADDRSTRLEN];
1108+
if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) == NULL) {
1109+
PyErr_SetFromErrno(PyExc_OSError);
11161110
return NULL;
11171111
}
11181112
return PyUnicode_FromString(buf);
11191113
}
11201114

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

11221130
#ifdef USE_BLUETOOTH
11231131
/* 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)
11821190

11831191
case AF_INET:
11841192
{
1185-
struct sockaddr_in *a;
1186-
PyObject *addrobj = makeipaddr(addr, sizeof(*a));
1193+
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
1194+
PyObject *addrobj = make_ipv4_addr(a);
11871195
PyObject *ret = NULL;
11881196
if (addrobj) {
1189-
a = (struct sockaddr_in *)addr;
11901197
ret = Py_BuildValue("Oi", addrobj, ntohs(a->sin_port));
11911198
Py_DECREF(addrobj);
11921199
}
@@ -1230,11 +1237,10 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
12301237
#ifdef ENABLE_IPV6
12311238
case AF_INET6:
12321239
{
1233-
struct sockaddr_in6 *a;
1234-
PyObject *addrobj = makeipaddr(addr, sizeof(*a));
1240+
const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
1241+
PyObject *addrobj = make_ipv6_addr(a);
12351242
PyObject *ret = NULL;
12361243
if (addrobj) {
1237-
a = (struct sockaddr_in6 *)addr;
12381244
ret = Py_BuildValue("OiII",
12391245
addrobj,
12401246
ntohs(a->sin6_port),
@@ -5154,14 +5160,14 @@ static PyObject *
51545160
socket_gethostbyname(PyObject *self, PyObject *args)
51555161
{
51565162
char *name;
5157-
sock_addr_t addrbuf;
5163+
struct sockaddr_in addrbuf;
51585164
PyObject *ret = NULL;
51595165

51605166
if (!PyArg_ParseTuple(args, "et:gethostbyname", "idna", &name))
51615167
return NULL;
5162-
if (setipaddr(name, SAS2SA(&addrbuf), sizeof(addrbuf), AF_INET) < 0)
5168+
if (setipaddr(name, (struct sockaddr *)&addrbuf, sizeof(addrbuf), AF_INET) < 0)
51635169
goto finally;
5164-
ret = makeipaddr(SAS2SA(&addrbuf), sizeof(struct sockaddr_in));
5170+
ret = make_ipv4_addr(&addrbuf);
51655171
finally:
51665172
PyMem_Free(name);
51675173
return ret;
@@ -5263,7 +5269,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
52635269
sin.sin_len = sizeof(sin);
52645270
#endif
52655271
memcpy(&sin.sin_addr, *pch, sizeof(sin.sin_addr));
5266-
tmp = makeipaddr((struct sockaddr *)&sin, sizeof(sin));
5272+
tmp = make_ipv4_addr(&sin);
52675273

52685274
if (pch == h->h_addr_list && alen >= sizeof(sin))
52695275
memcpy((char *) addr, &sin, sizeof(sin));
@@ -5280,8 +5286,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, size_t alen, int af)
52805286
sin6.sin6_len = sizeof(sin6);
52815287
#endif
52825288
memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr));
5283-
tmp = makeipaddr((struct sockaddr *)&sin6,
5284-
sizeof(sin6));
5289+
tmp = make_ipv6_addr(&sin6);
52855290

52865291
if (pch == h->h_addr_list && alen >= sizeof(sin6))
52875292
memcpy((char *) addr, &sin6, sizeof(sin6));
@@ -6052,14 +6057,11 @@ socket_inet_ntop(PyObject *self, PyObject *args)
60526057
Py_buffer packed_ip;
60536058
const char* retval;
60546059
#ifdef ENABLE_IPV6
6055-
char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
6060+
char ip[Py_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
60566061
#else
6057-
char ip[INET_ADDRSTRLEN + 1];
6062+
char ip[INET_ADDRSTRLEN];
60586063
#endif
60596064

6060-
/* Guarantee NUL-termination for PyUnicode_FromString() below */
6061-
memset((void *) &ip[0], '\0', sizeof(ip));
6062-
60636065
if (!PyArg_ParseTuple(args, "iy*:inet_ntop", &af, &packed_ip)) {
60646066
return NULL;
60656067
}
@@ -6087,6 +6089,7 @@ socket_inet_ntop(PyObject *self, PyObject *args)
60876089
return NULL;
60886090
}
60896091

6092+
/* inet_ntop guarantee NUL-termination of resulting string. */
60906093
retval = inet_ntop(af, packed_ip.buf, ip, sizeof(ip));
60916094
PyBuffer_Release(&packed_ip);
60926095
if (!retval) {

0 commit comments

Comments
 (0)