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