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