Skip to content
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

bpo-27584: New addition of vSockets to the python socket module #2489

Merged
merged 8 commits into from
Sep 6, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 20 additions & 0 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
107 changes: 107 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
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
Expand All @@ -44,6 +46,16 @@
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."""
Expand Down Expand Up @@ -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()
Expand All @@ -93,6 +110,8 @@ def _have_socket_alg():

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

Expand Down Expand Up @@ -387,6 +406,42 @@ def clientTearDown(self):
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.

Expand Down Expand Up @@ -1874,6 +1929,54 @@ def _testCongestion(self):
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):

Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ David Ascher
Ammar Askar
Chris AtLee
Aymeric Augustin
Cathy Avery
John Aycock
Donovan Baarda
Arne Babenhauserheide
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``AF_VSOCK`` has been added to the socket interface which allows
communication between virtual machines and their host.
93 changes: 91 additions & 2 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

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

This is the line failing in the Travis. https://travis-ci.org/python/cpython/jobs/248412030#L2063

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You don't have the proper headers.
checking for linux/vm_sockets.h... no

So it looks like my checking for HAVE_LINUX_VM_SOCKETS_H is faulty.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, so please update that, and push in your branch, the bots will pick up the changes and will test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've restored the original aclocal.m4 and fixed the issues you pointed out.

Thank you for your review,

Cathy

}
#endif /* AF_VSOCK */

#ifdef ENABLE_IPV6
case AF_INET6:
{
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions Modules/socketmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ typedef int socklen_t;
#define SOL_ALG 279
#endif

#ifdef HAVE_LINUX_VM_SOCKETS_H
# include <linux/vm_sockets.h>
#else
# undef AF_VSOCK
Copy link
Member

Choose a reason for hiding this comment

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

I don't think #undef AF_VSOCK is necessary. Please remove it.

Copy link
Contributor Author

@caavery caavery Aug 27, 2017

Choose a reason for hiding this comment

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

It needs to be there. In the case that vm_sockets.h is not available in include/linux which is the case on the last build bot run. Not all of the bots have the newer kernel.That way the code dependent on vm_sockets.h (AF_VSOCK) will not try to compile.

checking for linux/vm_sockets.h... no

This is also the way its done it at the top of socketmodule.h with AF_NETLINK

I also noticed AF_CAN was not adding the #undef AF_CAN in socketmodule.h so I renamed linux/can.h, ran configure and make and sure enough any code conditionally compiling under #ifdef AF_CAN in socketmodule.c failed.

#endif

/* Linux 3.19 */
#ifndef ALG_SET_AEAD_ASSOCLEN
#define ALG_SET_AEAD_ASSOCLEN 4
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -8100,6 +8100,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 <sys/socket.h>
#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 :
Expand Down
Loading