From 2e3f48637e2e4fbd99424a98f361a0df277ea351 Mon Sep 17 00:00:00 2001 From: Piotr Mikolajczyk Date: Thu, 14 Jan 2021 11:29:06 +0100 Subject: Linux: Add abstract address support for QLocal{Socket,Server} Takes advantage of Linux's and Android's support for abstract namespace when binding sockets, which is independent of the filesystem (see man entry for unix domain sockets). To make QLocalServer and QLocalSocket use an abstract socket address, one needs to set the socket options to QLocalServer::AbstractNamespaceOption. Fixes: QTBUG-16090 Change-Id: Ia9f9c9cc1ac5c28f9d44b0a48d854a7cfbd39b11 Reviewed-by: Thiago Macieira --- src/network/socket/qlocalsocket_unix.cpp | 122 +++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 16 deletions(-) (limited to 'src/network/socket/qlocalsocket_unix.cpp') diff --git a/src/network/socket/qlocalsocket_unix.cpp b/src/network/socket/qlocalsocket_unix.cpp index 6fd17a6213..5e050ad323 100644 --- a/src/network/socket/qlocalsocket_unix.cpp +++ b/src/network/socket/qlocalsocket_unix.cpp @@ -58,13 +58,38 @@ #define QT_CONNECT_TIMEOUT 30000 + QT_BEGIN_NAMESPACE +namespace { +// determine the full server path +static QString pathNameForConnection(const QString &connectingName, + QLocalSocket::SocketOptions options) +{ + if (options.testFlag(QLocalSocket::AbstractNamespaceOption) + || connectingName.startsWith(QLatin1Char('/'))) { + return connectingName; + } + + return QDir::tempPath() + QLatin1Char('/') + connectingName; +} + +static QLocalSocket::SocketOptions optionsForPlatform(QLocalSocket::SocketOptions srcOptions) +{ + // For OS that does not support abstract namespace the AbstractNamespaceOption + // option is cleared. + if (!PlatformSupportsAbstractNamespace) + return QLocalSocket::NoOptions; + return srcOptions; +} +} + QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), delayConnect(nullptr), connectTimer(nullptr), connectingSocket(-1), - state(QLocalSocket::UnconnectedState) + state(QLocalSocket::UnconnectedState), + socketOptions(QLocalSocket::NoOptions) { } @@ -261,27 +286,33 @@ void QLocalSocket::connectToServer(OpenMode openMode) void QLocalSocketPrivate::_q_connectToSocket() { Q_Q(QLocalSocket); - QString connectingPathName; - - // determine the full server path - if (connectingName.startsWith(QLatin1Char('/'))) { - connectingPathName = connectingName; - } else { - connectingPathName = QDir::tempPath(); - connectingPathName += QLatin1Char('/') + connectingName; - } + QLocalSocket::SocketOptions options = optionsForPlatform(socketOptions); + const QString connectingPathName = pathNameForConnection(connectingName, options); const QByteArray encodedConnectingPathName = QFile::encodeName(connectingPathName); - struct sockaddr_un name; - name.sun_family = PF_UNIX; - if (sizeof(name.sun_path) < (uint)encodedConnectingPathName.size() + 1) { + struct ::sockaddr_un addr; + addr.sun_family = PF_UNIX; + memset(addr.sun_path, 0, sizeof(addr.sun_path)); + + // for abstract socket add 2 to length, to take into account trailing AND leading null + constexpr unsigned int extraCharacters = PlatformSupportsAbstractNamespace ? 2 : 1; + + if (sizeof(addr.sun_path) < static_cast(encodedConnectingPathName.size() + extraCharacters)) { QString function = QLatin1String("QLocalSocket::connectToServer"); setErrorAndEmit(QLocalSocket::ServerNotFoundError, function); return; } - ::memcpy(name.sun_path, encodedConnectingPathName.constData(), - encodedConnectingPathName.size() + 1); - if (-1 == qt_safe_connect(connectingSocket, (struct sockaddr *)&name, sizeof(name))) { + + QT_SOCKLEN_T addrSize = sizeof(::sockaddr_un); + if (options.testFlag(QLocalSocket::AbstractNamespaceOption)) { + ::memcpy(addr.sun_path + 1, encodedConnectingPathName.constData(), + encodedConnectingPathName.size() + 1); + addrSize = offsetof(::sockaddr_un, sun_path) + encodedConnectingPathName.size() + 1; + } else { + ::memcpy(addr.sun_path, encodedConnectingPathName.constData(), + encodedConnectingPathName.size() + 1); + } + if (-1 == qt_safe_connect(connectingSocket, (struct sockaddr *)&addr, addrSize)) { QString function = QLatin1String("QLocalSocket::connectToServer"); switch (errno) { @@ -359,10 +390,69 @@ bool QLocalSocket::setSocketDescriptor(qintptr socketDescriptor, } QIODevice::open(openMode); d->state = socketState; + d->describeSocket(socketDescriptor); return d->unixSocket.setSocketDescriptor(socketDescriptor, newSocketState, openMode); } +void QLocalSocketPrivate::describeSocket(qintptr socketDescriptor) +{ + bool abstractAddress = false; + + struct ::sockaddr_un addr; + QT_SOCKLEN_T len = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + const int getpeernameStatus = ::getpeername(socketDescriptor, (sockaddr *)&addr, &len); + if (getpeernameStatus != 0 || len == offsetof(sockaddr_un, sun_path)) { + // this is the case when we call it from QLocalServer, then there is no peername + len = sizeof(addr); + if (::getsockname(socketDescriptor, (sockaddr *)&addr, &len) != 0) + return; + } + if (parseSockaddr(addr, static_cast(len), fullServerName, serverName, abstractAddress)) { + QLocalSocket::SocketOptions options = socketOptions.value(); + socketOptions = options.setFlag(QLocalSocket::AbstractNamespaceOption, abstractAddress); + } +} + +bool QLocalSocketPrivate::parseSockaddr(const struct ::sockaddr_un &addr, + uint len, + QString &fullServerName, + QString &serverName, + bool &abstractNamespace) +{ + if (len <= offsetof(::sockaddr_un, sun_path)) + return false; + len -= offsetof(::sockaddr_un, sun_path); + // check for abstract socket address + abstractNamespace = PlatformSupportsAbstractNamespace + && (addr.sun_family == PF_UNIX && addr.sun_path[0] == 0); + QStringDecoder toUtf16(QStringDecoder::System, QStringDecoder::Flag::Stateless); + // An abstract socket address can be arbitrary binary. To properly handle such a case, + // we'd have to add new access functions for this very specific case. Instead, we just + // attempt to decode it according to OS text encoding. If it fails we ignore the result. + QByteArrayView textData(addr.sun_path + (abstractNamespace ? 1 : 0), + len - (abstractNamespace ? 1 : 0)); + QString name = toUtf16(textData); + if (!name.isEmpty() && !toUtf16.hasError()) { + //conversion encodes the trailing zeros. So, in case of non-abstract namespace we + //chop them off as \0 character is not allowed in filenames + if (!abstractNamespace && (name.at(name.length() - 1) == QChar::fromLatin1('\0'))) { + int truncPos = name.length() - 1; + while (truncPos > 0 && name.at(truncPos - 1) == QChar::fromLatin1('\0')) + truncPos--; + name.truncate(truncPos); + } + fullServerName = name; + serverName = abstractNamespace + ? name + : fullServerName.mid(fullServerName.lastIndexOf(QLatin1Char('/')) + 1); + if (serverName.isEmpty()) + serverName = fullServerName; + } + return true; +} + void QLocalSocketPrivate::_q_abortConnectionAttempt() { Q_Q(QLocalSocket); -- cgit v1.2.3