diff options
author | dmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-20 06:19:07 +0000 |
---|---|---|
committer | dmaclach@chromium.org <dmaclach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-20 06:19:07 +0000 |
commit | 9a44a4db61927c16d5670163faa319e682f7e6fd (patch) | |
tree | 5bdbe999dfccbc7a8d0d3e2cc9bf3924487fcfe0 | |
parent | 25bfd35858599448c8739ec6d5d93b2a4fef09e3 (diff) | |
download | chromium_src-9a44a4db61927c16d5670163faa319e682f7e6fd.zip chromium_src-9a44a4db61927c16d5670163faa319e682f7e6fd.tar.gz chromium_src-9a44a4db61927c16d5670163faa319e682f7e6fd.tar.bz2 |
Add support for sockets that can listen and accept a connection.
These sockets allow one connection at a time, however clients can
connect and disconnect repeatedly.
These are going to be used by Cloud Print, Remoting and
Automation.
BUG=NONE
TEST=BUILD
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=69660
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=69690
Review URL: http://codereview.chromium.org/5749001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69694 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ipc/file_descriptor_set_posix_unittest.cc | 3 | ||||
-rw-r--r-- | ipc/ipc.gyp | 1 | ||||
-rw-r--r-- | ipc/ipc_channel.h | 58 | ||||
-rw-r--r-- | ipc/ipc_channel_posix.cc | 641 | ||||
-rw-r--r-- | ipc/ipc_channel_posix.h | 37 | ||||
-rw-r--r-- | ipc/ipc_channel_posix_unittest.cc | 355 | ||||
-rw-r--r-- | ipc/ipc_channel_win.cc | 16 | ||||
-rw-r--r-- | ipc/ipc_message_unittest.cc | 3 | ||||
-rw-r--r-- | ipc/ipc_switches.cc | 4 | ||||
-rw-r--r-- | ipc/ipc_switches.h | 1 | ||||
-rw-r--r-- | ipc/ipc_sync_channel_unittest.cc | 3 | ||||
-rw-r--r-- | ipc/sync_socket_unittest.cc | 3 |
12 files changed, 803 insertions, 322 deletions
diff --git a/ipc/file_descriptor_set_posix_unittest.cc b/ipc/file_descriptor_set_posix_unittest.cc index 31b8bf4..5fc8c50 100644 --- a/ipc/file_descriptor_set_posix_unittest.cc +++ b/ipc/file_descriptor_set_posix_unittest.cc @@ -4,12 +4,13 @@ // This test is POSIX only. +#include "ipc/file_descriptor_set_posix.h" + #include <unistd.h> #include <fcntl.h> #include "base/basictypes.h" #include "base/eintr_wrapper.h" -#include "ipc/file_descriptor_set_posix.h" #include "testing/gtest/include/gtest/gtest.h" namespace { diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp index ebc590a..6865ee3 100644 --- a/ipc/ipc.gyp +++ b/ipc/ipc.gyp @@ -46,6 +46,7 @@ ], 'sources': [ 'file_descriptor_set_posix_unittest.cc', + 'ipc_channel_posix_unittest.cc', 'ipc_fuzzing_tests.cc', 'ipc_message_unittest.cc', 'ipc_send_fds_test.cc', diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h index 132b494..c257a8e 100644 --- a/ipc/ipc_channel.h +++ b/ipc/ipc_channel.h @@ -13,6 +13,21 @@ namespace IPC { //------------------------------------------------------------------------------ +// See +// http://www.chromium.org/developers/design-documents/inter-process-communication +// for overview of IPC in Chromium. + +// Channels are implemented using named pipes on Windows, and +// socket pairs (or in some special cases unix domain sockets) on POSIX. +// On Windows we access pipes in various processes by name. +// On POSIX we pass file descriptors to child processes and assign names to them +// in a lookup table. +// In general on POSIX we do not use unix domain sockets due to security +// concerns and the fact that they can leave garbage around the file system +// (MacOS does not support abstract named unix domain sockets). +// You can use unix domain sockets if you like on POSIX by constructing the +// the channel with the mode set to one of the NAMED modes. NAMED modes are +// currently used by automation and service processes. class Channel : public Message::Sender { // Security tests need access to the pipe handle. @@ -34,12 +49,29 @@ class Channel : public Message::Sender { // Called when an error is detected that causes the channel to close. // This method is not called when a channel is closed normally. virtual void OnChannelError() {} + +#if defined(OS_POSIX) + // Called on the server side when a channel that listens for connections + // denies an attempt to connect. + virtual void OnChannelDenied() {} + + // Called on the server side when a channel that listens for connections + // has an error that causes the listening channel to close. + virtual void OnChannelListenError() {} +#endif // OS_POSIX }; enum Mode { MODE_NONE, MODE_SERVER, - MODE_CLIENT + MODE_CLIENT, + // Channels on Windows are named by default and accessible from other + // processes. On POSIX channels are anonymous by default and not accessible + // from other processes. Named channels work via named unix domain sockets. + // On Windows MODE_NAMED_SERVER == MODE_SERVER and + // MODE_NAMED_CLIENT == MODE_CLIENT. + MODE_NAMED_SERVER, + MODE_NAMED_CLIENT, }; enum { @@ -77,6 +109,10 @@ class Channel : public Message::Sender { bool Connect() WARN_UNUSED_RESULT; // Close this Channel explicitly. May be called multiple times. + // On POSIX calling close on an IPC channel that listens for connections will + // cause it to close any accepted connections, and it will stop listening for + // new connections. If you just want to close the currently accepted + // connection and listen for new ones, use ResetToAcceptingConnectionState. void Close(); // Modify the Channel's listener. @@ -92,11 +128,23 @@ class Channel : public Message::Sender { // On POSIX an IPC::Channel wraps a socketpair(), this method returns the // FD # for the client end of the socket. // This method may only be called on the server side of a channel. - // - // If the kTestingChannelID flag is specified on the command line then - // a named FIFO is used as the channel transport mechanism rather than a - // socketpair() in which case this method returns -1. int GetClientFileDescriptor() const; + + // On POSIX an IPC::Channel can either wrap an established socket, or it + // can wrap a socket that is listening for connections. Currently an + // IPC::Channel that listens for connections can only accept one connection + // at a time. + + // Returns true if the channel supports listening for connections. + bool AcceptsConnections() const; + + // Returns true if the channel supports listening for connections and is + // currently connected. + bool HasAcceptedConnection() const; + + // Closes any currently connected socket, and returns to a listening state + // for more connections. + void ResetToAcceptingConnectionState(); #endif // defined(OS_POSIX) protected: diff --git a/ipc/ipc_channel_posix.cc b/ipc/ipc_channel_posix.cc index 0d0160b..24eedd7 100644 --- a/ipc/ipc_channel_posix.cc +++ b/ipc/ipc_channel_posix.cc @@ -17,6 +17,8 @@ #include "base/command_line.h" #include "base/eintr_wrapper.h" +#include "base/file_path.h" +#include "base/file_util.h" #include "base/global_descriptors_posix.h" #include "base/lock.h" #include "base/logging.h" @@ -33,15 +35,18 @@ namespace IPC { // IPC channels on Windows use named pipes (CreateNamedPipe()) with -// channel ids as the pipe names. Channels on POSIX use anonymous -// Unix domain sockets created via socketpair() as pipes. These don't -// quite line up. +// channel ids as the pipe names. Channels on POSIX use sockets as +// pipes These don't quite line up. // -// When creating a child subprocess, the parent side of the fork -// arranges it such that the initial control channel ends up on the +// When creating a child subprocess we use a socket pair and the parent side of +// the fork arranges it such that the initial control channel ends up on the // magic file descriptor kPrimaryIPCChannel in the child. Future // connections (file descriptors) can then be passed via that // connection via sendmsg(). +// +// A POSIX IPC channel can also be set up as a server for a bound UNIX domain +// socket, and will handle multiple connect and disconnect sequences. Currently +// it is limited to one connection at a time. //------------------------------------------------------------------------------ namespace { @@ -128,31 +133,15 @@ class PipeMap { friend struct DefaultSingletonTraits<PipeMap>; }; -// Used to map a channel name to the equivalent FD # in the current process. -// Returns -1 if the channel is unknown. -int ChannelNameToFD(const std::string& channel_id) { - // See the large block comment above PipeMap for the reasoning here. - const int fd = PipeMap::GetInstance()->Lookup(channel_id); - - if (fd != -1) { - int dup_fd = dup(fd); - if (dup_fd < 0) - PLOG(FATAL) << "dup(" << fd << ") " << channel_id; - return dup_fd; - } - - return fd; -} - //------------------------------------------------------------------------------ -// The standard size on linux is 108, mac is 104. To maintain consistency -// across platforms we standardize on the smaller value. -const size_t kMaxPipeNameLength = 104; +// Verify that kMaxPipeNameLength is a decent size. COMPILE_ASSERT(sizeof(((sockaddr_un*)0)->sun_path) >= kMaxPipeNameLength, BAD_SUN_PATH_LENGTH); -// Creates a Fifo with the specified name ready to listen on. -bool CreateServerFifo(const std::string& pipe_name, int* server_listen_fd) { +// Creates a unix domain socket bound to the specified name that is listening +// for connections. +bool CreateServerUnixDomainSocket(const std::string& pipe_name, + int* server_listen_fd) { DCHECK(server_listen_fd); DCHECK_GT(pipe_name.length(), 0u); DCHECK_LT(pipe_name.length(), kMaxPipeNameLength); @@ -169,7 +158,7 @@ bool CreateServerFifo(const std::string& pipe_name, int* server_listen_fd) { // Make socket non-blocking if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { - PLOG(ERROR) << "fcntl " << pipe_name; + PLOG(ERROR) << "fcntl(O_NONBLOCK) " << pipe_name; if (HANDLE_EINTR(close(fd)) < 0) PLOG(ERROR) << "close " << pipe_name; return false; @@ -178,11 +167,20 @@ bool CreateServerFifo(const std::string& pipe_name, int* server_listen_fd) { // Delete any old FS instances. unlink(pipe_name.c_str()); + // Make sure the path we need exists. + FilePath path(pipe_name); + FilePath dir_path = path.DirName(); + if (!file_util::CreateDirectory(dir_path)) { + return false; + } + // Create unix_addr structure struct sockaddr_un unix_addr; memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sun_family = AF_UNIX; - snprintf(unix_addr.sun_path, kMaxPipeNameLength, "%s", pipe_name.c_str()); + DCHECK_EQ(static_cast<int>(pipe_name.length()), + snprintf(unix_addr.sun_path, kMaxPipeNameLength, "%s", + pipe_name.c_str())); size_t unix_addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(unix_addr.sun_path) + 1; @@ -208,15 +206,15 @@ bool CreateServerFifo(const std::string& pipe_name, int* server_listen_fd) { return true; } -// Accept a connection on a fifo. -bool ServerAcceptFifoConnection(int server_listen_fd, int* server_socket) { +// Accept a connection on a socket we are listening to. +bool ServerAcceptConnection(int server_listen_fd, int* server_socket) { DCHECK(server_socket); int accept_fd = HANDLE_EINTR(accept(server_listen_fd, NULL, 0)); if (accept_fd < 0) return false; if (fcntl(accept_fd, F_SETFL, O_NONBLOCK) == -1) { - PLOG(ERROR) << "fcntl " << accept_fd; + PLOG(ERROR) << "fcntl(O_NONBLOCK) " << accept_fd; if (HANDLE_EINTR(close(accept_fd)) < 0) PLOG(ERROR) << "close " << accept_fd; return false; @@ -226,7 +224,8 @@ bool ServerAcceptFifoConnection(int server_listen_fd, int* server_socket) { return true; } -bool ClientConnectToFifo(const std::string &pipe_name, int* client_socket) { +bool CreateClientUnixDomainSocket(const std::string& pipe_name, + int* client_socket) { DCHECK(client_socket); DCHECK_GT(pipe_name.length(), 0u); DCHECK_LT(pipe_name.length(), kMaxPipeNameLength); @@ -244,7 +243,7 @@ bool ClientConnectToFifo(const std::string &pipe_name, int* client_socket) { // Make socket non-blocking if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { - PLOG(ERROR) << "fcntl " << pipe_name; + PLOG(ERROR) << "fcntl(O_NONBLOCK) " << pipe_name; if (HANDLE_EINTR(close(fd)) < 0) PLOG(ERROR) << "close " << pipe_name; return false; @@ -254,8 +253,9 @@ bool ClientConnectToFifo(const std::string &pipe_name, int* client_socket) { struct sockaddr_un server_unix_addr; memset(&server_unix_addr, 0, sizeof(server_unix_addr)); server_unix_addr.sun_family = AF_UNIX; - snprintf(server_unix_addr.sun_path, kMaxPipeNameLength, "%s", - pipe_name.c_str()); + DCHECK_EQ(static_cast<int>(pipe_name.length()), + snprintf(server_unix_addr.sun_path, kMaxPipeNameLength, "%s", + pipe_name.c_str())); size_t server_unix_addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(server_unix_addr.sun_path) + 1; @@ -287,7 +287,7 @@ bool SocketWriteErrorIsRecoverable() { return errno == EAGAIN || errno == EMSGSIZE; #else return errno == EAGAIN; -#endif +#endif // OS_MACOSX } } // namespace @@ -297,24 +297,37 @@ Channel::ChannelImpl::ChannelImpl(const IPC::ChannelHandle& channel_handle, Mode mode, Listener* listener) : mode_(mode), is_blocked_on_write_(false), + waiting_connect_(true), message_send_bytes_written_(0), - uses_fifo_(CommandLine::ForCurrentProcess()->HasSwitch( - switches::kIPCUseFIFO)), server_listen_pipe_(-1), pipe_(-1), client_pipe_(-1), #if defined(IPC_USES_READWRITE) fd_pipe_(-1), remote_fd_pipe_(-1), -#endif +#endif // IPC_USES_READWRITE + pipe_name_(channel_handle.name), listener_(listener), - waiting_connect_(true), + must_unlink_(false), factory_(this) { - if (!CreatePipe(channel_handle, mode_)) { + // Check to see if we want to implement using domain sockets. + bool uses_domain_socket = false; + bool listening_socket = false; + if (mode_ == MODE_NAMED_SERVER) { + uses_domain_socket = true; + listening_socket = true; + mode_ = MODE_SERVER; + } else if (mode_ == MODE_NAMED_CLIENT) { + uses_domain_socket = true; + mode_ = MODE_CLIENT; + } + if (!CreatePipe(channel_handle, uses_domain_socket, listening_socket)) { + // The pipe may have been closed already. + const char *modestr = (mode_ == MODE_SERVER + || mode_ == MODE_NAMED_SERVER) ? "server" : "client"; // The pipe may have been closed already. LOG(WARNING) << "Unable to create pipe named \"" << channel_handle.name - << "\" in " << (mode_ == MODE_SERVER ? "server" : "client") - << " mode"; + << "\" in " << modestr << " mode"; } } @@ -322,22 +335,6 @@ Channel::ChannelImpl::~ChannelImpl() { Close(); } -// static -void AddChannelSocket(const std::string& name, int socket) { - PipeMap::GetInstance()->Insert(name, socket); -} - -// static -void RemoveAndCloseChannelSocket(const std::string& name) { - PipeMap::GetInstance()->RemoveAndClose(name); -} - -// static -bool ChannelSocketExists(const std::string& name) { - return PipeMap::GetInstance()->Lookup(name) != -1; -} - -// static bool SocketPair(int* fd1, int* fd2) { int pipe_fds[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds) != 0) { @@ -362,43 +359,58 @@ bool SocketPair(int* fd1, int* fd2) { return true; } -bool Channel::ChannelImpl::CreatePipe(const IPC::ChannelHandle &channel_handle, - Mode mode) { +bool Channel::ChannelImpl::CreatePipe(const IPC::ChannelHandle& channel_handle, + bool uses_domain_sockets, + bool listening_socket) { DCHECK(server_listen_pipe_ == -1 && pipe_ == -1); - pipe_name_ = channel_handle.name; - pipe_ = channel_handle.socket.fd; - if (uses_fifo_) { - // This only happens in unit tests; see the comment above PipeMap. - // TODO(playmobil): We shouldn't need to create fifos on disk. - // TODO(playmobil): If we do, they should be in the user data directory. - // TODO(playmobil): Cleanup any stale fifos. - if (mode == MODE_SERVER) { - if (!CreateServerFifo(pipe_name_, &server_listen_pipe_)) { + + // Four possible cases: + // 1) It's a channel wrapping a pipe that is given to us. + // 2) It's for a named channel, so we create it. + // 3) It's for a client that we implement ourself. This is used + // in unittesting. + // 4) It's the initial IPC channel: + // 4a) Client side: Pull the pipe out of the GlobalDescriptors set. + // 4b) Server side: create the pipe. + + if (channel_handle.socket.fd != -1) { + // Case 1 from comment above. + pipe_ = channel_handle.socket.fd; +#if defined(IPC_USES_READWRITE) + // Test the socket passed into us to make sure it is nonblocking. + // We don't want to call read/write on a blocking socket. + int value = fcntl(pipe_, F_GETFL); + if (value == -1) { + PLOG(ERROR) << "fcntl(F_GETFL) " << pipe_name_; + return false; + } + if (!(value & O_NONBLOCK)) { + LOG(ERROR) << "Socket " << pipe_name_ << " must be O_NONBLOCK"; + return false; + } +#endif // IPC_USES_READWRITE + } else if (uses_domain_sockets) { + // Case 2 from comment above. + must_unlink_ = true; + if (mode_ == MODE_SERVER) { + if (!CreateServerUnixDomainSocket(pipe_name_, &pipe_)) { return false; } - } else { - if (!ClientConnectToFifo(pipe_name_, &pipe_)) { + } else if (mode_ == MODE_CLIENT) { + if (!CreateClientUnixDomainSocket(pipe_name_, &pipe_)) { return false; } - waiting_connect_ = false; } } else { - // This is the normal (non-unit-test) case, where we're using sockets. - // Three possible cases: - // 1) It's for a channel we already have a pipe for; reuse it. - // 2) It's the initial IPC channel: - // 2a) Server side: create the pipe. - // 2b) Client side: Pull the pipe out of the GlobalDescriptors set. - if (pipe_ < 0) { - pipe_ = ChannelNameToFD(pipe_name_); - } - if (pipe_ < 0) { - // Initial IPC channel. - if (mode == MODE_SERVER) { - if (!SocketPair(&pipe_, &client_pipe_)) - return false; - AddChannelSocket(pipe_name_, client_pipe_); + pipe_ = PipeMap::GetInstance()->Lookup(pipe_name_); + if (mode_ == MODE_CLIENT) { + if (pipe_ != -1) { + // Case 3 from comment above. + // We only allow one connection. + pipe_ = HANDLE_EINTR(dup(pipe_)); + PipeMap::GetInstance()->RemoveAndClose(pipe_name_); } else { + // Case 4a from comment above. // Guard against inappropriate reuse of the initial IPC channel. If // an IPC channel closes and someone attempts to reuse it by name, the // initial channel must not be recycled here. http://crbug.com/26754. @@ -412,42 +424,51 @@ bool Channel::ChannelImpl::CreatePipe(const IPC::ChannelHandle &channel_handle, pipe_ = base::GlobalDescriptors::GetInstance()->Get(kPrimaryIPCChannel); } + } else if (mode_ == MODE_SERVER) { + // Case 4b from comment above. + if (pipe_ != -1) { + LOG(ERROR) << "Server already exists for " << pipe_name_; + return false; + } + if (!SocketPair(&pipe_, &client_pipe_)) + return false; + PipeMap::GetInstance()->Insert(pipe_name_, client_pipe_); } else { - waiting_connect_ = mode == MODE_SERVER; + LOG(FATAL) << "Unknown mode " << mode_; + return false; } } - // Create the Hello message to be sent when Connect is called - scoped_ptr<Message> msg(new Message(MSG_ROUTING_NONE, - HELLO_MESSAGE_TYPE, - IPC::Message::PRIORITY_NORMAL)); - #if defined(IPC_USES_READWRITE) - if (!uses_fifo_) { - // Create a dedicated socketpair() for exchanging file descriptors. - // See comments for IPC_USES_READWRITE for details. - if (mode == MODE_SERVER) { - fd_pipe_ = -1; - } else if (remote_fd_pipe_ == -1) { - if (!SocketPair(&fd_pipe_, &remote_fd_pipe_)) { - return false; - } + if (mode_ == MODE_SERVER) { + if (listening_socket) { + server_listen_pipe_ = pipe_; + pipe_ = -1; } } - #endif - if (!msg->WriteInt(base::GetCurrentProcId())) { - Close(); - return false; + +#if defined(IPC_USES_READWRITE) + // Create a dedicated socketpair() for exchanging file descriptors. + // See comments for IPC_USES_READWRITE for details. + if (mode_ == MODE_CLIENT) { + if (!SocketPair(&fd_pipe_, &remote_fd_pipe_)) { + return false; + } } +#endif // IPC_USES_READWRITE - output_queue_.push(msg.release()); return true; } bool Channel::ChannelImpl::Connect() { - if (mode_ == MODE_SERVER && uses_fifo_) { - if (server_listen_pipe_ == -1) { - return false; - } + if (server_listen_pipe_ == -1 && pipe_ == -1) { + NOTREACHED() << "Must call create on a channel before calling connect"; + return false; + } + + bool did_connect = true; + if (server_listen_pipe_ != -1) { + // Watch the pipe for connections, and turn any connections into + // active sockets. MessageLoopForIO::current()->WatchFileDescriptor( server_listen_pipe_, true, @@ -455,21 +476,9 @@ bool Channel::ChannelImpl::Connect() { &server_listen_connection_watcher_, this); } else { - if (pipe_ == -1) { - return false; - } - MessageLoopForIO::current()->WatchFileDescriptor( - pipe_, - true, - MessageLoopForIO::WATCH_READ, - &read_watcher_, - this); - waiting_connect_ = mode_ == MODE_SERVER; + did_connect = AcceptConnection(); } - - if (!waiting_connect_) - return ProcessOutgoingMessages(); - return true; + return did_connect; } bool Channel::ChannelImpl::ProcessIncomingMessages() { @@ -497,7 +506,7 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { Channel::kReadBufferSize)); msg.msg_controllen = 0; } else -#endif +#endif // IPC_USES_READWRITE { msg.msg_controllen = sizeof(input_cmsg_buf_); bytes_read = HANDLE_EINTR(recvmsg(pipe_, &msg, MSG_DONTWAIT)); @@ -511,7 +520,7 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { // treat this as a special case to prevent spurious error messages // to the console. return false; -#endif // defined(OS_MACOSX) +#endif // OS_MACOSX } else if (errno == ECONNRESET || errno == EPIPE) { return false; } else { @@ -566,7 +575,7 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { << " fd:" << pipe_; for (unsigned i = 0; i < num_wire_fds; ++i) if (HANDLE_EINTR(close(wire_fds[i])) < 0) - PLOG(ERROR) << "close" << i; + PLOG(ERROR) << "close " << i; return false; } break; @@ -625,51 +634,49 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { // the message has been completely received, but we didn't get // enough file descriptors. #if defined(IPC_USES_READWRITE) - if (!uses_fifo_) { - char dummy; - struct iovec fd_pipe_iov = { &dummy, 1 }; - msg.msg_iov = &fd_pipe_iov; - msg.msg_controllen = sizeof(input_cmsg_buf_); - ssize_t n = HANDLE_EINTR(recvmsg(fd_pipe_, &msg, MSG_DONTWAIT)); - if (n == 1 && msg.msg_controllen > 0) { - for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); - DCHECK(payload_len % sizeof(int) == 0); - wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); - num_wire_fds = payload_len / 4; - - if (msg.msg_flags & MSG_CTRUNC) { - LOG(ERROR) << "SCM_RIGHTS message was truncated" - << " cmsg_len:" << cmsg->cmsg_len - << " fd:" << pipe_; - for (unsigned i = 0; i < num_wire_fds; ++i) - if (HANDLE_EINTR(close(wire_fds[i])) < 0) - PLOG(ERROR) << "close" << i; - return false; - } - break; + char dummy; + struct iovec fd_pipe_iov = { &dummy, 1 }; + msg.msg_iov = &fd_pipe_iov; + msg.msg_controllen = sizeof(input_cmsg_buf_); + ssize_t n = HANDLE_EINTR(recvmsg(fd_pipe_, &msg, MSG_DONTWAIT)); + if (n == 1 && msg.msg_controllen > 0) { + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + num_wire_fds = payload_len / 4; + + if (msg.msg_flags & MSG_CTRUNC) { + LOG(ERROR) << "SCM_RIGHTS message was truncated" + << " cmsg_len:" << cmsg->cmsg_len + << " fd:" << pipe_; + for (unsigned i = 0; i < num_wire_fds; ++i) + if (HANDLE_EINTR(close(wire_fds[i])) < 0) + PLOG(ERROR) << "close " << i; + return false; } + break; } - if (input_overflow_fds_.empty()) { - fds = wire_fds; - num_fds = num_wire_fds; - } else { - if (num_wire_fds > 0) { - const size_t prev_size = input_overflow_fds_.size(); - input_overflow_fds_.resize(prev_size + num_wire_fds); - memcpy(&input_overflow_fds_[prev_size], wire_fds, - num_wire_fds * sizeof(int)); - } - fds = &input_overflow_fds_[0]; - num_fds = input_overflow_fds_.size(); + } + if (input_overflow_fds_.empty()) { + fds = wire_fds; + num_fds = num_wire_fds; + } else { + if (num_wire_fds > 0) { + const size_t prev_size = input_overflow_fds_.size(); + input_overflow_fds_.resize(prev_size + num_wire_fds); + memcpy(&input_overflow_fds_[prev_size], wire_fds, + num_wire_fds * sizeof(int)); } + fds = &input_overflow_fds_[0]; + num_fds = input_overflow_fds_.size(); } } if (header_fds > num_fds - fds_i) -#endif +#endif // IPC_USES_READWRITE error = "Message needs unreceived descriptors"; } @@ -690,11 +697,11 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { LOG(WARNING) << "In the case of SELinux this can be caused when " "using a --user-data-dir to which the default " "policy doesn't give the renderer access to. "; -#endif +#endif // CHROMIUM_SELINUX // close the existing file descriptors so that we don't leak them for (unsigned i = fds_i; i < num_fds; ++i) if (HANDLE_EINTR(close(fds[i])) < 0) - PLOG(ERROR) << "close" << i; + PLOG(ERROR) << "close " << i; input_overflow_fds_.clear(); // abort the connection return false; @@ -706,8 +713,7 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { } DVLOG(2) << "received message on channel @" << this << " with type " << m.type() << " on fd " << pipe_; - if (m.routing_id() == MSG_ROUTING_NONE && - m.type() == HELLO_MESSAGE_TYPE) { + if (IsHelloMessage(&m)) { // The Hello message contains only the process id. void *iter = NULL; int pid; @@ -715,9 +721,9 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { NOTREACHED(); } #if defined(IPC_USES_READWRITE) - if (mode_ == MODE_SERVER && !uses_fifo_) { - // On non-Mac, the Hello message from the client to the server - // also contains the fd_pipe_, which will be used for all + if (mode_ == MODE_SERVER) { + // With IPC_USES_READWRITE, the Hello message from the client to the + // server also contains the fd_pipe_, which will be used for all // subsequent file descriptor passing. DCHECK_EQ(m.file_descriptor_set()->size(), 1U); base::FileDescriptor descriptor; @@ -727,7 +733,7 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { fd_pipe_ = descriptor.fd; CHECK(descriptor.auto_close); } -#endif +#endif // IPC_USES_READWRITE listener_->OnChannelConnected(pid); } else { listener_->OnMessageReceived(m); @@ -755,52 +761,22 @@ bool Channel::ChannelImpl::ProcessIncomingMessages() { bytes_read = 0; // Get more data. } - - return true; } bool Channel::ChannelImpl::ProcessOutgoingMessages() { DCHECK(!waiting_connect_); // Why are we trying to send messages if there's // no connection? - is_blocked_on_write_ = false; - - if (output_queue_.empty()) { + if (output_queue_.empty()) return true; - } - if (pipe_ == -1) { + if (pipe_ == -1) return false; - } // Write out all the messages we can till the write blocks or there are no // more outgoing messages. while (!output_queue_.empty()) { Message* msg = output_queue_.front(); -#if defined(IPC_USES_READWRITE) - scoped_ptr<Message> hello; - if (remote_fd_pipe_ != -1 && - msg->routing_id() == MSG_ROUTING_NONE && - msg->type() == HELLO_MESSAGE_TYPE) { - hello.reset(new Message(MSG_ROUTING_NONE, - HELLO_MESSAGE_TYPE, - IPC::Message::PRIORITY_NORMAL)); - void* iter = NULL; - int pid; - if (!msg->ReadInt(&iter, &pid) || - !hello->WriteInt(pid)) { - NOTREACHED(); - } - DCHECK_EQ(hello->size(), msg->size()); - if (!hello->WriteFileDescriptor(base::FileDescriptor(remote_fd_pipe_, - false))) { - NOTREACHED(); - } - msg = hello.get(); - DCHECK_EQ(msg->file_descriptor_set()->size(), 1U); - } -#endif - size_t amt_to_write = msg->size() - message_send_bytes_written_; DCHECK(amt_to_write != 0); const char* out_bytes = reinterpret_cast<const char*>(msg->data()) + @@ -848,9 +824,7 @@ bool Channel::ChannelImpl::ProcessOutgoingMessages() { msg->header()->num_fds = static_cast<uint16>(num_fds); #if defined(IPC_USES_READWRITE) - if (!uses_fifo_ && - (msg->routing_id() != MSG_ROUTING_NONE || - msg->type() != HELLO_MESSAGE_TYPE)) { + if (!IsHelloMessage(msg)) { // Only the Hello message sends the file descriptor with the message. // Subsequently, we can send file descriptors on the dedicated // fd_pipe_ which makes Seccomp sandbox operation more efficient. @@ -864,21 +838,19 @@ bool Channel::ChannelImpl::ProcessOutgoingMessages() { msg->file_descriptor_set()->CommitAll(); } } -#endif +#endif // IPC_USES_READWRITE } if (bytes_written == 1) { fd_written = pipe_; #if defined(IPC_USES_READWRITE) - if (mode_ != MODE_SERVER && !uses_fifo_ && - msg->routing_id() == MSG_ROUTING_NONE && - msg->type() == HELLO_MESSAGE_TYPE) { + if (mode_ != MODE_SERVER && IsHelloMessage(msg)) { DCHECK_EQ(msg->file_descriptor_set()->size(), 1U); } - if (!uses_fifo_ && !msgh.msg_controllen) { + if (!msgh.msg_controllen) { bytes_written = HANDLE_EINTR(write(pipe_, out_bytes, amt_to_write)); } else -#endif +#endif // IPC_USES_READWRITE { bytes_written = HANDLE_EINTR(sendmsg(pipe_, &msgh, MSG_DONTWAIT)); } @@ -940,14 +912,11 @@ bool Channel::ChannelImpl::Send(Message* message) { #ifdef IPC_MESSAGE_LOG_ENABLED Logging::GetInstance()->OnSendMessage(message, ""); -#endif +#endif // IPC_MESSAGE_LOG_ENABLED output_queue_.push(message); - if (!waiting_connect_) { - if (!is_blocked_on_write_) { - if (!ProcessOutgoingMessages()) - return false; - } + if (!is_blocked_on_write_ && !waiting_connect_) { + return ProcessOutgoingMessages(); } return true; @@ -957,43 +926,88 @@ int Channel::ChannelImpl::GetClientFileDescriptor() const { return client_pipe_; } -// Called by libevent when we can read from th pipe without blocking. -void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { - bool send_server_hello_msg = false; - if (waiting_connect_ && mode_ == MODE_SERVER) { - if (uses_fifo_) { - if (!ServerAcceptFifoConnection(server_listen_pipe_, &pipe_)) { - Close(); - } +bool Channel::ChannelImpl::AcceptsConnections() const { + return server_listen_pipe_ != -1; +} - // No need to watch the listening socket any longer since only one client - // can connect. So unregister with libevent. - server_listen_connection_watcher_.StopWatchingFileDescriptor(); +bool Channel::ChannelImpl::HasAcceptedConnection() const { + return AcceptsConnections() && pipe_ != -1; +} - // Start watching our end of the socket. - MessageLoopForIO::current()->WatchFileDescriptor( - pipe_, - true, - MessageLoopForIO::WATCH_READ, - &read_watcher_, - this); +void Channel::ChannelImpl::ResetToAcceptingConnectionState() { + // Unregister libevent for the unix domain socket and close it. + read_watcher_.StopWatchingFileDescriptor(); + write_watcher_.StopWatchingFileDescriptor(); + if (pipe_ != -1) { + if (HANDLE_EINTR(close(pipe_)) < 0) + PLOG(ERROR) << "close pipe_ " << pipe_name_; + pipe_ = -1; + } +#if defined(IPC_USES_READWRITE) + if (fd_pipe_ != -1) { + if (HANDLE_EINTR(close(fd_pipe_)) < 0) + PLOG(ERROR) << "close fd_pipe_ " << pipe_name_; + fd_pipe_ = -1; + } + if (remote_fd_pipe_ != -1) { + if (HANDLE_EINTR(close(remote_fd_pipe_)) < 0) + PLOG(ERROR) << "close remote_fd_pipe_ " << pipe_name_; + remote_fd_pipe_ = -1; + } +#endif // IPC_USES_READWRITE - waiting_connect_ = false; - } else { - // In the case of a socketpair() the server starts listening on its end - // of the pipe in Connect(). - waiting_connect_ = false; - } - send_server_hello_msg = true; + while (!output_queue_.empty()) { + Message* m = output_queue_.front(); + output_queue_.pop(); + delete m; } - if (!waiting_connect_ && fd == pipe_) { - if (!ProcessIncomingMessages()) { + // Close any outstanding, received file descriptors. + for (std::vector<int>::iterator + i = input_overflow_fds_.begin(); i != input_overflow_fds_.end(); ++i) { + if (HANDLE_EINTR(close(*i)) < 0) + PLOG(ERROR) << "close"; + } + input_overflow_fds_.clear(); +} + +// Called by libevent when we can read from the pipe without blocking. +void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { + bool send_server_hello_msg = false; + if (fd == server_listen_pipe_) { + int new_pipe = 0; + if (!ServerAcceptConnection(server_listen_pipe_, &new_pipe)) { Close(); - listener_->OnChannelError(); - // The OnChannelError() call may delete this, so we need to exit now. + listener_->OnChannelListenError(); + } + + if (pipe_ != -1) { + // We already have a connection. We only handle one at a time. + // close our new descriptor. + if (HANDLE_EINTR(shutdown(new_pipe, SHUT_RDWR)) < 0) + PLOG(ERROR) << "shutdown " << pipe_name_; + if (HANDLE_EINTR(close(new_pipe)) < 0) + PLOG(ERROR) << "close " << pipe_name_; + listener_->OnChannelDenied(); return; } + pipe_ = new_pipe; + + if (!AcceptConnection()) { + NOTREACHED() << "AcceptConnection should not fail on server"; + } + send_server_hello_msg = true; + waiting_connect_ = false; + } else if (fd == pipe_) { + if (waiting_connect_ && mode_ == MODE_SERVER) { + send_server_hello_msg = true; + waiting_connect_ = false; + } + if (!ProcessIncomingMessages()) { + ClosePipeOnError(); + } + } else { + NOTREACHED() << "Unknown pipe " << fd; } // If we're a server and handshaking, then we want to make sure that we @@ -1007,68 +1021,95 @@ void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { // Called by libevent when we can write to the pipe without blocking. void Channel::ChannelImpl::OnFileCanWriteWithoutBlocking(int fd) { + DCHECK(fd == pipe_); + is_blocked_on_write_ = false; if (!ProcessOutgoingMessages()) { - Close(); + ClosePipeOnError(); + } +} + +bool Channel::ChannelImpl::AcceptConnection() { + MessageLoopForIO::current()->WatchFileDescriptor(pipe_, + true, + MessageLoopForIO::WATCH_READ, + &read_watcher_, + this); + QueueHelloMessage(); + + if (mode_ == MODE_CLIENT) { + // If we are a client we want to send a hello message out immediately. + // In server mode we will send a hello message when we receive one from a + // client. + waiting_connect_ = false; + return ProcessOutgoingMessages(); + } else { + waiting_connect_ = true; + return true; + } +} + +void Channel::ChannelImpl::ClosePipeOnError() { + if (HasAcceptedConnection()) { + ResetToAcceptingConnectionState(); listener_->OnChannelError(); + } else { + Close(); + if (AcceptsConnections()) { + listener_->OnChannelListenError(); + } else { + listener_->OnChannelError(); + } } } +void Channel::ChannelImpl::QueueHelloMessage() { + // Create the Hello message + scoped_ptr<Message> msg(new Message(MSG_ROUTING_NONE, + HELLO_MESSAGE_TYPE, + IPC::Message::PRIORITY_NORMAL)); + + if (!msg->WriteInt(base::GetCurrentProcId())) { + NOTREACHED() << "Unable to pickle hello message proc id"; + } +#if defined(IPC_USES_READWRITE) + scoped_ptr<Message> hello; + if (remote_fd_pipe_ != -1) { + if (!msg->WriteFileDescriptor(base::FileDescriptor(remote_fd_pipe_, + false))) { + NOTREACHED() << "Unable to pickle hello message file descriptors"; + } + DCHECK_EQ(msg->file_descriptor_set()->size(), 1U); + } +#endif // IPC_USES_READWRITE + output_queue_.push(msg.release()); +} + +bool Channel::ChannelImpl::IsHelloMessage(const Message* m) const { + return m->routing_id() == MSG_ROUTING_NONE && m->type() == HELLO_MESSAGE_TYPE; +} + void Channel::ChannelImpl::Close() { // Close can be called multiple time, so we need to make sure we're // idempotent. - // Unregister libevent for the listening socket and close it. - server_listen_connection_watcher_.StopWatchingFileDescriptor(); + ResetToAcceptingConnectionState(); + if (must_unlink_) { + unlink(pipe_name_.c_str()); + must_unlink_ = false; + } if (server_listen_pipe_ != -1) { if (HANDLE_EINTR(close(server_listen_pipe_)) < 0) PLOG(ERROR) << "close " << server_listen_pipe_; server_listen_pipe_ = -1; + // Unregister libevent for the listening socket and close it. + server_listen_connection_watcher_.StopWatchingFileDescriptor(); } - // Unregister libevent for the FIFO and close it. - read_watcher_.StopWatchingFileDescriptor(); - write_watcher_.StopWatchingFileDescriptor(); - if (pipe_ != -1) { - if (HANDLE_EINTR(close(pipe_)) < 0) - PLOG(ERROR) << "close " << pipe_; - pipe_ = -1; - } if (client_pipe_ != -1) { PipeMap::GetInstance()->RemoveAndClose(pipe_name_); client_pipe_ = -1; } -#if defined(IPC_USES_READWRITE) - if (fd_pipe_ != -1) { - if (HANDLE_EINTR(close(fd_pipe_)) < 0) - PLOG(ERROR) << "close " << fd_pipe_; - fd_pipe_ = -1; - } - if (remote_fd_pipe_ != -1) { - if (HANDLE_EINTR(close(remote_fd_pipe_)) < 0) - PLOG(ERROR) << "close " << remote_fd_pipe_; - remote_fd_pipe_ = -1; - } -#endif - - if (uses_fifo_) { - // Unlink the FIFO - unlink(pipe_name_.c_str()); - } - - while (!output_queue_.empty()) { - Message* m = output_queue_.front(); - output_queue_.pop(); - delete m; - } - - // Close any outstanding, received file descriptors - for (std::vector<int>::iterator - i = input_overflow_fds_.begin(); i != input_overflow_fds_.end(); ++i) { - if (HANDLE_EINTR(close(*i)) < 0) - PLOG(ERROR) << "close " << *i; - } - input_overflow_fds_.clear(); } //------------------------------------------------------------------------------ @@ -1102,4 +1143,16 @@ int Channel::GetClientFileDescriptor() const { return channel_impl_->GetClientFileDescriptor(); } +bool Channel::AcceptsConnections() const { + return channel_impl_->AcceptsConnections(); +} + +bool Channel::HasAcceptedConnection() const { + return channel_impl_->HasAcceptedConnection(); +} + +void Channel::ResetToAcceptingConnectionState() { + channel_impl_->ResetToAcceptingConnectionState(); +} + } // namespace IPC diff --git a/ipc/ipc_channel_posix.h b/ipc/ipc_channel_posix.h index 4ff3de1..ecfd41a 100644 --- a/ipc/ipc_channel_posix.h +++ b/ipc/ipc_channel_posix.h @@ -40,12 +40,10 @@ namespace IPC { -// An implementation of ChannelImpl for POSIX systems that works via -// socketpairs. See the .cc file for an overview of the implementation. class Channel::ChannelImpl : public MessageLoopForIO::Watcher { public: // Mirror methods of Channel, see ipc_channel.h for description. - ChannelImpl(const IPC::ChannelHandle &channel_handle, Mode mode, + ChannelImpl(const IPC::ChannelHandle& channel_handle, Mode mode, Listener* listener); ~ChannelImpl(); bool Connect(); @@ -53,13 +51,23 @@ class Channel::ChannelImpl : public MessageLoopForIO::Watcher { void set_listener(Listener* listener) { listener_ = listener; } bool Send(Message* message); int GetClientFileDescriptor() const; + bool AcceptsConnections() const; + bool HasAcceptedConnection() const; + void ResetToAcceptingConnectionState(); private: - bool CreatePipe(const IPC::ChannelHandle &channel_handle, Mode mode); + bool CreatePipe(const IPC::ChannelHandle& channel_handle, + bool uses_domain_sockets, + bool listening_socket); bool ProcessIncomingMessages(); bool ProcessOutgoingMessages(); + bool AcceptConnection(); + void ClosePipeOnError(); + void QueueHelloMessage(); + bool IsHelloMessage(const Message* m) const; + // MessageLoopForIO::Watcher implementation. virtual void OnFileCanReadWithoutBlocking(int fd); virtual void OnFileCanWriteWithoutBlocking(int fd); @@ -74,17 +82,14 @@ class Channel::ChannelImpl : public MessageLoopForIO::Watcher { // Indicates whether we're currently blocked waiting for a write to complete. bool is_blocked_on_write_; + bool waiting_connect_; // If sending a message blocks then we use this variable // to keep track of where we are. size_t message_send_bytes_written_; - // If the kTestingChannelID flag is specified, we use a FIFO instead of - // a socketpair(). - bool uses_fifo_; - - // File descriptor we're listening on for new connections in the FIFO case; - // unused otherwise. + // File descriptor we're listening on for new connections if we listen + // for connections. int server_listen_pipe_; // The pipe used for communication. @@ -132,16 +137,20 @@ class Channel::ChannelImpl : public MessageLoopForIO::Watcher { std::string input_overflow_buf_; std::vector<int> input_overflow_fds_; - // In server-mode, we have to wait for the client to connect before we - // can begin reading. We make use of the input_state_ when performing - // the connect operation in overlapped mode. - bool waiting_connect_; + // True if we are responsible for unlinking the unix domain socket file. + bool must_unlink_; ScopedRunnableMethodFactory<ChannelImpl> factory_; DISALLOW_COPY_AND_ASSIGN(ChannelImpl); }; +// The maximum length of the name of a pipe for MODE_NAMED_SERVER or +// MODE_NAMED_CLIENT if you want to pass in your own socket. +// The standard size on linux is 108, mac is 104. To maintain consistency +// across platforms we standardize on the smaller value. +static const size_t kMaxPipeNameLength = 104; + } // namespace IPC #endif // IPC_IPC_CHANNEL_POSIX_H_ diff --git a/ipc/ipc_channel_posix_unittest.cc b/ipc/ipc_channel_posix_unittest.cc new file mode 100644 index 0000000..c86e418 --- /dev/null +++ b/ipc/ipc_channel_posix_unittest.cc @@ -0,0 +1,355 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// These tests are POSIX only. + +#include "ipc/ipc_channel_posix.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/eintr_wrapper.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "testing/multiprocess_func_list.h" + +namespace { + +enum { + QUIT_MESSAGE = 47 +}; + +class IPCChannelPosixTestListener : public IPC::Channel::Listener { + public: + enum STATUS { + DISCONNECTED, + MESSAGE_RECEIVED, + CHANNEL_ERROR, + CONNECTED, + DENIED, + LISTEN_ERROR + }; + + IPCChannelPosixTestListener(bool quit_only_on_message) + : status_(DISCONNECTED), quit_only_on_message_(quit_only_on_message) {} + + virtual ~IPCChannelPosixTestListener() {} + + virtual void OnMessageReceived(const IPC::Message& message) { + EXPECT_EQ(message.type(), QUIT_MESSAGE); + status_ = MESSAGE_RECEIVED; + QuitRunLoop(); + } + + virtual void OnChannelConnected(int32 peer_pid) { + status_ = CONNECTED; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelError() { + status_ = CHANNEL_ERROR; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelDenied() { + status_ = DENIED; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelListenError() { + status_ = LISTEN_ERROR; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + STATUS status() { return status_; } + + void QuitRunLoop() { + MessageLoopForIO::current()->QuitNow(); + } + + private: + // The current status of the listener. + STATUS status_; + // If |quit_only_on_message_| then the listener will only break out of + // the run loop when the QUIT_MESSAGE is received. + bool quit_only_on_message_; +}; + +} // namespace + +class IPCChannelPosixTest : public base::MultiProcessTest { + public: + static const char kConnectionSocketTestName[]; + static void SetUpSocket(IPC::ChannelHandle *handle, + IPC::Channel::Mode mode); + static void SpinRunLoop(int milliseconds); + + protected: + virtual void SetUp(); + virtual void TearDown(); + +private: + scoped_ptr<MessageLoopForIO> message_loop_; +}; + +const char IPCChannelPosixTest::kConnectionSocketTestName[] = + "/var/tmp/chrome_IPCChannelPosixTest__ConnectionSocket"; + +void IPCChannelPosixTest::SetUp() { + MultiProcessTest::SetUp(); + // Construct a fresh IO Message loop for the duration of each test. + message_loop_.reset(new MessageLoopForIO()); +} + +void IPCChannelPosixTest::TearDown() { + message_loop_.reset(NULL); + MultiProcessTest::TearDown(); +} + +// Create up a socket and bind and listen to it, or connect it +// depending on the |mode|. +void IPCChannelPosixTest::SetUpSocket(IPC::ChannelHandle *handle, + IPC::Channel::Mode mode) { + const std::string& name = handle->name; + + int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(socket_fd, 0) << name; + ASSERT_GE(fcntl(socket_fd, F_SETFL, O_NONBLOCK), 0); + struct sockaddr_un server_address = { 0 }; + memset(&server_address, 0, sizeof(server_address)); + server_address.sun_family = AF_UNIX; + DCHECK_EQ(static_cast<int>(name.length()), + snprintf(server_address.sun_path, IPC::kMaxPipeNameLength, "%s", + name.c_str())); + size_t server_address_len = offsetof(struct sockaddr_un, sun_path) + + strlen(server_address.sun_path) + 1; + + if (mode == IPC::Channel::MODE_NAMED_SERVER) { + // Only one server at a time. Cleanup garbage if it exists. + unlink(name.c_str()); + // Make sure the path we need exists. + FilePath path(name); + FilePath dir_path = path.DirName(); + ASSERT_TRUE(file_util::CreateDirectory(dir_path)); + ASSERT_GE(bind(socket_fd, + reinterpret_cast<struct sockaddr *>(&server_address), + server_address_len), 0) << server_address.sun_path + << ": " << strerror(errno) + << "(" << errno << ")"; + ASSERT_GE(listen(socket_fd, SOMAXCONN), 0) << server_address.sun_path + << ": " << strerror(errno) + << "(" << errno << ")"; + } else if (mode == IPC::Channel::MODE_NAMED_CLIENT) { + ASSERT_GE(connect(socket_fd, + reinterpret_cast<struct sockaddr *>(&server_address), + server_address_len), 0) << server_address.sun_path + << ": " << strerror(errno) + << "(" << errno << ")"; + } else { + FAIL() << "Unknown mode " << mode; + } + handle->socket.fd = socket_fd; +} + +void IPCChannelPosixTest::SpinRunLoop(int milliseconds) { + MessageLoopForIO *loop = MessageLoopForIO::current(); + // Post a quit task so that this loop eventually ends and we don't hang + // in the case of a bad test. Usually, the run loop will quit sooner than + // that because all tests use a IPCChannelPosixTestListener which quits the + // current run loop on any channel activity. + loop->PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), milliseconds); + loop->Run(); +} + +TEST_F(IPCChannelPosixTest, BasicListen) { + // Test creating a socket that is listening. + IPC::ChannelHandle handle("/var/tmp/IPCChannelPosixTest_BasicListen"); + SetUpSocket(&handle, IPC::Channel::MODE_NAMED_SERVER); + unlink(handle.name.c_str()); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_SERVER, NULL); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + channel.ResetToAcceptingConnectionState(); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, BasicConnected) { + // Test creating a socket that is connected. + int pipe_fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds)); + std::string socket_name("/var/tmp/IPCChannelPosixTest_BasicConnected"); + ASSERT_GE(fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK), 0); + + base::FileDescriptor fd(pipe_fds[0], false); + IPC::ChannelHandle handle(socket_name, fd); + IPC::Channel channel(handle, IPC::Channel::MODE_SERVER, NULL); + ASSERT_TRUE(channel.Connect()); + ASSERT_FALSE(channel.AcceptsConnections()); + channel.Close(); + ASSERT_TRUE(HANDLE_EINTR(close(pipe_fds[1])) == 0); + + // Make sure that we can use the socket that is created for us by + // a standard channel. + IPC::Channel channel2(socket_name, IPC::Channel::MODE_SERVER, NULL); + ASSERT_TRUE(channel2.Connect()); + ASSERT_FALSE(channel2.AcceptsConnections()); +} + +TEST_F(IPCChannelPosixTest, AdvancedConnected) { + // Test creating a connection to an external process. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(kConnectionSocketTestName); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + QUIT_MESSAGE, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(0, exit_code); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, ResetState) { + // Test creating a connection to an external process. Close the connection, + // but continue to listen and make sure another external process can connect + // to us. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(kConnectionSocketTestName); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + channel.ResetToAcceptingConnectionState(); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle2 = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle2); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + QUIT_MESSAGE, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + EXPECT_TRUE(base::KillProcess(handle, 0, false)); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle2, &exit_code)); + EXPECT_EQ(0, exit_code); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, MultiConnection) { + // Test setting up a connection to an external process, and then have + // another external process attempt to connect to us. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(kConnectionSocketTestName); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + base::ProcessHandle handle2 = SpawnChild("IPCChannelPosixFailConnectionProc", + false); + ASSERT_TRUE(handle2); + SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle2, &exit_code)); + EXPECT_EQ(exit_code, 0); + ASSERT_EQ(IPCChannelPosixTestListener::DENIED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + QUIT_MESSAGE, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout_ms()); + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(exit_code, 0); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +// A long running process that connects to us +MULTIPROCESS_TEST_MAIN(IPCChannelPosixTestConnectionProc) { + MessageLoopForIO message_loop; + IPCChannelPosixTestListener listener(true); + IPC::ChannelHandle handle(IPCChannelPosixTest::kConnectionSocketTestName); + IPCChannelPosixTest::SetUpSocket(&handle, IPC::Channel::MODE_NAMED_CLIENT); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_CLIENT, &listener); + EXPECT_TRUE(channel.Connect()); + IPCChannelPosixTest::SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + EXPECT_EQ(IPCChannelPosixTestListener::MESSAGE_RECEIVED, listener.status()); + return 0; +} + +// Simple external process that shouldn't be able to connect to us. +MULTIPROCESS_TEST_MAIN(IPCChannelPosixFailConnectionProc) { + MessageLoopForIO message_loop; + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle handle(IPCChannelPosixTest::kConnectionSocketTestName); + IPCChannelPosixTest::SetUpSocket(&handle, IPC::Channel::MODE_NAMED_CLIENT); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_CLIENT, &listener); + + // In this case connect may succeed or fail depending on if the packet + // actually gets sent at sendmsg. Since we never delay on send, we may not + // see the error. However even if connect succeeds, eventually we will get an + // error back since the channel will be closed when we attempt to read from + // it. + bool connected = channel.Connect(); + if (connected) { + IPCChannelPosixTest::SpinRunLoop(TestTimeouts::action_max_timeout_ms()); + EXPECT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + } else { + EXPECT_EQ(IPCChannelPosixTestListener::DISCONNECTED, listener.status()); + } + return 0; +} + diff --git a/ipc/ipc_channel_win.cc b/ipc/ipc_channel_win.cc index e12c521..0e7ba1b 100644 --- a/ipc/ipc_channel_win.cc +++ b/ipc/ipc_channel_win.cc @@ -106,6 +106,22 @@ Channel::ChannelImpl::ChannelImpl(const IPC::ChannelHandle &channel_handle, waiting_connect_(mode == MODE_SERVER), processing_incoming_(false), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { + switch(mode) { + case MODE_NONE: + LOG(FATAL) << "Bad mode for " << channel_handle.name; + break; + case MODE_SERVER: + case MODE_CLIENT: + break; + case MODE_NAMED_SERVER: + mode = MODE_SERVER; + break; + case MODE_NAMED_CLIENT: + mode = MODE_CLIENT; + break; + // Intentionally no default case here so that the compiler + // will check that we handle all the cases in the enum. + } if (!CreatePipe(channel_handle, mode)) { // The pipe may have been closed already. LOG(WARNING) << "Unable to create pipe named \"" << channel_handle.name << diff --git a/ipc/ipc_message_unittest.cc b/ipc/ipc_message_unittest.cc index f66751f..9480fe6 100644 --- a/ipc/ipc_message_unittest.cc +++ b/ipc/ipc_message_unittest.cc @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "ipc/ipc_message.h" + #include <string.h> #include "base/scoped_ptr.h" #include "base/values.h" -#include "ipc/ipc_message.h" #include "ipc/ipc_message_utils.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/ipc/ipc_switches.cc b/ipc/ipc_switches.cc index 2a8f470..bcb1225 100644 --- a/ipc/ipc_switches.cc +++ b/ipc/ipc_switches.cc @@ -11,10 +11,6 @@ namespace switches { // Can't find the switch you are looking for? try looking in // base/base_switches.cc instead. -// On POSIX only: use FIFO for IPC channels so that "unrelated" process -// can connect to a channel, provided it knows its name. For debugging purposes. -const char kIPCUseFIFO[] = "ipc-use-fifo"; - // The value of this switch tells the child process which // IPC channel the browser expects to use to communicate with it. const char kProcessChannelID[] = "channel"; diff --git a/ipc/ipc_switches.h b/ipc/ipc_switches.h index dc34eeb..1b754b4 100644 --- a/ipc/ipc_switches.h +++ b/ipc/ipc_switches.h @@ -12,7 +12,6 @@ namespace switches { -extern const char kIPCUseFIFO[]; extern const char kProcessChannelID[]; extern const char kDebugChildren[]; diff --git a/ipc/ipc_sync_channel_unittest.cc b/ipc/ipc_sync_channel_unittest.cc index 5617146..772df0b 100644 --- a/ipc/ipc_sync_channel_unittest.cc +++ b/ipc/ipc_sync_channel_unittest.cc @@ -4,6 +4,8 @@ // // Unit test for SyncChannel. +#include "ipc/ipc_sync_channel.h" + #include <string> #include <vector> @@ -18,7 +20,6 @@ #include "base/thread.h" #include "base/waitable_event.h" #include "ipc/ipc_message.h" -#include "ipc/ipc_sync_channel.h" #include "ipc/ipc_sync_message_filter.h" #include "ipc/ipc_sync_message_unittest.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/ipc/sync_socket_unittest.cc b/ipc/sync_socket_unittest.cc index 9448487..451f307 100644 --- a/ipc/sync_socket_unittest.cc +++ b/ipc/sync_socket_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/sync_socket.h" + #include <stdio.h> #include <string> #include <sstream> @@ -12,7 +14,6 @@ #endif // defined(OS_LINUX) || defined(OS_MACOSX) #include "base/platform_thread.h" #include "base/process_util.h" -#include "base/sync_socket.h" #include "ipc/ipc_channel.h" #include "ipc/ipc_channel_proxy.h" #include "ipc/ipc_message_utils.h" |