Skip to content

gh-107545: Fix misleading setsockopt error message #107546

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
35 changes: 35 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,41 @@ def testSetSockOpt(self):
reuse = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)
self.assertFalse(reuse == 0, "failed to set reuse mode")

def test_setsockopt_errors(self):
# See issue #107546.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.addCleanup(sock.close)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # No error expected.

with self.assertRaises(OverflowError):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 2 ** 100)

with self.assertRaises(OverflowError):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, - 2 ** 100)

with self.assertRaises(OverflowError):
sock.setsockopt(socket.SOL_SOCKET, 2 ** 100, 1)

with self.assertRaises(OverflowError):
sock.setsockopt(2 ** 100, socket.SO_REUSEADDR, 1)

msg = "socket option should be should be integer, bytes-like object or None"
with self.assertRaises(TypeError, msg=msg):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be assertRaisesRegex. And the expected error message is a regular expression, so you need to escape parentheses.

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, dict())

msg = "setsockopt() takes 3 or 4 arguments (2 given)"
with self.assertRaises(TypeError, msg=msg):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)

msg = "setsockopt() take 4 arguments when socket option is None (3 given)"
with self.assertRaises(TypeError, msg=msg):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, None)

msg = "setsockopt() argument 3 must be NoneType, not int"
with self.assertRaises(TypeError, msg=msg):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1, 2)

def testSendAfterClose(self):
# testing send() after close() with timeout
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve error message when :meth:`~socket.socket.setsockopt` raises an error
other than a :exc:`TypeError`.
92 changes: 62 additions & 30 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3332,32 +3332,55 @@ sock_setsockopt(PyObject *self, PyObject *args)
{
PySocketSockObject *s = _PySocketSockObject_CAST(self);

Py_ssize_t arglen;
int level;
int optname;
int res;
Py_buffer optval;
Py_buffer buffer;
int flag;
unsigned int optlen;
PyObject *none;
PyObject *optval;

arglen = PyTuple_Size(args);
switch (arglen) {
case 3:
if (!PyArg_ParseTuple(args, "iiO:setsockopt",
&level, &optname, &optval)) {
return NULL;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please move this test for None and 4 arguments here?

if (optval == Py_None) { PyErr_Format(...) }

break;
case 4:
if (!PyArg_ParseTuple(args, "iiO!I:setsockopt",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!PyArg_ParseTuple(args, "iiO!I:setsockopt",
if (!PyArg_ParseTuple(args, "iiOI:setsockopt",

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that the 3rd argument would be of the specified type.
In my case Py_TYPE(Py_None) that way I ensure that if we have 4 arguments the 3rd must be None.

&level, &optname, Py_TYPE(Py_None), &optval, &optlen)) {
return NULL;
}
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need if (optval != Py_None) {error...} here.

Copy link
Author

@naweiss naweiss Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at https://github.com/python/cpython/pull/107546/files#r2192102447.
The "O!" format along with Py_TYPE(Py_None) ensures that the type of the 3rd argument would be None.

default:
PyErr_Format(PyExc_TypeError,
"setsockopt() takes 3 or 4 arguments (%zd given)",
arglen);
return NULL;
}

#ifdef AF_VSOCK
if (s->sock_family == AF_VSOCK) {
uint64_t vflag; // Must be set width of 64 bits
/* setsockopt(level, opt, flag) */
if (PyArg_ParseTuple(args, "iiK:setsockopt",
&level, &optname, &vflag)) {
// level should always be set to AF_VSOCK
res = setsockopt(get_sock_fd(s), level, optname,
(void*)&vflag, sizeof vflag);
goto done;
if (!PyArg_Parse(optval, "K", &vflag)) {
return NULL;
}
return NULL;
// level should always be set to AF_VSOCK
res = setsockopt(get_sock_fd(s), level, optname,
(void*)&vflag, sizeof vflag);
goto done;
}
#endif

/* setsockopt(level, opt, flag) */
if (PyArg_ParseTuple(args, "iii:setsockopt",
&level, &optname, &flag)) {
if (PyIndex_Check(optval)) {
if (!PyArg_Parse(optval, "i", &flag)) {
return NULL;
}
#ifdef MS_WINDOWS
if (optname == SIO_TCP_SET_ACK_FREQUENCY) {
DWORD dummy;
Expand All @@ -3374,36 +3397,45 @@ sock_setsockopt(PyObject *self, PyObject *args)
goto done;
}

PyErr_Clear();
/* setsockopt(level, opt, None, flag) */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* setsockopt(level, opt, None, flag) */
/* setsockopt(level, opt, None, optlen) */

if (PyArg_ParseTuple(args, "iiO!I:setsockopt",
&level, &optname, Py_TYPE(Py_None), &none, &optlen)) {
if (optval == Py_None) {
if (arglen != 4) {
PyErr_Format(PyExc_TypeError,
"setsockopt() take 4 arguments when socket option is None (%zd given)",
arglen);
return NULL;
}
assert(sizeof(socklen_t) >= sizeof(unsigned int));
res = setsockopt(get_sock_fd(s), level, optname,
NULL, (socklen_t)optlen);
goto done;
}

PyErr_Clear();
/* setsockopt(level, opt, buffer) */
if (!PyArg_ParseTuple(args, "iiy*:setsockopt",
&level, &optname, &optval))
return NULL;

if (PyObject_CheckBuffer(optval)) {
if (!PyArg_Parse(optval, "y*", &buffer)) {
return NULL;
}
#ifdef MS_WINDOWS
if (optval.len > INT_MAX) {
PyBuffer_Release(&optval);
PyErr_Format(PyExc_OverflowError,
"socket option is larger than %i bytes",
INT_MAX);
return NULL;
}
res = setsockopt(get_sock_fd(s), level, optname,
optval.buf, (int)optval.len);
if (buffer.len > INT_MAX) {
PyBuffer_Release(&buffer);
PyErr_Format(PyExc_OverflowError,
"socket option is larger than %i bytes",
INT_MAX);
return NULL;
}
res = setsockopt(get_sock_fd(s), level, optname,
buffer.buf, (int)buffer.len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
res = setsockopt(get_sock_fd(s), level, optname,
buffer.buf, (int)buffer.len);
res = setsockopt(get_sock_fd(s), level, optname,
buffer.buf, (int)buffer.len);

#else
res = setsockopt(get_sock_fd(s), level, optname, optval.buf, optval.len);
res = setsockopt(get_sock_fd(s), level, optname, buffer.buf, buffer.len);
#endif
PyBuffer_Release(&optval);
PyBuffer_Release(&buffer);
goto done;
}

PyErr_Format(PyExc_TypeError,
"socket option should be integer, bytes-like object or None");
return NULL;

done:
if (res < 0) {
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy