162 lines
6.5 KiB
C++
162 lines
6.5 KiB
C++
|
|
/// @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__
|