userver: userver/engine/io/sockaddr.hpp Source File
Loading...
Searching...
No Matches
sockaddr.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/engine/io/sockaddr.hpp
4/// @brief @copybrief engine::io::Sockaddr
5
6#include <netinet/in.h>
7#include <sys/socket.h>
8#include <sys/types.h>
9#include <sys/un.h>
10
11#include <cstring>
12#include <string>
13
14#include <fmt/format.h>
15#include <userver/utils/fmt_compat.hpp>
16
17#include <userver/logging/log_helper_fwd.hpp>
18
19USERVER_NAMESPACE_BEGIN
20
21namespace engine::io {
22
23/// Socket address-related exceptions
24class AddrException : public std::runtime_error {
25public:
26 using std::runtime_error::runtime_error;
27};
28
29/// Communication domain
30enum class AddrDomain {
31 kUnspecified = AF_UNSPEC, ///< Unspecified
32 kInet = AF_INET, ///< IPv4
33 kInet6 = AF_INET6, ///< IPv6
34 kUnix = AF_UNIX, ///< Unix socket
35};
36
37static_assert(
38 AF_UNSPEC == 0, // NOLINT(misc-redundant-expression)
39 "Your socket subsystem looks broken, please contact support chat."
40);
41
42/// Native socket address wrapper
43class Sockaddr final {
44public:
45 /// Constructs an unspecified native socket address.
46 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
47 Sockaddr() noexcept { ::memset(&data_, 0, sizeof(data_)); }
48
49 /// @brief Wraps a native socket address structure.
50 /// @warning sa_family must contain a correct address family.
51 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
52 explicit Sockaddr(const void* data) {
53 const auto* sockaddr = reinterpret_cast<const struct sockaddr*>(data);
54 const auto domain = static_cast<AddrDomain>(sockaddr->sa_family);
55 ::memcpy(&data_, data, Sockaddr::Addrlen(domain));
56 }
57
58 /// @brief Creates address of a Unix socket located at the specified path.
59 static Sockaddr MakeUnixSocketAddress(std::string_view path);
60
61 /// @brief Creates the IPv6 loopback address `[::1]:0` that also handles IPv4
62 /// connections.
63 ///
64 /// A program needs to support only this API type to support IPv4 and IPv6.
65 static Sockaddr MakeLoopbackAddress() noexcept;
66
67 /// @brief Creates the IPv4 only loopback address `127.0.0.1:0`.
68 ///
69 /// Prefer a more generic MakeLoopbackAddress() function if not sure.
70 static Sockaddr MakeIPv4LoopbackAddress() noexcept;
71
72 /// @brief Domain-specific native socket address structure pointer.
73 /// @warning No type checking is performed, user must ensure that only the
74 /// correct domain is accessed.
75 template <typename T>
76 T* As() {
77 static_assert(sizeof(T) <= sizeof(data_), "Invalid socket address type");
78 return reinterpret_cast<T*>(&data_);
79 }
80
81 /// @brief Domain-specific native socket address structure pointer.
82 /// @warning No type checking is performed, user must ensure that only the
83 /// correct domain is accessed.
84 template <typename T>
85 const T* As() const {
86 static_assert(sizeof(T) <= sizeof(data_), "Invalid socket address type");
87 return reinterpret_cast<const T*>(&data_);
88 }
89
90 /// Native socket address structure pointer.
91 struct sockaddr* Data() { return As<struct sockaddr>(); }
92
93 /// Native socket address structure pointer.
94 const struct sockaddr* Data() const { return As<struct sockaddr>(); }
95
96 /// Maximum supported native socket address structure size.
97 socklen_t Size() const { return Addrlen(Domain()); }
98
99 /// Maximum supported native socket address structure size.
100 socklen_t Capacity() const { return sizeof(data_); }
101
102 /// Protocol family.
103 sa_family_t Family() const { return Data()->sa_family; }
104
105 /// Communication domain.
106 AddrDomain Domain() const { return static_cast<AddrDomain>(Family()); }
107
108 /// Whether the stored socket address family expects a port.
109 bool HasPort() const;
110
111 /// Returns the stored port number if available, otherwise throws.
112 std::uint16_t Port() const;
113
114 /// Sets a port for address families that allow for one, otherwise throws.
115 void SetPort(std::uint16_t port);
116
117 /// @brief Human-readable address representation.
118 /// @note Does not include port number.
119 std::string PrimaryAddressString() const;
120
121 /// Domain-specific native socket address structure size.
122 static constexpr socklen_t Addrlen(AddrDomain domain) {
123 const auto res = AddrlenImpl(domain);
124
125 if (res == 0) {
126 throw AddrException(fmt::format("Unexpected address family {}", static_cast<int>(domain)));
127 }
128
129 return res;
130 }
131
132private:
133 static constexpr socklen_t AddrlenImpl(AddrDomain domain) noexcept {
134 switch (domain) {
136 return sizeof(struct sockaddr);
138 return sizeof(struct sockaddr_in);
140 return sizeof(struct sockaddr_in6);
142 return sizeof(struct sockaddr_un);
143 }
144
145 return 0;
146 }
147
148 union Storage {
149 struct sockaddr sa_any;
150 struct sockaddr_in sa_inet;
151 struct sockaddr_in6 sa_inet6;
152 struct sockaddr_un sa_unix;
153 } data_;
154};
155
156/// Outputs human-readable address representation, including port number.
157logging::LogHelper& operator<<(logging::LogHelper&, const Sockaddr&);
158
159} // namespace engine::io
160
161USERVER_NAMESPACE_END
162
163/// Socket address fmt formatter.
164template <>
165struct fmt::formatter<USERVER_NAMESPACE::engine::io::Sockaddr> {
166 static constexpr auto parse(format_parse_context&);
167
168 template <typename FormatContext>
169 auto format(const USERVER_NAMESPACE::engine::io::Sockaddr& sa, FormatContext& ctx) USERVER_FMT_CONST;
170};
171
172inline constexpr auto fmt::formatter<USERVER_NAMESPACE::engine::io::Sockaddr>::parse(format_parse_context& ctx) {
173 const auto* it = ctx.begin();
174 if (it != ctx.end() && *it != '}') {
175 throw format_error("invalid Sockaddr format");
176 }
177 return it;
178}
179
180template <typename FormatContext>
181inline auto fmt::formatter<USERVER_NAMESPACE::engine::io::Sockaddr>::format(
182 const USERVER_NAMESPACE::engine::io::Sockaddr& sa,
183 FormatContext& ctx
185 switch (sa.Domain()) {
186 case USERVER_NAMESPACE::engine::io::AddrDomain::kInet:
187 return fmt::format_to(ctx.out(), "{}:{}", sa.PrimaryAddressString(), sa.Port());
188
189 case USERVER_NAMESPACE::engine::io::AddrDomain::kInet6:
190 return fmt::format_to(ctx.out(), "[{}]:{}", sa.PrimaryAddressString(), sa.Port());
191
192 default:
193 return fmt::format_to(ctx.out(), "{}", sa.PrimaryAddressString());
194 }
195}