From effc12f8e9e20d0951d2ba5883587666bd8218e3 Mon Sep 17 00:00:00 2001 From: caavery Date: Wed, 6 Sep 2017 18:18:10 -0400 Subject: [PATCH] bpo-27584: New addition of vSockets to the python socket module (#2489) * bpo-27584: New addition of vSockets to the python socket module Support for AF_VSOCK on Linux only * bpo-27584: Fixes for V2 Fixed syntax and naming problems. Fixed #ifdef AF_VSOCK checking Restored original aclocal.m4 * bpo-27584: Fixes for V3 Added checking for fcntl and thread modules. * bpo-27584: Fixes for V4 Fixed white space error * bpo-27584: Fixes for V5 Added back comma in (CID, port). * bpo-27584: Fixes for V6 Added news file. socket.rst now reflects first Linux introduction of AF_VSOCK. Fixed get_cid in test_socket.py. Replaced PyLong_FromLong with PyLong_FromUnsignedLong in socketmodule.c Got rid of extra AF_VSOCK #define. Added sockaddr_vm to sock_addr. * bpo-27584: Fixes for V7 Minor cleanup. * bpo-27584: Fixes for V8 Put back #undef AF_VSOCK as it is necessary when vm_sockets.h is not installed. --- Doc/library/socket.rst | 20 ++++ Lib/test/test_socket.py | 107 ++++++++++++++++++ Misc/ACKS | 1 + .../2017-08-24-14-03-14.bpo-27584.r11JHZ.rst | 2 + Modules/socketmodule.c | 93 ++++++++++++++- Modules/socketmodule.h | 9 ++ configure | 18 +++ configure.ac | 6 + pyconfig.h.in | 3 + 9 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-08-24-14-03-14.bpo-27584.r11JHZ.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index c5064e92c21..42fd7ea0f0b 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -153,6 +153,14 @@ created. Socket addresses are represented as follows: .. versionadded:: 3.6 +- :const:`AF_VSOCK` allows communication between virtual machines and + their hosts. The sockets are represented as a ``(CID, port)`` tuple + where the context ID or CID and port are integers. + + Availability: Linux >= 4.8 QEMU >= 2.8 ESX >= 4.0 ESX Workstation >= 6.5 + + .. versionadded:: 3.7 + - Certain other address families (:const:`AF_PACKET`, :const:`AF_CAN`) support specific representations. @@ -395,6 +403,18 @@ Constants .. versionadded:: 3.6 + +.. data:: AF_VSOCK + IOCTL_VM_SOCKETS_GET_LOCAL_CID + VMADDR* + SO_VM* + + Constants for Linux host/guest communication. + + Availability: Linux >= 4.8. + + .. versionadded:: 3.7 + .. data:: AF_LINK Availability: BSD, OSX. diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 84234887747..50016ab615a 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -33,6 +33,8 @@ except ImportError: HOST = support.HOST MSG = 'Michael Gilfix was here\u1234\r\n'.encode('utf-8') ## test unicode string and carriage return +VSOCKPORT = 1234 + try: import _thread as thread import threading @@ -44,6 +46,16 @@ try: except ImportError: _socket = None +def get_cid(): + if fcntl is None: + return None + try: + with open("/dev/vsock", "rb") as f: + r = fcntl.ioctl(f, socket.IOCTL_VM_SOCKETS_GET_LOCAL_CID, " ") + except OSError: + return None + else: + return struct.unpack("I", r)[0] def _have_socket_can(): """Check whether CAN sockets are supported on this host.""" @@ -85,6 +97,11 @@ def _have_socket_alg(): s.close() return True +def _have_socket_vsock(): + """Check whether AF_VSOCK sockets are supported on this host.""" + ret = get_cid() is not None + return ret + HAVE_SOCKET_CAN = _have_socket_can() HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp() @@ -93,6 +110,8 @@ HAVE_SOCKET_RDS = _have_socket_rds() HAVE_SOCKET_ALG = _have_socket_alg() +HAVE_SOCKET_VSOCK = _have_socket_vsock() + # Size in bytes of the int type SIZEOF_INT = array.array("i").itemsize @@ -387,6 +406,42 @@ class ThreadedRDSSocketTest(SocketRDSTest, ThreadableTest): self.cli = None ThreadableTest.clientTearDown(self) +@unittest.skipIf(fcntl is None, "need fcntl") +@unittest.skipUnless(thread, 'Threading required for this test.') +@unittest.skipUnless(HAVE_SOCKET_VSOCK, + 'VSOCK sockets required for this test.') +@unittest.skipUnless(get_cid() != 2, + "This test can only be run on a virtual guest.") +class ThreadedVSOCKSocketStreamTest(unittest.TestCase, ThreadableTest): + + def __init__(self, methodName='runTest'): + unittest.TestCase.__init__(self, methodName=methodName) + ThreadableTest.__init__(self) + + def setUp(self): + self.serv = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) + self.addCleanup(self.serv.close) + self.serv.bind((socket.VMADDR_CID_ANY, VSOCKPORT)) + self.serv.listen() + self.serverExplicitReady() + self.conn, self.connaddr = self.serv.accept() + self.addCleanup(self.conn.close) + + def clientSetUp(self): + time.sleep(0.1) + self.cli = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) + self.addCleanup(self.cli.close) + cid = get_cid() + self.cli.connect((cid, VSOCKPORT)) + + def testStream(self): + msg = self.conn.recv(1024) + self.assertEqual(msg, MSG) + + def _testStream(self): + self.cli.send(MSG) + self.cli.close() + class SocketConnectedTest(ThreadedTCPSocketTest): """Socket tests for client-server connection. @@ -1874,6 +1929,54 @@ class RDSTest(ThreadedRDSSocketTest): self.assertIn(self.serv, r) +@unittest.skipIf(fcntl is None, "need fcntl") +@unittest.skipUnless(HAVE_SOCKET_VSOCK, + 'VSOCK sockets required for this test.') +class BasicVSOCKTest(unittest.TestCase): + + def testCrucialConstants(self): + socket.AF_VSOCK + + def testVSOCKConstants(self): + socket.SO_VM_SOCKETS_BUFFER_SIZE + socket.SO_VM_SOCKETS_BUFFER_MIN_SIZE + socket.SO_VM_SOCKETS_BUFFER_MAX_SIZE + socket.VMADDR_CID_ANY + socket.VMADDR_PORT_ANY + socket.VMADDR_CID_HOST + socket.VM_SOCKETS_INVALID_VERSION + socket.IOCTL_VM_SOCKETS_GET_LOCAL_CID + + def testCreateSocket(self): + with socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) as s: + pass + + def testSocketBufferSize(self): + with socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) as s: + orig_max = s.getsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_MAX_SIZE) + orig = s.getsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_SIZE) + orig_min = s.getsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_MIN_SIZE) + + s.setsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_MAX_SIZE, orig_max * 2) + s.setsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_SIZE, orig * 2) + s.setsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_MIN_SIZE, orig_min * 2) + + self.assertEqual(orig_max * 2, + s.getsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_MAX_SIZE)) + self.assertEqual(orig * 2, + s.getsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_SIZE)) + self.assertEqual(orig_min * 2, + s.getsockopt(socket.AF_VSOCK, + socket.SO_VM_SOCKETS_BUFFER_MIN_SIZE)) + @unittest.skipUnless(thread, 'Threading required for this test.') class BasicTCPTest(SocketConnectedTest): @@ -5681,6 +5784,10 @@ def test_main(): tests.extend([BasicCANTest, CANTest]) tests.extend([BasicRDSTest, RDSTest]) tests.append(LinuxKernelCryptoAPI) + tests.extend([ + BasicVSOCKTest, + ThreadedVSOCKSocketStreamTest, + ]) tests.extend([ CmsgMacroTests, SendmsgUDPTest, diff --git a/Misc/ACKS b/Misc/ACKS index e1127bcc72b..eadc5d34c40 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -65,6 +65,7 @@ David Ascher Ammar Askar Chris AtLee Aymeric Augustin +Cathy Avery John Aycock Donovan Baarda Arne Babenhauserheide diff --git a/Misc/NEWS.d/next/Library/2017-08-24-14-03-14.bpo-27584.r11JHZ.rst b/Misc/NEWS.d/next/Library/2017-08-24-14-03-14.bpo-27584.r11JHZ.rst new file mode 100644 index 00000000000..af4c583e186 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-08-24-14-03-14.bpo-27584.r11JHZ.rst @@ -0,0 +1,2 @@ +``AF_VSOCK`` has been added to the socket interface which allows +communication between virtual machines and their host. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 37626e67cb0..a431e254d57 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -1225,6 +1225,14 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto) } #endif /* AF_NETLINK */ +#if defined(AF_VSOCK) + case AF_VSOCK: + { + struct sockaddr_vm *a = (struct sockaddr_vm *) addr; + return Py_BuildValue("II", a->svm_cid, a->svm_port); + } +#endif /* AF_VSOCK */ + #ifdef ENABLE_IPV6 case AF_INET6: { @@ -1586,6 +1594,32 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, } #endif +#if defined(AF_VSOCK) + case AF_VSOCK: + { + struct sockaddr_vm* addr; + int port, cid; + addr = (struct sockaddr_vm *)addr_ret; + memset(addr, 0, sizeof(struct sockaddr_vm)); + if (!PyTuple_Check(args)) { + PyErr_Format( + PyExc_TypeError, + "getsockaddrarg: " + "AF_VSOCK address must be tuple, not %.500s", + Py_TYPE(args)->tp_name); + return 0; + } + if (!PyArg_ParseTuple(args, "II:getsockaddrarg", &cid, &port)) + return 0; + addr->svm_family = s->sock_family; + addr->svm_port = port; + addr->svm_cid = cid; + *len_ret = sizeof(*addr); + return 1; + } +#endif + + #ifdef AF_RDS case AF_RDS: /* RDS sockets use sockaddr_in: fall-through */ @@ -2103,6 +2137,14 @@ getsockaddrlen(PySocketSockObject *s, socklen_t *len_ret) } #endif +#if defined(AF_VSOCK) + case AF_VSOCK: + { + *len_ret = sizeof (struct sockaddr_vm); + return 1; + } +#endif + #ifdef AF_RDS case AF_RDS: /* RDS sockets use sockaddr_in: fall-through */ @@ -2598,6 +2640,21 @@ sock_setsockopt(PySocketSockObject *s, PyObject *args) unsigned int optlen; PyObject *none; +#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(s->sock_fd, level, optname, + (void*)&vflag, sizeof vflag); + goto done; + } + return NULL; + } +#endif + /* setsockopt(level, opt, flag) */ if (PyArg_ParseTuple(args, "iii:setsockopt", &level, &optname, &flag)) { @@ -2668,20 +2725,39 @@ sock_getsockopt(PySocketSockObject *s, PyObject *args) int res; PyObject *buf; socklen_t buflen = 0; + int flag = 0; + socklen_t flagsize; if (!PyArg_ParseTuple(args, "ii|i:getsockopt", &level, &optname, &buflen)) return NULL; if (buflen == 0) { - int flag = 0; - socklen_t flagsize = sizeof flag; +#ifdef AF_VSOCK + if (s->sock_family == AF_VSOCK) { + uint64_t vflag = 0; // Must be set width of 64 bits + flagsize = sizeof vflag; + res = getsockopt(s->sock_fd, level, optname, + (void *)&vflag, &flagsize); + if (res < 0) + return s->errorhandler(); + return PyLong_FromUnsignedLong(vflag); + } +#endif + flagsize = sizeof flag; res = getsockopt(s->sock_fd, level, optname, (void *)&flag, &flagsize); if (res < 0) return s->errorhandler(); return PyLong_FromLong(flag); } +#ifdef AF_VSOCK + if (s->sock_family == AF_VSOCK) { + PyErr_SetString(PyExc_OSError, + "getsockopt string buffer not allowed"); + return NULL; + } +#endif if (buflen <= 0 || buflen > 1024) { PyErr_SetString(PyExc_OSError, "getsockopt buflen out of range"); @@ -6645,6 +6721,19 @@ PyInit__socket(void) PyModule_AddIntMacro(m, NETLINK_CRYPTO); #endif #endif /* AF_NETLINK */ + +#ifdef AF_VSOCK + PyModule_AddIntConstant(m, "AF_VSOCK", AF_VSOCK); + PyModule_AddIntConstant(m, "SO_VM_SOCKETS_BUFFER_SIZE", 0); + PyModule_AddIntConstant(m, "SO_VM_SOCKETS_BUFFER_MIN_SIZE", 1); + PyModule_AddIntConstant(m, "SO_VM_SOCKETS_BUFFER_MAX_SIZE", 2); + PyModule_AddIntConstant(m, "VMADDR_CID_ANY", 0xffffffff); + PyModule_AddIntConstant(m, "VMADDR_PORT_ANY", 0xffffffff); + PyModule_AddIntConstant(m, "VMADDR_CID_HOST", 2); + PyModule_AddIntConstant(m, "VM_SOCKETS_INVALID_VERSION", 0xffffffff); + PyModule_AddIntConstant(m, "IOCTL_VM_SOCKETS_GET_LOCAL_CID", _IO(7, 0xb9)); +#endif + #ifdef AF_ROUTE /* Alias to emulate 4.4BSD */ PyModule_AddIntMacro(m, AF_ROUTE); diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index 03f982b9108..03dbf18f7bd 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -107,6 +107,12 @@ typedef int socklen_t; #define SOL_ALG 279 #endif +#ifdef HAVE_LINUX_VM_SOCKETS_H +# include +#else +# undef AF_VSOCK +#endif + /* Linux 3.19 */ #ifndef ALG_SET_AEAD_ASSOCLEN #define ALG_SET_AEAD_ASSOCLEN 4 @@ -193,6 +199,9 @@ typedef union sock_addr { #ifdef HAVE_SOCKADDR_ALG struct sockaddr_alg alg; #endif +#ifdef AF_VSOCK + struct sockaddr_vm vm; +#endif } sock_addr_t; /* The object holding a socket. It holds some extra information, diff --git a/configure b/configure index 3880421dba1..75d64324e6b 100755 --- a/configure +++ b/configure @@ -8088,6 +8088,24 @@ fi done +for ac_header in linux/vm_sockets.h +do : + ac_fn_c_check_header_compile "$LINENO" "linux/vm_sockets.h" "ac_cv_header_linux_vm_sockets_h" " +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +" +if test "x$ac_cv_header_linux_vm_sockets_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LINUX_VM_SOCKETS_H 1 +_ACEOF + +fi + +done + + # On Linux, can.h and can/raw.h require sys/socket.h for ac_header in linux/can.h linux/can/raw.h linux/can/bcm.h do : diff --git a/configure.ac b/configure.ac index 3bf01386d66..00d5abaaaed 100644 --- a/configure.ac +++ b/configure.ac @@ -2097,6 +2097,12 @@ AC_CHECK_HEADERS(linux/netlink.h,,,[ #endif ]) +AC_CHECK_HEADERS(linux/vm_sockets.h,,,[ +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +]) + # On Linux, can.h and can/raw.h require sys/socket.h AC_CHECK_HEADERS(linux/can.h linux/can/raw.h linux/can/bcm.h,,,[ #ifdef HAVE_SYS_SOCKET_H diff --git a/pyconfig.h.in b/pyconfig.h.in index 69033ef9e2e..1356eb58ab4 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -574,6 +574,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_TIPC_H +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_VM_SOCKETS_H + /* Define to 1 if you have the 'lockf' function and the F_LOCK macro. */ #undef HAVE_LOCKF