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