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