I need to make getsockopt calls from Python, however the calls I intended to make require that the buffer passed to the C-extended getsockopt function be initialized in a certain way. The Python’s API is not designed as so, it only takes as a parameter (level, optname, bufflen). I’d like to extend to (level, optname, buff, bufflen). Is there any reason to why Python didn’t want to take a buffer from Python directly ?
The main reason is: you almost never need that, and it’s a pain to have to pass buffers around. Can you give an example of where the input buffer needs to be pre-initialized? Preferably a pair of examples, one in C one in Python, showing different behaviour?
int main(int argc, char const *argv[])
{
/* code */
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);
struct mptcp_subflow_data inf;
memset(&inf, 0, sizeof(inf));
inf.size_subflow_data = sizeof(struct mptcp_subflow_data);
socklen_t optlen = sizeof(inf);
/* We should get EOPNOTSUPP in case of fallback */
if (getsockopt(fd, SOL_MPTCP, 2, &inf, &optlen) < 0)
return 1;
printf("The number of subflows used are %d\n", inf.num_subflows);
return 0;
}
The most reassembling python code I could come up :
print(struct.calcsize("=IIII"))
returned = sock.getsockopt(284, 2, 16)
print(struct.unpack("=IIII", returned))
Note that this doesn’t work because the kernel expects the buffer passed to getsocketmodule to be initialized in a certain way. But the buffer passed on in the socketmodule.c from CPython doesn’t do such a thing.
With the above idea, we would have getsockopt that looks like this :
Hmm. Unfortunately I can’t test this; it fails in compilation, which I suspect means that Linux 5.10.158 doesn’t have SOL_MPTCP support. Either that, or it’s a massive case of PEBCAK and I just messed up which headers to include, but I grepped all my headers for that identifier and didn’t find any. Is this an incredibly new feature, or am I derping badly?
@barry-scott Actually, the error on my part was about the fact that buffer should be all zeroed out. However, we need to zero out a part of the buffer and set another part of the buffer to a specific value, as showed in the C example. The kernel reads from this buffer.
@Rosuav
Here’s the full C code in order to test it out :
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <linux/tcp.h>
#include <linux/types.h>
#ifndef SOL_MPTCP
#define SOL_MPTCP 284
#endif
#define IPPROTO_MPTCP 262
#define MPTCPLIB_OPERATION_UNSUPPORTED -2
#define MPTCPLIB_SOCKET_FALLBACK_TCP -1
#define MPTCPLIB_ERROR_FLAG -3
#ifndef _UAPI_MPTCP_H
#define MPTCP_INFO 1
#define MPTCP_TCPINFO 2
struct mptcp_subflow_data {
__u32 size_subflow_data; /* size of this structure in userspace */
__u32 num_subflows; /* must be 0, set by kernel */
__u32 size_kernel; /* must be 0, set by kernel */
__u32 size_user; /* size of one element in data[] */
} __attribute__((aligned(8)));
#endif
int main(int argc, char const *argv[])
{
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);
struct mptcp_subflow_data inf;
memset(&inf, 0, sizeof(inf)); // If you comment out this part then you get an error
inf.size_subflow_data = sizeof(struct mptcp_subflow_data);
socklen_t optlen = sizeof(inf);
if (getsockopt(fd, SOL_MPTCP, 2, &inf, &optlen) < 0){
perror("The error");
return 1;
}
printf("The number of subflows used are %d\n", inf.num_subflows);
return 0;
}
Thanks for posting the code. I still can’t test this reliably though - I get “Bad file descriptor” whether the line is commented out or not. Adding a perror just after socket creation shows “Protocol not supported”.
Given that you’re defining a lot of the constants manually, I have to ask: is this a non-standard feature that works only on certain network devices? Maybe the driver is misbehaving by assuming that it can read from the socket option buffer.
I’m defining certain constants cause the protocol hasn’t been widely deployed, but it is indeed implemented in the Linux kernel from 5.6, for some operations it requires a kernel >= 5.16
@barry-scott I agree with you thet one can use struct or ctypes libs to pass a buffer from python, but in my use-case I wanted to know if there’s already a python implementation of the desired functionality.
In case not, (which seems to be the case) I wanted to know why they didn’t make the getsockopt interface as the one in C. Since I’ll have to make my own C-extension.
FWIW, os.read(fd, n, /) is similar. I think it’s for convenience, so that users don’t need to allocate a buffer themselves, and it supports chain-calling such as print(os.read(fd, 16).decode(), int(os.read(fd, 16))). OTOH the disadvantage is obvious, it’s impossible to provide your own buffer.
In case you haven’t used ctypes before, here’s an example to get you started.
The getsockopt() function should be available in the global symbol table, i.e. ctypes.CDLL(None)[1]. You can capture errno for calls by specifying use_errno=True. The last captured value of errno for the current thread is available as ctypes.get_errno(). The number and types of function parameters are specified via the argtypes attribute of a function pointer.
import os
import socket
SOL_MPTCP = 284
IPPROTO_MPTCP = 262
MPTCPLIB_OPERATION_UNSUPPORTED = -2
MPTCPLIB_SOCKET_FALLBACK_TCP = -1
MPTCPLIB_ERROR_FLAG = -3
MPTCP_INFO = 1
MPTCP_TCPINFO = 2
try:
import ctypes
# distinct socklen_t type
class socklen_t(ctypes.c_uint32):
pass
# alias socklen_t type
# socklen_t = ctypes.c_uint32
class mptcp_subflow_data(ctypes.Structure):
_fields_ = (
('size_subflow_data', ctypes.c_uint32), # this structure size
('num_subflows', ctypes.c_uint32), # must be 0
('size_kernel', ctypes.c_uint32), # must be 0
('size_user', ctypes.c_uint32), # element size in data[]
)
__slots__ = ()
def __init__(self, size_user=0):
super().__init__(size_user=size_user)
self.size_subflow_data = ctypes.sizeof(self)
getsockopt = ctypes.CDLL(None, use_errno=True).getsockopt
getsockopt.restype = ctypes.c_int
getsockopt.argtypes = (
ctypes.c_int, # sockfd
ctypes.c_int, # level
ctypes.c_int, # optname
ctypes.c_void_p, # optval
ctypes.POINTER(socklen_t), # optlen
)
except (ImportError, TypeError, AttributeError):
getsockopt = None
Usage:
if getsockopt is None:
raise OSError('platform not supported')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, IPPROTO_MPTCP)
optval = mptcp_subflow_data()
optlen = socklen_t(ctypes.sizeof(optval))
if getsockopt(s.fileno(), SOL_MPTCP, 2, ctypes.byref(optval),
ctypes.byref(optlen)) == -1:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
print(f"The number of subflows used is {optval.num_subflows}.")
On Windows this raises TypeError, but only Linux matters here. ↩︎