Implement IPC::ChannelFactory, a class that accept()s on a UNIX socket.
IPC::ChannelFactory listens on a UNIX domain socket and notifies its delegate when a client connects. The delegate is expected to craft an IPC::Channel from the handle it is given. Previously committed: - https://src.chromium.org/viewvc/chrome?view=rev&revision=186912 - https://src.chromium.org/viewvc/chrome?view=rev&revision=187233 - https://src.chromium.org/viewvc/chrome?view=rev&revision=187554 Review URL: https://codereview.chromium.org/12386010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187772 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
@ -58,6 +58,7 @@
|
||||
'ipc_test_base.cc',
|
||||
'ipc_test_base.h',
|
||||
'sync_socket_unittest.cc',
|
||||
'unix_domain_socket_util_unittest.cc',
|
||||
],
|
||||
'conditions': [
|
||||
['toolkit_uses_gtk == 1', {
|
||||
@ -65,6 +66,11 @@
|
||||
'../build/linux/system.gyp:gtk',
|
||||
],
|
||||
}],
|
||||
['OS == "win" or OS == "ios"', {
|
||||
'sources!': [
|
||||
'unix_domain_socket_util_unittest.cc',
|
||||
],
|
||||
}],
|
||||
['OS == "android" and gtest_target_type == "shared_library"', {
|
||||
'dependencies': [
|
||||
'../testing/android/native_test.gyp:native_test_native_code',
|
||||
|
14
ipc/ipc.gypi
14
ipc/ipc.gypi
@ -13,8 +13,10 @@
|
||||
'sources': [
|
||||
'file_descriptor_set_posix.cc',
|
||||
'file_descriptor_set_posix.h',
|
||||
'ipc_channel.h',
|
||||
'ipc_channel.cc',
|
||||
'ipc_channel.h',
|
||||
'ipc_channel_factory.cc',
|
||||
'ipc_channel_factory.h',
|
||||
'ipc_channel_handle.h',
|
||||
'ipc_channel_nacl.cc',
|
||||
'ipc_channel_nacl.h',
|
||||
@ -57,6 +59,8 @@
|
||||
'param_traits_write_macros.h',
|
||||
'struct_constructor_macros.h',
|
||||
'struct_destructor_macros.h',
|
||||
'unix_domain_socket_util.cc',
|
||||
'unix_domain_socket_util.h',
|
||||
],
|
||||
'defines': [
|
||||
'IPC_IMPLEMENTATION',
|
||||
@ -68,7 +72,15 @@
|
||||
['>(nacl_untrusted_build)==1', {
|
||||
'sources!': [
|
||||
'ipc_channel.cc',
|
||||
'ipc_channel_factory.cc',
|
||||
'ipc_channel_posix.cc',
|
||||
'unix_domain_socket_util.cc',
|
||||
],
|
||||
}],
|
||||
['OS == "win" or OS == "ios"', {
|
||||
'sources!': [
|
||||
'ipc_channel_factory.cc',
|
||||
'unix_domain_socket_util.cc',
|
||||
],
|
||||
}],
|
||||
],
|
||||
|
@ -167,8 +167,8 @@ class IPC_EXPORT Channel : public Sender {
|
||||
bool HasAcceptedConnection() const;
|
||||
|
||||
// Returns true if the peer process' effective user id can be determined, in
|
||||
// which case the supplied client_euid is updated with it.
|
||||
bool GetClientEuid(uid_t* client_euid) const;
|
||||
// which case the supplied peer_euid is updated with it.
|
||||
bool GetPeerEuid(uid_t* peer_euid) const;
|
||||
|
||||
// Closes any currently connected socket, and returns to a listening state
|
||||
// for more connections.
|
||||
|
88
ipc/ipc_channel_factory.cc
Normal file
88
ipc/ipc_channel_factory.cc
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#include "ipc/ipc_channel_factory.h"
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "ipc/unix_domain_socket_util.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
ChannelFactory::ChannelFactory(const base::FilePath& path, Delegate* delegate)
|
||||
: path_(path), delegate_(delegate), listen_fd_(-1) {
|
||||
DCHECK(delegate_);
|
||||
CreateSocket();
|
||||
}
|
||||
|
||||
ChannelFactory::~ChannelFactory() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool ChannelFactory::CreateSocket() {
|
||||
DCHECK(listen_fd_ < 0);
|
||||
|
||||
// Create the socket.
|
||||
return CreateServerUnixDomainSocket(path_, &listen_fd_);
|
||||
}
|
||||
|
||||
bool ChannelFactory::Listen() {
|
||||
if (listen_fd_ < 0)
|
||||
return false;
|
||||
|
||||
// Watch the fd for connections, and turn any connections into
|
||||
// active sockets.
|
||||
MessageLoopForIO::current()->WatchFileDescriptor(
|
||||
listen_fd_,
|
||||
true,
|
||||
MessageLoopForIO::WATCH_READ,
|
||||
&server_listen_connection_watcher_,
|
||||
this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called by libevent when we can read from the fd without blocking.
|
||||
void ChannelFactory::OnFileCanReadWithoutBlocking(int fd) {
|
||||
DCHECK(fd == listen_fd_);
|
||||
int new_fd = -1;
|
||||
if (!ServerAcceptConnection(listen_fd_, &new_fd)) {
|
||||
Close();
|
||||
delegate_->OnListenError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_fd < 0) {
|
||||
// The accept() failed, but not in such a way that the factory needs to be
|
||||
// shut down.
|
||||
return;
|
||||
}
|
||||
|
||||
file_util::ScopedFD scoped_fd(&new_fd);
|
||||
|
||||
// Verify that the IPC channel peer is running as the same user.
|
||||
if (!IsPeerAuthorized(new_fd))
|
||||
return;
|
||||
|
||||
ChannelHandle handle("", base::FileDescriptor(*scoped_fd.release(), true));
|
||||
delegate_->OnClientConnected(handle);
|
||||
}
|
||||
|
||||
void ChannelFactory::OnFileCanWriteWithoutBlocking(int fd) {
|
||||
NOTREACHED() << "Listen fd should never be writable.";
|
||||
}
|
||||
|
||||
void ChannelFactory::Close() {
|
||||
if (listen_fd_ < 0)
|
||||
return;
|
||||
if (HANDLE_EINTR(close(listen_fd_)) < 0)
|
||||
PLOG(ERROR) << "close";
|
||||
listen_fd_ = -1;
|
||||
if (unlink(path_.value().c_str()) < 0)
|
||||
PLOG(ERROR) << "unlink";
|
||||
|
||||
// Unregister libevent for the listening socket and close it.
|
||||
server_listen_connection_watcher_.StopWatchingFileDescriptor();
|
||||
}
|
||||
|
||||
} // namespace IPC
|
57
ipc/ipc_channel_factory.h
Normal file
57
ipc/ipc_channel_factory.h
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#ifndef IPC_IPC_CHANNEL_FACTORY_H_
|
||||
#define IPC_IPC_CHANNEL_FACTORY_H_
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "ipc/ipc_channel_handle.h"
|
||||
#include "ipc/ipc_export.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
// A ChannelFactory listens on a UNIX domain socket. When a client connects to
|
||||
// the socket, it accept()s the connection and passes the new FD to the
|
||||
// delegate. The delegate is then responsible for creating a new IPC::Channel
|
||||
// for the FD.
|
||||
class IPC_EXPORT ChannelFactory : public MessageLoopForIO::Watcher {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
// Called when a client connects to the factory. It is the delegate's
|
||||
// responsibility to create an IPC::Channel for the handle, or else close
|
||||
// the file descriptor contained therein.
|
||||
virtual void OnClientConnected(const ChannelHandle& handle) = 0;
|
||||
|
||||
// Called when an error occurs and the channel is closed.
|
||||
virtual void OnListenError() = 0;
|
||||
};
|
||||
|
||||
ChannelFactory(const base::FilePath& path, Delegate* delegate);
|
||||
|
||||
virtual ~ChannelFactory();
|
||||
|
||||
// Call this to start listening on the socket.
|
||||
bool Listen();
|
||||
|
||||
// Close and unlink the socket, and stop accepting connections.
|
||||
void Close();
|
||||
|
||||
private:
|
||||
bool CreateSocket();
|
||||
virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
|
||||
virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
|
||||
|
||||
MessageLoopForIO::FileDescriptorWatcher server_listen_connection_watcher_;
|
||||
base::FilePath path_;
|
||||
Delegate* delegate_;
|
||||
int listen_fd_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ChannelFactory);
|
||||
};
|
||||
|
||||
} // namespace IPC
|
||||
|
||||
#endif // IPC_IPC_CHANNEL_FACTORY_H_
|
@ -40,6 +40,7 @@
|
||||
#include "ipc/ipc_logging.h"
|
||||
#include "ipc/ipc_message_utils.h"
|
||||
#include "ipc/ipc_switches.h"
|
||||
#include "ipc/unix_domain_socket_util.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
@ -137,143 +138,6 @@ class PipeMap {
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Verify that kMaxPipeNameLength is a decent size.
|
||||
COMPILE_ASSERT(sizeof(((sockaddr_un*)0)->sun_path) >= kMaxPipeNameLength,
|
||||
BAD_SUN_PATH_LENGTH);
|
||||
|
||||
// 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);
|
||||
|
||||
if (pipe_name.length() == 0 || pipe_name.length() >= kMaxPipeNameLength) {
|
||||
DLOG(ERROR) << "pipe_name.length() == " << pipe_name.length();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create socket.
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make socket non-blocking
|
||||
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
|
||||
PLOG(ERROR) << "fcntl(O_NONBLOCK) " << pipe_name;
|
||||
if (HANDLE_EINTR(close(fd)) < 0)
|
||||
PLOG(ERROR) << "close " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete any old FS instances.
|
||||
unlink(pipe_name.c_str());
|
||||
|
||||
// Make sure the path we need exists.
|
||||
base::FilePath path(pipe_name);
|
||||
base::FilePath dir_path = path.DirName();
|
||||
if (!file_util::CreateDirectory(dir_path)) {
|
||||
if (HANDLE_EINTR(close(fd)) < 0)
|
||||
PLOG(ERROR) << "close " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create unix_addr structure.
|
||||
struct sockaddr_un unix_addr;
|
||||
memset(&unix_addr, 0, sizeof(unix_addr));
|
||||
unix_addr.sun_family = AF_UNIX;
|
||||
int path_len = snprintf(unix_addr.sun_path, IPC::kMaxPipeNameLength,
|
||||
"%s", pipe_name.c_str());
|
||||
DCHECK_EQ(static_cast<int>(pipe_name.length()), path_len);
|
||||
size_t unix_addr_len = offsetof(struct sockaddr_un,
|
||||
sun_path) + path_len + 1;
|
||||
|
||||
// Bind the socket.
|
||||
if (bind(fd, reinterpret_cast<const sockaddr*>(&unix_addr),
|
||||
unix_addr_len) != 0) {
|
||||
PLOG(ERROR) << "bind " << pipe_name;
|
||||
if (HANDLE_EINTR(close(fd)) < 0)
|
||||
PLOG(ERROR) << "close " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening on the socket.
|
||||
const int listen_queue_length = 1;
|
||||
if (listen(fd, listen_queue_length) != 0) {
|
||||
PLOG(ERROR) << "listen " << pipe_name;
|
||||
if (HANDLE_EINTR(close(fd)) < 0)
|
||||
PLOG(ERROR) << "close " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
*server_listen_fd = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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(O_NONBLOCK) " << accept_fd;
|
||||
if (HANDLE_EINTR(close(accept_fd)) < 0)
|
||||
PLOG(ERROR) << "close " << accept_fd;
|
||||
return false;
|
||||
}
|
||||
|
||||
*server_socket = accept_fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (pipe_name.length() == 0 || pipe_name.length() >= kMaxPipeNameLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create socket.
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "socket " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make socket non-blocking
|
||||
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
|
||||
PLOG(ERROR) << "fcntl(O_NONBLOCK) " << pipe_name;
|
||||
if (HANDLE_EINTR(close(fd)) < 0)
|
||||
PLOG(ERROR) << "close " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create server side of socket.
|
||||
struct sockaddr_un server_unix_addr;
|
||||
memset(&server_unix_addr, 0, sizeof(server_unix_addr));
|
||||
server_unix_addr.sun_family = AF_UNIX;
|
||||
int path_len = snprintf(server_unix_addr.sun_path, IPC::kMaxPipeNameLength,
|
||||
"%s", pipe_name.c_str());
|
||||
DCHECK_EQ(static_cast<int>(pipe_name.length()), path_len);
|
||||
size_t server_unix_addr_len = offsetof(struct sockaddr_un,
|
||||
sun_path) + path_len + 1;
|
||||
|
||||
if (HANDLE_EINTR(connect(fd, reinterpret_cast<sockaddr*>(&server_unix_addr),
|
||||
server_unix_addr_len)) != 0) {
|
||||
PLOG(ERROR) << "connect " << pipe_name;
|
||||
if (HANDLE_EINTR(close(fd)) < 0)
|
||||
PLOG(ERROR) << "close " << pipe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
*client_socket = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketWriteErrorIsRecoverable() {
|
||||
#if defined(OS_MACOSX)
|
||||
@ -388,12 +252,14 @@ bool Channel::ChannelImpl::CreatePipe(
|
||||
} else if (mode_ & MODE_NAMED_FLAG) {
|
||||
// Case 2 from comment above.
|
||||
if (mode_ & MODE_SERVER_FLAG) {
|
||||
if (!CreateServerUnixDomainSocket(pipe_name_, &local_pipe)) {
|
||||
if (!CreateServerUnixDomainSocket(base::FilePath(pipe_name_),
|
||||
&local_pipe)) {
|
||||
return false;
|
||||
}
|
||||
must_unlink_ = true;
|
||||
} else if (mode_ & MODE_CLIENT_FLAG) {
|
||||
if (!CreateClientUnixDomainSocket(pipe_name_, &local_pipe)) {
|
||||
if (!CreateClientUnixDomainSocket(base::FilePath(pipe_name_),
|
||||
&local_pipe)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -674,33 +540,9 @@ bool Channel::ChannelImpl::HasAcceptedConnection() const {
|
||||
return AcceptsConnections() && pipe_ != -1;
|
||||
}
|
||||
|
||||
bool Channel::ChannelImpl::GetClientEuid(uid_t* client_euid) const {
|
||||
DCHECK(HasAcceptedConnection());
|
||||
#if defined(OS_MACOSX) || defined(OS_OPENBSD)
|
||||
uid_t peer_euid;
|
||||
gid_t peer_gid;
|
||||
if (getpeereid(pipe_, &peer_euid, &peer_gid) != 0) {
|
||||
PLOG(ERROR) << "getpeereid " << pipe_;
|
||||
return false;
|
||||
}
|
||||
*client_euid = peer_euid;
|
||||
return true;
|
||||
#elif defined(OS_SOLARIS)
|
||||
return false;
|
||||
#else
|
||||
struct ucred cred;
|
||||
socklen_t cred_len = sizeof(cred);
|
||||
if (getsockopt(pipe_, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) != 0) {
|
||||
PLOG(ERROR) << "getsockopt " << pipe_;
|
||||
return false;
|
||||
}
|
||||
if (static_cast<unsigned>(cred_len) < sizeof(cred)) {
|
||||
NOTREACHED() << "Truncated ucred from SO_PEERCRED?";
|
||||
return false;
|
||||
}
|
||||
*client_euid = cred.uid;
|
||||
return true;
|
||||
#endif
|
||||
bool Channel::ChannelImpl::GetPeerEuid(uid_t* peer_euid) const {
|
||||
DCHECK(!(mode_ & MODE_SERVER) || HasAcceptedConnection());
|
||||
return IPC::GetPeerEuid(pipe_, peer_euid);
|
||||
}
|
||||
|
||||
void Channel::ChannelImpl::ResetToAcceptingConnectionState() {
|
||||
@ -753,7 +595,8 @@ 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)) {
|
||||
if (!ServerAcceptConnection(server_listen_pipe_, &new_pipe) ||
|
||||
new_pipe < 0) {
|
||||
Close();
|
||||
listener()->OnChannelListenError();
|
||||
}
|
||||
@ -773,7 +616,7 @@ void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) {
|
||||
if ((mode_ & MODE_OPEN_ACCESS_FLAG) == 0) {
|
||||
// Verify that the IPC channel peer is running as the same user.
|
||||
uid_t client_euid;
|
||||
if (!GetClientEuid(&client_euid)) {
|
||||
if (!GetPeerEuid(&client_euid)) {
|
||||
DLOG(ERROR) << "Unable to query client euid";
|
||||
ResetToAcceptingConnectionState();
|
||||
return;
|
||||
@ -1157,8 +1000,8 @@ bool Channel::HasAcceptedConnection() const {
|
||||
return channel_impl_->HasAcceptedConnection();
|
||||
}
|
||||
|
||||
bool Channel::GetClientEuid(uid_t* client_euid) const {
|
||||
return channel_impl_->GetClientEuid(client_euid);
|
||||
bool Channel::GetPeerEuid(uid_t* peer_euid) const {
|
||||
return channel_impl_->GetPeerEuid(peer_euid);
|
||||
}
|
||||
|
||||
void Channel::ResetToAcceptingConnectionState() {
|
||||
|
@ -63,7 +63,7 @@ class Channel::ChannelImpl : public internal::ChannelReader,
|
||||
void CloseClientFileDescriptor();
|
||||
bool AcceptsConnections() const;
|
||||
bool HasAcceptedConnection() const;
|
||||
bool GetClientEuid(uid_t* client_euid) const;
|
||||
bool GetPeerEuid(uid_t* peer_euid) const;
|
||||
void ResetToAcceptingConnectionState();
|
||||
base::ProcessId peer_pid() const { return peer_pid_; }
|
||||
static bool IsNamedServerInitialized(const std::string& channel_id);
|
||||
@ -194,12 +194,6 @@ class Channel::ChannelImpl : public internal::ChannelReader,
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(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_
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "base/test/multiprocess_test.h"
|
||||
#include "base/test/test_timeouts.h"
|
||||
#include "ipc/ipc_listener.h"
|
||||
#include "ipc/unix_domain_socket_util.h"
|
||||
#include "testing/multiprocess_func_list.h"
|
||||
|
||||
namespace {
|
||||
@ -145,7 +146,7 @@ void IPCChannelPosixTest::SetUpSocket(IPC::ChannelHandle *handle,
|
||||
struct sockaddr_un server_address = { 0 };
|
||||
memset(&server_address, 0, sizeof(server_address));
|
||||
server_address.sun_family = AF_UNIX;
|
||||
int path_len = snprintf(server_address.sun_path, IPC::kMaxPipeNameLength,
|
||||
int path_len = snprintf(server_address.sun_path, IPC::kMaxSocketNameLength,
|
||||
"%s", name.c_str());
|
||||
DCHECK_EQ(static_cast<int>(name.length()), path_len);
|
||||
size_t server_address_len = offsetof(struct sockaddr_un,
|
||||
@ -311,7 +312,7 @@ TEST_F(IPCChannelPosixTest, BadChannelName) {
|
||||
"future-proof_growth_strategies_Continually"
|
||||
"pontificate_proactive_potentialities_before"
|
||||
"leading-edge_processes";
|
||||
EXPECT_GE(strlen(kTooLongName), IPC::kMaxPipeNameLength);
|
||||
EXPECT_GE(strlen(kTooLongName), IPC::kMaxSocketNameLength);
|
||||
IPC::ChannelHandle handle2(kTooLongName);
|
||||
IPC::Channel channel2(handle2, IPC::Channel::MODE_NAMED_SERVER, NULL);
|
||||
EXPECT_FALSE(channel2.Connect());
|
||||
|
@ -414,13 +414,13 @@ int ChannelProxy::TakeClientFileDescriptor() {
|
||||
return channel->TakeClientFileDescriptor();
|
||||
}
|
||||
|
||||
bool ChannelProxy::GetClientEuid(uid_t* client_euid) const {
|
||||
bool ChannelProxy::GetPeerEuid(uid_t* peer_euid) const {
|
||||
DCHECK(CalledOnValidThread());
|
||||
|
||||
Channel* channel = context_.get()->channel_.get();
|
||||
// Channel must have been created first.
|
||||
DCHECK(channel) << context_.get()->channel_id_;
|
||||
return channel->GetClientEuid(client_euid);
|
||||
return channel->GetPeerEuid(peer_euid);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -181,7 +181,7 @@ class IPC_EXPORT ChannelProxy : public Sender, public base::NonThreadSafe {
|
||||
// Calls through to the underlying channel's methods.
|
||||
int GetClientFileDescriptor();
|
||||
int TakeClientFileDescriptor();
|
||||
bool GetClientEuid(uid_t* client_euid) const;
|
||||
bool GetPeerEuid(uid_t* peer_euid) const;
|
||||
#endif // defined(OS_POSIX)
|
||||
|
||||
protected:
|
||||
|
202
ipc/unix_domain_socket_util.cc
Normal file
202
ipc/unix_domain_socket_util.cc
Normal file
@ -0,0 +1,202 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#include "ipc/unix_domain_socket_util.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
// Verify that kMaxSocketNameLength is a decent size.
|
||||
COMPILE_ASSERT(sizeof(((sockaddr_un*)0)->sun_path) >= kMaxSocketNameLength,
|
||||
BAD_SUN_PATH_LENGTH);
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns fd (>= 0) on success, -1 on failure. If successful, fills in
|
||||
// |unix_addr| with the appropriate data for the socket, and sets
|
||||
// |unix_addr_len| to the length of the data therein.
|
||||
int MakeUnixAddrForPath(const std::string& socket_name,
|
||||
struct sockaddr_un* unix_addr,
|
||||
size_t* unix_addr_len) {
|
||||
DCHECK(unix_addr);
|
||||
DCHECK(unix_addr_len);
|
||||
|
||||
if (socket_name.length() == 0) {
|
||||
LOG(ERROR) << "Empty socket name provided for unix socket address.";
|
||||
return -1;
|
||||
}
|
||||
// We reject socket_name.length() == kMaxSocketNameLength to make room for
|
||||
// the NUL terminator at the end of the string.
|
||||
if (socket_name.length() >= kMaxSocketNameLength) {
|
||||
LOG(ERROR) << "Socket name too long: " << socket_name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create socket.
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "socket";
|
||||
return -1;
|
||||
}
|
||||
file_util::ScopedFD scoped_fd(&fd);
|
||||
|
||||
// Make socket non-blocking
|
||||
if (HANDLE_EINTR(fcntl(fd, F_SETFL, O_NONBLOCK)) < 0) {
|
||||
PLOG(ERROR) << "fcntl(O_NONBLOCK)";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create unix_addr structure.
|
||||
memset(unix_addr, 0, sizeof(struct sockaddr_un));
|
||||
unix_addr->sun_family = AF_UNIX;
|
||||
strncpy(unix_addr->sun_path, socket_name.c_str(), kMaxSocketNameLength);
|
||||
*unix_addr_len =
|
||||
offsetof(struct sockaddr_un, sun_path) + socket_name.length();
|
||||
return *scoped_fd.release();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CreateServerUnixDomainSocket(const base::FilePath& socket_path,
|
||||
int* server_listen_fd) {
|
||||
DCHECK(server_listen_fd);
|
||||
|
||||
std::string socket_name = socket_path.value();
|
||||
base::FilePath socket_dir = socket_path.DirName();
|
||||
|
||||
struct sockaddr_un unix_addr;
|
||||
size_t unix_addr_len;
|
||||
int fd = MakeUnixAddrForPath(socket_name, &unix_addr, &unix_addr_len);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
file_util::ScopedFD scoped_fd(&fd);
|
||||
|
||||
// Make sure the path we need exists.
|
||||
if (!file_util::CreateDirectory(socket_dir)) {
|
||||
LOG(ERROR) << "Couldn't create directory: " << socket_dir.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete any old FS instances.
|
||||
if (unlink(socket_name.c_str()) < 0 && errno != ENOENT) {
|
||||
PLOG(ERROR) << "unlink " << socket_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind the socket.
|
||||
if (bind(fd, reinterpret_cast<const sockaddr*>(&unix_addr),
|
||||
unix_addr_len) < 0) {
|
||||
PLOG(ERROR) << "bind " << socket_path.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening on the socket.
|
||||
if (listen(fd, SOMAXCONN) < 0) {
|
||||
PLOG(ERROR) << "listen " << socket_path.value();
|
||||
unlink(socket_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
*server_listen_fd = *scoped_fd.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateClientUnixDomainSocket(const base::FilePath& socket_path,
|
||||
int* client_socket) {
|
||||
DCHECK(client_socket);
|
||||
|
||||
std::string socket_name = socket_path.value();
|
||||
base::FilePath socket_dir = socket_path.DirName();
|
||||
|
||||
struct sockaddr_un unix_addr;
|
||||
size_t unix_addr_len;
|
||||
int fd = MakeUnixAddrForPath(socket_name, &unix_addr, &unix_addr_len);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
file_util::ScopedFD scoped_fd(&fd);
|
||||
|
||||
if (HANDLE_EINTR(connect(fd, reinterpret_cast<sockaddr*>(&unix_addr),
|
||||
unix_addr_len)) < 0) {
|
||||
PLOG(ERROR) << "connect " << socket_path.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
*client_socket = *scoped_fd.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetPeerEuid(int fd, uid_t* peer_euid) {
|
||||
DCHECK(peer_euid);
|
||||
#if defined(OS_MACOSX) || defined(OS_OPENBSD) || defined(OS_FREEBSD)
|
||||
uid_t socket_euid;
|
||||
gid_t socket_gid;
|
||||
if (getpeereid(fd, &socket_euid, &socket_gid) < 0) {
|
||||
PLOG(ERROR) << "getpeereid " << fd;
|
||||
return false;
|
||||
}
|
||||
*peer_euid = socket_euid;
|
||||
return true;
|
||||
#else
|
||||
struct ucred cred;
|
||||
socklen_t cred_len = sizeof(cred);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0) {
|
||||
PLOG(ERROR) << "getsockopt " << fd;
|
||||
return false;
|
||||
}
|
||||
if (static_cast<unsigned>(cred_len) < sizeof(cred)) {
|
||||
NOTREACHED() << "Truncated ucred from SO_PEERCRED?";
|
||||
return false;
|
||||
}
|
||||
*peer_euid = cred.uid;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsPeerAuthorized(int peer_fd) {
|
||||
uid_t peer_euid;
|
||||
if (!GetPeerEuid(peer_fd, &peer_euid))
|
||||
return false;
|
||||
if (peer_euid != geteuid()) {
|
||||
DLOG(ERROR) << "Client euid is not authorised";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsRecoverableError(int err) {
|
||||
return errno == ECONNABORTED || errno == EMFILE || errno == ENFILE ||
|
||||
errno == ENOMEM || errno == ENOBUFS;
|
||||
}
|
||||
|
||||
bool ServerAcceptConnection(int server_listen_fd, int* server_socket) {
|
||||
DCHECK(server_socket);
|
||||
*server_socket = -1;
|
||||
|
||||
int accept_fd = HANDLE_EINTR(accept(server_listen_fd, NULL, 0));
|
||||
if (accept_fd < 0)
|
||||
return IsRecoverableError(errno);
|
||||
file_util::ScopedFD scoped_fd(&accept_fd);
|
||||
if (HANDLE_EINTR(fcntl(accept_fd, F_SETFL, O_NONBLOCK)) < 0) {
|
||||
PLOG(ERROR) << "fcntl(O_NONBLOCK) " << accept_fd;
|
||||
// It's safe to keep listening on |server_listen_fd| even if the attempt to
|
||||
// set O_NONBLOCK failed on the client fd.
|
||||
return true;
|
||||
}
|
||||
|
||||
*server_socket = *scoped_fd.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace IPC
|
64
ipc/unix_domain_socket_util.h
Normal file
64
ipc/unix_domain_socket_util.h
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#ifndef IPC_UNIX_DOMAIN_SOCKET_UTIL_H_
|
||||
#define IPC_UNIX_DOMAIN_SOCKET_UTIL_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ipc/ipc_export.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
} // namespace base
|
||||
|
||||
namespace IPC {
|
||||
|
||||
// Creates a UNIX-domain socket at |socket_name| and bind()s it, then listen()s
|
||||
// on it. If successful, |server_listen_fd| will be set to the new file
|
||||
// descriptor, and the function will return true. Otherwise returns false.
|
||||
//
|
||||
// This function also effectively performs `mkdir -p` on the dirname of
|
||||
// |socket_name| to ensure that all the directories up to |socket_name| exist.
|
||||
// As a result of which this function must be run on a thread that allows
|
||||
// blocking I/O, e.g. the FILE thread in Chrome's browser process.
|
||||
IPC_EXPORT bool CreateServerUnixDomainSocket(const base::FilePath& socket_name,
|
||||
int* server_listen_fd);
|
||||
|
||||
// Opens a UNIX-domain socket at |socket_name| and connect()s to it. If
|
||||
// successful, |client_socket| will be set to the new file descriptor, and the
|
||||
// function will return true. Otherwise returns false.
|
||||
IPC_EXPORT bool CreateClientUnixDomainSocket(const base::FilePath& socket_name,
|
||||
int* client_socket);
|
||||
|
||||
// Gets the effective user ID of the other end of the UNIX-domain socket
|
||||
// specified by |fd|. If successful, sets |peer_euid| to the uid, and returns
|
||||
// true. Otherwise returns false.
|
||||
IPC_EXPORT bool GetPeerEuid(int fd, uid_t* peer_euid);
|
||||
|
||||
// Checks that the process on the other end of the UNIX domain socket
|
||||
// represented by |peer_fd| shares the same EUID as this process.
|
||||
IPC_EXPORT bool IsPeerAuthorized(int peer_fd);
|
||||
|
||||
// Accepts a client attempting to connect to |server_listen_fd|, storing the
|
||||
// new file descriptor for the connection in |server_socket|.
|
||||
//
|
||||
// Returns false if |server_listen_fd| encounters an unrecoverable error.
|
||||
// Returns true if it's valid to keep listening on |server_listen_fd|. In this
|
||||
// case, it's possible that a connection wasn't successfully established; then,
|
||||
// |server_socket| will be set to -1.
|
||||
IPC_EXPORT bool ServerAcceptConnection(int server_listen_fd,
|
||||
int* server_socket);
|
||||
|
||||
// The maximum length of the name of a socket 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 kMaxSocketNameLength = 104;
|
||||
|
||||
} // namespace IPC
|
||||
|
||||
#endif // IPC_UNIX_DOMAIN_SOCKET_UTIL_H_
|
179
ipc/unix_domain_socket_util_unittest.cc
Normal file
179
ipc/unix_domain_socket_util_unittest.cc
Normal file
@ -0,0 +1,179 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "ipc/unix_domain_socket_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class SocketAcceptor : public MessageLoopForIO::Watcher {
|
||||
public:
|
||||
SocketAcceptor(int fd, base::MessageLoopProxy* target_thread)
|
||||
: server_fd_(-1),
|
||||
target_thread_(target_thread),
|
||||
started_watching_event_(false, false),
|
||||
accepted_event_(false, false) {
|
||||
target_thread->PostTask(FROM_HERE,
|
||||
base::Bind(&SocketAcceptor::StartWatching, base::Unretained(this), fd));
|
||||
}
|
||||
|
||||
virtual ~SocketAcceptor() {
|
||||
Close();
|
||||
}
|
||||
|
||||
int server_fd() const { return server_fd_; }
|
||||
|
||||
void WaitUntilReady() {
|
||||
started_watching_event_.Wait();
|
||||
}
|
||||
|
||||
void WaitForAccept() {
|
||||
accepted_event_.Wait();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (watcher_.get()) {
|
||||
target_thread_->PostTask(FROM_HERE,
|
||||
base::Bind(&SocketAcceptor::StopWatching, base::Unretained(this),
|
||||
watcher_.release()));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void StartWatching(int fd) {
|
||||
watcher_.reset(new MessageLoopForIO::FileDescriptorWatcher);
|
||||
MessageLoopForIO::current()->WatchFileDescriptor(
|
||||
fd,
|
||||
true,
|
||||
MessageLoopForIO::WATCH_READ,
|
||||
watcher_.get(),
|
||||
this);
|
||||
started_watching_event_.Signal();
|
||||
}
|
||||
void StopWatching(MessageLoopForIO::FileDescriptorWatcher* watcher) {
|
||||
watcher->StopWatchingFileDescriptor();
|
||||
delete watcher;
|
||||
}
|
||||
virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
|
||||
ASSERT_EQ(-1, server_fd_);
|
||||
IPC::ServerAcceptConnection(fd, &server_fd_);
|
||||
watcher_->StopWatchingFileDescriptor();
|
||||
accepted_event_.Signal();
|
||||
}
|
||||
virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {}
|
||||
|
||||
int server_fd_;
|
||||
base::MessageLoopProxy* target_thread_;
|
||||
scoped_ptr<MessageLoopForIO::FileDescriptorWatcher> watcher_;
|
||||
base::WaitableEvent started_watching_event_;
|
||||
base::WaitableEvent accepted_event_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SocketAcceptor);
|
||||
};
|
||||
|
||||
const base::FilePath GetChannelDir() {
|
||||
#if defined(OS_ANDROID)
|
||||
base::FilePath tmp_dir;
|
||||
PathService::Get(base::DIR_CACHE, &tmp_dir);
|
||||
return tmp_dir;
|
||||
#else
|
||||
return base::FilePath("/var/tmp");
|
||||
#endif
|
||||
}
|
||||
|
||||
class TestUnixSocketConnection {
|
||||
public:
|
||||
TestUnixSocketConnection()
|
||||
: worker_("WorkerThread"),
|
||||
server_listen_fd_(-1),
|
||||
server_fd_(-1),
|
||||
client_fd_(-1) {
|
||||
socket_name_ = GetChannelDir().Append("TestSocket");
|
||||
base::Thread::Options options;
|
||||
options.message_loop_type = MessageLoop::TYPE_IO;
|
||||
worker_.StartWithOptions(options);
|
||||
}
|
||||
|
||||
bool CreateServerSocket() {
|
||||
IPC::CreateServerUnixDomainSocket(socket_name_, &server_listen_fd_);
|
||||
if (server_listen_fd_ < 0)
|
||||
return false;
|
||||
struct stat socket_stat;
|
||||
stat(socket_name_.value().c_str(), &socket_stat);
|
||||
EXPECT_TRUE(S_ISSOCK(socket_stat.st_mode));
|
||||
acceptor_.reset(new SocketAcceptor(server_listen_fd_,
|
||||
worker_.message_loop_proxy()));
|
||||
acceptor_->WaitUntilReady();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateClientSocket() {
|
||||
DCHECK(server_listen_fd_ >= 0);
|
||||
IPC::CreateClientUnixDomainSocket(socket_name_, &client_fd_);
|
||||
if (client_fd_ < 0)
|
||||
return false;
|
||||
acceptor_->WaitForAccept();
|
||||
server_fd_ = acceptor_->server_fd();
|
||||
return server_fd_ >= 0;
|
||||
}
|
||||
|
||||
virtual ~TestUnixSocketConnection() {
|
||||
if (client_fd_ >= 0)
|
||||
close(client_fd_);
|
||||
if (server_fd_ >= 0)
|
||||
close(server_fd_);
|
||||
if (server_listen_fd_ >= 0) {
|
||||
close(server_listen_fd_);
|
||||
unlink(socket_name_.value().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int client_fd() const { return client_fd_; }
|
||||
int server_fd() const { return server_fd_; }
|
||||
|
||||
private:
|
||||
base::Thread worker_;
|
||||
base::FilePath socket_name_;
|
||||
int server_listen_fd_;
|
||||
int server_fd_;
|
||||
int client_fd_;
|
||||
scoped_ptr<SocketAcceptor> acceptor_;
|
||||
};
|
||||
|
||||
// Ensure that IPC::CreateServerUnixDomainSocket creates a socket that
|
||||
// IPC::CreateClientUnixDomainSocket can successfully connect to.
|
||||
TEST(UnixDomainSocketUtil, Connect) {
|
||||
TestUnixSocketConnection connection;
|
||||
ASSERT_TRUE(connection.CreateServerSocket());
|
||||
ASSERT_TRUE(connection.CreateClientSocket());
|
||||
}
|
||||
|
||||
// Ensure that messages can be sent across the resulting socket.
|
||||
TEST(UnixDomainSocketUtil, SendReceive) {
|
||||
TestUnixSocketConnection connection;
|
||||
ASSERT_TRUE(connection.CreateServerSocket());
|
||||
ASSERT_TRUE(connection.CreateClientSocket());
|
||||
|
||||
const char buffer[] = "Hello, server!";
|
||||
size_t buf_len = sizeof(buffer);
|
||||
size_t sent_bytes =
|
||||
HANDLE_EINTR(send(connection.client_fd(), buffer, buf_len, 0));
|
||||
ASSERT_EQ(buf_len, sent_bytes);
|
||||
char recv_buf[sizeof(buffer)];
|
||||
size_t received_bytes =
|
||||
HANDLE_EINTR(recv(connection.server_fd(), recv_buf, buf_len, 0));
|
||||
ASSERT_EQ(buf_len, received_bytes);
|
||||
ASSERT_EQ(0, memcmp(recv_buf, buffer, buf_len));
|
||||
}
|
||||
|
||||
} // namespace
|
Reference in New Issue
Block a user