@@ -58,7 +58,7 @@ struct relayserverinternal
5858
5959 pingtimer->tag (this );
6060 pingtimer->on_tick (serverpingtimertick);
61- pingMS = 5000 ;
61+ tcpPingMS = 5000 ;
6262
6363 // Some firewalls/router set to mark UDP connections as over after 30 seconds of inactivity,
6464 // but a general consensus is around 60 seconds.
@@ -69,7 +69,7 @@ struct relayserverinternal
6969 // It's not perfect, but since the ping timer will keep repeating anyway past the 3 mark, and
7070 // activity on either side won't cause the no-new-connections bug to occur, it shouldn't
7171 // matter either way.
72- udpKeepAliveMS = 30000 - (pingMS * 3 );
72+ udpKeepAliveMS = 30000 - (tcpPingMS * 3 );
7373
7474 // Some buggy client versions don't close their connection on end of app, forcing the app to stay
7575 // alive. We can't force them to close, but we can disconnect them.
@@ -117,18 +117,32 @@ struct relayserverinternal
117117 std::vector<std::shared_ptr<relayserver::channel>> channels;
118118
119119 bool channellistingenabled;
120- long pingMS ;
120+ long tcpPingMS ;
121121 long udpKeepAliveMS;
122122 long maxInactivityMS;
123123
124+ // / <summary> Lacewing timer function for pinging and inactivity tests. </summary>
125+ // / <remarks> There are three things this function does:
126+ // / 1) If the client has not sent a TCP message within tcpPingMS milliseconds, send a ping request.
127+ // / -> If client still hasn't responded after another tcpPingMS, notify server via error handler,
128+ // / and disconnect client.
129+ // / 2) If the client has not sent a UDP message within udpKeepAliveMS milliseconds, send a ping request.
130+ // / -> UDP ping response is not checked for by this function; one-way UDP activity is enough to keep the
131+ // / UDP psuedo-connection alive in routers.
132+ // / Note using default timing, three UDP messages will be sent before routers are likely to close connection.
133+ // / 3) If the client has only replied to pings, and not sent any channel, peer, or server messages besides,
134+ // / within a period of maxInactivityMS, then the client will be messaged and disconnected, and the server notified
135+ // / via error handler.
136+ // / Worth noting channel messages when there is no other peers, and serve messages when there is no server message
137+ // / handler, and channel join/leave requests as well as other messages, do not qualify as activity. </remarks>
124138 void pingtimertick ()
125139 {
126- std::vector<std::shared_ptr<relayserver::client>> todisconnect ;
127- std::vector<std::shared_ptr<relayserver::client>> inactiveDisconnects ;
140+ std::vector<std::shared_ptr<relayserver::client>> pingUnresponsivesToDisconnect ;
141+ std::vector<std::shared_ptr<relayserver::client>> inactivesToDisconnects ;
128142
129- framebuilder builderPingTCP (false ), builderPingUDP (true );
130- builderPingTCP .addheader (11 , 0 ); /* ping */
131- builderPingUDP .addheader (11 , 0 , true ); /* ping with UDP */
143+ framebuilder msgBuilderTCP (false ), msgBuilderUDP (true );
144+ msgBuilderTCP .addheader (11 , 0 ); /* ping header */
145+ msgBuilderUDP .addheader (11 , 0 , true ); /* ping header, true for UDP */
132146
133147 std::chrono::steady_clock::time_point currentTime = std::chrono::steady_clock::now ();
134148 auto serverReadLock = server.lock .createReadLock ();
@@ -137,32 +151,42 @@ struct relayserverinternal
137151 if (client->_readonly )
138152 continue ;
139153
140- long long msElapsedTCP = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - client->lasttcpmessagetime ).count ();
141- long long msElapsedUDP = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - client->lastudpmessagetime ).count ();
142- long long msElapsedNonPing = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - client->lastnonpingmessagetime ).count ();
154+ auto msElapsedTCP = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - client->lasttcpmessagetime ).count ();
155+ auto msElapsedNonPing = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - client->lastchannelorpeermessagetime ).count ();
143156
144- // less than 5 seconds passed, skip the TCP ping
145- if (msElapsedTCP < pingMS)
157+ // Psuedo UDP is true unless a UDPHello packet is received, i.e. the client connect handshake UDP packet.
158+ decltype (msElapsedTCP) msElapsedUDP = 0 ;
159+ if (!client->pseudoUDP )
160+ msElapsedUDP = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - client->lastudpmessagetime ).count ();
161+
162+ // detect a buggy version of std::chrono::steady_clock (was in a VS 2019 preview)
163+ if (msElapsedTCP < 0 || msElapsedUDP < 0 || msElapsedNonPing < 0 )
164+ DebugBreak ();
165+
166+ // less than 5 seconds (or tcpPingMS) passed since last TCP message, skip the TCP ping
167+ if (msElapsedTCP < tcpPingMS)
146168 {
147169 client->pongedOnTCP = true ;
148170
149- // Also no UDP ping needed, skip both pings
171+ // No UDP keep-alive message needed either , skip both pings
150172 if (msElapsedUDP < udpKeepAliveMS)
151173 continue ;
152174 }
175+
176+ // More than 10 minutes passed, prep to kick for inactivity
153177 if (msElapsedNonPing > maxInactivityMS)
154178 {
155- inactiveDisconnects .push_back (client);
179+ inactivesToDisconnects .push_back (client);
156180 continue ;
157181 }
158182
159183 // pongedOnTCP is true until client hasn't sent a message within PingMS period.
160- // Then it's set to false and a ping message sent, which happens AFTER this if() .
161- // The ping timer ticks again pingMS ms later, and if pongedOnTCP is still false (this if() ),
162- // then client hasn't responded to ping, and so should be disconnected.
184+ // Then it's set to false and a ping message sent, which happens AFTER this if block .
185+ // The ping timer ticks again tcpPingMS ms later, and if pongedOnTCP is still false
186+ // (in this if condition), then client hasn't responded to ping, and so should be disconnected.
163187 if (!client->pongedOnTCP )
164188 {
165- todisconnect .push_back (client);
189+ pingUnresponsivesToDisconnect .push_back (client);
166190 continue ;
167191 }
168192
@@ -171,29 +195,39 @@ struct relayserverinternal
171195 if (client->_readonly )
172196 continue ;
173197
174- if (msElapsedTCP >= pingMS )
198+ if (msElapsedTCP >= tcpPingMS )
175199 {
176200 client->pongedOnTCP = false ;
177- builderPingTCP .send (client->socket , false );
201+ msgBuilderTCP .send (client->socket , false );
178202 }
203+
204+ // Keep UDP alive by sending a UDP message.
205+ // Worth noting Relay clients and Blue client b82 and below don't have UDP ping responses; they will
206+ // ignore the message entirely.
207+ // Fortunately, we don't actually *need* a ping responses from the client; one-way activity ought to be
208+ // enough to keep the UDP psuedo-connections open in routers... assuming, of course, that the UDP packet
209+ // goes all the way to the client and thus through all the routers.
179210 if (msElapsedUDP >= udpKeepAliveMS)
180- builderPingUDP .send (server.udp , client->udpaddress , false );
211+ msgBuilderUDP .send (server.udp , client->udpaddress , false );
181212 }
182213
183- builderPingTCP.framereset ();
184-
185- if (todisconnect.empty () && inactiveDisconnects.empty ())
214+ if (pingUnresponsivesToDisconnect.empty () && inactivesToDisconnects.empty ())
186215 return ;
187216
188217 serverReadLock.lw_unlock ();
189218
190- for (auto & client : todisconnect)
219+ // Loop all pending ping disconnects
220+ for (auto & client : pingUnresponsivesToDisconnect)
191221 {
222+ // One final disconnect check
192223 if (client->_readonly )
193224 continue ;
194225
226+ // To allow client disconnect handlers to run without clashes, we keep the lock open
227+ // as frequently as possible.
195228 if (!serverReadLock.isEnabled ())
196229 serverReadLock.lw_relock ();
230+
197231 if (std::find (clients.begin (), clients.end (), client) != clients.end ())
198232 {
199233 serverReadLock.lw_unlock ();
@@ -226,7 +260,8 @@ struct relayserverinternal
226260 }
227261 }
228262
229- for (auto & client : inactiveDisconnects)
263+ // Loop all pending inactivity disconnects
264+ for (auto & client : inactivesToDisconnects)
230265 {
231266 if (client->_readonly )
232267 continue ;
@@ -245,11 +280,11 @@ struct relayserverinternal
245280 impl.erase (impl.cend ());
246281
247282 auto error = lacewing::error_new ();
248- error->add (" Disconnecting client ID %i due to 10 min inactivity timeout; client impl \" %s\" ." , client->_id , impl.c_str ());
283+ error->add (" Disconnecting client ID %i due to inactivity timeout; client impl \" %s\" ." , client->_id , impl.c_str ());
249284 handlererror (this ->server , error);
250285 lacewing::error_delete (error);
251286
252- client->send (0 , " You're being kicked for 10 minutes of inactivity." , 0 );
287+ client->send (0 , " You're being kicked for inactivity." , 0 );
253288 // Close nicely
254289 client->socket ->close (lw_false);
255290 }
@@ -936,7 +971,7 @@ void relayserver::host(lacewing::filter &_filter)
936971 lacewing::filter_delete (filter);
937972
938973 relayserverinternal * serverInternal = (relayserverinternal *)internaltag;
939- serverInternal->pingtimer ->start (serverInternal->pingMS );
974+ serverInternal->pingtimer ->start (serverInternal->tcpPingMS );
940975}
941976
942977void relayserver::unhost ()
@@ -1369,9 +1404,6 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
13691404 else
13701405 client->lasttcpmessagetime = ::std::chrono::steady_clock::now ();
13711406
1372- if (messagetypeid != 9 )
1373- client->lastnonpingmessagetime = ::std::chrono::steady_clock::now ();
1374-
13751407 std::stringstream errStr;
13761408 bool trustedClient = true ;
13771409
@@ -1683,12 +1715,7 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
16831715 cliReadLock.lw_unlock ();
16841716
16851717 const lw_ui8 subchannel = reader.get <lw_ui8> ();
1686-
1687- const char * message2;
1688- size_t size2;
1689-
1690- reader.getremaining (message2, size2);
1691- std::string_view message3 (message2, size2);
1718+ std::string_view message3 = reader.getremaining (false );
16921719
16931720 if (reader.failed )
16941721 {
@@ -1698,8 +1725,13 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
16981725 }
16991726
17001727 if (handlermessage_server)
1728+ {
17011729 handlermessage_server (server, client, blasted, subchannel, message3, variant);
17021730
1731+ // Server messages, we'll assume it is activity.
1732+ client->lastchannelorpeermessagetime = ::std::chrono::steady_clock::now ();
1733+ }
1734+
17031735 break ;
17041736 }
17051737
@@ -1743,6 +1775,10 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
17431775 }
17441776 cliReadLock.lw_unlock ();
17451777
1778+ // Channel messages not sent to anyone is not activity.
1779+ if (channel->clientcount () > 1 )
1780+ client->lastchannelorpeermessagetime = ::std::chrono::steady_clock::now ();
1781+
17461782 if (handlermessage_channel)
17471783 handlermessage_channel (server, client, channel,
17481784 blasted, subchannel, message2, variant);
@@ -1780,6 +1816,8 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
17801816 break ;
17811817 }
17821818
1819+ client->lastchannelorpeermessagetime = ::std::chrono::steady_clock::now ();
1820+
17831821 if (handlermessage_peer)
17841822 handlermessage_peer (server, client, channel,
17851823 peer, blasted, subchannel, message3, variant);
@@ -1845,18 +1883,21 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
18451883 case 10 : /* implementation response */
18461884 {
18471885 std::string_view impl = reader.get (reader.bytesleft ());
1848- if (reader.failed )
1886+ if (reader.failed || impl. empty () )
18491887 {
18501888 errStr << " Failed to read implementation response" ;
18511889 trustedClient = false ;
18521890 break ;
18531891 }
18541892
1893+ // LW_ESCALATION_NOTE
18551894 cliReadLock.lw_unlock ();
18561895 auto cliWriteLock = client->lock .createWriteLock ();
18571896 if (client->_readonly )
18581897 break ;
18591898
1899+ // Implementation responses were added in Bluewing Client build 70.
1900+
18601901 if (impl.find (" Windows" ) != std::string_view::npos)
18611902 {
18621903 if (impl.find (" Unicode" ) != std::string_view::npos)
@@ -1876,7 +1917,8 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
18761917 client->clientImpl = relayserver::client::clientimpl::HTML5;
18771918 else
18781919 {
1879- errStr << " Failed to recognise implementation \" " << impl << " \" . Leaving it as Unknown." ;
1920+ errStr << " Failed to recognise platform of implementation \" " << impl << " \" . Leaving it as Unknown." ;
1921+ reader.failed = true ;
18801922 }
18811923
18821924 client->clientImplStr = impl;
@@ -1919,7 +1961,6 @@ bool relayserverinternal::client_messagehandler(std::shared_ptr<relayserver::cli
19191961 // only return false if socket is emergency closing and
19201962 // you cannot trust further message content is readable
19211963 return trustedClient;
1922- /* socket.disconnect(); */
19231964 }
19241965
19251966 return true ;
@@ -2034,8 +2075,7 @@ relayserver::client::client(relayserverinternal &internal, lacewing::server_clie
20342075
20352076 // connectTime not started until handshake is done
20362077 // last message can be considered the connect time
2037- lastnonpingmessagetime = lasttcpmessagetime = ::std::chrono::steady_clock::now ();
2038- lastudpmessagetime = ::std::chrono::steady_clock::time_point::time_point ();
2078+ lastudpmessagetime = lastchannelorpeermessagetime = lasttcpmessagetime = ::std::chrono::steady_clock::now ();
20392079}
20402080
20412081::lacewing::relayserver::channel::channel (relayserverinternal &_server, std::string_view _name) :
@@ -2219,8 +2259,8 @@ using namespace ::std::chrono;
22192259lw_i64 relayserver::client::getconnecttime () const
22202260{
22212261 // No need for read lock: connect time is set in ctor
2222- high_resolution_clock ::time_point end = high_resolution_clock ::now ();
2223- nanoseconds time = end - connectTime;
2262+ steady_clock ::time_point end = steady_clock ::now ();
2263+ auto time = end - connectTime;
22242264 return duration_cast<seconds>(time).count ();
22252265}
22262266
@@ -2318,7 +2358,7 @@ void relayserver::connect_response(
23182358
23192359 lw_trace (" Connect request accepted in relayserver::connectresponse" );
23202360 client->connectRequestApproved = true ;
2321- client->connectTime = std::chrono::high_resolution_clock ::now ();
2361+ client->connectTime = std::chrono::steady_clock ::now ();
23222362 client->clientImpl = relayserver::client::clientimpl::Unknown;
23232363
23242364 builder.addheader (0 , 0 ); /* response */
@@ -2338,7 +2378,7 @@ void relayserver::connect_response(
23382378 // Send request implementation: this is the best time to send it
23392379
23402380 builder.addheader (12 , 0 ); /* request implementation */
2341- // response on 10, only responded to by Bluewing, Relay just ignores it
2381+ // response on 10, only responded to by Bluewing b70+ , Relay just ignores it
23422382
23432383 builder.send (client->socket );
23442384}
0 commit comments