E-MailRelay
gaddress6.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 gaddress6.cpp
19///
20
21#include "gdef.h"
22#include "gaddress6.h"
23#include "gstr.h"
24#include "gtest.h"
25#include "gprocess.h"
26#include "glog.h"
27#include <algorithm> // std::swap()
28#include <utility> // std::swap()
29#include <cstring> // std::memcpy()
30#include <type_traits>
31#include <climits>
32#include <sys/types.h>
33#include <sstream>
34#include <vector>
35#include <iomanip>
36#include <array>
37
38namespace GNet
39{
40 namespace Address6Imp
41 {
42 const char * port_separators = ":." ;
43 char port_separator = '.' ;
44 }
45}
46
47unsigned short GNet::Address6::af() noexcept
48{
49 return AF_INET6 ;
50}
51
52int GNet::Address6::domain() noexcept
53{
54 return PF_INET6 ;
55}
56
57GNet::Address6::Address6( std::nullptr_t ) :
58 m_inet{}
59{
60 m_inet.sin6_family = af() ;
61 m_inet.sin6_port = 0 ;
62 m_inet.sin6_flowinfo = 0 ;
63 gdef_address6_init( m_inet ) ; // gdef.h
64}
65
66GNet::Address6::Address6( unsigned int port ) :
67 Address6(nullptr)
68{
69 m_inet.sin6_addr = in6addr_any ;
70 const char * reason = setPort( m_inet , port ) ;
71 if( reason ) throw Address::Error(reason) ;
72}
73
74GNet::Address6::Address6( unsigned int port , int /*loopback_overload*/ ) :
75 Address6(nullptr)
76{
77 m_inet.sin6_addr = in6addr_loopback ;
78 const char * reason = setPort( m_inet , port ) ;
79 if( reason ) throw Address::Error(reason) ;
80}
81
82GNet::Address6::Address6( const sockaddr * addr , socklen_t len , bool ipv6_scope_id_fixup ) :
83 Address6(nullptr)
84{
85 if( addr == nullptr )
86 throw Address::Error() ;
87 if( addr->sa_family != af() || static_cast<std::size_t>(len) < sizeof(sockaddr_type) )
88 throw Address::BadFamily() ;
89
90 std::memcpy( &m_inet , addr , sizeof(m_inet) ) ;
91
92 if( ipv6_scope_id_fixup ) // for eg. NetBSD v7 getifaddrs() -- see FreeBSD Handbook "Scope Index"
93 {
94 auto hi = static_cast<unsigned int>( m_inet.sin6_addr.s6_addr[2] ) ;
95 auto lo = static_cast<unsigned int>( m_inet.sin6_addr.s6_addr[3] ) ;
96 m_inet.sin6_addr.s6_addr[2] = 0 ;
97 m_inet.sin6_addr.s6_addr[3] = 0 ;
98 m_inet.sin6_scope_id = ( hi << 8U | lo ) ;
99 }
100}
101
102GNet::Address6::Address6( const std::string & host_part , const std::string & port_part ) :
103 Address6(nullptr)
104{
105 const char * reason = setHostAddress( m_inet , host_part ) ;
106 if( !reason )
107 reason = setPort( m_inet , port_part ) ;
108 if( reason )
109 throw Address::BadString( std::string(reason) + ": [" + host_part + "][" + port_part + "]" ) ;
110}
111
112GNet::Address6::Address6( const std::string & display_string ) :
113 Address6(nullptr)
114{
115 const char * reason = setAddress( m_inet , display_string ) ;
116 if( reason )
117 throw Address::BadString( std::string(reason) + ": " + display_string ) ;
118}
119
120const char * GNet::Address6::setAddress( sockaddr_type & inet , const std::string & display_string )
121{
122 const std::string::size_type pos = display_string.find_last_of( Address6Imp::port_separators ) ;
123 if( pos == std::string::npos )
124 return "no port separator" ;
125
126 std::string host_part = G::Str::head( display_string , pos ) ;
127 std::string port_part = G::Str::tail( display_string , pos ) ;
128
129 const char * reason = setHostAddress( inet , host_part ) ;
130 if( !reason )
131 reason = setPort( inet , port_part ) ;
132 return reason ;
133}
134
135const char * GNet::Address6::setHostAddress( sockaddr_type & inet , const std::string & host_part )
136{
137 // wikipedia: "because all link-local addresses in a host have a common prefix, normal routing
138 // procedures cannot be used to choose the outgoing interface when sending packets
139 // to a link-local destination -- a special identifier, known as a zone index, is needed
140 // to provide the additional routing information -- in the case of link-local addresses
141 // zone indices correspond to interface identifiers -- when an address is written
142 // textually the zone index is appended to the address separated by a percent sign --
143 // the actual syntax of zone indices depends on the operating system"
144 //
145 // see also RFC-2553 section 4
146 //
147 std::string zone = G::Str::tail( host_part , host_part.find('%') , std::string() ) ;
148 std::string host_part_head = G::Str::head( host_part , host_part.find('%') , host_part ) ;
149
150 int rc = inet_pton( af() , host_part_head.c_str() , &inet.sin6_addr ) ;
151
152 if( rc == 1 && !zone.empty() )
153 {
154 if( !setZone( inet , zone ) )
155 return "invalid address zone/scope" ;
156 }
157
158 return rc == 1 ? nullptr : "invalid network address" ;
159}
160
161void GNet::Address6::setPort( unsigned int port )
162{
163 const char * reason = setPort( m_inet , port ) ;
164 if( reason )
165 throw Address::Error( "invalid port number" ) ;
166}
167
168const char * GNet::Address6::setPort( sockaddr_type & inet , const std::string & port_part )
169{
170 if( port_part.length() == 0U ) return "empty port string" ;
171 if( !G::Str::isNumeric(port_part) || !G::Str::isUInt(port_part) ) return "non-numeric port string" ;
172 return setPort( inet , G::Str::toUInt(port_part) ) ;
173}
174
175const char * GNet::Address6::setPort( sockaddr_type & inet , unsigned int port )
176{
177 if( port > 0xFFFFU ) return "port number too big" ;
178 const g_port_t in_port = static_cast<g_port_t>(port) ;
179 inet.sin6_port = htons( in_port ) ;
180 return nullptr ;
181}
182
183bool GNet::Address6::setZone( const std::string & zone )
184{
185 return setZone( m_inet , zone ) ;
186}
187
188bool GNet::Address6::setZone( sockaddr_type & inet , const std::string & zone )
189{
190 unsigned long scope_id = 0UL ; // uint on unix, ULONG on windows
191 if( G::Str::isULong(zone) )
192 {
193 scope_id = G::Str::toULong( zone ) ;
194 }
195 else
196 {
197 scope_id = gdef_if_nametoindex( zone.c_str() ) ; // see gdef.h
198 if( scope_id == 0U )
199 return false ;
200 }
201 inet.sin6_scope_id = scope_id ; // narrowing conversion on unix
202 const bool no_overflow = scope_id == inet.sin6_scope_id ;
203 return no_overflow ;
204}
205
206void GNet::Address6::setScopeId( unsigned long ipv6_scope_id )
207{
208 m_inet.sin6_scope_id = ipv6_scope_id ; // narrowing conversion on unix
209}
210
211std::string GNet::Address6::displayString( bool ipv6_with_scope_id ) const
212{
213 std::ostringstream ss ;
214 ss << hostPartString() ;
215 if( ipv6_with_scope_id && scopeId() != 0U )
216 ss << "%" << scopeId() ;
217 ss << Address6Imp::port_separator << port() ;
218 return ss.str() ;
219}
220
221std::string GNet::Address6::hostPartString() const
222{
223 std::array<char,INET6_ADDRSTRLEN+1U> buffer {} ;
224 const void * vp = & m_inet.sin6_addr ;
225 const char * p = inet_ntop( af() , const_cast<void*>(vp) , &buffer[0] , buffer.size() ) ; // cast for win32
226 if( p == nullptr )
227 throw Address::Error( "inet_ntop() failure" ) ;
228 buffer[buffer.size()-1U] = '\0' ;
229 return { &buffer[0] } ; // sic
230}
231
232std::string GNet::Address6::queryString() const
233{
234 std::ostringstream ss ;
235 const char * hexmap = "0123456789abcdef" ;
236 for( std::size_t i = 0U ; i < 16U ; i++ )
237 {
238 unsigned int n = static_cast<unsigned int>(m_inet.sin6_addr.s6_addr[15U-i]) % 256U ;
239 ss << (i==0U?"":".") << hexmap[(n&15U)%16U] << "." << hexmap[(n>>4U)%16U] ;
240 }
241 return ss.str() ;
242}
243
244bool GNet::Address6::validData( const sockaddr * addr , socklen_t len )
245{
246 return addr != nullptr && addr->sa_family == af() && len == sizeof(sockaddr_type) ;
247}
248
249bool GNet::Address6::validString( const std::string & s , std::string * reason_p )
250{
251 sockaddr_type inet {} ;
252 const char * reason = setAddress( inet , s ) ;
253 if( reason && reason_p )
254 *reason_p = std::string(reason) ;
255 return reason == nullptr ;
256}
257
258bool GNet::Address6::validStrings( const std::string & host_part , const std::string & port_part ,
259 std::string * reason_p )
260{
261 sockaddr_type inet {} ;
262 const char * reason = setHostAddress( inet , host_part ) ;
263 if( !reason )
264 reason = setPort( inet , port_part ) ;
265 if( reason && reason_p )
266 *reason_p = std::string(reason) ;
267 return reason == nullptr ;
268}
269
270#ifndef G_LIB_SMALL
271bool GNet::Address6::validPort( unsigned int port )
272{
273 sockaddr_type inet {} ;
274 const char * reason = setPort( inet , port ) ;
275 return reason == nullptr ;
276}
277#endif
278
279bool GNet::Address6::same( const Address6 & other , bool with_scope ) const
280{
281 return
282 m_inet.sin6_family == af() &&
283 other.m_inet.sin6_family == af() &&
284 sameAddr( m_inet.sin6_addr , other.m_inet.sin6_addr ) &&
285 ( !with_scope || m_inet.sin6_scope_id == other.m_inet.sin6_scope_id ) &&
286 m_inet.sin6_port == other.m_inet.sin6_port ;
287}
288
289bool GNet::Address6::sameHostPart( const Address6 & other , bool with_scope ) const
290{
291 return
292 m_inet.sin6_family == af() &&
293 other.m_inet.sin6_family == af() &&
294 sameAddr( m_inet.sin6_addr , other.m_inet.sin6_addr ) &&
295 ( !with_scope || m_inet.sin6_scope_id == other.m_inet.sin6_scope_id ) ;
296}
297
298bool GNet::Address6::sameAddr( const ::in6_addr & a , const ::in6_addr & b )
299{
300 for( std::size_t i = 0 ; i < 16U ; i++ )
301 {
302 if( a.s6_addr[i] != b.s6_addr[i] )
303 return false ;
304 }
305 return true ;
306}
307
308unsigned int GNet::Address6::port() const
309{
310 return ntohs( m_inet.sin6_port ) ;
311}
312
313unsigned long GNet::Address6::scopeId( unsigned long /*default*/ ) const
314{
315 return m_inet.sin6_scope_id ;
316}
317
318#ifndef G_LIB_SMALL
319const sockaddr * GNet::Address6::address() const
320{
321 // core guidelines: C.183
322 // type-punning allowed by "common initial sequence" rule
323 return reinterpret_cast<const sockaddr*>( &m_inet ) ;
324}
325#endif
326
327sockaddr * GNet::Address6::address()
328{
329 return reinterpret_cast<sockaddr*>( &m_inet ) ;
330}
331
332socklen_t GNet::Address6::length() noexcept
333{
334 return sizeof(sockaddr_type) ;
335}
336
337namespace GNet
338{
339 namespace Address6Imp
340 {
341 bool shiftLeft( struct in6_addr & mask )
342 {
343 bool carry_out = false ;
344 bool carry_in = false ;
345 for( int i = 15 ; i >= 0 ; i-- )
346 {
347 const unsigned char top_bit = 128U ;
348 carry_out = !!( mask.s6_addr[i] & top_bit ) ;
349 mask.s6_addr[i] <<= 1U ;
350 if( carry_in ) ( mask.s6_addr[i] |= 1U ) ;
351 carry_in = carry_out ;
352 }
353 return carry_out ;
354 }
355 void shiftLeft( struct in6_addr & mask , unsigned int bits )
356 {
357 for( unsigned int i = 0U ; i < bits ; i++ )
358 shiftLeft( mask ) ;
359 }
360 void reset( struct in6_addr & addr )
361 {
362 for( unsigned int i = 0 ; i < 16U ; i++ )
363 addr.s6_addr[i] = 0 ;
364 }
365 void fill( struct in6_addr & addr )
366 {
367 for( unsigned int i = 0 ; i < 16U ; i++ )
368 addr.s6_addr[i] = 0xff ;
369 }
370 struct in6_addr make( unsigned int lhs_hi , unsigned int lhs_lo , unsigned int rhs )
371 {
372 struct in6_addr addr {} ;
373 reset( addr ) ;
374 using lhs_type = std::remove_reference<decltype(addr.s6_addr[0])>::type ;
375 addr.s6_addr[15] = static_cast<lhs_type>(rhs) ;
376 addr.s6_addr[0] = static_cast<lhs_type>(lhs_hi) ;
377 addr.s6_addr[1] = static_cast<lhs_type>(lhs_lo) ;
378 return addr ;
379 }
380 void applyMask( struct in6_addr & addr , const struct in6_addr & mask )
381 {
382 for( int i = 0 ; i < 16 ; i++ )
383 {
384 addr.s6_addr[i] &= mask.s6_addr[i] ;
385 }
386 }
387 struct in6_addr mask( unsigned int bits )
388 {
389 struct in6_addr addr {} ;
390 fill( addr ) ;
391 shiftLeft( addr , 128U - bits ) ;
392 return addr ;
393 }
394 struct in6_addr masked( const struct in6_addr & addr_in , const struct in6_addr & mask )
395 {
396 struct in6_addr result = addr_in ;
397 applyMask( result , mask ) ;
398 return result ;
399 }
400 }
401}
402
403G::StringArray GNet::Address6::wildcards() const
404{
405 namespace imp = Address6Imp ;
406 Address6 a( *this ) ;
407
408 G::StringArray result ;
409 result.reserve( 128U ) ;
410 result.push_back( hostPartString() ) ;
411
412 struct in6_addr mask {} ;
413 imp::fill( mask ) ;
414
415 for( int bit = 0 ; bit <= 128 ; bit++ )
416 {
417 std::ostringstream ss ;
418 ss << a.hostPartString() << "/" << (128-bit) ;
419 result.push_back( ss.str() ) ;
420
421 imp::shiftLeft( mask ) ;
422 imp::applyMask( a.m_inet.sin6_addr , mask ) ;
423 }
424 return result ;
425}
426
427unsigned int GNet::Address6::bits() const
428{
429 namespace imp = Address6Imp ;
430 struct in6_addr a = m_inet.sin6_addr ;
431 unsigned int count = 0U ;
432 while( imp::shiftLeft(a) )
433 count++ ;
434 return count ;
435}
436
437bool GNet::Address6::isLocal( std::string & reason ) const
438{
439 if( isLoopback() || isLinkLocal() || isUniqueLocal() )
440 {
441 return true ;
442 }
443 else
444 {
445 std::ostringstream ss ;
446 ss << hostPartString() << " is not in ::1/128 or fe80::/64 or fc00::/7" ;
447 reason = ss.str() ;
448 return false ;
449 }
450}
451
453{
454 // ::1/128 (cf. 127.0.0.0/8)
455 namespace imp = Address6Imp ;
456 struct in6_addr _1 = imp::make( 0U , 0U , 1U ) ; /// ::1/128
457 return sameAddr( _1 , m_inet.sin6_addr ) ;
458}
459
460bool GNet::Address6::isLinkLocal() const
461{
462 // fe80::/64 (cf. 169.254.0.0/16)
463 namespace imp = Address6Imp ;
464 struct in6_addr addr_64 = imp::masked( m_inet.sin6_addr , imp::mask(64U) ) ;
465 struct in6_addr _fe80 = imp::make( 0xfeU , 0x80U , 0U ) ;
466 return sameAddr( _fe80 , addr_64 ) ;
467}
468
469bool GNet::Address6::isMulticast() const
470{
471 return false ; // TODO ipv6 multicast
472}
473
474bool GNet::Address6::isUniqueLocal() const
475{
476 // fc00::/7 (cf. 192.168.0.0/16 or 10.0.0.0/8)
477 namespace imp = Address6Imp ;
478 struct in6_addr addr_7 = imp::masked( m_inet.sin6_addr , imp::mask(7U) ) ;
479 struct in6_addr _fc00 = imp::make( 0xfcU , 0U , 0U ) ;
480 return sameAddr( _fc00 , addr_7 ) ;
481}
482
483bool GNet::Address6::isAny() const
484{
485 for( int i = 0 ; i < 16 ; i++ )
486 {
487 if( m_inet.sin6_addr.s6_addr[i] != in6addr_any.s6_addr[i] )
488 return false ;
489 }
490 return true ;
491}
492
bool isLoopback() const
Definition: gaddress6.cpp:452
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 bool isULong(string_view s) noexcept
Returns true if the string can be converted into an unsigned long without throwing an exception.
Definition: gstr.cpp:457
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 unsigned long toULong(string_view s, Limited)
Converts string 's' to an unsigned long.
Definition: gstr.cpp:672
static std::string tail(string_view in, std::size_t pos, string_view default_={})
Returns the last part of the string after the given position.
Definition: gstr.cpp:1325
static std::string head(string_view in, std::size_t pos, string_view default_={})
Returns the first part of the string up to just before the given position.
Definition: gstr.cpp:1297
Network classes.
Definition: gdef.h:1144
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30