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