Commit 2adb99bf authored by Orvid King's avatar Orvid King Committed by Facebook Github Bot

Add a map to convert between sockets and file descriptors

Summary: Lookups through the map are free on platforms where the socket type is a file descriptor, but this lookup is needed to make incremental conversion of Folly APIs viable.

Reviewed By: yfeldblum

Differential Revision: D10304788

fbshipit-source-id: 286e206986743f9484f687f6efc0ae7f0e363fd0
parent d4a7542e
...@@ -585,6 +585,9 @@ if (BUILD_TESTS) ...@@ -585,6 +585,9 @@ if (BUILD_TESTS)
SOURCES ThreadCachedArenaTest.cpp SOURCES ThreadCachedArenaTest.cpp
TEST mallctl_helper_test SOURCES MallctlHelperTest.cpp TEST mallctl_helper_test SOURCES MallctlHelperTest.cpp
DIRECTORY net/detail/test/
TEST socket_file_descriptor_map_test SOURCES SocketFileDescriptorMapTest.cpp
DIRECTORY portability/test/ DIRECTORY portability/test/
TEST constexpr_test SOURCES ConstexprTest.cpp TEST constexpr_test SOURCES ConstexprTest.cpp
TEST libgen-test SOURCES LibgenTest.cpp TEST libgen-test SOURCES LibgenTest.cpp
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <cstddef> #include <cstddef>
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/net/detail/SocketFileDescriptorMap.h>
#if _WIN32 #if _WIN32
#include <event2/util.h> // @manual #include <event2/util.h> // @manual
...@@ -86,6 +87,10 @@ int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen) { ...@@ -86,6 +87,10 @@ int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen) {
return wrapSocketFunction<int>(::bind, s, name, namelen); return wrapSocketFunction<int>(::bind, s, name, namelen);
} }
int close(NetworkSocket s) {
return netops::detail::SocketFileDescriptorMap::close(s.data);
}
int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen) { int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen) {
auto r = wrapSocketFunction<int>(::connect, s, name, namelen); auto r = wrapSocketFunction<int>(::connect, s, name, namelen);
#if _WIN32 #if _WIN32
......
...@@ -133,6 +133,7 @@ struct PollDescriptor { ...@@ -133,6 +133,7 @@ struct PollDescriptor {
NetworkSocket accept(NetworkSocket s, sockaddr* addr, socklen_t* addrlen); NetworkSocket accept(NetworkSocket s, sockaddr* addr, socklen_t* addrlen);
int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen); int bind(NetworkSocket s, const sockaddr* name, socklen_t namelen);
int close(NetworkSocket s);
int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen); int connect(NetworkSocket s, const sockaddr* name, socklen_t namelen);
int getpeername(NetworkSocket s, sockaddr* name, socklen_t* namelen); int getpeername(NetworkSocket s, sockaddr* name, socklen_t* namelen);
int getsockname(NetworkSocket s, sockaddr* name, socklen_t* namelen); int getsockname(NetworkSocket s, sockaddr* name, socklen_t* namelen);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <ostream> #include <ostream>
#include <folly/net/detail/SocketFileDescriptorMap.h>
#include <folly/portability/Windows.h> #include <folly/portability/Windows.h>
namespace folly { namespace folly {
...@@ -40,6 +41,15 @@ struct NetworkSocket { ...@@ -40,6 +41,15 @@ struct NetworkSocket {
constexpr NetworkSocket() : data(invalid_handle_value) {} constexpr NetworkSocket() : data(invalid_handle_value) {}
constexpr explicit NetworkSocket(native_handle_type d) : data(d) {} constexpr explicit NetworkSocket(native_handle_type d) : data(d) {}
static NetworkSocket fromFd(int fd) {
return NetworkSocket(
netops::detail::SocketFileDescriptorMap::fdToSocket(fd));
}
int toFd() const {
return netops::detail::SocketFileDescriptorMap::socketToFd(data);
}
friend constexpr bool operator==( friend constexpr bool operator==(
const NetworkSocket& a, const NetworkSocket& a,
const NetworkSocket& b) noexcept { const NetworkSocket& b) noexcept {
......
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/net/detail/SocketFileDescriptorMap.h>
#if _WIN32
#include <shared_mutex>
#include <unordered_map>
#include <fcntl.h>
// Including ntdef.h requires building as a driver, but all we want
// is a status code, but we need NTSTATUS defined for that. Luckily
// bcrypt.h also defines NTSTATUS, so we'll use that one instead.
#include <bcrypt.h> // @manual
#include <ntstatus.h> // @manual
namespace folly {
namespace netops {
namespace detail {
static std::unordered_map<SOCKET, int> socketMap;
static std::shared_mutex socketMapMutex;
static int closeOnlyFileDescriptor(int fd) {
HANDLE h = (HANDLE)_get_osfhandle(fd);
// If we were to just call _close on the descriptor, it would
// close the HANDLE, but it wouldn't free any of the resources
// associated to the SOCKET, and we can't call _close after
// calling closesocket, because closesocket has already closed
// the HANDLE, and _close would attempt to close the HANDLE
// again, resulting in a double free.
// We can however protect the HANDLE from actually being closed
// long enough to close the file descriptor, then close the
// socket itself.
constexpr DWORD protectFlag = HANDLE_FLAG_PROTECT_FROM_CLOSE;
DWORD handleFlags = 0;
if (!GetHandleInformation(h, &handleFlags)) {
return -1;
}
if (!SetHandleInformation(h, protectFlag, protectFlag)) {
return -1;
}
int c = 0;
__try {
// We expect this to fail. It still closes the file descriptor though.
c = ::_close(fd);
// We just have to catch the SEH exception that gets thrown when we do
// this with a debugger attached -_-....
} __except (
GetExceptionCode() == STATUS_HANDLE_NOT_CLOSABLE
? EXCEPTION_CONTINUE_EXECUTION
: EXCEPTION_CONTINUE_SEARCH) {
// We told it to continue execution, so nothing here would
// be run anyways.
}
// We're at the core, we don't get the luxery of SCOPE_EXIT because
// of circular dependencies.
if (!SetHandleInformation(h, protectFlag, handleFlags)) {
return -1;
}
if (c != -1) {
return -1;
}
return 0;
}
int SocketFileDescriptorMap::close(int fd) noexcept {
auto hand = SocketFileDescriptorMap::fdToSocket(fd);
{
std::unique_lock<std::shared_mutex> lock{socketMapMutex};
if (socketMap.find(hand) != socketMap.end()) {
socketMap.erase(hand);
}
}
auto r = closeOnlyFileDescriptor(fd);
if (r != 0) {
return r;
}
return closesocket((SOCKET)hand);
}
int SocketFileDescriptorMap::close(SOCKET sock) noexcept {
bool found = false;
int fd = 0;
{
std::unique_lock<std::shared_mutex> lock{socketMapMutex};
auto lookup = socketMap.find(sock);
found = lookup != socketMap.end();
if (found) {
fd = lookup->second;
}
}
if (found) {
return SocketFileDescriptorMap::close(fd);
}
return closesocket(sock);
}
SOCKET SocketFileDescriptorMap::fdToSocket(int fd) noexcept {
if (fd == -1) {
return INVALID_SOCKET;
}
return (SOCKET)_get_osfhandle(fd);
}
int SocketFileDescriptorMap::socketToFd(SOCKET sock) noexcept {
if (sock == INVALID_SOCKET) {
return -1;
}
{
std::shared_lock<std::shared_mutex> lock{socketMapMutex};
auto const found = socketMap.find(sock);
if (found != socketMap.end()) {
return found->second;
}
}
std::unique_lock<std::shared_mutex> lock{socketMapMutex};
auto const found = socketMap.find(sock);
if (found != socketMap.end()) {
return found->second;
}
int fd = _open_osfhandle((intptr_t)sock, O_RDWR | O_BINARY);
socketMap.emplace(sock, fd);
return fd;
}
} // namespace detail
} // namespace netops
} // namespace folly
#endif
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/portability/Windows.h>
#ifndef _WIN32
// This can't go via the portability header, because
// the portability header depends on us.
#include <unistd.h>
#endif
namespace folly {
namespace netops {
namespace detail {
struct SocketFileDescriptorMap {
#if _WIN32
static int close(int fd) noexcept;
static int close(SOCKET sock) noexcept;
static SOCKET fdToSocket(int fd) noexcept;
static int socketToFd(SOCKET sock) noexcept;
#else
static int close(int fd) noexcept {
return ::close(fd);
}
static int fdToSocket(int fd) noexcept {
return fd;
}
static int socketToFd(int sock) noexcept {
return sock;
}
#endif
};
} // namespace detail
} // namespace netops
} // namespace folly
/*
* Copyright 2013-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/net/detail/SocketFileDescriptorMap.h>
#include <folly/Portability.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/GTest.h>
#include <folly/portability/Sockets.h>
#include <folly/portability/Windows.h>
namespace folly {
namespace netops {
using folly::netops::detail::SocketFileDescriptorMap;
namespace fsp = folly::portability::sockets;
TEST(SocketFileDescriptorMap, fd_to_socket_consistent) {
auto fd = fsp::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
auto sockA = SocketFileDescriptorMap::fdToSocket(fd);
auto sockB = SocketFileDescriptorMap::fdToSocket(fd);
EXPECT_EQ(sockA, sockB);
int fd2 = SocketFileDescriptorMap::socketToFd(sockA);
EXPECT_EQ(fd, fd2);
SocketFileDescriptorMap::close(fd);
}
TEST(SocketFileDescriptorMap, no_socket_reuse) {
auto sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int fdA = SocketFileDescriptorMap::socketToFd(sock);
int fdB = SocketFileDescriptorMap::socketToFd(sock);
EXPECT_EQ(fdA, fdB);
auto sock2 = SocketFileDescriptorMap::fdToSocket(fdA);
EXPECT_EQ(sock, sock2);
SocketFileDescriptorMap::close(sock);
// We're on Windows, so let's do some additional testing to ensure
// that we aren't using stale entries in the map.
// This is guarded to only on Windows because we need to assert on
// the specifics of how file descriptors & sockets are allocated,
// which varies between platforms.
if (kIsWindows) {
auto sock3 = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Nothing else is running in this context, so we should,
// in theory, have re-allocated the same socket/file descriptor.
EXPECT_EQ(sock, sock3);
// Now we mess up the order by creating a new FD.
int devNull = ::open("/dev/null", O_RDONLY);
EXPECT_EQ(devNull, fdA);
int fdC = SocketFileDescriptorMap::socketToFd(sock);
EXPECT_NE(fdA, fdC);
SocketFileDescriptorMap::close(sock3);
::close(devNull);
}
}
TEST(SocketFileDescriptorMap, close_non_mapped_native_socket) {
auto sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SocketFileDescriptorMap::close(sock);
}
} // namespace netops
} // namespace folly
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <folly/ScopeGuard.h> #include <folly/ScopeGuard.h>
#include <folly/net/NetworkSocket.h> #include <folly/net/NetworkSocket.h>
#include <folly/net/detail/SocketFileDescriptorMap.h>
namespace folly { namespace folly {
namespace portability { namespace portability {
...@@ -50,24 +51,11 @@ bool is_fh_socket(int fh) { ...@@ -50,24 +51,11 @@ bool is_fh_socket(int fh) {
} }
SOCKET fd_to_socket(int fd) { SOCKET fd_to_socket(int fd) {
if (fd == -1) { return netops::detail::SocketFileDescriptorMap::fdToSocket(fd);
return INVALID_SOCKET;
}
// We do this in a roundabout way to allow us to compile even if
// we're doing a bit of trickery to ensure that things aren't
// being implicitly converted to a SOCKET by temporarily
// adjusting the windows headers to define SOCKET as a
// structure.
static_assert(sizeof(HANDLE) == sizeof(SOCKET), "Handle size mismatch.");
HANDLE tmp = (HANDLE)_get_osfhandle(fd);
return *(SOCKET*)&tmp;
} }
int socket_to_fd(SOCKET s) { int socket_to_fd(SOCKET s) {
if (s == INVALID_SOCKET) { return netops::detail::SocketFileDescriptorMap::socketToFd(s);
return -1;
}
return _open_osfhandle((intptr_t)s, O_RDWR | O_BINARY);
} }
template <class R, class F, class... Args> template <class R, class F, class... Args>
......
...@@ -26,15 +26,10 @@ ...@@ -26,15 +26,10 @@
#include <fcntl.h> #include <fcntl.h>
#include <folly/net/detail/SocketFileDescriptorMap.h>
#include <folly/portability/Sockets.h> #include <folly/portability/Sockets.h>
#include <folly/portability/Windows.h> #include <folly/portability/Windows.h>
// Including ntdef.h requires building as a driver, but all we want
// is a status code, but we need NTSTATUS defined for that. Luckily
// bcrypt.h also defines NTSTATUS, so we'll use that one instead.
#include <bcrypt.h> // @manual
#include <ntstatus.h> // @manual
// Generic wrapper for the p* family of functions. // Generic wrapper for the p* family of functions.
template <class F, class... Args> template <class F, class... Args>
static int wrapPositional(F f, int fd, off_t offset, Args... args) { static int wrapPositional(F f, int fd, off_t offset, Args... args) {
...@@ -73,47 +68,7 @@ int chdir(const char* path) { ...@@ -73,47 +68,7 @@ int chdir(const char* path) {
int close(int fh) { int close(int fh) {
if (folly::portability::sockets::is_fh_socket(fh)) { if (folly::portability::sockets::is_fh_socket(fh)) {
SOCKET h = (SOCKET)_get_osfhandle(fh); return netops::detail::SocketFileDescriptorMap::close(fh);
// If we were to just call _close on the descriptor, it would
// close the HANDLE, but it wouldn't free any of the resources
// associated to the SOCKET, and we can't call _close after
// calling closesocket, because closesocket has already closed
// the HANDLE, and _close would attempt to close the HANDLE
// again, resulting in a double free.
// We can however protect the HANDLE from actually being closed
// long enough to close the file descriptor, then close the
// socket itself.
constexpr DWORD protectFlag = HANDLE_FLAG_PROTECT_FROM_CLOSE;
DWORD handleFlags = 0;
if (!GetHandleInformation((HANDLE)h, &handleFlags)) {
return -1;
}
if (!SetHandleInformation((HANDLE)h, protectFlag, protectFlag)) {
return -1;
}
int c = 0;
__try {
// We expect this to fail. It still closes the file descriptor though.
c = _close(fh);
// We just have to catch the SEH exception that gets thrown when we do
// this with a debugger attached -_-....
} __except (
GetExceptionCode() == STATUS_HANDLE_NOT_CLOSABLE
? EXCEPTION_CONTINUE_EXECUTION
: EXCEPTION_CONTINUE_SEARCH) {
// We told it to continue execution, so there's nothing here would
// be run anyways.
}
// We're at the core, we don't get the luxery of SCOPE_EXIT because
// of circular dependencies.
if (!SetHandleInformation((HANDLE)h, protectFlag, handleFlags)) {
return -1;
}
if (c != -1) {
return -1;
}
return closesocket(h);
} }
return _close(fh); return _close(fh);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment