Add sendmsg(), recvmsg() and recvmsg_into() methods to socket object. Based on a patch by Heiko Wundram. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -249,6 +249,7 @@ #ifdef HAVE_SYS_TYPES_H #include #endif +#include /* Generic socket object definitions and includes */ #define PySocket_BUILDING_SOCKET @@ -2494,6 +2495,303 @@ Like recv_into(buffer[, nbytes[, flags]]) but also return the sender's address info."); +/* This is the minimum ancillary data size required by RFC 3542. */ +#define RECVMSG_DEFAULT_CONTROLLEN 10240 + +/* Largest value that socklen_t is guaranteed to hold by POSIX, and + maximum recommended for application use. */ +/* XXX: could larger values be usable on 64-bit systems? */ +#define SOCKLEN_T_LIMIT 0x7fffffff + +/* + * Call recvmsg() with the supplied iovec structures, flags, and + * ancillary data buffer size (controllen). Returns the tuple return + * value for recvmsg() or recvmsg_into(), with the first item provided + * by the supplied makeval() function. makeval() will be called with + * the length read and makeval_data as arguments, and must return a + * new reference (which will be decrefed if there is a subsequent + * error). On error, closes any file descriptors received via + * SCM_RIGHTS. + */ +static PyObject * +sock_recvmsg_guts(PySocketSockObject *s, struct iovec *iov, int iovlen, + int flags, Py_ssize_t controllen, + PyObject *(*makeval)(ssize_t, void *), void *makeval_data) +{ + ssize_t res; + int timeout; + Py_ssize_t cmsgdatalen; + sock_addr_t addrbuf; + struct msghdr msg; + struct cmsghdr *cmsgh; + PyObject *curitem, *rv = NULL, *cmsg_list = NULL; + + if (!IS_SELECTABLE(s)) + return select_error(); + + if (controllen < 0 || controllen > SOCKLEN_T_LIMIT) { + PyErr_SetString(PyExc_ValueError, + "invalid ancillary data buffer length"); + return NULL; + } + + /* Use supplied iovecs for message data and allocate buffer + for ancillary data. */ + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + msg.msg_control = NULL; + msg.msg_controllen = controllen; + + if (msg.msg_controllen > 0 && + (msg.msg_control = PyMem_Malloc(msg.msg_controllen)) == NULL) + return PyErr_NoMemory(); + + /* XXX: POSIX says that msg_name "shall be ignored" for a + connected socket, but Linux fills it in anyway. Try to + initialize it to something that won't be mistaken for a + real address if it comes back unaltered. */ + memset(&addrbuf, 0, sizeof(addrbuf)); + SAS2SA(&addrbuf)->sa_family = AF_UNSPEC; + msg.msg_name = &addrbuf; + msg.msg_namelen = sizeof(addrbuf); + + /* Make the system call. */ + Py_BEGIN_ALLOW_THREADS; + timeout = internal_select(s, 0); + if (!timeout) + res = recvmsg(s->sock_fd, &msg, flags); + Py_END_ALLOW_THREADS; + + if (timeout) { + PyErr_SetString(socket_timeout, "timed out"); + goto finally; + } else if (res < 0) { + s->errorhandler(); + goto finally; + } + + /* Make list of (level, type, data) tuples from control messages. */ + if ((cmsg_list = PyList_New(0)) == NULL) + goto err_closefds; + /* XXX: Does CMSG_FIRSTHDR always check msg_controllen? */ + for (cmsgh = CMSG_FIRSTHDR(&msg); cmsgh != NULL; + cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { + PyObject *bytes; + int tmp; + + if (cmsgh->cmsg_len > PY_SSIZE_T_MAX) { + PyErr_SetString(socket_error, + "control message too long"); + goto err_closefds; + } else if (cmsgh->cmsg_len < CMSG_LEN(0)) + /* XXX: issue a warning here? */ + cmsgdatalen = 0; + else + /* XXX: is this calculation necessarily correct? */ + cmsgdatalen = cmsgh->cmsg_len - CMSG_LEN(0); + + bytes = PyBytes_FromStringAndSize((char *)CMSG_DATA(cmsgh), + cmsgdatalen); + curitem = Py_BuildValue("iiN", cmsgh->cmsg_level, + cmsgh->cmsg_type, bytes); + if (curitem == NULL) + goto err_closefds; + tmp = PyList_Append(cmsg_list, curitem); + Py_DECREF(curitem); + if (tmp != 0) + goto err_closefds; + } + + rv = Py_BuildValue("NNOi", + (*makeval)(res, makeval_data), + makesockaddr(s->sock_fd, SAS2SA(&addrbuf), + msg.msg_namelen > sizeof(addrbuf) ? + sizeof(addrbuf) : msg.msg_namelen, + s->sock_proto), + cmsg_list, + msg.msg_flags); + if (rv == NULL) + goto err_closefds; + +finally: + Py_XDECREF(cmsg_list); + PyMem_Free(msg.msg_control); + return rv; + +err_closefds: + /* Close all descriptors coming from SCM_RIGHTS, so they don't leak. */ + for (cmsgh = CMSG_FIRSTHDR(&msg); cmsgh != NULL; + cmsgh = CMSG_NXTHDR(&msg, cmsgh)) { + if (cmsgh->cmsg_level == SOL_SOCKET && + cmsgh->cmsg_type == SCM_RIGHTS) { + size_t numfds; + int *fdp; + + if (cmsgh->cmsg_len < CMSG_LEN(0)) + continue; + numfds = ((cmsgh->cmsg_len - CMSG_LEN(0)) / + sizeof(int)); + fdp = (int *)CMSG_DATA(cmsgh); + while (numfds-- > 0) + close(*fdp++); + } + } + goto finally; +} + + +static PyObject * +makeval_recvmsg(ssize_t received, void *data) +{ + PyObject **buf = data; + + if (received < PyBytes_GET_SIZE(*buf)) + _PyBytes_Resize(buf, received); + Py_XINCREF(*buf); + return *buf; +} + +/* s.recvmsg(nbytes[, flags[, ancillary_len]]) method */ + +static PyObject * +sock_recvmsg(PySocketSockObject *s, PyObject *args) +{ + Py_ssize_t recvlen; + int flags = 0; + Py_ssize_t controllen = RECVMSG_DEFAULT_CONTROLLEN; + struct iovec iov; + PyObject *rv = NULL; + PyObject *buf = NULL; + + if (!PyArg_ParseTuple(args, "n|in:recvmsg", + &recvlen, &flags, &controllen)) + return NULL; + + if (recvlen < 0) { + PyErr_SetString(PyExc_ValueError, + "negative message buffer length"); + return NULL; + } + if ((buf = PyBytes_FromStringAndSize(NULL, recvlen)) == NULL) + return NULL; + iov.iov_base = PyBytes_AS_STRING(buf); + iov.iov_len = recvlen; + + /* N.B. we're passing a pointer to *our pointer* to the bytes + object here (&buf); makeval_recvmsg() may incref the + object, or deallocate it and set our pointer to NULL. */ + rv = sock_recvmsg_guts(s, &iov, 1, flags, controllen, + &makeval_recvmsg, &buf); + Py_XDECREF(buf); + return rv; +} + +PyDoc_STRVAR(recvmsg_doc, +"recvmsg(nbytes[, flags[, ancillary_len]]) ->\n\ +(message_data, source_address, ancillary_data, msg_flags)\n\ +\n\ +Read message data (up to nbytes long) and ancillary data from the\n\ +socket. flags has the same meaning as for recv(). ancillary_len is\n\ +the size in bytes of the internal buffer used to receive the ancillary\n\ +data; it defaults to 10240, and control messages which do not fit into\n\ +this buffer might be truncated or discarded.\n\ +\n\ +The return value is a tuple. message_data is a bytes objects\n\ +representing the received message data. For an unconnected socket,\n\ +source_address is the address of the sending socket; otherwise, its\n\ +value is unspecified. ancillary_data is a sequence of tuples\n\ +(cmsg_level, cmsg_type, cmsg_data), where cmsg_data is a bytes\n\ +instance, representing the individual control messages received in the\n\ +form used by sendmsg(). msg_flags is the bitwise OR of the flags on\n\ +the received message (consult your system documentation for their\n\ +meanings)."); + + +static PyObject * +makeval_recvmsg_into(ssize_t received, void *data) +{ + return PyLong_FromSsize_t(received); +} + +/* s.recvmsg_into(recv_buffers[, flags[, ancillary_len]]) method */ + +static PyObject * +sock_recvmsg_into(PySocketSockObject *s, PyObject *args) +{ + int flags = 0; + Py_ssize_t controllen = RECVMSG_DEFAULT_CONTROLLEN; + struct iovec *iovs = NULL; + Py_ssize_t i, nitems, nbufs = 0; + Py_buffer *bufs = NULL; + PyObject *bufs_arg, *fast, *rv = NULL; + + if (!PyArg_ParseTuple(args, "O|in:recvmsg_into", + &bufs_arg, &flags, &controllen)) + return NULL; + + if ((fast = PySequence_Fast( + bufs_arg, "buffers argument is not iterable")) == NULL) + return NULL; + nitems = PySequence_Fast_GET_SIZE(fast); + if (nitems > INT_MAX) { + PyErr_SetString(PyExc_ValueError, "too many items"); + goto finally; + } + + /* Fill in an iovec for each item, and save the Py_buffer + structs to release afterwards. */ + if (nitems > 0 && + ((iovs = PyMem_New(struct iovec, nitems)) == NULL || + (bufs = PyMem_New(Py_buffer, nitems)) == NULL)) { + PyErr_NoMemory(); + goto finally; + } + for (; nbufs < nitems; nbufs++) { + if (!PyArg_Parse(PySequence_Fast_GET_ITEM(fast, nbufs), + "w*:[recvmsg_into() buffer items]", + &bufs[nbufs])) + goto finally; + iovs[nbufs].iov_base = bufs[nbufs].buf; + iovs[nbufs].iov_len = bufs[nbufs].len; + } + + rv = sock_recvmsg_guts(s, iovs, nitems, flags, controllen, + &makeval_recvmsg_into, NULL); +finally: + for (i = 0; i < nbufs; i++) + PyBuffer_Release(&bufs[i]); + PyMem_Free(bufs); + PyMem_Free(iovs); + Py_DECREF(fast); + return rv; +} + +PyDoc_STRVAR(recvmsg_into_doc, +"recvmsg_into(recv_buffers[, flags[, ancillary_len]]) ->\n\ +(received_length, source_address, ancillary_data, msg_flags)\n\ +\n\ +Read message data and ancillary data from the socket, scattering the\n\ +message data across multiple buffers. recv_buffers must be an\n\ +iterable of objects supporting the read-write buffer interface; these\n\ +will be filled with successive chunks of the message data until all\n\ +the data has been written or there are no more buffers. flags has the\n\ +same meaning as for recv(). ancillary_len is the size in bytes of the\n\ +internal buffer used to receive the ancillary data; it defaults to\n\ +10240, and control messages which do not fit into this buffer might be\n\ +truncated or discarded.\n\ +\n\ +The return value is a tuple. received_length is the total number of\n\ +bytes written into the supplied buffers. For an unconnected socket,\n\ +source_address is the address of the sending socket; otherwise, its\n\ +value is unspecified. ancillary_data is a sequence of tuples\n\ +(cmsg_level, cmsg_type, cmsg_data), where cmsg_data is a bytes\n\ +instance, representing the individual control messages received in the\n\ +form used by sendmsg(). msg_flags is the bitwise OR of the flags on\n\ +the received message (consult your system documentation for their\n\ +meanings)."); + + /* s.send(data [,flags]) method */ static PyObject * @@ -2655,6 +2953,192 @@ For IP sockets, the address is a pair (hostaddr, port)."); +/* s.sendmsg(data_parts[, ancillary_data[, flags[, address]]]) method */ + +static PyObject * +sock_sendmsg(PySocketSockObject *s, PyObject *args) +{ + ssize_t res; + int flags = 0, timeout; + sock_addr_t addrbuf; + int addrlen; + struct msghdr msg; + Py_ssize_t i, ndataparts, ndatabufs = 0, ncmsgs, ncmsgbufs = 0; + Py_buffer *databufs = NULL; + struct cmsghdr *cmsgh; + struct cmsginfo { + int level; + int type; + Py_buffer data; + } *cmsgs = NULL; + size_t controllen, controllen_last; + PyObject *data_arg, *cmsg_arg = NULL, *rv = NULL, *addr_arg = NULL, + *data_fast = NULL, *cmsg_fast = NULL; + + if (!IS_SELECTABLE(s)) + return select_error(); + + if (!PyArg_ParseTuple(args, "O|OiO:sendmsg", &data_arg, &cmsg_arg, + &flags, &addr_arg)) + return NULL; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = NULL; + msg.msg_control = NULL; + + /* Fill in an iovec for each message part, and save the + Py_buffer structs to release afterwards. */ + if ((data_fast = PySequence_Fast(data_arg, "message data argument is " + "not iterable")) == NULL) + goto finally; + ndataparts = PySequence_Fast_GET_SIZE(data_fast); + if (ndataparts > INT_MAX) { + PyErr_SetString(PyExc_ValueError, "too many message parts"); + goto finally; + } + msg.msg_iovlen = ndataparts; + if (ndataparts > 0 && + ((msg.msg_iov = PyMem_New(struct iovec, ndataparts)) == NULL || + (databufs = PyMem_New(Py_buffer, ndataparts)) == NULL)) { + PyErr_NoMemory(); + goto finally; + } + for (; ndatabufs < ndataparts; ndatabufs++) { + if (!PyArg_Parse( + PySequence_Fast_GET_ITEM(data_fast, ndatabufs), + "y*:[sendmsg() message data items]", + &databufs[ndatabufs])) + goto finally; + msg.msg_iov[ndatabufs].iov_base = databufs[ndatabufs].buf; + msg.msg_iov[ndatabufs].iov_len = databufs[ndatabufs].len; + } + + /* Save level, type and Py_buffer for each control message, + and calculate total size. */ + if (cmsg_arg == NULL) + ncmsgs = 0; + else { + if ((cmsg_fast = PySequence_Fast(cmsg_arg, + "ancillary data argument is " + "not iterable")) == NULL) + goto finally; + ncmsgs = PySequence_Fast_GET_SIZE(cmsg_fast); + if (ncmsgs > 0 && + (cmsgs = PyMem_New(struct cmsginfo, ncmsgs)) == NULL) { + PyErr_NoMemory(); + goto finally; + } + } + /* XXX: is CMSG_SPACE always available? */ + controllen = controllen_last = 0; + while (ncmsgbufs < ncmsgs) { + size_t bufsize, space; + + if (!PyArg_Parse( + PySequence_Fast_GET_ITEM(cmsg_fast, ncmsgbufs), + "(iiy*):[sendmsg() ancillary data items]", + &cmsgs[ncmsgbufs].level, + &cmsgs[ncmsgbufs].type, + &cmsgs[ncmsgbufs].data)) + goto finally; + bufsize = cmsgs[ncmsgbufs++].data.len; + + /* XXX: rather excessive safety margin to avoid signed + overflow in CMSG_SPACE (notionally, CMSG_SPACE has + argument and return type socklen_t, which could be + signed). */ + if (bufsize > (SOCKLEN_T_LIMIT / 2)) { + PyErr_SetString(PyExc_ValueError, + "ancillary data item too large"); + goto finally; + } + space = CMSG_SPACE(bufsize); + controllen += space; + if (space < bufsize || + controllen > SOCKLEN_T_LIMIT || + controllen < controllen_last) { + PyErr_SetString(PyExc_ValueError, + "too much ancillary data"); + goto finally; + } + controllen_last = controllen; + } + + /* Construct ancillary data block from control message info. */ + msg.msg_controllen = controllen; + if (controllen > 0) { + if ((msg.msg_control = PyMem_Malloc(controllen)) == NULL) { + PyErr_NoMemory(); + goto finally; + } + cmsgh = CMSG_FIRSTHDR(&msg); + for (i = 0; i < ncmsgbufs; i++) { + cmsgh->cmsg_level = cmsgs[i].level; + cmsgh->cmsg_type = cmsgs[i].type; + cmsgh->cmsg_len = CMSG_LEN(cmsgs[i].data.len); + memcpy(CMSG_DATA(cmsgh), + cmsgs[i].data.buf, cmsgs[i].data.len); + cmsgh = CMSG_NXTHDR(&msg, cmsgh); + } + } + + /* Parse destination address. */ + if (addr_arg == NULL || addr_arg == Py_None) { + msg.msg_name = NULL; + msg.msg_namelen = 0; + } else { + if (!getsockaddrarg(s, addr_arg, SAS2SA(&addrbuf), &addrlen)) + goto finally; + msg.msg_name = &addrbuf; + msg.msg_namelen = addrlen; + } + + /* Make the system call. */ + Py_BEGIN_ALLOW_THREADS; + timeout = internal_select(s, 1); + if (!timeout) + res = sendmsg(s->sock_fd, &msg, flags); + Py_END_ALLOW_THREADS; + + if (timeout) { + PyErr_SetString(socket_timeout, "timed out"); + goto finally; + } else if (res < 0) { + s->errorhandler(); + goto finally; + } + rv = PyLong_FromSsize_t(res); + +finally: + PyMem_Free(msg.msg_control); + for (i = 0; i < ncmsgbufs; i++) + PyBuffer_Release(&cmsgs[i].data); + PyMem_Free(cmsgs); + Py_XDECREF(cmsg_fast); + for (i = 0; i < ndatabufs; i++) + PyBuffer_Release(&databufs[i]); + PyMem_Free(databufs); + PyMem_Free(msg.msg_iov); + Py_XDECREF(data_fast); + return rv; +} + +PyDoc_STRVAR(sendmsg_doc, +"sendmsg(data_parts[, ancillary_data[, flags[, address]]]) ->\n\ +sent length\n\ +\n\ +Send message and ancillary data to the socket, gathering the message\n\ +data from multiple buffers. data_parts must be an iterable of objects\n\ +supporting the buffer interface (e.g. bytes objects); data from these\n\ +is concatenated into a single message when sent. ancillary_data must\n\ +be an iterable of tuples: (cmsg_level, cmsg_type, cmsg_data),\n\ +representing individual control messages; cmsg_level and cmsg_type are\n\ +integers specifying the protocol level and protocol-specific type\n\ +respectively, while cmsg_data is a buffer-compatible object holding\n\ +the control message data. flags and address have the same meaning as\n\ +for sendto(). Returns the number of bytes of message data sent."); + + /* s.shutdown(how) method */ static PyObject * @@ -2779,6 +3263,12 @@ setsockopt_doc}, {"shutdown", (PyCFunction)sock_shutdown, METH_O, shutdown_doc}, + {"recvmsg", (PyCFunction)sock_recvmsg, METH_VARARGS, + recvmsg_doc}, + {"recvmsg_into", (PyCFunction)sock_recvmsg_into, METH_VARARGS, + recvmsg_into_doc,}, + {"sendmsg", (PyCFunction)sock_sendmsg, METH_VARARGS, + sendmsg_doc}, {NULL, NULL} /* sentinel */ }; @@ -4534,6 +5024,12 @@ #ifdef SO_TYPE PyModule_AddIntConstant(m, "SO_TYPE", SO_TYPE); #endif +#ifdef SO_PASSCRED + PyModule_AddIntConstant(m, "SO_PASSCRED", SO_PASSCRED); +#endif +#ifdef SO_PEERCRED + PyModule_AddIntConstant(m, "SO_PEERCRED", SO_PEERCRED); +#endif /* Maximum number of connections for "listen" */ #ifdef SOMAXCONN @@ -4542,6 +5038,14 @@ PyModule_AddIntConstant(m, "SOMAXCONN", 5); /* Common value */ #endif + /* Ancilliary message types */ +#ifdef SCM_RIGHTS + PyModule_AddIntConstant(m, "SCM_RIGHTS", SCM_RIGHTS); +#endif +#ifdef SCM_CREDENTIALS + PyModule_AddIntConstant(m, "SCM_CREDENTIALS", SCM_CREDENTIALS); +#endif + /* Flags for send, recv */ #ifdef MSG_OOB PyModule_AddIntConstant(m, "MSG_OOB", MSG_OOB);