common/Common.hpp | 5 + common/UnitHTTP.hpp | 2 kit/ForKit.cpp | 6 +- kit/Kit.cpp | 23 ++++---- net/ServerSocket.hpp | 18 ++++++ net/Socket.cpp | 136 ++++++++++++++++++++++++++++++++++++++++----------- net/Socket.hpp | 25 ++++++--- wsd/LOOLWSD.cpp | 68 +++++++++++-------------- 8 files changed, 194 insertions(+), 89 deletions(-)
New commits: commit 81a27e26aab2fed410310c3cda46090b8a2c8a50 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Sat Mar 30 14:06:16 2019 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 30 16:51:06 2019 +0000 Switch local prisoner sockets to abstract UDS Unix Domain Sockets are inaddressable remotely, and more efficient, as well as allowing future SCM_CREDENTIALS / SCM_RIGHTS. Change-Id: Ia2472260f75feb43e9022cdfa0fe005ccd489454 diff --git a/common/Common.hpp b/common/Common.hpp index 013b30798..af37a77dd 100644 --- a/common/Common.hpp +++ b/common/Common.hpp @@ -7,12 +7,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <string> + // Default values and other shared data between processes. #ifndef INCLUDED_COMMON_HPP #define INCLUDED_COMMON_HPP constexpr int DEFAULT_CLIENT_PORT_NUMBER = 9980; -constexpr int DEFAULT_MASTER_PORT_NUMBER = 9981; constexpr int COMMAND_TIMEOUT_MS = 5000; constexpr long CHILD_TIMEOUT_MS = COMMAND_TIMEOUT_MS; @@ -46,7 +47,7 @@ constexpr const char* WOPI_AGENT_STRING = "LOOLWSD WOPI Agent " LOOLWSD_VERSION; // The client port number, both loolwsd and the kits have this. extern int ClientPortNumber; -extern int MasterPortNumber; +extern std::string MasterLocation; #endif diff --git a/common/UnitHTTP.hpp b/common/UnitHTTP.hpp index d87ce8e3f..2eb1c629f 100644 --- a/common/UnitHTTP.hpp +++ b/common/UnitHTTP.hpp @@ -74,7 +74,7 @@ public: UnitHTTPServerRequest(UnitHTTPServerResponse& inResponse, const std::string& uri) : _response(inResponse), - _serverAddress(MasterPortNumber) + _serverAddress(9981) // FIXME: Unix Sockets now ... { setURI(uri); } diff --git a/kit/ForKit.cpp b/kit/ForKit.cpp index 38c15d215..1cbf59a13 100644 --- a/kit/ForKit.cpp +++ b/kit/ForKit.cpp @@ -60,7 +60,7 @@ static std::map<Process::PID, std::string> childJails; #ifndef KIT_IN_PROCESS int ClientPortNumber = DEFAULT_CLIENT_PORT_NUMBER; -int MasterPortNumber = DEFAULT_MASTER_PORT_NUMBER; +std::string MasterLocation; #endif /// Dispatcher class to demultiplex requests from @@ -401,7 +401,7 @@ int main(int argc, char** argv) ClientPortNumber = std::stoi(clientPort); static const char* masterPort = std::getenv("LOOL_TEST_MASTER_PORT"); if (masterPort) - MasterPortNumber = std::stoi(masterPort); + MasterLocation = masterPort; #endif for (int i = 0; i < argc; ++i) @@ -436,7 +436,7 @@ int main(int argc, char** argv) else if (std::strstr(cmd, "--masterport=") == cmd) { eq = std::strchr(cmd, '='); - MasterPortNumber = std::stoll(std::string(eq+1)); + MasterLocation = std::string(eq+1); } else if (std::strstr(cmd, "--version") == cmd) { diff --git a/kit/Kit.cpp b/kit/Kit.cpp index faf6ad4f8..819fb2698 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -2486,20 +2486,21 @@ void lokit_main( LOG_INF("Process is ready."); static const std::string pid = std::to_string(Process::id()); - - Poco::URI uri("ws://127.0.0.1"); - uri.setPort(MasterPortNumber); - uri.setPath(NEW_CHILD_URI); - uri.addQueryParameter("pid", std::to_string(Process::id())); - uri.addQueryParameter("jailid", jailId); - + std::string pathAndQuery(NEW_CHILD_URI); + pathAndQuery.append("?pid="); + pathAndQuery.append(pid); + pathAndQuery.append("&jailid="); + pathAndQuery.append(jailId); if (queryVersion) { char* versionInfo = loKit->getVersionInfo(); std::string versionString(versionInfo); if (displayVersion) std::cout << "office version details: " << versionString << std::endl; - uri.addQueryParameter("version", versionString); + std::string encodedVersion; + Poco::URI::encode(versionString, "?#/", encodedVersion); + pathAndQuery.append("&version="); + pathAndQuery.append(encodedVersion); free(versionInfo); } @@ -2528,10 +2529,12 @@ void lokit_main( SocketPoll mainKit("kit"); mainKit.runOnClientThread(); // We will do the polling on this thread. + std::shared_ptr<SocketHandlerInterface> websocketHandler = + std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId, mainKit); #if !MOBILEAPP - mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId, mainKit)); + mainKit.insertNewUnixSocket(MasterLocation, pathAndQuery, websocketHandler); #else - mainKit.insertNewWebSocketSync(docBrokerSocket, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId, mainKit)); + mainKit.insertNewFakeSocketSync(docBrokerSocket, websocketHandler); #endif LOG_INF("New kit client websocket inserted."); diff --git a/net/ServerSocket.hpp b/net/ServerSocket.hpp index eb0bb46ca..79a7795bc 100644 --- a/net/ServerSocket.hpp +++ b/net/ServerSocket.hpp @@ -35,12 +35,13 @@ public: { } + /// Control access to a bound TCP socket enum Type { Local, Public }; /// Binds to a local address (Servers only). /// Does not retry on error. /// Returns true only on success. - bool bind(Type type, int port); + virtual bool bind(Type type, int port); /// Listen to incoming connections (Servers only). /// Does not retry on error. @@ -145,6 +146,21 @@ private: std::shared_ptr<SocketFactory> _sockFactory; }; +/// A non-blocking, streaming Unix Domain Socket for local use +class LocalServerSocket : public ServerSocket +{ +public: + LocalServerSocket(SocketPoll& clientPoller, std::shared_ptr<SocketFactory> sockFactory) : + ServerSocket(Socket::Type::Unix, clientPoller, sockFactory) + { + } + virtual bool bind(Type, int) { assert(false); return false; } + std::string bind(); + +private: + std::string _name; +}; + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/net/Socket.cpp b/net/Socket.cpp index 8d2cd3533..a1762352e 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -37,10 +37,20 @@ int SocketPoll::DefaultPollTimeoutMs = 5000; std::atomic<bool> SocketPoll::InhibitThreadChecks(false); std::atomic<bool> Socket::InhibitThreadChecks(false); +#define SOCKET_ABSTRACT_UNIX_NAME "0loolwsd-" + int Socket::createSocket(Socket::Type type) { #if !MOBILEAPP - int domain = type == Type::IPv4 ? AF_INET : AF_INET6; + int domain; + switch (type) + { + case Type::IPv4: domain = AF_INET; break; + case Type::IPv6: domain = AF_INET6; break; + case Type::All: domain = AF_INET6; break; + case Type::Unix: domain = AF_UNIX; break; + default: assert (false); break; + } return socket(domain, SOCK_STREAM | SOCK_NONBLOCK, 0); #else return fakeSocketSocket(); @@ -190,14 +200,9 @@ void SocketPoll::wakeupWorld() } void SocketPoll::insertNewWebSocketSync( -#if !MOBILEAPP - const Poco::URI &uri, -#else - int peerSocket, -#endif - const std::shared_ptr<SocketHandlerInterface>& websocketHandler) + const Poco::URI &uri, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler) { -#if !MOBILEAPP LOG_INF("Connecting to " << uri.getHost() << " : " << uri.getPort() << " : " << uri.getPath()); // FIXME: put this in a ClientSocket class ? @@ -247,24 +252,7 @@ void SocketPoll::insertNewWebSocketSync( if (socket) { LOG_DBG("Connected to client websocket " << uri.getHost() << " #" << socket->getFD()); - - // cf. WebSocketHandler::upgradeToWebSocket (?) - // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 - - std::ostringstream oss; - oss << "GET " << uri.getPathAndQuery() << " HTTP/1.1\r\n" - "Connection:Upgrade\r\n" - "User-Foo: Adminbits\r\n" - "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" - "Upgrade:websocket\r\n" - "Accept-Language:en\r\n" - "Cache-Control:no-cache\r\n" - "Pragma:no-cache\r\n" - "Sec-WebSocket-Version:13\r\n" - "User-Agent: " << WOPI_AGENT_STRING << "\r\n" - "\r\n"; - socket->send(oss.str()); - websocketHandler->onConnect(socket); + clientRequestWebsocketUpgrade(socket, websocketHandler, uri.getPathAndQuery()); insertNewSocket(socket); } else @@ -282,7 +270,71 @@ void SocketPoll::insertNewWebSocketSync( } else LOG_ERR("Failed to lookup client websocket host '" << uri.getHost() << "' skipping"); -#else +} + +// should this be a static method in the WebsocketHandler(?) +void SocketPoll::clientRequestWebsocketUpgrade(const std::shared_ptr<StreamSocket>& socket, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler, + const std::string &pathAndQuery) +{ + // cf. WebSocketHandler::upgradeToWebSocket (?) + // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 + + LOG_TRC("Requesting upgrade of websocket at path " << pathAndQuery << " #" << socket->getFD()); + + std::ostringstream oss; + oss << "GET " << pathAndQuery << " HTTP/1.1\r\n" + "Connection:Upgrade\r\n" + "User-Foo: Adminbits\r\n" + "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" + "Upgrade:websocket\r\n" + "Accept-Language:en\r\n" + "Cache-Control:no-cache\r\n" + "Pragma:no-cache\r\n" + "Sec-WebSocket-Version:13\r\n" + "User-Agent: " << WOPI_AGENT_STRING << "\r\n" + "\r\n"; + socket->send(oss.str()); + websocketHandler->onConnect(socket); +} + +void SocketPoll::insertNewUnixSocket( + const std::string &location, + const std::string &pathAndQuery, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler) +{ + int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + + struct sockaddr_un addrunix; + std::memset(&addrunix, 0, sizeof(addrunix)); + addrunix.sun_family = AF_UNIX; + addrunix.sun_path[0] = '\0'; // abstract name + memcpy(&addrunix.sun_path[1], location.c_str(), location.length()); + + int res = connect(fd, (const struct sockaddr *)&addrunix, sizeof(addrunix)); + if (fd < 0 || (res < 0 && errno != EINPROGRESS)) + { + LOG_ERR("Failed to connect to unix socket at " << location); + ::close(fd); + } + else + { + std::shared_ptr<StreamSocket> socket; + socket = StreamSocket::create<StreamSocket>(fd, true, websocketHandler); + if (socket) + { + LOG_DBG("Connected to local UDS " << location << " #" << socket->getFD()); + clientRequestWebsocketUpgrade(socket, websocketHandler, pathAndQuery); + insertNewSocket(socket); + } + } +} + +#if MOBILEAPP +void SocketPoll::insertNewFakeSocket( + int peerSocket, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler) +{ LOG_INF("Connecting to " << peerSocket); int fd = fakeSocketSocket(); int res = fakeSocketConnect(fd, peerSocket); @@ -307,8 +359,8 @@ void SocketPoll::insertNewWebSocketSync( fakeSocketClose(fd); } } -#endif } +#endif void ServerSocket::dumpState(std::ostream& os) { @@ -390,6 +442,7 @@ bool ServerSocket::bind(Type type, int port) int rc; + assert (_type != Socket::Type::Unix); if (_type == Socket::Type::IPv4) { struct sockaddr_in addrv4; @@ -430,6 +483,33 @@ bool ServerSocket::bind(Type type, int port) #endif } +/// Returns true on success only. +std::string LocalServerSocket::bind() +{ +#if !MOBILEAPP + int rc; + struct sockaddr_un addrunix; + do + { + std::memset(&addrunix, 0, sizeof(addrunix)); + addrunix.sun_family = AF_UNIX; + std::memcpy(addrunix.sun_path, SOCKET_ABSTRACT_UNIX_NAME, sizeof(SOCKET_ABSTRACT_UNIX_NAME)); + addrunix.sun_path[0] = '\0'; // abstract name + + std::string rand = Util::rng::getFilename(8); + memcpy(addrunix.sun_path + sizeof(SOCKET_ABSTRACT_UNIX_NAME) - 1, rand.c_str(), 8); + + rc = ::bind(getFD(), (const sockaddr *)&addrunix, sizeof(struct sockaddr_un)); + LOG_TRC("Bind to location " << std::string(&addrunix.sun_path[1]) << + " result - " << rc << "errno: " << ((rc >= 0) ? "no error" : ::strerror(errno))); + } while (rc < 0 && errno == EADDRINUSE); + + if (rc >= 0) + return std::string(&addrunix.sun_path[1]); +#endif + return ""; +} + #if !MOBILEAPP bool StreamSocket::parseHeader(const char *clientName, diff --git a/net/Socket.hpp b/net/Socket.hpp index 1a074ce2d..10460fe4b 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -101,7 +101,7 @@ public: static const int MaximumSendBufferSize = 128 * 1024; static std::atomic<bool> InhibitThreadChecks; - enum Type { IPv4, IPv6, All }; + enum Type { IPv4, IPv6, All, Unix }; Socket(Type type) : _fd(createSocket(type)), @@ -642,15 +642,21 @@ public: } } - /// Inserts a new websocket to be polled. - /// NOTE: The DNS lookup is synchronous. - void insertNewWebSocketSync( #if !MOBILEAPP - const Poco::URI &uri, + /// Inserts a new remote websocket to be polled. + /// NOTE: The DNS lookup is synchronous. + void insertNewWebSocketSync(const Poco::URI &uri, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler); + + void insertNewUnixSocket( + const std::string &location, + const std::string &pathAndQuery, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler); #else - int peerSocket, + void insertNewFakeSocket( + int peerSocket, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler); #endif - const std::shared_ptr<SocketHandlerInterface>& websocketHandler); typedef std::function<void()> CallbackFn; @@ -718,6 +724,11 @@ protected: } private: + /// Generate the request to connect & upgrade this socket to a given path + void clientRequestWebsocketUpgrade(const std::shared_ptr<StreamSocket>& socket, + const std::shared_ptr<SocketHandlerInterface>& websocketHandler, + const std::string &pathAndQuery); + /// Initialize the poll fds array with the right events void setupPollFds(std::chrono::steady_clock::time_point now, int &timeoutMaxMs) diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 095795cc1..0b3ccfe86 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -188,8 +188,10 @@ Socket::Type ClientPortProto = Socket::Type::All; /// INET address to listen on ServerSocket::Type ClientListenAddr = ServerSocket::Type::Public; -/// Port for prisoners to connect to -int MasterPortNumber = DEFAULT_MASTER_PORT_NUMBER; +#if !MOBILEAPP +/// UDS address for kits to connect to. +std::string MasterLocation; +#endif // Tracks the set of prisoners / children waiting to be used. static std::mutex NewChildrenMutex; @@ -1255,8 +1257,7 @@ void LOOLWSD::defineOptions(OptionSet& optionSet) .repeatable(false)); optionSet.addOption(Option("port", "", "Port number to listen to (default: " + - std::to_string(DEFAULT_CLIENT_PORT_NUMBER) + ")," - " must not be " + std::to_string(MasterPortNumber) + ".") + std::to_string(DEFAULT_CLIENT_PORT_NUMBER) + "),") .required(false) .repeatable(false) .argument("port_number")); @@ -1342,10 +1343,6 @@ void LOOLWSD::handleOption(const std::string& optionName, if (clientPort) ClientPortNumber = std::stoi(clientPort); - static const char* masterPort = std::getenv("LOOL_TEST_MASTER_PORT"); - if (masterPort) - MasterPortNumber = std::stoi(masterPort); - static const char* latencyMs = std::getenv("LOOL_DELAY_SOCKET_MS"); if (latencyMs) SimulatedLatencyMs = std::stoi(latencyMs); @@ -1556,7 +1553,7 @@ bool LOOLWSD::createForKit() args.push_back("--lotemplate=" + LoTemplate); args.push_back("--childroot=" + ChildRoot); args.push_back("--clientport=" + std::to_string(ClientPortNumber)); - args.push_back("--masterport=" + std::to_string(MasterPortNumber)); + args.push_back("--masterport=" + MasterLocation); const DocProcSettings& docProcSettings = Admin::instance().getDefDocProcSettings(); std::ostringstream ossRLimits; @@ -2869,10 +2866,10 @@ public: stop(); } - void startPrisoners(int& port) + void startPrisoners() { PrisonerPoll.startThread(); - PrisonerPoll.insertNewSocket(findPrisonerServerPort(port)); + PrisonerPoll.insertNewSocket(findPrisonerServerPort()); } void stopPrisoners() @@ -2911,7 +2908,7 @@ public: os << "LOOLWSDServer:\n" << " Ports: server " << ClientPortNumber - << " prisoner " << MasterPortNumber << "\n" + << " prisoner " << MasterLocation << "\n" << " SSL: " << (LOOLWSD::isSSLEnabled() ? "https" : "http") << "\n" << " SSL-Termination: " << (LOOLWSD::isSSLTermination() ? "yes" : "no") << "\n" << " Security " << (LOOLWSD::NoCapsForKit ? "no" : "") << " chroot, " @@ -2974,8 +2971,7 @@ private: const std::shared_ptr<SocketFactory>& factory) { auto serverSocket = std::make_shared<ServerSocket>( - type == ServerSocket::Type::Local ? Socket::Type::IPv4 : ClientPortProto, - clientSocket, factory); + ClientPortProto, clientSocket, factory); if (!serverSocket->bind(type, port)) return nullptr; @@ -2987,35 +2983,37 @@ private: } /// Create the internal only, local socket for forkit / kits prisoners to talk to. - std::shared_ptr<ServerSocket> findPrisonerServerPort(int& port) + std::shared_ptr<ServerSocket> findPrisonerServerPort() { std::shared_ptr<SocketFactory> factory = std::make_shared<PrisonerSocketFactory>(); - std::shared_ptr<ServerSocket> socket = getServerSocket( - ServerSocket::Type::Local, port, PrisonerPoll, factory); +#if !MOBILEAPP + std::string location; + auto socket = std::make_shared<LocalServerSocket>(PrisonerPoll, factory);; -#ifdef BUILDING_TESTS - // If we fail, try the next 100 ports. - for (int i = 0; i < 100 && !socket; ++i) + location = socket->bind(); + if (!location.length()) { - ++port; - LOG_INF("Prisoner port " << (port - 1) << " is busy, trying " << port << "."); - socket = getServerSocket( - ServerSocket::Type::Local, port, PrisonerPoll, factory); + LOG_FTL("Failed to create local unix domain socket. Exiting."); + Log::shutdown(); + _exit(Application::EXIT_SOFTWARE); + return nullptr; } -#endif - if (!socket) + if (!socket->listen()) { - LOG_FTL("Failed to listen on Prisoner port(s) (" << - MasterPortNumber << '-' << port << "). Exiting."); + LOG_FTL("Failed to listen on local unix domain socket at " << location << ". Exiting."); Log::shutdown(); _exit(Application::EXIT_SOFTWARE); } - MasterPortNumber = port; -#if !MOBILEAPP - LOG_INF("Listening to prisoner connections on port " << port); + LOG_INF("Listening to prisoner connections on " << location); + MasterLocation = location; #else + // TESTME ... + constexpr int DEFAULT_MASTER_PORT_NUMBER = 9981; + std::shared_ptr<ServerSocket> socket = getServerSocket( + ServerSocket::Type::Public, DEFAULT_MASTER_PORT_NUMBER, PrisonerPoll, factory); + LOOLWSD::prisonerServerSocketFD = socket->getFD(); LOG_INF("Listening to prisoner connections on #" << LOOLWSD::prisonerServerSocketFD); #endif @@ -3041,8 +3039,7 @@ private: { ++port; LOG_INF("Client port " << (port - 1) << " is busy, trying " << port << "."); - socket = getServerSocket( - ServerSocket::Type::Public, port, WebServerPoll, factory); + socket = getServerSocket(port, WebServerPoll, factory); } #endif @@ -3138,16 +3135,13 @@ int LOOLWSD::innerMain() FileServerRoot = Poco::Path(Application::instance().commandPath()).parent().toString(); FileServerRoot = Poco::Path(FileServerRoot).absolute().toString(); LOG_DBG("FileServerRoot: " << FileServerRoot); - - if (ClientPortNumber == MasterPortNumber) - throw IncompatibleOptionsException("port"); #endif ClientRequestDispatcher::InitStaticFileContentCache(); // Start the internal prisoner server and spawn forkit, // which in turn forks first child. - srv.startPrisoners(MasterPortNumber); + srv.startPrisoners(); // No need to "have at least one child" beforehand on mobile #if !MOBILEAPP _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits