userver: userver/utils/ip.hpp Source File
Loading...
Searching...
No Matches
ip.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/ip.hpp
4/// @brief IPv4 and IPv6 addresses and networks
5
6#include <array>
7#include <cstddef>
8#include <exception>
9#include <string>
10#include <system_error>
11#include <vector>
12
13#include <sys/socket.h>
14
15#include <fmt/format.h>
16
17USERVER_NAMESPACE_BEGIN
18
19// IP address and utilities
20namespace utils::ip {
21
22/// @ingroup userver_containers
23///
24/// @brief Base class for IPv4/IPv6 addresses
25template <std::size_t N>
26class AddressBase final {
27 static_assert(N == 4 || N == 16, "Address can only be 4 or 16 bytes size");
28
29public:
30 static constexpr std::size_t AddressSize = N;
31 using BytesType = std::array<unsigned char, N>;
32
33 AddressBase() noexcept : address_({0}) {}
34 explicit AddressBase(const BytesType& address) : address_(address) {}
35
36 /// @brief Get the address in bytes, in network byte order.
37 const BytesType& GetBytes() const noexcept { return address_; }
38
39 friend bool operator==(const AddressBase<N>& a1, const AddressBase<N>& a2) noexcept {
40 return a1.address_ == a2.address_;
41 }
42
43 friend bool operator!=(const AddressBase<N>& a1, const AddressBase<N>& a2) noexcept {
44 return a1.address_ != a2.address_;
45 }
46
47private:
48 BytesType address_;
49};
50
51/// @ingroup userver_containers
52///
53/// @brief IPv4 address in network bytes order
54using AddressV4 = AddressBase<4>;
55
56/// @ingroup userver_containers
57///
58/// @brief IPv6 address in network bytes order
59using AddressV6 = AddressBase<16>;
60
61template <typename T>
62inline constexpr bool kIsAddressType = std::is_same_v<T, AddressV4> || std::is_same_v<T, AddressV6>;
63
64/// @brief Create an IPv4 address from an IP address string in dotted decimal
65/// form.
66/// @throw AddressSystemError
67AddressV4 AddressV4FromString(const std::string& str);
68
69/// @brief Create an IPv6 address from an IP address string in dotted decimal
70/// form.
71AddressV6 AddressV6FromString(const std::string& str);
72
73/// @brief Get the address as a string in dotted decimal format.
74std::string AddressV4ToString(const AddressV4& address);
75
76/// @brief Get the address as a string in dotted decimal format.
77std::string AddressV6ToString(const AddressV6& address);
78
79/// @ingroup userver_containers
80///
81/// @brief Base class for IPv4/IPv6 network
82template <typename Address, typename = std::enable_if_t<kIsAddressType<Address>>>
83class NetworkBase final {
84public:
85 using AddressType = Address;
86 static constexpr unsigned char kMaximumPrefixLength = std::is_same_v<Address, AddressV4> ? 32 : 128;
87
88 NetworkBase() noexcept = default;
89
90 NetworkBase(const AddressType& address, unsigned short prefix_length)
91 : address_(address), prefix_length_(prefix_length) {
92 if (prefix_length > kMaximumPrefixLength) {
93 throw std::out_of_range(fmt::format(
94 "{} prefix length is too large", std::is_same_v<Address, AddressV4> ? "NetworkV4" : "NetworkV6"
95 ));
96 }
97 }
98
99 /// @brief Get the address address of network
100 AddressType GetAddress() const noexcept { return address_; }
101
102 /// @brief Get prefix length of address network
103 unsigned char GetPrefixLength() const noexcept { return prefix_length_; }
104
105 /// @brief Returns true if the address is in network
106 bool ContainsAddress(const AddressType& address) const {
107 const auto network_bytes = address_.GetBytes();
108 const auto address_bytes = address.GetBytes();
109
110 std::uint8_t diff = 0;
111 for (std::size_t byte_index = 0; byte_index < kMaximumPrefixLength / 8; ++byte_index) {
112 std::uint8_t mask_byte = 0;
113 if (byte_index == prefix_length_ / 8) {
114 mask_byte = ~((1 << (8 - prefix_length_ % 8)) - 1);
115 }
116 if (byte_index < prefix_length_ / 8) {
117 mask_byte = 255;
118 }
119
120 diff |= (network_bytes[byte_index] ^ address_bytes[byte_index]) & mask_byte;
121 }
122 return !diff;
123 }
124
125 friend bool operator==(const NetworkBase<Address>& a, const NetworkBase<Address>& b) noexcept {
126 return a.address_ == b.address_ && a.prefix_length_ == b.prefix_length_;
127 }
128
129 friend bool operator!=(const NetworkBase<Address>& a, const NetworkBase<Address>& b) noexcept { return !(a == b); }
130
131private:
132 AddressType address_;
133 unsigned char prefix_length_ = 0;
134};
135
136/// @ingroup userver_containers
137///
138/// @brief IPv4 network.
139using NetworkV4 = NetworkBase<AddressV4>;
140
141/// @ingroup userver_containers
142///
143/// @brief IPv6 network.
144using NetworkV6 = NetworkBase<AddressV6>;
145
146///@brief Create an IPv4 network from a string containing IP address and prefix
147/// length.
148/// @throw std::invalid_argument, AddressSystemError
150
151/// @brief Create an IPv6 network from a string containing IP address and prefix
152/// length.
154
155///@brief Get the network as an address in dotted decimal format.
156std::string NetworkV4ToString(const NetworkV4& network);
157
158/// @brief Get the network as an address in dotted decimal format.
159std::string NetworkV6ToString(const NetworkV6& network);
160
161/// @brief Convert NetworkV4 to CIDR format
163
164/// @brief Convert NetworkV4 to CIDR format
166
167/// @ingroup userver_containers
168///
169/// @brief INET IPv4/IPv4 network
170/// @warning InetNetwork class is deprecated. You should use InetNetwork class
171/// via transformation function to/from NetworkV4/NetworkV6.
172/// Use this class only if you need to work with INET PostgreSQL format.
173class InetNetwork final {
174public:
175 enum class AddressFamily : unsigned char { IPv4 = AF_INET, IPv6 = AF_INET6 };
176
177 // Default constructor: IPv4 address
178 InetNetwork();
179 InetNetwork(std::vector<unsigned char>&& bytes, unsigned char prefix_length, AddressFamily address_family);
180
181 /// @brief Get the address in bytes
182 const std::vector<unsigned char>& GetBytes() const noexcept { return bytes_; }
183
184 /// @brief Get the prefix length of network
185 unsigned char GetPrefixLength() const noexcept { return prefix_length_; }
186
187 /// @brief Get the address family
188 AddressFamily GetAddressFamily() const noexcept { return address_family_; }
189
190 friend bool operator==(const InetNetwork& lhs, const InetNetwork& rhs) {
191 return lhs.address_family_ == rhs.address_family_ && lhs.prefix_length_ == rhs.prefix_length_ &&
192 lhs.bytes_ == rhs.bytes_;
193 }
194
195 friend bool operator!=(const InetNetwork& lhs, const InetNetwork& rhs) { return !operator==(lhs, rhs); }
196
197private:
198 std::vector<unsigned char> bytes_;
199 unsigned char prefix_length_;
200 AddressFamily address_family_;
201};
202
203/// @brief Convert InetNetwork to NetworkV4
205
206/// @brief Convert InetNetwork to NetworkV6
208
209/// @brief Convert NetworkV4 to InetNetwork
210InetNetwork NetworkV4ToInetNetwork(const NetworkV4& network);
211
212/// @brief Convert NetworkV6 to InetNetwork
213InetNetwork NetworkV6ToInetNetwork(const NetworkV6& network);
214
215/// @brief Invalid network or address
216class AddressSystemError final : public std::exception {
217public:
218 AddressSystemError(std::error_code code, std::string_view msg) : msg_(msg), code_(code) {}
219
220 /// Operating system error code.
221 const std::error_code& Code() const { return code_; }
222
223 const char* what() const noexcept final { return msg_.c_str(); }
224
225private:
226 std::string msg_;
227 std::error_code code_;
228};
229
230} // namespace utils::ip
231
232USERVER_NAMESPACE_END