summaryrefslogtreecommitdiffstats
path: root/src/network/socket
diff options
context:
space:
mode:
authorPiotr Mikolajczyk <piotr.mikolajczyk@qt.io>2021-01-14 11:29:06 +0100
committerPiotr Mikolajczyk <piotr.mikolajczyk@qt.io>2021-04-01 06:57:04 +0100
commit2e3f48637e2e4fbd99424a98f361a0df277ea351 (patch)
tree274ad045334d025b87bb8973fb4afb47b6c3f8c9 /src/network/socket
parentaa84de1afa78cf4b63239b35a4b65f1c9c4eab6c (diff)
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 <thiago.macieira@intel.com>
Diffstat (limited to 'src/network/socket')
-rw-r--r--src/network/socket/qlocalserver.cpp15
-rw-r--r--src/network/socket/qlocalserver.h3
-rw-r--r--src/network/socket/qlocalserver_unix.cpp80
-rw-r--r--src/network/socket/qlocalsocket.cpp49
-rw-r--r--src/network/socket/qlocalsocket.h12
-rw-r--r--src/network/socket/qlocalsocket_p.h8
-rw-r--r--src/network/socket/qlocalsocket_unix.cpp122
7 files changed, 243 insertions, 46 deletions
diff --git a/src/network/socket/qlocalserver.cpp b/src/network/socket/qlocalserver.cpp
index 16dc907149..6517d6dc6a 100644
--- a/src/network/socket/qlocalserver.cpp
+++ b/src/network/socket/qlocalserver.cpp
@@ -90,6 +90,8 @@ QT_BEGIN_NAMESPACE
socket. This changes the access permissions on platforms (Linux, Windows)
that support access permissions on the socket. Both GroupAccess and OtherAccess
may vary slightly in meanings depending on the platform.
+ On Linux and Android it is possible to use sockets with abstract addresses;
+ socket permissions have no meaning for such sockets.
\value NoOptions No access restrictions have been set.
\value UserAccessOption
@@ -102,6 +104,10 @@ QT_BEGIN_NAMESPACE
Access is available to everyone on Windows.
\value WorldAccessOption
No access restrictions.
+ \value AbstractNamespaceOption
+ The listening socket will be created in the abstract namespace. This flag is specific to Linux.
+ In case of other platforms, for the sake of code portability, this flag is equivalent
+ to WorldAccessOption.
\sa socketOptions
*/
@@ -161,6 +167,11 @@ QLocalServer::~QLocalServer()
in the Windows documentation). OtherAccessOption refers to
the well known "Everyone" group.
+ On Linux platforms it is possible to create a socket in the abstract
+ namespace, which is independent of the filesystem. Using this kind
+ of socket implies ignoring permission options. On other platforms
+ AbstractNamespaceOption is equivalent to WorldAccessOption.
+
By default none of the flags are set, access permissions
are the platform default.
@@ -365,7 +376,9 @@ bool QLocalServer::listen(const QString &name)
serverName(), fullServerName() may return a string with
a name if this option is supported by the platform;
- otherwise, they return an empty QString.
+ otherwise, they return an empty QString. In particular, the addresses
+ of sockets in the abstract namespace supported by Linux will
+ not yield useful names if they contain unprintable characters.
\sa isListening(), close()
*/
diff --git a/src/network/socket/qlocalserver.h b/src/network/socket/qlocalserver.h
index 22b99d3e67..5da07653cb 100644
--- a/src/network/socket/qlocalserver.h
+++ b/src/network/socket/qlocalserver.h
@@ -67,7 +67,8 @@ public:
UserAccessOption = 0x01,
GroupAccessOption = 0x2,
OtherAccessOption = 0x4,
- WorldAccessOption = 0x7
+ WorldAccessOption = 0x7,
+ AbstractNamespaceOption = 0x8
};
Q_FLAG(SocketOption)
Q_DECLARE_FLAGS(SocketOptions, SocketOption)
diff --git a/src/network/socket/qlocalserver_unix.cpp b/src/network/socket/qlocalserver_unix.cpp
index 45fdc6dd74..1695332d55 100644
--- a/src/network/socket/qlocalserver_unix.cpp
+++ b/src/network/socket/qlocalserver_unix.cpp
@@ -44,6 +44,7 @@
#include "qnet_unix_p.h"
#include "qtemporarydir.h"
+#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
@@ -57,6 +58,23 @@
QT_BEGIN_NAMESPACE
+namespace {
+QLocalServer::SocketOptions optionsForPlatform(QLocalServer::SocketOptions srcOptions)
+{
+ // For OS that does not support abstract namespace the AbstractNamespaceOption
+ // means that we go for WorldAccessOption - as it is the closest option in
+ // regards of access rights. In Linux/Android case we clean-up the access rights.
+
+ if (srcOptions.testFlag(QLocalServer::AbstractNamespaceOption)) {
+ if (PlatformSupportsAbstractNamespace)
+ return QLocalServer::AbstractNamespaceOption;
+ else
+ return QLocalServer::WorldAccessOption;
+ }
+ return srcOptions;
+}
+}
+
void QLocalServerPrivate::init()
{
}
@@ -80,8 +98,12 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
{
Q_Q(QLocalServer);
+ // socket options adjusted for current platform
+ auto options = optionsForPlatform(socketOptions.value());
+
// determine the full server path
- if (requestedServerName.startsWith(QLatin1Char('/'))) {
+ if (options.testFlag(QLocalServer::AbstractNamespaceOption)
+ || requestedServerName.startsWith(QLatin1Char('/'))) {
fullServerName = requestedServerName;
} else {
fullServerName = QDir::cleanPath(QDir::tempPath());
@@ -93,8 +115,6 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
const QByteArray encodedFullServerName = QFile::encodeName(fullServerName);
QScopedPointer<QTemporaryDir> tempDir;
- // Check any of the flags
- const auto options = socketOptions.value();
if (options & QLocalServer::WorldAccessOption) {
QFileInfo serverNameFileInfo(fullServerName);
tempDir.reset(new QTemporaryDir(serverNameFileInfo.absolutePath() + QLatin1Char('/')));
@@ -115,15 +135,28 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
// Construct the unix address
struct ::sockaddr_un addr;
+
addr.sun_family = PF_UNIX;
- if (sizeof(addr.sun_path) < (uint)encodedFullServerName.size() + 1) {
+ ::memset(addr.sun_path, 0, sizeof(addr.sun_path));
+
+ // for abstract namespace 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<size_t>(encodedFullServerName.size() + extraCharacters)) {
setError(QLatin1String("QLocalServer::listen"));
closeServer();
return false;
}
- if (options & QLocalServer::WorldAccessOption) {
- if (sizeof(addr.sun_path) < (uint)encodedTempPath.size() + 1) {
+ QT_SOCKLEN_T addrSize = sizeof(::sockaddr_un);
+ if (options.testFlag(QLocalServer::AbstractNamespaceOption)) {
+ // Abstract socket address is distinguished by the fact
+ // that sun_path[0] is a null byte ('\0')
+ ::memcpy(addr.sun_path + 1, encodedFullServerName.constData(),
+ encodedFullServerName.size() + 1);
+ addrSize = offsetof(::sockaddr_un, sun_path) + encodedFullServerName.size() + 1;
+ } else if (options & QLocalServer::WorldAccessOption) {
+ if (sizeof(addr.sun_path) < static_cast<size_t>(encodedTempPath.size() + 1)) {
setError(QLatin1String("QLocalServer::listen"));
closeServer();
return false;
@@ -136,7 +169,7 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
}
// bind
- if (-1 == QT_SOCKET_BIND(listenSocket, (sockaddr *)&addr, sizeof(sockaddr_un))) {
+ if (-1 == QT_SOCKET_BIND(listenSocket, (sockaddr *)&addr, addrSize)) {
setError(QLatin1String("QLocalServer::listen"));
// if address is in use already, just close the socket, but do not delete the file
if (errno == EADDRINUSE)
@@ -152,9 +185,6 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
if (-1 == qt_safe_listen(listenSocket, 50)) {
setError(QLatin1String("QLocalServer::listen"));
closeServer();
- listenSocket = -1;
- if (error != QAbstractSocket::AddressInUseError)
- QFile::remove(fullServerName);
return false;
}
@@ -202,28 +232,17 @@ bool QLocalServerPrivate::listen(qintptr socketDescriptor)
::fcntl(listenSocket, F_SETFD, FD_CLOEXEC);
::fcntl(listenSocket, F_SETFL, ::fcntl(listenSocket, F_GETFL) | O_NONBLOCK);
-#ifdef Q_OS_LINUX
+ bool abstractAddress = false;
struct ::sockaddr_un addr;
QT_SOCKLEN_T len = sizeof(addr);
memset(&addr, 0, sizeof(addr));
- if (0 == ::getsockname(listenSocket, (sockaddr *)&addr, &len)) {
- // check for absract sockets
- if (addr.sun_family == PF_UNIX && addr.sun_path[0] == 0) {
- addr.sun_path[0] = '@';
- }
- QString name = QString::fromLatin1(addr.sun_path);
- if (!name.isEmpty()) {
- fullServerName = name;
- serverName = fullServerName.mid(fullServerName.lastIndexOf(QLatin1Char('/')) + 1);
- if (serverName.isEmpty()) {
- serverName = fullServerName;
- }
+ if (::getsockname(socketDescriptor, (sockaddr *)&addr, &len) == 0) {
+ if (QLocalSocketPrivate::parseSockaddr(addr, len, fullServerName, serverName,
+ abstractAddress)) {
+ QLocalServer::SocketOptions options = socketOptions.value();
+ socketOptions = options.setFlag(QLocalServer::AbstractNamespaceOption, abstractAddress);
}
}
-#else
- serverName.clear();
- fullServerName.clear();
-#endif
Q_ASSERT(!socketNotifier);
socketNotifier = new QSocketNotifier(listenSocket,
@@ -251,8 +270,13 @@ void QLocalServerPrivate::closeServer()
QT_CLOSE(listenSocket);
listenSocket = -1;
- if (!fullServerName.isEmpty())
+ if (!fullServerName.isEmpty()
+ && !optionsForPlatform(socketOptions).testFlag(QLocalServer::AbstractNamespaceOption)) {
QFile::remove(fullServerName);
+ }
+
+ serverName.clear();
+ fullServerName.clear();
}
/*!
diff --git a/src/network/socket/qlocalsocket.cpp b/src/network/socket/qlocalsocket.cpp
index 3ee98b85c8..39c981c8d4 100644
--- a/src/network/socket/qlocalsocket.cpp
+++ b/src/network/socket/qlocalsocket.cpp
@@ -66,6 +66,21 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \enum QLocalSocket::SocketOption
+ \since 6.2
+ This enum describes the possible options that can be used to connect to
+ a server. Currently, on Linux and Android it is used for specifying
+ connection to a server listening to a socket bound to an abstract address.
+
+ \value NoOptions No options have been set.
+ \value AbstractNamespaceOption
+ The socket will try to connect to an abstract address. This flag is specific
+ to Linux and Android. On other platforms is ignored.
+
+ \sa socketOptions
+*/
+
+/*!
\fn void QLocalSocket::connectToServer(OpenMode openMode)
\since 5.1
@@ -439,6 +454,40 @@ QString QLocalSocket::serverName() const
}
/*!
+ Returns the socket options as specified by setSocketOptions(),
+
+ \sa connectToServer()
+
+ */
+QLocalSocket::SocketOptions QLocalSocket::socketOptions() const
+{
+ Q_D(const QLocalSocket);
+ return d->socketOptions;
+}
+
+/*!
+ \since 6.2
+
+ Set the socket \a options of the connection.
+
+*/
+void QLocalSocket::setSocketOptions(QLocalSocket::SocketOptions option)
+{
+ Q_D(QLocalSocket);
+ if (d->state != UnconnectedState) {
+ qWarning("QLocalSocket::setSocketOptions() called while not in unconnected state");
+ return;
+ }
+ d->socketOptions = option;
+}
+
+QBindable<QLocalSocket::SocketOptions> QLocalSocket::bindableSocketOptions()
+{
+ Q_D(QLocalSocket);
+ return &d->socketOptions;
+}
+
+/*!
Returns the server path that the socket is connected to.
\note The return value of this function is platform specific.
diff --git a/src/network/socket/qlocalsocket.h b/src/network/socket/qlocalsocket.h
index 22763cb339..817990a45c 100644
--- a/src/network/socket/qlocalsocket.h
+++ b/src/network/socket/qlocalsocket.h
@@ -54,6 +54,7 @@ class Q_NETWORK_EXPORT QLocalSocket : public QIODevice
{
Q_OBJECT
Q_DECLARE_PRIVATE(QLocalSocket)
+ Q_PROPERTY(SocketOptions socketOptions READ socketOptions WRITE setSocketOptions BINDABLE bindableSocketOptions)
public:
enum LocalSocketError
@@ -79,6 +80,13 @@ public:
ClosingState = QAbstractSocket::ClosingState
};
+ enum SocketOption {
+ NoOptions = 0x00,
+ AbstractNamespaceOption = 0x01
+ };
+ Q_DECLARE_FLAGS(SocketOptions, SocketOption)
+ Q_FLAG(SocketOptions)
+
QLocalSocket(QObject *parent = nullptr);
~QLocalSocket();
@@ -108,6 +116,10 @@ public:
OpenMode openMode = ReadWrite);
qintptr socketDescriptor() const;
+ void setSocketOptions(SocketOptions option);
+ SocketOptions socketOptions() const;
+ QBindable<SocketOptions> bindableSocketOptions();
+
LocalSocketState state() const;
bool waitForBytesWritten(int msecs = 30000) override;
bool waitForConnected(int msecs = 30000);
diff --git a/src/network/socket/qlocalsocket_p.h b/src/network/socket/qlocalsocket_p.h
index e68cc72f9d..a80c09b517 100644
--- a/src/network/socket/qlocalsocket_p.h
+++ b/src/network/socket/qlocalsocket_p.h
@@ -73,9 +73,12 @@ QT_REQUIRE_CONFIG(localserver);
# include <errno.h>
#endif
+struct sockaddr_un;
+
QT_BEGIN_NAMESPACE
#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP)
+
class QLocalUnixSocket : public QTcpSocket
{
@@ -146,6 +149,9 @@ public:
void _q_connectToSocket();
void _q_abortConnectionAttempt();
void cancelDelayedConnect();
+ void describeSocket(qintptr socketDescriptor);
+ static bool parseSockaddr(const sockaddr_un &addr, uint len,
+ QString &fullServerName, QString &serverName, bool &abstractNamespace);
QSocketNotifier *delayConnect;
QTimer *connectTimer;
QString connectingName;
@@ -155,6 +161,8 @@ public:
QLocalSocket::LocalSocketState state;
QString serverName;
QString fullServerName;
+
+ Q_OBJECT_BINDABLE_PROPERTY(QLocalSocketPrivate, QLocalSocket::SocketOptions, socketOptions)
};
QT_END_NAMESPACE
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<size_t>(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<uint>(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);