874 lines
23 KiB
C++
874 lines
23 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* 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/io/async/AsyncUDPSocket.h>
|
|
|
|
#include <folly/Likely.h>
|
|
#include <folly/Utility.h>
|
|
#include <folly/io/SocketOptionMap.h>
|
|
#include <folly/io/async/EventBase.h>
|
|
#include <folly/portability/Fcntl.h>
|
|
#include <folly/portability/Sockets.h>
|
|
#include <folly/portability/Unistd.h>
|
|
|
|
#include <boost/preprocessor/control/if.hpp>
|
|
#include <cerrno>
|
|
|
|
// Due to the way kernel headers are included, this may or may not be defined.
|
|
// Number pulled from 3.10 kernel headers.
|
|
#ifndef SO_REUSEPORT
|
|
#define SO_REUSEPORT 15
|
|
#endif
|
|
|
|
#if FOLLY_HAVE_VLA
|
|
#define FOLLY_HAVE_VLA_01 1
|
|
#else
|
|
#define FOLLY_HAVE_VLA_01 0
|
|
#endif
|
|
|
|
namespace fsp = folly::portability::sockets;
|
|
|
|
namespace folly {
|
|
|
|
AsyncUDPSocket::AsyncUDPSocket(EventBase* evb)
|
|
: EventHandler(CHECK_NOTNULL(evb)),
|
|
readCallback_(nullptr),
|
|
eventBase_(evb),
|
|
fd_() {
|
|
evb->dcheckIsInEventBaseThread();
|
|
}
|
|
|
|
AsyncUDPSocket::~AsyncUDPSocket() {
|
|
if (fd_ != NetworkSocket()) {
|
|
close();
|
|
}
|
|
}
|
|
|
|
void AsyncUDPSocket::bind(const folly::SocketAddress& address) {
|
|
NetworkSocket socket = netops::socket(
|
|
address.getFamily(),
|
|
SOCK_DGRAM,
|
|
address.getFamily() != AF_UNIX ? IPPROTO_UDP : 0);
|
|
if (socket == NetworkSocket()) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"error creating async udp socket",
|
|
errno);
|
|
}
|
|
|
|
auto g = folly::makeGuard([&] { netops::close(socket); });
|
|
|
|
// put the socket in non-blocking mode
|
|
int ret = netops::set_socket_non_blocking(socket);
|
|
if (ret != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to put socket in non-blocking mode",
|
|
errno);
|
|
}
|
|
|
|
if (reuseAddr_) {
|
|
// put the socket in reuse mode
|
|
int value = 1;
|
|
if (netops::setsockopt(
|
|
socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to put socket in reuse mode",
|
|
errno);
|
|
}
|
|
}
|
|
|
|
if (reusePort_) {
|
|
// put the socket in port reuse mode
|
|
int value = 1;
|
|
if (netops::setsockopt(
|
|
socket, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to put socket in reuse_port mode",
|
|
errno);
|
|
}
|
|
}
|
|
|
|
if (busyPollUs_ > 0) {
|
|
#ifdef SO_BUSY_POLL
|
|
// Set busy_poll time in microseconds on the socket.
|
|
// It sets how long socket will be in busy_poll mode when no event occurs.
|
|
int value = busyPollUs_;
|
|
if (netops::setsockopt(
|
|
socket, SOL_SOCKET, SO_BUSY_POLL, &value, sizeof(value)) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to set SO_BUSY_POLL on the socket",
|
|
errno);
|
|
}
|
|
#else /* SO_BUSY_POLL is not supported*/
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN, "SO_BUSY_POLL is not supported", errno);
|
|
#endif
|
|
}
|
|
|
|
if (rcvBuf_ > 0) {
|
|
// Set the size of the buffer for the received messages in rx_queues.
|
|
int value = rcvBuf_;
|
|
if (netops::setsockopt(
|
|
socket, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to set SO_RCVBUF on the socket",
|
|
errno);
|
|
}
|
|
}
|
|
|
|
if (sndBuf_ > 0) {
|
|
// Set the size of the buffer for the sent messages in tx_queues.
|
|
int value = sndBuf_;
|
|
if (netops::setsockopt(
|
|
socket, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to set SO_SNDBUF on the socket",
|
|
errno);
|
|
}
|
|
}
|
|
|
|
// If we're using IPv6, make sure we don't accept V4-mapped connections
|
|
if (address.getFamily() == AF_INET6) {
|
|
int flag = 1;
|
|
if (netops::setsockopt(
|
|
socket, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN, "Failed to set IPV6_V6ONLY", errno);
|
|
}
|
|
}
|
|
|
|
// bind to the address
|
|
sockaddr_storage addrStorage;
|
|
address.getAddress(&addrStorage);
|
|
auto& saddr = reinterpret_cast<sockaddr&>(addrStorage);
|
|
if (netops::bind(socket, &saddr, address.getActualSize()) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"failed to bind the async udp socket for:" + address.describe(),
|
|
errno);
|
|
}
|
|
|
|
// success
|
|
g.dismiss();
|
|
fd_ = socket;
|
|
ownership_ = FDOwnership::OWNS;
|
|
|
|
// attach to EventHandler
|
|
EventHandler::changeHandlerFD(fd_);
|
|
|
|
if (address.getFamily() == AF_UNIX || address.getPort() != 0) {
|
|
localAddress_ = address;
|
|
} else {
|
|
localAddress_.setFromLocalAddress(fd_);
|
|
}
|
|
}
|
|
|
|
void AsyncUDPSocket::dontFragment(bool df) {
|
|
(void)df; // to avoid potential unused variable warning
|
|
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) && \
|
|
defined(IP_PMTUDISC_WANT)
|
|
if (address().getFamily() == AF_INET) {
|
|
int v4 = df ? IP_PMTUDISC_DO : IP_PMTUDISC_WANT;
|
|
if (netops::setsockopt(fd_, IPPROTO_IP, IP_MTU_DISCOVER, &v4, sizeof(v4))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"Failed to set DF with IP_MTU_DISCOVER",
|
|
errno);
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) && \
|
|
defined(IPV6_PMTUDISC_WANT)
|
|
if (address().getFamily() == AF_INET6) {
|
|
int v6 = df ? IPV6_PMTUDISC_DO : IPV6_PMTUDISC_WANT;
|
|
if (netops::setsockopt(
|
|
fd_, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &v6, sizeof(v6))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"Failed to set DF with IPV6_MTU_DISCOVER",
|
|
errno);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void AsyncUDPSocket::setDFAndTurnOffPMTU() {
|
|
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_PROBE)
|
|
if (address().getFamily() == AF_INET) {
|
|
int v4 = IP_PMTUDISC_PROBE;
|
|
if (folly::netops::setsockopt(
|
|
fd_, IPPROTO_IP, IP_MTU_DISCOVER, &v4, sizeof(v4))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"Failed to set PMTUDISC_PROBE with IP_MTU_DISCOVER",
|
|
errno);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_PROBE)
|
|
if (address().getFamily() == AF_INET6) {
|
|
int v6 = IPV6_PMTUDISC_PROBE;
|
|
if (folly::netops::setsockopt(
|
|
fd_, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &v6, sizeof(v6))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN,
|
|
"Failed to set PMTUDISC_PROBE with IPV6_MTU_DISCOVER",
|
|
errno);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void AsyncUDPSocket::setErrMessageCallback(
|
|
ErrMessageCallback* errMessageCallback) {
|
|
errMessageCallback_ = errMessageCallback;
|
|
int err = (errMessageCallback_ != nullptr);
|
|
#if defined(IP_RECVERR)
|
|
if (address().getFamily() == AF_INET &&
|
|
netops::setsockopt(fd_, IPPROTO_IP, IP_RECVERR, &err, sizeof(err))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN, "Failed to set IP_RECVERR", errno);
|
|
}
|
|
#endif
|
|
#if defined(IPV6_RECVERR)
|
|
if (address().getFamily() == AF_INET6 &&
|
|
netops::setsockopt(fd_, IPPROTO_IPV6, IPV6_RECVERR, &err, sizeof(err))) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN, "Failed to set IPV6_RECVERR", errno);
|
|
}
|
|
#endif
|
|
(void)err;
|
|
}
|
|
|
|
void AsyncUDPSocket::setFD(NetworkSocket fd, FDOwnership ownership) {
|
|
CHECK_EQ(NetworkSocket(), fd_) << "Already bound to another FD";
|
|
|
|
fd_ = fd;
|
|
ownership_ = ownership;
|
|
|
|
EventHandler::changeHandlerFD(fd_);
|
|
localAddress_.setFromLocalAddress(fd_);
|
|
}
|
|
|
|
ssize_t AsyncUDPSocket::writeGSO(
|
|
const folly::SocketAddress& address,
|
|
const std::unique_ptr<folly::IOBuf>& buf,
|
|
int gso) {
|
|
// UDP's typical MTU size is 1500, so high number of buffers
|
|
// really do not make sense. Optimize for buffer chains with
|
|
// buffers less than 16, which is the highest I can think of
|
|
// for a real use case.
|
|
iovec vec[16];
|
|
size_t iovec_len = buf->fillIov(vec, sizeof(vec) / sizeof(vec[0])).numIovecs;
|
|
if (UNLIKELY(iovec_len == 0)) {
|
|
buf->coalesce();
|
|
vec[0].iov_base = const_cast<uint8_t*>(buf->data());
|
|
vec[0].iov_len = buf->length();
|
|
iovec_len = 1;
|
|
}
|
|
|
|
return writev(address, vec, iovec_len, gso);
|
|
}
|
|
|
|
ssize_t AsyncUDPSocket::write(
|
|
const folly::SocketAddress& address,
|
|
const std::unique_ptr<folly::IOBuf>& buf) {
|
|
return writeGSO(address, buf, 0);
|
|
}
|
|
|
|
ssize_t AsyncUDPSocket::writev(
|
|
const folly::SocketAddress& address,
|
|
const struct iovec* vec,
|
|
size_t iovec_len,
|
|
int gso) {
|
|
CHECK_NE(NetworkSocket(), fd_) << "Socket not yet bound";
|
|
sockaddr_storage addrStorage;
|
|
address.getAddress(&addrStorage);
|
|
|
|
struct msghdr msg;
|
|
if (!connected_) {
|
|
msg.msg_name = reinterpret_cast<void*>(&addrStorage);
|
|
msg.msg_namelen = address.getActualSize();
|
|
} else {
|
|
if (connectedAddress_ != address) {
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
msg.msg_name = nullptr;
|
|
msg.msg_namelen = 0;
|
|
}
|
|
msg.msg_iov = const_cast<struct iovec*>(vec);
|
|
msg.msg_iovlen = iovec_len;
|
|
msg.msg_control = nullptr;
|
|
msg.msg_controllen = 0;
|
|
msg.msg_flags = 0;
|
|
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
if (gso > 0) {
|
|
char control[CMSG_SPACE(sizeof(uint16_t))];
|
|
msg.msg_control = control;
|
|
msg.msg_controllen = sizeof(control);
|
|
|
|
struct cmsghdr* cm = CMSG_FIRSTHDR(&msg);
|
|
cm->cmsg_level = SOL_UDP;
|
|
cm->cmsg_type = UDP_SEGMENT;
|
|
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
|
auto gso_len = static_cast<uint16_t>(gso);
|
|
memcpy(CMSG_DATA(cm), &gso_len, sizeof(gso_len));
|
|
|
|
return sendmsg(fd_, &msg, 0);
|
|
}
|
|
#else
|
|
CHECK_LT(gso, 1) << "GSO not supported";
|
|
#endif
|
|
|
|
return sendmsg(fd_, &msg, 0);
|
|
}
|
|
|
|
ssize_t AsyncUDPSocket::writev(
|
|
const folly::SocketAddress& address,
|
|
const struct iovec* vec,
|
|
size_t iovec_len) {
|
|
return writev(address, vec, iovec_len, 0);
|
|
}
|
|
|
|
/**
|
|
* Send the data in buffers to destination. Returns the return code from
|
|
* ::sendmmsg.
|
|
*/
|
|
int AsyncUDPSocket::writem(
|
|
const folly::SocketAddress& address,
|
|
const std::unique_ptr<folly::IOBuf>* bufs,
|
|
size_t count) {
|
|
return writemGSO(address, bufs, count, nullptr);
|
|
}
|
|
|
|
int AsyncUDPSocket::writemGSO(
|
|
const folly::SocketAddress& address,
|
|
const std::unique_ptr<folly::IOBuf>* bufs,
|
|
size_t count,
|
|
const int* gso) {
|
|
int ret;
|
|
constexpr size_t kSmallSizeMax = 8;
|
|
char* gsoControl = nullptr;
|
|
#ifndef FOLLY_HAVE_MSG_ERRQUEUE
|
|
CHECK(!gso) << "GSO not supported";
|
|
#endif
|
|
if (count <= kSmallSizeMax) {
|
|
// suppress "warning: variable length array 'vec' is used [-Wvla]"
|
|
FOLLY_PUSH_WARNING
|
|
FOLLY_GNU_DISABLE_WARNING("-Wvla")
|
|
mmsghdr vec[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)];
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
// we will allocate this on the stack anyway even if we do not use it
|
|
char control
|
|
[(BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)) *
|
|
(CMSG_SPACE(sizeof(uint16_t)))];
|
|
|
|
if (gso) {
|
|
gsoControl = control;
|
|
}
|
|
#endif
|
|
FOLLY_POP_WARNING
|
|
ret = writeImpl(address, bufs, count, vec, gso, gsoControl);
|
|
} else {
|
|
std::unique_ptr<mmsghdr[]> vec(new mmsghdr[count]);
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
std::unique_ptr<char[]> control(
|
|
gso ? (new char[count * (CMSG_SPACE(sizeof(uint16_t)))]) : nullptr);
|
|
if (gso) {
|
|
gsoControl = control.get();
|
|
}
|
|
#endif
|
|
ret = writeImpl(address, bufs, count, vec.get(), gso, gsoControl);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void AsyncUDPSocket::fillMsgVec(
|
|
sockaddr_storage* addr,
|
|
socklen_t addr_len,
|
|
const std::unique_ptr<folly::IOBuf>* bufs,
|
|
size_t count,
|
|
struct mmsghdr* msgvec,
|
|
struct iovec* iov,
|
|
size_t iov_count,
|
|
const int* gso,
|
|
char* gsoControl) {
|
|
size_t remaining = iov_count;
|
|
|
|
size_t iov_pos = 0;
|
|
for (size_t i = 0; i < count; i++) {
|
|
// we can use remaining here to avoid calling countChainElements() again
|
|
auto ret = bufs[i]->fillIov(&iov[iov_pos], remaining);
|
|
size_t iovec_len = ret.numIovecs;
|
|
remaining -= iovec_len;
|
|
auto& msg = msgvec[i].msg_hdr;
|
|
msg.msg_name = reinterpret_cast<void*>(addr);
|
|
msg.msg_namelen = addr_len;
|
|
msg.msg_iov = &iov[iov_pos];
|
|
msg.msg_iovlen = iovec_len;
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
if (gso && gso[i] > 0) {
|
|
msg.msg_control = &gsoControl[i * CMSG_SPACE(sizeof(uint16_t))];
|
|
msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
|
|
|
|
struct cmsghdr* cm = CMSG_FIRSTHDR(&msg);
|
|
cm->cmsg_level = SOL_UDP;
|
|
cm->cmsg_type = UDP_SEGMENT;
|
|
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
|
auto gso_len = static_cast<uint16_t>(gso[i]);
|
|
memcpy(CMSG_DATA(cm), &gso_len, sizeof(gso_len));
|
|
} else {
|
|
msg.msg_control = nullptr;
|
|
msg.msg_controllen = 0;
|
|
}
|
|
#else
|
|
(void)gso;
|
|
(void)gsoControl;
|
|
msg.msg_control = nullptr;
|
|
msg.msg_controllen = 0;
|
|
#endif
|
|
msg.msg_flags = 0;
|
|
|
|
msgvec[i].msg_len = 0;
|
|
|
|
iov_pos += iovec_len;
|
|
}
|
|
}
|
|
|
|
int AsyncUDPSocket::writeImpl(
|
|
const folly::SocketAddress& address,
|
|
const std::unique_ptr<folly::IOBuf>* bufs,
|
|
size_t count,
|
|
struct mmsghdr* msgvec,
|
|
const int* gso,
|
|
char* gsoControl) {
|
|
sockaddr_storage addrStorage;
|
|
address.getAddress(&addrStorage);
|
|
|
|
size_t iov_count = 0;
|
|
for (size_t i = 0; i < count; i++) {
|
|
iov_count += bufs[i]->countChainElements();
|
|
}
|
|
|
|
int ret;
|
|
constexpr size_t kSmallSizeMax = 16;
|
|
if (iov_count <= kSmallSizeMax) {
|
|
// suppress "warning: variable length array 'vec' is used [-Wvla]"
|
|
FOLLY_PUSH_WARNING
|
|
FOLLY_GNU_DISABLE_WARNING("-Wvla")
|
|
iovec iov[BOOST_PP_IF(FOLLY_HAVE_VLA_01, iov_count, kSmallSizeMax)];
|
|
FOLLY_POP_WARNING
|
|
fillMsgVec(
|
|
&addrStorage,
|
|
folly::to_narrow(address.getActualSize()),
|
|
bufs,
|
|
count,
|
|
msgvec,
|
|
iov,
|
|
iov_count,
|
|
gso,
|
|
gsoControl);
|
|
ret = sendmmsg(fd_, msgvec, count, 0);
|
|
} else {
|
|
std::unique_ptr<iovec[]> iov(new iovec[iov_count]);
|
|
fillMsgVec(
|
|
&addrStorage,
|
|
folly::to_narrow(address.getActualSize()),
|
|
bufs,
|
|
count,
|
|
msgvec,
|
|
iov.get(),
|
|
iov_count,
|
|
gso,
|
|
gsoControl);
|
|
ret = sendmmsg(fd_, msgvec, count, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ssize_t AsyncUDPSocket::recvmsg(struct msghdr* msg, int flags) {
|
|
return netops::recvmsg(fd_, msg, flags);
|
|
}
|
|
|
|
int AsyncUDPSocket::recvmmsg(
|
|
struct mmsghdr* msgvec,
|
|
unsigned int vlen,
|
|
unsigned int flags,
|
|
struct timespec* timeout) {
|
|
return netops::recvmmsg(fd_, msgvec, vlen, flags, timeout);
|
|
}
|
|
|
|
void AsyncUDPSocket::resumeRead(ReadCallback* cob) {
|
|
CHECK(!readCallback_) << "Another read callback already installed";
|
|
CHECK_NE(NetworkSocket(), fd_)
|
|
<< "UDP server socket not yet bind to an address";
|
|
|
|
readCallback_ = CHECK_NOTNULL(cob);
|
|
if (!updateRegistration()) {
|
|
AsyncSocketException ex(
|
|
AsyncSocketException::NOT_OPEN, "failed to register for accept events");
|
|
|
|
readCallback_ = nullptr;
|
|
cob->onReadError(ex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void AsyncUDPSocket::pauseRead() {
|
|
// It is ok to pause an already paused socket
|
|
readCallback_ = nullptr;
|
|
updateRegistration();
|
|
}
|
|
|
|
void AsyncUDPSocket::close() {
|
|
eventBase_->dcheckIsInEventBaseThread();
|
|
|
|
if (readCallback_) {
|
|
auto cob = readCallback_;
|
|
readCallback_ = nullptr;
|
|
|
|
cob->onReadClosed();
|
|
}
|
|
|
|
// Unregister any events we are registered for
|
|
unregisterHandler();
|
|
|
|
if (fd_ != NetworkSocket() && ownership_ == FDOwnership::OWNS) {
|
|
netops::close(fd_);
|
|
}
|
|
|
|
fd_ = NetworkSocket();
|
|
}
|
|
|
|
void AsyncUDPSocket::handlerReady(uint16_t events) noexcept {
|
|
if (events & EventHandler::READ) {
|
|
DCHECK(readCallback_);
|
|
handleRead();
|
|
}
|
|
}
|
|
|
|
size_t AsyncUDPSocket::handleErrMessages() noexcept {
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
if (errMessageCallback_ == nullptr) {
|
|
return 0;
|
|
}
|
|
uint8_t ctrl[1024];
|
|
unsigned char data;
|
|
struct msghdr msg;
|
|
iovec entry;
|
|
|
|
entry.iov_base = &data;
|
|
entry.iov_len = sizeof(data);
|
|
msg.msg_iov = &entry;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_name = nullptr;
|
|
msg.msg_namelen = 0;
|
|
msg.msg_control = ctrl;
|
|
msg.msg_controllen = sizeof(ctrl);
|
|
msg.msg_flags = 0;
|
|
|
|
int ret;
|
|
size_t num = 0;
|
|
while (fd_ != NetworkSocket()) {
|
|
ret = netops::recvmsg(fd_, &msg, MSG_ERRQUEUE);
|
|
VLOG(5) << "AsyncSocket::handleErrMessages(): recvmsg returned " << ret;
|
|
|
|
if (ret < 0) {
|
|
if (errno != EAGAIN) {
|
|
auto errnoCopy = errno;
|
|
LOG(ERROR) << "::recvmsg exited with code " << ret
|
|
<< ", errno: " << errnoCopy;
|
|
AsyncSocketException ex(
|
|
AsyncSocketException::INTERNAL_ERROR,
|
|
"recvmsg() failed",
|
|
errnoCopy);
|
|
failErrMessageRead(ex);
|
|
}
|
|
return num;
|
|
}
|
|
|
|
for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg != nullptr && cmsg->cmsg_len != 0;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
++num;
|
|
errMessageCallback_->errMessage(*cmsg);
|
|
if (fd_ == NetworkSocket()) {
|
|
// once the socket is closed there is no use for more read errors.
|
|
return num;
|
|
}
|
|
}
|
|
}
|
|
return num;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void AsyncUDPSocket::failErrMessageRead(const AsyncSocketException& ex) {
|
|
if (errMessageCallback_ != nullptr) {
|
|
ErrMessageCallback* callback = errMessageCallback_;
|
|
errMessageCallback_ = nullptr;
|
|
callback->errMessageError(ex);
|
|
}
|
|
}
|
|
|
|
int AsyncUDPSocket::connect(const folly::SocketAddress& address) {
|
|
CHECK_NE(NetworkSocket(), fd_) << "Socket not yet bound";
|
|
sockaddr_storage addrStorage;
|
|
address.getAddress(&addrStorage);
|
|
int ret = netops::connect(
|
|
fd_, reinterpret_cast<sockaddr*>(&addrStorage), address.getActualSize());
|
|
if (ret == 0) {
|
|
connected_ = true;
|
|
connectedAddress_ = address;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void AsyncUDPSocket::handleRead() noexcept {
|
|
void* buf{nullptr};
|
|
size_t len{0};
|
|
|
|
if (handleErrMessages()) {
|
|
return;
|
|
}
|
|
|
|
if (fd_ == NetworkSocket()) {
|
|
// The socket may have been closed by the error callbacks.
|
|
return;
|
|
}
|
|
if (readCallback_->shouldOnlyNotify()) {
|
|
return readCallback_->onNotifyDataAvailable(*this);
|
|
}
|
|
|
|
readCallback_->getReadBuffer(&buf, &len);
|
|
if (buf == nullptr || len == 0) {
|
|
AsyncSocketException ex(
|
|
AsyncSocketException::BAD_ARGS,
|
|
"AsyncUDPSocket::getReadBuffer() returned empty buffer");
|
|
|
|
auto cob = readCallback_;
|
|
readCallback_ = nullptr;
|
|
|
|
cob->onReadError(ex);
|
|
updateRegistration();
|
|
return;
|
|
}
|
|
|
|
struct sockaddr_storage addrStorage;
|
|
socklen_t addrLen = sizeof(addrStorage);
|
|
memset(&addrStorage, 0, size_t(addrLen));
|
|
auto rawAddr = reinterpret_cast<sockaddr*>(&addrStorage);
|
|
rawAddr->sa_family = localAddress_.getFamily();
|
|
|
|
ssize_t bytesRead;
|
|
ReadCallback::OnDataAvailableParams params;
|
|
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
if (gro_.has_value() && (gro_ != 0)) {
|
|
char control[CMSG_SPACE(sizeof(uint16_t))] = {};
|
|
struct msghdr msg = {};
|
|
struct iovec iov = {};
|
|
struct cmsghdr* cmsg;
|
|
uint16_t* grosizeptr;
|
|
|
|
iov.iov_base = buf;
|
|
iov.iov_len = len;
|
|
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
msg.msg_name = rawAddr;
|
|
msg.msg_namelen = addrLen;
|
|
|
|
msg.msg_control = control;
|
|
msg.msg_controllen = sizeof(control);
|
|
|
|
bytesRead = netops::recvmsg(fd_, &msg, MSG_TRUNC);
|
|
|
|
if (bytesRead >= 0) {
|
|
addrLen = msg.msg_namelen;
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
|
|
grosizeptr = (uint16_t*)CMSG_DATA(cmsg);
|
|
params.gro_ = *grosizeptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
bytesRead = netops::recvfrom(fd_, buf, len, MSG_TRUNC, rawAddr, &addrLen);
|
|
}
|
|
#else
|
|
bytesRead = netops::recvfrom(fd_, buf, len, MSG_TRUNC, rawAddr, &addrLen);
|
|
#endif
|
|
if (bytesRead >= 0) {
|
|
clientAddress_.setFromSockaddr(rawAddr, addrLen);
|
|
|
|
if (bytesRead > 0) {
|
|
bool truncated = false;
|
|
if ((size_t)bytesRead > len) {
|
|
truncated = true;
|
|
bytesRead = ssize_t(len);
|
|
}
|
|
|
|
readCallback_->onDataAvailable(
|
|
clientAddress_, size_t(bytesRead), truncated, params);
|
|
}
|
|
} else {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
// No data could be read without blocking the socket
|
|
return;
|
|
}
|
|
|
|
AsyncSocketException ex(
|
|
AsyncSocketException::INTERNAL_ERROR, "::recvfrom() failed", errno);
|
|
|
|
// In case of UDP we can continue reading from the socket
|
|
// even if the current request fails. We notify the user
|
|
// so that he can do some logging/stats collection if he wants.
|
|
auto cob = readCallback_;
|
|
readCallback_ = nullptr;
|
|
|
|
cob->onReadError(ex);
|
|
updateRegistration();
|
|
}
|
|
}
|
|
|
|
bool AsyncUDPSocket::updateRegistration() noexcept {
|
|
uint16_t flags = NONE;
|
|
|
|
if (readCallback_) {
|
|
flags |= READ;
|
|
}
|
|
|
|
return registerHandler(uint16_t(flags | PERSIST));
|
|
}
|
|
|
|
bool AsyncUDPSocket::setGSO(int val) {
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
int ret = netops::setsockopt(fd_, SOL_UDP, UDP_SEGMENT, &val, sizeof(val));
|
|
|
|
gso_ = ret ? -1 : val;
|
|
|
|
return !ret;
|
|
#else
|
|
(void)val;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
int AsyncUDPSocket::getGSO() {
|
|
// check if we can return the cached value
|
|
if (FOLLY_UNLIKELY(!gso_.has_value())) {
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
int gso = -1;
|
|
socklen_t optlen = sizeof(gso);
|
|
if (!netops::getsockopt(fd_, SOL_UDP, UDP_SEGMENT, &gso, &optlen)) {
|
|
gso_ = gso;
|
|
} else {
|
|
gso_ = -1;
|
|
}
|
|
#else
|
|
gso_ = -1;
|
|
#endif
|
|
}
|
|
|
|
return gso_.value();
|
|
}
|
|
|
|
bool AsyncUDPSocket::setGRO(bool bVal) {
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
int val = bVal ? 1 : 0;
|
|
int ret = netops::setsockopt(fd_, SOL_UDP, UDP_GRO, &val, sizeof(val));
|
|
|
|
gro_ = ret ? -1 : val;
|
|
|
|
return !ret;
|
|
#else
|
|
(void)bVal;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
int AsyncUDPSocket::getGRO() {
|
|
// check if we can return the cached value
|
|
if (FOLLY_UNLIKELY(!gro_.has_value())) {
|
|
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
|
|
int gro = -1;
|
|
socklen_t optlen = sizeof(gro);
|
|
if (!netops::getsockopt(fd_, SOL_UDP, UDP_GRO, &gro, &optlen)) {
|
|
gro_ = gro;
|
|
} else {
|
|
gro_ = -1;
|
|
}
|
|
#else
|
|
gro_ = -1;
|
|
#endif
|
|
}
|
|
|
|
return gro_.value();
|
|
}
|
|
|
|
void AsyncUDPSocket::setTrafficClass(int tclass) {
|
|
if (netops::setsockopt(
|
|
fd_, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(int)) != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::NOT_OPEN, "Failed to set IPV6_TCLASS", errno);
|
|
}
|
|
}
|
|
|
|
void AsyncUDPSocket::applyOptions(
|
|
const SocketOptionMap& options,
|
|
SocketOptionKey::ApplyPos pos) {
|
|
auto result = applySocketOptions(fd_, options, pos);
|
|
if (result != 0) {
|
|
throw AsyncSocketException(
|
|
AsyncSocketException::INTERNAL_ERROR,
|
|
"failed to set socket option",
|
|
result);
|
|
}
|
|
}
|
|
|
|
void AsyncUDPSocket::detachEventBase() {
|
|
DCHECK(eventBase_ && eventBase_->isInEventBaseThread());
|
|
registerHandler(uint16_t(NONE));
|
|
eventBase_ = nullptr;
|
|
EventHandler::detachEventBase();
|
|
}
|
|
|
|
void AsyncUDPSocket::attachEventBase(folly::EventBase* evb) {
|
|
DCHECK(!eventBase_);
|
|
DCHECK(evb && evb->isInEventBaseThread());
|
|
eventBase_ = evb;
|
|
EventHandler::attachEventBase(evb);
|
|
updateRegistration();
|
|
}
|
|
|
|
} // namespace folly
|