415 lines
14 KiB
C++
415 lines
14 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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <system_error>
|
|
|
|
#include <folly/Expected.h>
|
|
#include <folly/Optional.h>
|
|
#include <folly/String.h>
|
|
#include <folly/net/NetOpsDispatcher.h>
|
|
#include <folly/net/NetworkSocket.h>
|
|
#include <folly/net/TcpInfoTypes.h>
|
|
|
|
namespace folly {
|
|
namespace tcpinfo {
|
|
|
|
/**
|
|
* Structure specifying options for TcpInfo::initFromFd.
|
|
*/
|
|
struct LookupOptions {
|
|
// On supported platforms, whether to fetch the name of the congestion
|
|
// control algorithm and any information exposed via TCP_CC_INFO.
|
|
bool getCcInfo{false};
|
|
|
|
// On supported platforms, whether to fetch socket buffer utilization.
|
|
bool getMemInfo{false};
|
|
};
|
|
|
|
/**
|
|
* Abstraction layer for capturing current TCP and congestion control state.
|
|
*
|
|
* Fetches information from four different resources:
|
|
* - TCP_INFO (state of TCP)
|
|
* - TCP_CONGESTION (name of congestion control algorithm)
|
|
* - TCP_CC_INFO (details for a given congestion control algorithm)
|
|
* - SIOCOUTQ/SIOCINQ (socket buffers)
|
|
*
|
|
* To save space, the structure only allocates fields for which the underlying
|
|
* platform supports lookups. For instance, if TCP_CONGESTION is not supported,
|
|
* then the TcpInfo structure will not have fields to store the CC name / type.
|
|
*
|
|
* This abstraction layer solves two problems:
|
|
*
|
|
* 1. It unblocks use of the latest tcp_info structs and related structs.
|
|
*
|
|
* As of 2020, the tcp_info struct shipped with glibc
|
|
* (sysdeps/gnu/netinet/tcp.h) has not been updated since 2007 due to
|
|
* compatibility concerns; see commit titled "Update netinet/tcp.h from
|
|
* Linux 4.18" in glibc repository. This creates scenarios where fields
|
|
* that have long been available in the kernel ABI cannot be accessed.
|
|
* Even if glibc does eventually update the tcp_info shipped, we don't
|
|
* want to be limited to their update cycle.
|
|
*
|
|
* folly::TcpInfo solves this in two ways:
|
|
* - First, TcpInfoTypes.h contains a copy of the latest tcp_info struct
|
|
* for Linux, and folly::TcpInfo always uses this struct for lookups;
|
|
* this decouples TcpInfo from glibc's / the platform's tcp_info.
|
|
*
|
|
* - Second, folly::TcpInfo determines which fields in the struct the
|
|
* kernel ABI populated (and thus which fields are valid) based on the
|
|
* number of bytes the kernel ABI copies into the struct during the
|
|
* corresponding getsockopt operation. When a field is accessed
|
|
* through getFieldAsOptUInt64 or through an accessor, folly::TcpInfo
|
|
* returns an empty optional if the field is unavailable at run-time.
|
|
* In this manner, folly::TcpInfo enables the latest struct to always
|
|
* be used while ensuring that programs can determine at run-time
|
|
* which fields are available for use --- there's no risk of a program
|
|
* assuming that a field is valid when it in fact was never
|
|
* initialized/set by the ABI.
|
|
*
|
|
* 2. Eliminates platform differences while still retaining details.
|
|
*
|
|
* The tcp_info structure varies significantly between Apple and Linux.
|
|
* folly::TcpInfo exposes a subset of tcp_info and other fields through
|
|
* accessors that abstract these differences, and reduce potential errors
|
|
* (e.g., Apple stores srtt in milliseconds, Linux stores in microseconds).
|
|
* When a field is unavailable on a platform, the accessor returns an empty
|
|
* optional.
|
|
*
|
|
* In parallel, the underlying structures remain accessible and can be
|
|
* safely accessed through the appropriate getFieldAsOptUInt64(...). This
|
|
* enables platform-specific code to have full access to the structure
|
|
* while also benefiting from folly::TcpInfo's knowledge of whether a
|
|
* given field was populated by the ABI at run-time.
|
|
*/
|
|
struct TcpInfo {
|
|
enum class CongestionControlName {
|
|
UNKNOWN = 0,
|
|
CUBIC = 1,
|
|
BIC = 2,
|
|
DCTCP = 3,
|
|
DCTCP_RENO = 4,
|
|
BBR = 5,
|
|
RENO = 6,
|
|
DCTCP_CUBIC = 7,
|
|
VEGAS = 8,
|
|
NumCcTypes,
|
|
};
|
|
|
|
/**
|
|
* Dispatcher that enables calls to ioctl to be intercepted for tests.
|
|
*
|
|
* Also enables ioctl calls to be disabled for unsupported platforms.
|
|
*/
|
|
class IoctlDispatcher {
|
|
public:
|
|
static IoctlDispatcher* getDefaultInstance();
|
|
virtual int ioctl(int fd, unsigned long request, void* argp);
|
|
|
|
protected:
|
|
IoctlDispatcher() = default;
|
|
virtual ~IoctlDispatcher() = default;
|
|
};
|
|
|
|
/**
|
|
* Initializes and returns TcpInfo struct.
|
|
*
|
|
* @param fd Socket file descriptor encapsulated in NetworkSocket.
|
|
* @param options Options for lookup.
|
|
* @param netopsDispatcher Dispatcher to use for netops calls;
|
|
* facilitates mocking during unit tests.
|
|
* @param ioctlDispatcher Dispatcher to use for ioctl calls;
|
|
* facilitates mocking during unit tests.
|
|
*/
|
|
static Expected<TcpInfo, std::errc> initFromFd(
|
|
const NetworkSocket& fd,
|
|
const LookupOptions& options = LookupOptions(),
|
|
netops::Dispatcher& netopsDispatcher =
|
|
*netops::Dispatcher::getDefaultInstance(),
|
|
IoctlDispatcher& ioctlDispatcher =
|
|
*IoctlDispatcher::getDefaultInstance());
|
|
|
|
/**
|
|
* Accessors for tcp_info.
|
|
*
|
|
* These accessors are always available regardless of platform, they return
|
|
* folly::none if the underlying field is unavailable.
|
|
*/
|
|
|
|
Optional<std::chrono::microseconds> minrtt() const;
|
|
Optional<std::chrono::microseconds> srtt() const;
|
|
|
|
Optional<uint64_t> bytesSent() const;
|
|
Optional<uint64_t> bytesReceived() const;
|
|
Optional<uint64_t> bytesRetransmitted() const;
|
|
Optional<uint64_t> bytesNotSent() const;
|
|
|
|
Optional<uint64_t> packetsSent() const;
|
|
Optional<uint64_t> packetsWithDataSent() const;
|
|
Optional<uint64_t> packetsReceived() const;
|
|
Optional<uint64_t> packetsWithDataReceived() const;
|
|
Optional<uint64_t> packetsRetransmitted() const;
|
|
Optional<uint64_t> packetsInFlight() const;
|
|
|
|
Optional<uint64_t> cwndInPackets() const;
|
|
Optional<uint64_t> cwndInBytes() const;
|
|
Optional<uint64_t> ssthresh() const;
|
|
Optional<uint64_t> mss() const;
|
|
|
|
Optional<uint64_t> deliveryRateBitsPerSecond() const;
|
|
Optional<uint64_t> deliveryRateBytesPerSecond() const;
|
|
Optional<bool> deliveryRateAppLimited() const;
|
|
|
|
/**
|
|
* Accessors for congestion control information.
|
|
*
|
|
* These accessors are always available regardless of platform, they return
|
|
* folly::none if the underlying field is unavailable.
|
|
*/
|
|
Optional<std::string> ccNameRaw() const;
|
|
Optional<CongestionControlName> ccNameEnum() const;
|
|
Optional<folly::StringPiece> ccNameEnumAsStr() const;
|
|
|
|
Optional<uint64_t> bbrBwBitsPerSecond() const;
|
|
Optional<uint64_t> bbrBwBytesPerSecond() const;
|
|
Optional<std::chrono::microseconds> bbrMinrtt() const;
|
|
Optional<uint64_t> bbrPacingGain() const;
|
|
Optional<uint64_t> bbrCwndGain() const;
|
|
|
|
/**
|
|
* Accessors for memory info information.
|
|
*
|
|
* These accessors are always available regardless of platform, they return
|
|
* folly::none if the underlying field is unavailable.
|
|
*/
|
|
|
|
Optional<size_t> sendBufInUseBytes() const;
|
|
Optional<size_t> recvBufInUseBytes() const;
|
|
|
|
private:
|
|
/**
|
|
* Returns pointer containing requested field from passed struct.
|
|
*
|
|
* If field is unavailable, returns a nullptr.
|
|
*/
|
|
template <typename T1, typename T2>
|
|
static const T1* getFieldAsPtr(
|
|
const T2& tgtStruct, const int tgtBytesRead, T1 T2::*field) {
|
|
if (field != nullptr && tgtBytesRead > 0 &&
|
|
getFieldOffset(field) + sizeof(tgtStruct.*field) <=
|
|
(unsigned long)tgtBytesRead) {
|
|
return &(tgtStruct.*field);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Get the offset of a field in a struct.
|
|
*
|
|
* Requires that struct (T1) be POD (else undefined behavior).
|
|
*
|
|
* Alternative to `offsetof` that enables us to avoid use of macro.
|
|
* Approach from:
|
|
* https://gist.github.com/graphitemaster/494f21190bb2c63c5516
|
|
*/
|
|
template <typename T1, typename T2>
|
|
static size_t constexpr getFieldOffset(T1 T2::*field) {
|
|
static_assert(std::is_pod<T1>());
|
|
constexpr T2 dummy{};
|
|
return size_t(&(dummy.*field)) - size_t(&dummy);
|
|
}
|
|
|
|
/**
|
|
* Converts an optional containing a value with units bits/s to byte/s.
|
|
*
|
|
* If input optional is empty, returns empty.
|
|
*/
|
|
static Optional<uint64_t> bytesPerSecondToBitsPerSecond(
|
|
const Optional<uint64_t>& bytesPerSecondOpt) {
|
|
if (bytesPerSecondOpt.hasValue()) {
|
|
return bytesPerSecondOpt.value() * 8;
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
/**
|
|
* Initializes the congestion control fields in passed WrappedTcpInfo.
|
|
*/
|
|
static void initCcInfoFromFd(
|
|
const NetworkSocket& fd,
|
|
TcpInfo& tcpInfo,
|
|
netops::Dispatcher& netopsDispatcher =
|
|
*netops::Dispatcher::getDefaultInstance());
|
|
|
|
/**
|
|
* Initializes the socker buffer memory fields in passed WrappedTcpInfo.
|
|
*/
|
|
static void initMemInfoFromFd(
|
|
const NetworkSocket& fd,
|
|
TcpInfo& tcpInfo,
|
|
IoctlDispatcher& ioctlDispatcher =
|
|
*IoctlDispatcher::getDefaultInstance());
|
|
|
|
#if defined(FOLLY_HAVE_TCP_INFO)
|
|
public:
|
|
using tcp_info = folly::tcpinfo::tcp_info;
|
|
|
|
/**
|
|
* Returns pointer containing requested field from tcp_info struct.
|
|
*
|
|
* The tcp_info struct type is platform specific (and thus templated). If
|
|
* no tcp_info is supprted for this platform, this accessor is unavailable.
|
|
*
|
|
* To access tcp_info fields without needing to consider platform specifics,
|
|
* use accessors, such as bytesSent().
|
|
*/
|
|
template <typename T1>
|
|
const T1* getFieldAsPtr(T1 tcp_info::*field) const {
|
|
return getFieldAsPtr(tcpInfo, tcpInfoBytesRead, field);
|
|
}
|
|
|
|
/**
|
|
* Returns Optional<uint64_t> containing requested field from tcp_info struct.
|
|
*
|
|
* The tcp_info struct type is platform specific (and thus templated). If
|
|
* no tcp_info is supprted for this platform, this accessor is unavailable.
|
|
*
|
|
* To access tcp_info fields without needing to consider platform specifics,
|
|
* use accessors such as bytesSent().
|
|
*/
|
|
template <typename T1>
|
|
folly::Optional<uint64_t> getFieldAsOptUInt64(T1 tcp_info::*field) const {
|
|
if (auto ptr = getFieldAsPtr(field)) {
|
|
return *ptr;
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
private:
|
|
// tcp_info struct for this system, may be backported.
|
|
tcp_info tcpInfo = {};
|
|
|
|
// number of bytes read during getsockopt for TCP_INFO.
|
|
int tcpInfoBytesRead{0};
|
|
#endif
|
|
|
|
#if defined(FOLLY_HAVE_TCP_CC_INFO)
|
|
public:
|
|
using tcp_cc_info = folly::tcpinfo::tcp_cc_info;
|
|
|
|
// TCP_CA_NAME_MAX from <net/tcp.h> (Linux) or <netinet/tcp.h> (FreeBSD)
|
|
static constexpr socklen_t kLinuxTcpCaNameMax = 16;
|
|
|
|
/**
|
|
* Returns Optional<uint64_t> containing requested field from BBR struct.
|
|
*
|
|
* If tcp_cc_info is unavailable or the congestion controller is not BBR,
|
|
* returns folly::none for all fields.
|
|
*
|
|
* To access tcp_cc_info fields without needing to consider platform
|
|
* specifics, use accessors such as bbrBwBitsPerSecond().
|
|
*/
|
|
template <typename T1>
|
|
folly::Optional<uint64_t> getFieldAsOptUInt64(
|
|
T1 tcpinfo::tcp_bbr_info::*field) const {
|
|
if (maybeCcInfo.has_value() && ccNameEnum() == CongestionControlName::BBR) {
|
|
return getFieldAsOptUInt64(maybeCcInfo.value().bbr, field);
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
/**
|
|
* Returns Optional<uint64_t> containing requested field from Vegas struct.
|
|
*
|
|
* If tcp_cc_info is unavailable or the congestion controller is not Vegas,
|
|
* returns folly::none for all fields.
|
|
*/
|
|
template <typename T1>
|
|
folly::Optional<uint64_t> getFieldAsOptUInt64(
|
|
T1 tcpinfo::tcpvegas_info::*field) const {
|
|
if (maybeCcInfo.hasValue() &&
|
|
ccNameEnum() == CongestionControlName::VEGAS) {
|
|
return getFieldAsOptUInt64(maybeCcInfo.value().vegas, field);
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
/**
|
|
* Returns Optional<uint64_t> containing requested field from DCTCP struct.
|
|
*
|
|
* If tcp_cc_info is unavailable or the congestion controller is not DCTCP,
|
|
* returns folly::none for all fields.
|
|
*/
|
|
template <typename T1>
|
|
const folly::Optional<uint64_t> getFieldAsOptUInt64(
|
|
T1 tcpinfo::tcp_dctcp_info::*field) const {
|
|
if (maybeCcInfo.has_value() &&
|
|
(ccNameEnum() == CongestionControlName::DCTCP ||
|
|
ccNameEnum() == CongestionControlName::DCTCP_CUBIC ||
|
|
ccNameEnum() == CongestionControlName::DCTCP_RENO)) {
|
|
return getFieldAsOptUInt64(maybeCcInfo.value().dctcp, field);
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
private:
|
|
/**
|
|
* Returns Optional<uint64_t> containing requested field from tcp_cc_info.
|
|
*
|
|
* The tcp_cc_info struct type is platform specific (and thus templated). If
|
|
* no tcp_cc_info is supprted for this platform, this accessor is unavailable.
|
|
*
|
|
* To access tcp_cc_info fields without needing to consider platform
|
|
* specifics, use accessors such as bbrBwBitsPerSecond().
|
|
*/
|
|
template <typename T1, typename T2>
|
|
folly::Optional<uint64_t> getFieldAsOptUInt64(
|
|
const T2& tgtStruct, T1 T2::*field) const {
|
|
if (field != nullptr && tcpCcInfoBytesRead > 0 &&
|
|
getFieldOffset(field) + sizeof(tgtStruct.*field) <=
|
|
(unsigned long)tcpCcInfoBytesRead) {
|
|
return folly::Optional<uint64_t>(tgtStruct.*field);
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
// raw congestion control algorithm name returned by underlying platform
|
|
folly::Optional<std::string> maybeCcNameRaw;
|
|
|
|
// enum for congestion control algorithm.
|
|
folly::Optional<CongestionControlName> maybeCcEnum;
|
|
|
|
// additional information from congestion control algorithm.
|
|
folly::Optional<tcp_cc_info> maybeCcInfo;
|
|
|
|
// number of bytes read during getsockopt for TCP_CC_INFO.
|
|
//
|
|
// if TCP_CC_INFO was not able to be fetched, will be 0.
|
|
int tcpCcInfoBytesRead{0};
|
|
|
|
#endif // #if defined(FOLLY_HAVE_TCP_CC_INFO)
|
|
|
|
private:
|
|
folly::Optional<size_t> maybeSendBufInUseBytes;
|
|
folly::Optional<size_t> maybeRecvBufInUseBytes;
|
|
};
|
|
|
|
} // namespace tcpinfo
|
|
} // namespace folly
|