E-MailRelay
gmultiserver.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 gmultiserver.cpp
19///
20
21#include "gdef.h"
22#include "gmultiserver.h"
23#include "gdatetime.h"
24#include "gstr.h"
25#include "gtest.h"
26#include "glog.h"
27#include "gassert.h"
28#include <list>
29#include <algorithm>
30
31namespace GNet
32{
33 class Listeners ;
34}
35
36class GNet::Listeners /// Used by GNet::MultiServer to represent a set of listening inputs (fd, interface or address).
37{
38public:
39 Listeners( Interfaces & , const G::StringArray & , unsigned int port ) ;
40 bool empty() const ; // no inputs
41 bool defunct() const ; // no inputs and static
42 bool idle() const ; // no inputs but some interfaces might come up
43 bool hasBad() const ; // one or more invalid inputs
44 std::string badName() const ; // first invalid input
45 bool hasEmpties() const ; // some interfaces have no addresses
46 std::string logEmpties() const ; // log line snippet for hasEmpties()
47 bool noUpdates() const ; // some inputs are interfaces but static
48 const std::vector<int> & fds() const ; // fd inputs
49 const std::vector<Address> & fixed() const ; // address inputs
50 const std::vector<Address> & dynamic() const ; // interface addresses
51
52private:
53 void addWildcards( unsigned int ) ;
54 static int parseFd( const std::string & ) ;
55 static bool isAddress( const std::string & , unsigned int ) ;
56 static Address address( const std::string & , unsigned int ) ;
57 static int af( const std::string & ) ;
58 static std::string basename( const std::string & ) ;
59 static bool isBad( const std::string & ) ;
60
61private:
62 std::string m_bad ;
63 G::StringArray m_empties ;
64 G::StringArray m_used ;
65 std::vector<Address> m_fixed ;
66 std::vector<Address> m_dynamic ;
67 std::vector<int> m_fds ;
68} ;
69
70GNet::MultiServer::MultiServer( ExceptionSink es , const G::StringArray & listener_list , unsigned int port ,
71 const std::string & server_type , ServerPeer::Config server_peer_config , Server::Config server_config ) :
72 m_es(es) ,
73 m_listener_list(listener_list) ,
74 m_port(port) ,
75 m_server_type(server_type) ,
76 m_server_peer_config(server_peer_config) ,
77 m_server_config(server_config) ,
78 m_if(es,*this) ,
79 m_interface_event_timer(*this,&MultiServer::onInterfaceEventTimeout,es)
80{
81 Listeners listeners( m_if , m_listener_list , m_port ) ;
82
83 // fail if any bad names (eg. "foo/bar")
84 if( listeners.hasBad() )
85 throw InvalidName( listeners.badName() ) ;
86
87 // fail if no addresses and no prospect of getting any
88 if( listeners.defunct() )
89 throw NoListeningAddresses() ;
90
91 // warn if no addresses from one or more interface names
92 if( listeners.hasEmpties() )
93 {
94 G_WARNING( "GNet::MultiServer::ctor: no addresses bound to named network interface"
95 << listeners.logEmpties() ) ;
96 }
97
98 // warn if doing nothing until an interface comes up
99 if( listeners.idle() )
100 {
101 G_WARNING( "GNet::MultiServer::ctor: " << m_server_type << " server: nothing to do: "
102 << "waiting for interface" << listeners.logEmpties() ) ;
103 }
104
105 // warn if we got addresses from an interface name but won't get dynamic updates
106 if( listeners.noUpdates() )
107 {
108 G_WARNING_ONCE( "GNet::MultiServer::ctor: named network interfaces "
109 "are not being monitored for address updates" ) ;
110 }
111
112 // instantiate the servers
113 for( const auto & fd : listeners.fds() )
114 createServer( Descriptor(fd) ) ;
115 for( const auto & a : listeners.fixed() )
116 createServer( a , true ) ;
117 for( const auto & a : listeners.dynamic() )
118 createServer( a , false ) ;
119}
120
122{
123 serverCleanup() ;
124}
125
126void GNet::MultiServer::createServer( Descriptor fd )
127{
128 m_server_list.emplace_back( std::make_unique<MultiServerImp>( *this , m_es ,
129 fd , m_server_peer_config , m_server_config ) ) ;
130}
131
132void GNet::MultiServer::createServer( const Address & address , bool fixed )
133{
134 m_server_list.emplace_back( std::make_unique<MultiServerImp>( *this , m_es ,
135 fixed , address , m_server_peer_config , m_server_config ) ) ;
136}
137
138void GNet::MultiServer::createServer( const Address & address , bool fixed , std::nothrow_t )
139{
140 try
141 {
142 createServer( address , fixed ) ;
143 G_LOG_S( "GNet::MultiServer::createServer: new " << m_server_type
144 << " server on " << displayString(address) ) ;
145 }
146 catch( Socket::SocketBindError & e )
147 {
148 // (can fail here if notified too soon, but succeeds later)
149 G_LOG( "GNet::MultiServer::createServer: failed to bind " << displayString(address)
150 << " for new " << m_server_type << " server:"
151 << G::Str::tail(e.what(),G::string_view(e.what()).rfind(':')) ) ;
152 }
153}
154
156{
157 for( auto & server : m_server_list )
158 {
159 server->cleanup() ;
160 }
161}
162
163void GNet::MultiServer::onInterfaceEvent( const std::string & /*description*/ )
164{
165 // notifications can be periodic and/or bursty, so minimal logging here
166 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: network configuration change event" ) ;
167 m_if.load() ;
168 m_interface_event_timer.startTimer( 1U , 500000U ) ; // maybe increase for fewer bind warnings
169}
170
171void GNet::MultiServer::onInterfaceEventTimeout()
172{
173 // get a fresh address list
174 Listeners listeners( m_if , m_listener_list , m_port ) ;
175
176 // delete old
177 for( auto server_iter = m_server_list.begin() ; server_iter != m_server_list.end() ; )
178 {
179 if( (*server_iter)->dynamic() && !gotAddressFor( **server_iter , listeners.dynamic() ) )
180 server_iter = removeServer( server_iter ) ;
181 else
182 ++server_iter ;
183 }
184
185 // create new
186 for( const auto & address : listeners.dynamic() )
187 {
188 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: address: " << displayString(address) ) ;
189 if( !gotServerFor(address) )
190 {
191 createServer( address , true , std::nothrow ) ;
192 }
193 }
194}
195
196GNet::MultiServer::ServerList::iterator GNet::MultiServer::removeServer( ServerList::iterator iter )
197{
198 auto & ptr = *iter ;
199 G_LOG_S( "GNet::MultiServer::removeServer: deleting " << m_server_type
200 << " server on " << displayString(ptr->address()) ) ;
201 return m_server_list.erase( iter ) ;
202}
203
204bool GNet::MultiServer::match( const Address & interface_address , const Address & server_address )
205{
206 // both addresses should have a well-defined scope-id, so include scope-ids
207 // in the match -- this allows for multiple interfaces to have the same link-local address
208 return interface_address.same( server_address , interface_address.scopeId() && server_address.scopeId() ) ;
209}
210
211bool GNet::MultiServer::gotAddressFor( const Listener & server , const AddressList & address_list ) const
212{
213 Address server_address = server.address() ;
214 return address_list.end() != std::find_if( address_list.begin() , address_list.end() ,
215 [server_address](const Address & a){ return match(a,server_address); } ) ;
216}
217
218bool GNet::MultiServer::gotServerFor( Address interface_address ) const
219{
220 return std::any_of( m_server_list.begin() , m_server_list.end() ,
221 [interface_address](const ServerPtr &ptr){ return match(interface_address,ptr->address()); } ) ;
222}
223
224std::string GNet::MultiServer::displayString( const Address & address )
225{
226 return address.displayString( true ) ;
227}
228
229void GNet::MultiServer::serverReport( const std::string & group ) const
230{
231 for( const auto & server : m_server_list )
232 {
233 G_ASSERT( server.get() != nullptr ) ;
234 if( !server ) continue ;
235 G_LOG_S( "GNet::MultiServer: " << (group.empty()?"":"[") << group << (group.empty()?"":"] ")
236 << m_server_type << " server on " << displayString(server->address()) ) ;
237 }
238}
239
240std::unique_ptr<GNet::ServerPeer> GNet::MultiServer::doNewPeer( ExceptionSinkUnbound esu ,
241 ServerPeerInfo && pi , const ServerInfo & si )
242{
243 return newPeer( esu , std::move(pi) , si ) ;
244}
245
247{
248 for( const auto & server : m_server_list )
249 {
250 G_ASSERT( server.get() != nullptr ) ;
251 if( !server ) continue ;
252 if( server->hasPeers() )
253 return true ;
254 }
255 return false ;
256}
257
258std::vector<std::weak_ptr<GNet::ServerPeer>> GNet::MultiServer::peers()
259{
260 using List = std::vector<std::weak_ptr<ServerPeer>> ;
261 List result ;
262 for( auto & server : m_server_list )
263 {
264 G_ASSERT( server.get() != nullptr ) ;
265 if( !server ) continue ;
266 List list = server->peers() ;
267 result.insert( result.end() , list.begin() , list.end() ) ;
268 }
269 return result ;
270}
271
272// ==
273
274GNet::MultiServerImp::MultiServerImp( MultiServer & ms , ExceptionSink es , bool fixed , const Address & address ,
275 ServerPeer::Config server_peer_config , Server::Config server_config ) :
276 GNet::Server(es,address,server_peer_config,server_config) ,
277 m_ms(ms) ,
278 m_fixed(fixed)
279{
280}
281
282GNet::MultiServerImp::MultiServerImp( MultiServer & ms , ExceptionSink es , Descriptor fd ,
283 ServerPeer::Config server_peer_config , Server::Config server_config ) :
284 GNet::Server(es,fd,server_peer_config,server_config) ,
285 m_ms(ms) ,
286 m_fixed(true)
287{
288}
289
290GNet::MultiServerImp::~MultiServerImp()
291= default;
292
293bool GNet::MultiServerImp::dynamic() const
294{
295 return !m_fixed ;
296}
297
298void GNet::MultiServerImp::cleanup()
299{
300 serverCleanup() ;
301}
302
303std::unique_ptr<GNet::ServerPeer> GNet::MultiServerImp::newPeer( ExceptionSinkUnbound esu , ServerPeerInfo && peer_info )
304{
305 MultiServer::ServerInfo server_info ;
306 server_info.m_address = address() ; // GNet::Server::address()
307 return m_ms.doNewPeer( esu , std::move(peer_info) , server_info ) ;
308}
309
310// ==
311
312GNet::MultiServer::ServerInfo::ServerInfo() :
313 m_address(Address::defaultAddress())
314{
315}
316
317// ==
318
319GNet::Listeners::Listeners( Interfaces & if_ , const G::StringArray & listener_list , unsigned int port )
320{
321 // listeners are file-descriptors, addresses or interface names (possibly decorated)
322 for( const auto & listener : listener_list )
323 {
324 int fd = G::is_windows() ? -1 : parseFd( listener ) ;
325 if( fd >= 0 )
326 {
327 m_fds.push_back( fd ) ;
328 }
329 else if( isAddress(listener,port) )
330 {
331 m_fixed.push_back( address(listener,port) ) ;
332 }
333 else
334 {
335 std::size_t n = if_.addresses( m_dynamic , basename(listener) , port , af(listener) ) ;
336 if( n == 0U && isBad(listener) )
337 m_bad = listener ;
338 (n?m_used:m_empties).push_back( listener ) ;
339 }
340 }
341 if( empty() )
342 addWildcards( port ) ;
343}
344
345int GNet::Listeners::af( const std::string & s )
346{
347 if( G::Str::tailMatch(s,"-ipv6") )
348 return AF_INET6 ;
349 else if( G::Str::tailMatch(s,"-ipv4") )
350 return AF_INET ;
351 else
352 return AF_UNSPEC ;
353}
354
355std::string GNet::Listeners::basename( const std::string & s )
356{
357 return
358 G::Str::tailMatch(s,"-ipv6") || G::Str::tailMatch(s,"-ipv4") ?
359 s.substr( 0U , s.length()-5U ) :
360 s ;
361}
362
363int GNet::Listeners::parseFd( const std::string & listener )
364{
365 if( listener.size() > 3U && listener.find("fd#") == 0U && G::Str::isUInt(listener.substr(3U)) )
366 {
367 int fd = G::Str::toInt( listener.substr(3U) ) ;
368 if( fd < 0 ) throw MultiServer::InvalidFd( listener ) ;
369 return fd ;
370 }
371 return -1 ;
372}
373
374void GNet::Listeners::addWildcards( unsigned int port )
375{
376 if( StreamSocket::supports(Address::Family::ipv4) )
377 m_fixed.emplace_back( Address::Family::ipv4 , port ) ;
378
379 if( StreamSocket::supports(Address::Family::ipv6) )
380 m_fixed.emplace_back( Address::Family::ipv6 , port ) ;
381}
382
383bool GNet::Listeners::isAddress( const std::string & s , unsigned int port )
384{
385 return Address::validStrings( s , G::Str::fromUInt(port) ) ;
386}
387
388GNet::Address GNet::Listeners::address( const std::string & s , unsigned int port )
389{
390 return Address::parse( s , port ) ;
391}
392
393bool GNet::Listeners::empty() const
394{
395 return m_fds.empty() && m_fixed.empty() && m_dynamic.empty() ;
396}
397
398bool GNet::Listeners::defunct() const
399{
400 return empty() && !Interfaces::active() ;
401}
402
403bool GNet::Listeners::idle() const
404{
405 return empty() && hasEmpties() && Interfaces::active() ;
406}
407
408bool GNet::Listeners::noUpdates() const
409{
410 return !m_used.empty() && !Interfaces::active() ;
411}
412
413bool GNet::Listeners::isBad( const std::string & s )
414{
415 // the input is not an address and not an interface-with-addresses so
416 // report it as bad if clearly not an interface-with-no-addresses --
417 // a slash is not normally allowed in an interface name, but allow "/dev/..."
418 // because of bsd
419 return s.empty() || ( s.find('/') != std::string::npos && s.find("/dev/") != 0U ) ;
420}
421
422bool GNet::Listeners::hasBad() const
423{
424 return !m_bad.empty() ;
425}
426
427std::string GNet::Listeners::badName() const
428{
429 return m_bad ;
430}
431
432bool GNet::Listeners::hasEmpties() const
433{
434 return !m_empties.empty() ;
435}
436
437std::string GNet::Listeners::logEmpties() const
438{
439 return std::string(m_empties.size()==1U?" \"":"s \"").append(G::Str::join("\", \"",m_empties)).append(1U,'"') ;
440}
441
442const std::vector<int> & GNet::Listeners::fds() const
443{
444 return m_fds ;
445}
446
447const std::vector<GNet::Address> & GNet::Listeners::fixed() const
448{
449 return m_fixed ;
450}
451
452const std::vector<GNet::Address> & GNet::Listeners::dynamic() const
453{
454 return m_dynamic ;
455}
456
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:62
static bool validStrings(const std::string &ip, const std::string &port_string, std::string *reason=nullptr)
Returns true if the combined network-address string and port string is valid.
Definition: gaddress.cpp:400
static Address parse(const std::string &display_string)
Factory function for any address family.
Definition: gaddress.cpp:178
A class that encapsulates a network socket file descriptor and an associated windows event handle.
Definition: gdescriptor.h:37
A potential ExceptionSink that is realised by bind()ing an exception source pointer.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
A class for getting a list of network interfaces and their addresses.
Definition: ginterfaces.h:48
static bool active()
Returns true if the implementation can raise InterfacesHandler events.
Used by GNet::MultiServer to represent a set of listening inputs (fd, interface or address).
A server that listens on more than one address using a facade pattern to multiple GNet::Server instan...
Definition: gmultiserver.h:48
~MultiServer() override
Destructor.
bool hasPeers() const
Returns true if peers() is not empty.
std::vector< std::weak_ptr< ServerPeer > > peers()
Returns the list of ServerPeer-derived objects.
MultiServer(ExceptionSink listener_exception_sink, const G::StringArray &listen_list, unsigned int port, const std::string &server_type, ServerPeer::Config server_peer_config, Server::Config server_config)
Constructor.
void serverReport(const std::string &group={}) const
Writes to the system log a summary of the underlying server objects and their addresses.
std::unique_ptr< ServerPeer > doNewPeer(ExceptionSinkUnbound, ServerPeerInfo &&, const ServerInfo &)
Pseudo-private method used by the pimple class.
void serverCleanup()
Should be called from all derived classes' destructors so that peer objects can use their Server obje...
A move-only structure used in GNet::Server::newPeer() and containing the new socket.
Definition: gserver.h:140
A network server class which listens on a specific port and spins off ServerPeer objects for each inc...
Definition: gserver.h:51
virtual std::unique_ptr< ServerPeer > newPeer(ExceptionSinkUnbound, ServerPeerInfo &&)=0
A factory method which new()s a ServerPeer-derived object.
static bool supports(Address::Family)
Returns true if stream sockets can be created with the given the address family.
Definition: gsocket.cpp:440
static int toInt(string_view s)
Converts string 's' to an int.
Definition: gstr.cpp:541
static bool tailMatch(const std::string &in, string_view ending) noexcept
Returns true if the string has the given ending (or the given ending is empty).
Definition: gstr.cpp:1355
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 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
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
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:56
A configuration structure for GNet::ServerPeer.
Definition: gserverpeer.h:63
A configuration structure for GNet::Server.
Definition: gserver.h:56