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(AF_UNSPEC == 0, "Your socket subsystem looks broken, please contact support chat.");
38
39/// Native socket address wrapper
40class Sockaddr final {
41public:
42 /// Constructs an unspecified native socket address.
43 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
44 Sockaddr() noexcept { ::memset(&data_, 0, sizeof(data_)); }
45
46 /// @brief Wraps a native socket address structure.
47 /// @warning sa_family must contain a correct address family.
48 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
49 explicit Sockaddr(const void* data) {
50 const auto* sockaddr = reinterpret_cast<const struct sockaddr*>(data);
51 const auto domain = static_cast<AddrDomain>(sockaddr->sa_family);
52 ::memcpy(&data_, data, Sockaddr::Addrlen(domain));
53 }
54
55 /// @brief Creates address of a Unix socket located at the specified path.
56 static Sockaddr MakeUnixSocketAddress(std::string_view path);
57
58 /// @brief Creates the IPv6 loopback address `[::1]:0` that also handles IPv4
59 /// connections.
60 ///
61 /// A program needs to support only this API type to support IPv4 and IPv6.
62 static Sockaddr MakeLoopbackAddress() noexcept;
63
64 /// @brief Creates the IPv4 only loopback address `127.0.0.1:0`.
65 ///
66 /// Prefer a more generic MakeLoopbackAddress() function if not sure.
67 static Sockaddr MakeIPv4LoopbackAddress() noexcept;
68
69 /// @brief Domain-specific native socket address structure pointer.
70 /// @warning No type checking is performed, user must ensure that only the
71 /// correct domain is accessed.
72 template <typename T>
73 T* As() {
74 static_assert(sizeof(T) <= sizeof(data_), "Invalid socket address type");
75 return reinterpret_cast<T*>(&data_);
76 }
77
78 /// @brief Domain-specific native socket address structure pointer.
79 /// @warning No type checking is performed, user must ensure that only the
80 /// correct domain is accessed.
81 template <typename T>
82 const T* As() const {
83 static_assert(sizeof(T) <= sizeof(data_), "Invalid socket address type");
84 return reinterpret_cast<const T*>(&data_);
85 }
86
87 /// Native socket address structure pointer.
88 struct sockaddr* Data() { return As<struct sockaddr>(); }
89
90 /// Native socket address structure pointer.
91 const struct sockaddr* Data() const { return As<struct sockaddr>(); }
92
93 /// Maximum supported native socket address structure size.
94 socklen_t Size() const { return Addrlen(Domain()); }
95
96 /// Maximum supported native socket address structure size.
97 socklen_t Capacity() const { return sizeof(data_); }
98
99 /// Protocol family.
100 sa_family_t Family() const { return Data()->sa_family; }
101
102 /// Communication domain.
103 AddrDomain Domain() const { return static_cast<AddrDomain>(Family()); }
104
105 /// Whether the stored socket address family expects a port.
106 bool HasPort() const;
107
108 /// Returns the stored port number if available, otherwise throws.
109 std::uint16_t Port() const;
110
111 /// Sets a port for address families that allow for one, otherwise throws.
112 void SetPort(std::uint16_t port);
113
114 /// @brief Human-readable address representation.
115 /// @note Does not include port number.
116 std::string PrimaryAddressString() const;
117
118 /// Domain-specific native socket address structure size.
119 static constexpr socklen_t Addrlen(AddrDomain domain) {
120 const auto res = AddrlenImpl(domain);
121
122 if (res == 0) {
123 throw AddrException(fmt::format("Unexpected address family {}", static_cast<int>(domain)));
124 }
125
126 return res;
127 }
128
129private:
130 static constexpr socklen_t AddrlenImpl(AddrDomain domain) noexcept {
131 switch (domain) {
133 return sizeof(struct sockaddr);
135 return sizeof(struct sockaddr_in);
137 return sizeof(struct sockaddr_in6);
139 return sizeof(struct sockaddr_un);
140 }
141
142 return 0;
143 }
144
145 union Storage {
146 struct sockaddr sa_any;
147 struct sockaddr_in sa_inet;
148 struct sockaddr_in6 sa_inet6;
149 struct sockaddr_un sa_unix;
150 } data_;
151};
152
153/// Outputs human-readable address representation, including port number.
154logging::LogHelper& operator<<(logging::LogHelper&, const Sockaddr&);
155
156} // namespace engine::io
157
158USERVER_NAMESPACE_END
159
160/// Socket address fmt formatter.
161template <>
162struct fmt::formatter<USERVER_NAMESPACE::engine::io::Sockaddr> {
163 static constexpr auto parse(format_parse_context&);
164
165 template <typename FormatContext>
166 auto format(const USERVER_NAMESPACE::engine::io::Sockaddr& sa, FormatContext& ctx) USERVER_FMT_CONST;
167};
168
169inline constexpr auto fmt::formatter<USERVER_NAMESPACE::engine::io::Sockaddr>::parse(format_parse_context& ctx) {
170 const auto* it = ctx.begin();
171 if (it != ctx.end() && *it != '}') {
172 throw format_error("invalid Sockaddr format");
173 }
174 return it;
175}
176
177template <typename FormatContext>
178inline auto fmt::formatter<USERVER_NAMESPACE::engine::io::Sockaddr>::format(
179 const USERVER_NAMESPACE::engine::io::Sockaddr& sa,
180 FormatContext& ctx
182 switch (sa.Domain()) {
183 case USERVER_NAMESPACE::engine::io::AddrDomain::kInet:
184 return fmt::format_to(ctx.out(), "{}:{}", sa.PrimaryAddressString(), sa.Port());
185
186 case USERVER_NAMESPACE::engine::io::AddrDomain::kInet6:
187 return fmt::format_to(ctx.out(), "[{}]:{}", sa.PrimaryAddressString(), sa.Port());
188
189 default:
190 return fmt::format_to(ctx.out(), "{}", sa.PrimaryAddressString());
191 }
192}