E-MailRelay
gaddress4.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gaddress4.cpp
19///
20
21#include "gdef.h"
22#include "gaddress4.h"
23#include "gstr.h"
24#include "gstringfield.h"
25#include "gtest.h"
26#include "gassert.h"
27#include "glog.h"
28#include <algorithm> // std::swap()
29#include <utility> // std::swap()
30#include <climits>
31#include <sys/types.h>
32#include <sstream>
33#include <array>
34
35namespace GNet
36{
37 namespace Address4Imp
38 {
39 constexpr const char * port_separators = ":" ;
40 constexpr char port_separator = ':' ;
41 }
42}
43
44unsigned short GNet::Address4::af() noexcept
45{
46 return AF_INET ;
47}
48
49int GNet::Address4::domain() noexcept
50{
51 return PF_INET ;
52}
53
54GNet::Address4::Address4( std::nullptr_t ) :
55 m_inet{}
56{
57 m_inet.sin_family = af() ;
58 m_inet.sin_port = 0 ;
59}
60
61GNet::Address4::Address4( unsigned int port ) :
62 Address4(nullptr)
63{
64 m_inet.sin_addr.s_addr = htonl(INADDR_ANY);
65 const char * reason = setPort( m_inet , port ) ;
66 if( reason ) throw Address::Error(reason) ;
67}
68
69GNet::Address4::Address4( unsigned int port , int /*loopback_overload*/ ) :
70 Address4(nullptr)
71{
72 m_inet.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
73 const char * reason = setPort( m_inet , port ) ;
74 if( reason ) throw Address::Error(reason) ;
75}
76
77GNet::Address4::Address4( const sockaddr * addr , socklen_t len ) :
78 Address4(nullptr)
79{
80 if( addr == nullptr )
81 throw Address::Error() ;
82 if( addr->sa_family != af() || static_cast<std::size_t>(len) < sizeof(sockaddr_type) )
83 throw Address::BadFamily() ;
84
85 m_inet = *(reinterpret_cast<const sockaddr_type*>(addr)) ;
86}
87
88GNet::Address4::Address4( const std::string & host_part , const std::string & port_part ) :
89 Address4(nullptr)
90{
91 const char * reason = setHostAddress( m_inet , host_part ) ;
92 if( !reason )
93 reason = setPort( m_inet , port_part ) ;
94 if( reason )
95 throw Address::BadString( std::string(reason).append(": [").append(host_part).append("][").append(port_part).append(1U,']') ) ;
96}
97
98GNet::Address4::Address4( const std::string & display_string ) :
99 Address4(nullptr)
100{
101 const char * reason = setAddress( m_inet , display_string ) ;
102 if( reason )
103 throw Address::BadString( std::string(reason).append(": ").append(display_string) ) ;
104}
105
106const char * GNet::Address4::setAddress( sockaddr_type & inet , G::string_view display_string )
107{
108 const std::string::size_type pos = display_string.find_last_of( Address4Imp::port_separators ) ;
109 if( pos == std::string::npos )
110 return "no port separator" ;
111
112 G::string_view host_part = G::Str::headView( display_string , pos ) ;
113 G::string_view port_part = G::Str::tailView( display_string , pos ) ;
114
115 const char * reason = setHostAddress( inet , host_part ) ;
116 if( !reason )
117 reason = setPort( inet , port_part ) ;
118 return reason ;
119}
120
121const char * GNet::Address4::setHostAddress( sockaddr_type & inet , G::string_view host_part )
122{
123 // start with a stricter check than inet_pton(), inet_addr() etc. since they allow eg. "123.123"
124 if( !Address4::format(host_part) )
125 return "invalid network address" ;
126
127 int rc = inet_pton( af() , G::sv_to_string(host_part).c_str() , &inet.sin_addr ) ;
128 return rc == 1 ? nullptr : "invalid network address" ;
129}
130
131void GNet::Address4::setPort( unsigned int port )
132{
133 const char * reason = setPort( m_inet , port ) ;
134 if( reason )
135 throw Address::Error( "invalid port number" ) ;
136}
137
138const char * GNet::Address4::setPort( sockaddr_type & inet , G::string_view port_part )
139{
140 if( port_part.empty() ) return "empty port string" ;
141 if( !G::Str::isNumeric(port_part) || !G::Str::isUInt(port_part) ) return "non-numeric port string" ;
142 return setPort( inet , G::Str::toUInt(port_part) ) ;
143}
144
145const char * GNet::Address4::setPort( sockaddr_type & inet , unsigned int port )
146{
147 if( port > 0xFFFFU ) return "port number too big" ;
148 const g_port_t in_port = static_cast<g_port_t>(port) ;
149 inet.sin_port = htons( in_port ) ;
150 return nullptr ;
151}
152
153bool GNet::Address4::setZone( const std::string & /*ipv6_zone_name_or_scope_id*/ )
154{
155 return true ;
156}
157
158void GNet::Address4::setScopeId( unsigned long /*ipv6_scope_id*/ )
159{
160}
161
162std::string GNet::Address4::displayString( bool /*ipv6_with_scope*/ ) const
163{
164 std::ostringstream ss ;
165 ss << hostPartString() ;
166 ss << Address4Imp::port_separator << port() ;
167 return ss.str() ;
168}
169
170std::string GNet::Address4::hostPartString() const
171{
172 std::array<char,INET_ADDRSTRLEN+1U> buffer {} ;
173 const void * vp = & m_inet.sin_addr ;
174 const char * p = inet_ntop( af() , const_cast<void*>(vp) , &buffer[0] , buffer.size() ) ;
175 if( p == nullptr )
176 throw Address::Error( "inet_ntop() failure" ) ;
177 buffer[buffer.size()-1U] = '\0' ;
178 return { &buffer[0] } ; // sic
179}
180
181std::string GNet::Address4::queryString() const
182{
183 G::StringArray parts = G::Str::splitIntoFields( hostPartString() , '.' ) ;
184 std::reverse( parts.begin() , parts.end() ) ;
185 return G::Str::join( "." , parts ) ;
186}
187
188bool GNet::Address4::validData( const sockaddr * addr , socklen_t len )
189{
190 return addr != nullptr && addr->sa_family == af() && len == sizeof(sockaddr_type) ;
191}
192
193bool GNet::Address4::validString( const std::string & s , std::string * reason_p )
194{
195 sockaddr_type inet {} ;
196 const char * reason = setAddress( inet , s ) ;
197 if( reason && reason_p )
198 *reason_p = std::string(reason) ;
199 return reason == nullptr ;
200}
201
202bool GNet::Address4::validStrings( const std::string & host_part , const std::string & port_part ,
203 std::string * reason_p )
204{
205 sockaddr_type inet {} ;
206 const char * reason = setHostAddress( inet , host_part ) ;
207 if( !reason )
208 reason = setPort( inet , port_part ) ;
209 if( reason && reason_p )
210 *reason_p = std::string(reason) ;
211 return reason == nullptr ;
212}
213
214bool GNet::Address4::validPort( unsigned int port )
215{
216 sockaddr_type inet {} ;
217 const char * reason = setPort( inet , port ) ;
218 return reason == nullptr ;
219}
220
221bool GNet::Address4::same( const Address4 & other , bool /*ipv6_compare_with_scope*/ ) const
222{
223 return
224 m_inet.sin_family == af() &&
225 other.m_inet.sin_family == af() &&
226 sameAddr( m_inet.sin_addr , other.m_inet.sin_addr ) &&
227 m_inet.sin_port == other.m_inet.sin_port ;
228}
229
230bool GNet::Address4::sameHostPart( const Address4 & other ) const
231{
232 return
233 m_inet.sin_family == af() &&
234 other.m_inet.sin_family == af() &&
235 sameAddr( m_inet.sin_addr , other.m_inet.sin_addr ) ;
236}
237
238bool GNet::Address4::sameAddr( const ::in_addr & a , const ::in_addr & b )
239{
240 return a.s_addr == b.s_addr ;
241}
242
243unsigned int GNet::Address4::port() const
244{
245 return ntohs( m_inet.sin_port ) ;
246}
247
248unsigned long GNet::Address4::scopeId( unsigned long default_ ) const
249{
250 return default_ ;
251}
252
253#ifndef G_LIB_SMALL
254const sockaddr * GNet::Address4::address() const
255{
256 return reinterpret_cast<const sockaddr*>(&m_inet) ;
257}
258#endif
259
260sockaddr * GNet::Address4::address()
261{
262 return reinterpret_cast<sockaddr*>(&m_inet) ;
263}
264
265socklen_t GNet::Address4::length() noexcept
266{
267 return sizeof(sockaddr_type) ;
268}
269
270G::StringArray GNet::Address4::wildcards() const
271{
272 std::string ip_str = hostPartString() ;
273
274 G::StringArray result ;
275 result.reserve( 38U ) ;
276 result.push_back( ip_str ) ;
277
278 G::string_view ip_sv( ip_str.data() , ip_str.size() ) ;
279 G::StringFieldT<G::string_view> part( ip_sv , "." , 1U ) ;
280 G::string_view part0 = part() ;
281 G::string_view part1 = (++part)() ;
282 G::string_view part2 = (++part)() ;
283 G::string_view part3 = (++part)() ;
284
285 G_ASSERT( part.valid() ) ;
286 if( !part.valid() )
287 return result ;
288
289 G_ASSERT( !(++part).valid() ) ;
290
291 if( part0.empty() || !G::Str::isUInt(part0) ||
292 part1.empty() || !G::Str::isUInt(part1) ||
293 part2.empty() || !G::Str::isUInt(part2) ||
294 part3.empty() || !G::Str::isUInt(part3) )
295 {
296 return result ;
297 }
298
299 unsigned int n0 = G::Str::toUInt(part0) ;
300 unsigned int n1 = G::Str::toUInt(part1) ;
301 unsigned int n2 = G::Str::toUInt(part2) ;
302 unsigned int n3 = G::Str::toUInt(part3) ;
303
304 std::string part_0_1_2 = std::string(part0.data(),part0.size()).append(1U,'.')
305 .append(part1.data(),part1.size()).append(1U,'.')
306 .append(part2.data(),part2.size()).append(1U,'.') ;
307 std::string part_0_1 = std::string(part0.data(),part0.size()).append(1U,'.')
308 .append(part1.data(),part1.size()).append(1U,'.') ;
309 std::string part_0 = std::string(part0.data(),part0.size()).append(1U,'.') ;
310
311 add( result , part_0_1_2 , n3 & 0xffU , "/32" ) ;
312 add( result , part_0_1_2 , n3 & 0xfeU , "/31" ) ;
313 add( result , part_0_1_2 , n3 & 0xfcU , "/30" ) ;
314 add( result , part_0_1_2 , n3 & 0xf8U , "/29" ) ;
315 add( result , part_0_1_2 , n3 & 0xf0U , "/28" ) ;
316 add( result , part_0_1_2 , n3 & 0xe0U , "/27" ) ;
317 add( result , part_0_1_2 , n3 & 0xc0U , "/26" ) ;
318 add( result , part_0_1_2 , n3 & 0x80U , "/25" ) ;
319 add( result , part_0_1_2 , 0 , "/24" ) ;
320 add( result , part_0_1_2 , "*" ) ;
321 add( result , part_0_1 , n2 & 0xfeU , ".0/23" ) ;
322 add( result , part_0_1 , n2 & 0xfcU , ".0/22" ) ;
323 add( result , part_0_1 , n2 & 0xfcU , ".0/21" ) ;
324 add( result , part_0_1 , n2 & 0xf8U , ".0/20" ) ;
325 add( result , part_0_1 , n2 & 0xf0U , ".0/19" ) ;
326 add( result , part_0_1 , n2 & 0xe0U , ".0/18" ) ;
327 add( result , part_0_1 , n2 & 0xc0U , ".0/17" ) ;
328 add( result , part_0_1 , 0 , ".0/16" ) ;
329 add( result , part_0_1 , "*.*" ) ;
330 add( result , part_0 , n1 & 0xfeU , ".0.0/15" ) ;
331 add( result , part_0 , n1 & 0xfcU , ".0.0/14" ) ;
332 add( result , part_0 , n1 & 0xf8U , ".0.0/13" ) ;
333 add( result , part_0 , n1 & 0xf0U , ".0.0/12" ) ;
334 add( result , part_0 , n1 & 0xe0U , ".0.0/11" ) ;
335 add( result , part_0 , n1 & 0xc0U , ".0.0/10" ) ;
336 add( result , part_0 , n1 & 0x80U , ".0.0/9" ) ;
337 add( result , part_0 , 0 , ".0.0/8" ) ;
338 add( result , part_0 , "*.*.*" ) ;
339 add( result , n0 & 0xfeU , ".0.0.0/7" ) ;
340 add( result , n0 & 0xfcU , ".0.0.0/6" ) ;
341 add( result , n0 & 0xf8U , ".0.0.0/5" ) ;
342 add( result , n0 & 0xf0U , ".0.0.0/4" ) ;
343 add( result , n0 & 0xe0U , ".0.0.0/3" ) ;
344 add( result , n0 & 0xc0U , ".0.0.0/2" ) ;
345 add( result , n0 & 0x80U , ".0.0.0/1" ) ;
346 add( result , 0 , ".0.0.0/0" ) ;
347 add( result , "*.*.*.*" ) ;
348
349 return result ;
350}
351
352void GNet::Address4::add( G::StringArray & result , G::string_view head , unsigned int n , const char * tail )
353{
354 result.push_back( G::sv_to_string(head).append(G::Str::fromUInt(n)).append(tail) ) ;
355}
356
357void GNet::Address4::add( G::StringArray & result , unsigned int n , const char * tail )
358{
359 result.push_back( G::Str::fromUInt(n).append(tail) ) ;
360}
361
362void GNet::Address4::add( G::StringArray & result , G::string_view head , const char * tail )
363{
364 result.push_back( G::sv_to_string(head).append(tail) ) ;
365}
366
367void GNet::Address4::add( G::StringArray & result , const char * tail )
368{
369 result.push_back( std::string(tail) ) ;
370}
371
372bool GNet::Address4::format( G::string_view s )
373{
374 // an independent check for the IPv4 dotted-quad format
375
376 if( s.empty() || s.find_first_not_of("0123456789.") != std::string::npos ||
377 std::count(s.begin(),s.end(),'.') != 3U || s.at(0U) == '.' ||
378 s.at(s.size()-1U) == '.' || s.find("..") != std::string::npos )
379 return false ;
380
381 unsigned int n = 0U ;
382 unsigned int z = static_cast<unsigned char>('0') ;
383 for( char c : s )
384 {
385 unsigned int uc = static_cast<unsigned char>(c) ;
386 n = c == '.' ? 0U : ( ( n * 10U ) + (uc-z) ) ;
387 if( n >= 256U )
388 return false ;
389 }
390 return true ;
391}
392
393unsigned int GNet::Address4::bits() const
394{
395 const unsigned long a = ntohl( m_inet.sin_addr.s_addr ) ;
396 unsigned int count = 0U ;
397 for( unsigned long mask = 0x80000000U ; mask && ( a & mask ) ; mask >>= 1U )
398 count++ ;
399 return count ;
400}
401
402bool GNet::Address4::isLocal( std::string & reason ) const
403{
404 if( isLoopback() || isLinkLocal() || isUniqueLocal() )
405 {
406 return true ;
407 }
408 else
409 {
410 reason = hostPartString().append( " is not in "
411 "127.0.0.0/8, 169.254.0.0/16, 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16" ) ;
412 return false ;
413 }
414}
415
416bool GNet::Address4::isLoopback() const
417{
418 // RFC-1918, RFC-6890
419 return ( ntohl(m_inet.sin_addr.s_addr) >> 24U ) == 127U ; // 127.0.0.0/8
420}
421
422bool GNet::Address4::isLinkLocal() const
423{
424 // RFC-3927, RFC-6890
425 return ( ntohl(m_inet.sin_addr.s_addr) >> 16U ) == 0xA9FEU ; // 169.254.0.0/16
426}
427
428bool GNet::Address4::isUniqueLocal() const
429{
430 // RFC-1918, RFC-6890
431 return
432 ( ntohl(m_inet.sin_addr.s_addr) >> 24U ) == 0x0AU || // 10.0.0.0/8
433 ( ntohl(m_inet.sin_addr.s_addr) >> 20U ) == 0xAC1U || // 172.16.0.0/12
434 ( ntohl(m_inet.sin_addr.s_addr) >> 16U ) == 0xC0A8U ; // 192.168.0.0/16
435}
436
437bool GNet::Address4::isMulticast() const
438{
439 // RFC-5771
440 return ( ntohl(m_inet.sin_addr.s_addr) >> 28U ) == 0x0EU ; // [224-239].x.x.x
441}
442
443bool GNet::Address4::isAny() const
444{
445 return m_inet.sin_addr.s_addr == htonl(INADDR_ANY) ;
446}
447
static string_view headView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like head() but returning a view into the input string.
Definition: gstr.cpp:1311
static string_view tailView(string_view in, std::size_t pos, string_view default_={}) noexcept
Like tail() but returning a view into the input string.
Definition: gstr.cpp:1340
static unsigned int toUInt(string_view s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:651
static bool isNumeric(string_view s, bool allow_minus_sign=false) noexcept
Returns true if every character is a decimal digit.
Definition: gstr.cpp:403
static std::string join(string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1224
static bool isUInt(string_view s) noexcept
Returns true if the string can be converted into an unsigned integer without throwing an exception.
Definition: gstr.cpp:449
static std::string fromUInt(unsigned int ui)
Converts unsigned int 'ui' to a string.
Definition: gstr.h:616
static void splitIntoFields(string_view in, StringArray &out, char sep, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1194
A zero-copy string field iterator where the field separators are short fixed strings.
Definition: gstringfield.h:53
A class like c++17's std::string_view.
Definition: gstringview.h:51
Network classes.
Definition: gdef.h:1144
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30