libdpf/include/dpf/net/link_log.hpp

162 lines
6.5 KiB
C++
Raw Normal View History

/// @file dpf/net/link_log.hpp
/// @brief Run-log records for listeners and established party links.
/// @details `log_link_up` is called once per socket after the handshake and
/// after the stream array has adopted and tuned it, so the socket
/// options it reads back are the ones the kernel applied (Linux
/// doubles `SO_SNDBUF`/`SO_RCVBUF` and clamps them to `wmem_max` and
/// `rmem_max`). `TCP_INFO` at that point carries the kernel's RTT
/// estimate from the connection setup and the handshake exchange.
/// Encrypted links record the TLS version and cipher, how this side
/// authenticated the peer (`auth=key` or `none`), the peer's key, and
/// whether the peer authenticated this side. With encryption off the
/// record says `auth=none encryption=none`, and the first plaintext
/// link to an address off this host also raises one warning.
#ifndef LIBDPF_INCLUDE_DPF_NET_LINK_LOG_HPP__
#define LIBDPF_INCLUDE_DPF_NET_LINK_LOG_HPP__
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "dpf/log.hpp"
#include "dpf/net/policy.hpp"
#include "dpf/net/security.hpp"
namespace dpf
{
namespace net
{
namespace detail
{
inline std::string sockaddr_text(const sockaddr_storage & ss)
{
char host[INET6_ADDRSTRLEN] = {};
if (ss.ss_family == AF_INET)
{
const auto & a = reinterpret_cast<const sockaddr_in &>(ss);
if (::inet_ntop(AF_INET, &a.sin_addr, host, sizeof(host)) == nullptr)
return "unknown";
return std::string(host) + ":" + std::to_string(ntohs(a.sin_port));
}
if (ss.ss_family == AF_INET6)
{
const auto & a = reinterpret_cast<const sockaddr_in6 &>(ss);
if (::inet_ntop(AF_INET6, &a.sin6_addr, host, sizeof(host)) == nullptr)
return "unknown";
return "[" + std::string(host) + "]:" + std::to_string(ntohs(a.sin6_port));
}
if (ss.ss_family == AF_UNIX)
{
const auto & a = reinterpret_cast<const sockaddr_un &>(ss);
return std::string("unix:") + (a.sun_path[0] != '\0' ? a.sun_path : "(unnamed)");
}
return "unknown";
}
inline bool loopback(const sockaddr_storage & ss)
{
if (ss.ss_family == AF_INET)
return (ntohl(reinterpret_cast<const sockaddr_in &>(ss).sin_addr.s_addr) >> 24)
== 127u;
if (ss.ss_family == AF_INET6)
{
const auto & a = reinterpret_cast<const sockaddr_in6 &>(ss).sin6_addr;
if (IN6_IS_ADDR_LOOPBACK(&a))
return true;
return IN6_IS_ADDR_V4MAPPED(&a) && a.s6_addr[12] == 127;
}
return ss.ss_family == AF_UNIX;
}
inline int int_opt(int fd, int level, int name)
{
int v = -1;
socklen_t len = sizeof(v);
if (::getsockopt(fd, level, name, &v, &len) != 0)
return -1;
return v;
}
} // namespace detail
/// @brief Record a listener: this process accepts any address on `port`.
/// @param encrypted whether connections on it must complete TLS 1.3 first
inline void log_listen(unsigned short port, bool sctp, bool encrypted = false)
{
DPF_LOG(info, "listen").kv("addr", "0.0.0.0").kv("port", port)
.kv("tcp", true).kv("sctp", sctp)
.kv("encryption", encrypted ? "tls1.3" : "none");
}
/// @brief Record one established socket of a party link.
/// @param how `accept` or `connect`
/// @param peer the other end's role (`p1`, `dealer`, ...)
/// @param lane which socket of a `parallel` link (0 otherwise)
/// @param sec how the link was secured (null or unencrypted: plaintext)
inline void log_link_up(const char * how, const std::string & peer, transport kind,
std::size_t lanes, std::uint32_t lane, std::uint32_t epoch, int fd,
const socket_options & requested, const link_security * sec = nullptr)
{
const bool encrypted = sec != nullptr && sec->encrypted;
if (!log::enabled(log::level::info) || fd < 0)
return;
sockaddr_storage local{};
sockaddr_storage remote{};
socklen_t local_len = sizeof(local);
socklen_t remote_len = sizeof(remote);
const bool have_local =
::getsockname(fd, reinterpret_cast<sockaddr *>(&local), &local_len) == 0;
const bool have_remote =
::getpeername(fd, reinterpret_cast<sockaddr *>(&remote), &remote_len) == 0;
{
log::record rec(log::level::info, "link.up");
rec.kv("how", how).kv("peer", peer).kv("transport", transport_name(kind))
.kv("lanes", lanes).kv("lane", lane).kv("epoch", epoch)
.kv("local", have_local ? detail::sockaddr_text(local) : std::string("unknown"))
.kv("remote", have_remote ? detail::sockaddr_text(remote) : std::string("unknown"))
.kv("auth", encrypted ? sec->peer_auth : std::string("none"))
.kv("encryption",
encrypted ? sec->protocol + "/" + sec->cipher : std::string("none"));
if (encrypted)
rec.kv("peer_key", sec->peer_key ? sec->peer_key->base64() : std::string("none"))
.kv("peer_verified_us", sec->peer_verified_us);
if (kind != transport::sctp)
{
rec.kv("nodelay", detail::int_opt(fd, IPPROTO_TCP, TCP_NODELAY))
.kv("quickack_req", requested.quickack)
.kv("keepalive", detail::int_opt(fd, SOL_SOCKET, SO_KEEPALIVE))
.kv("sndbuf_req", requested.send_buffer)
.kv("sndbuf", detail::int_opt(fd, SOL_SOCKET, SO_SNDBUF))
.kv("rcvbuf_req", requested.recv_buffer)
.kv("rcvbuf", detail::int_opt(fd, SOL_SOCKET, SO_RCVBUF));
#if defined(TCP_INFO)
tcp_info ti{};
socklen_t ti_len = sizeof(ti);
if (::getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &ti_len) == 0)
rec.kv("rtt_us", ti.tcpi_rtt).kv("rttvar_us", ti.tcpi_rttvar)
.kv("pmtu", ti.tcpi_pmtu).kv("snd_mss", ti.tcpi_snd_mss)
.kv("snd_cwnd", ti.tcpi_snd_cwnd)
.kv("retrans", ti.tcpi_total_retrans);
#endif
}
}
if (!encrypted && have_remote && !detail::loopback(remote)
&& log::first_time("net.plaintext_remote"))
DPF_LOG(warning, "link.plaintext").kv("remote", detail::sockaddr_text(remote))
.kv("detail", "encryption is off: this party link is unauthenticated "
"and unencrypted, and the handshake's party id is not verified");
}
} // namespace net
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_NET_LINK_LOG_HPP__