Skip to content

Commit f50a5fb

Browse files
committed
Update Bluewing Server to build 19
Build 19 has no major changes, but has a couple rare-case fixes, as well as some increased documentation in the ping function. It's also a rebuild for the steady_clock issue. Changed the error message created by Lacewing, so there's not an awkward line break. The error of clients remaining connected constantly turned out to be an error in the standard library provided in Visual Studio 2019, which I've documented more in the ansi-build-18 release. In preparation for release, now server messages also count as activity - if server has a handler set up for them. No server-message handler = not activity.
1 parent d31405e commit f50a5fb

File tree

3 files changed

+107
-67
lines changed

3 files changed

+107
-67
lines changed

Lacewing/Lacewing.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,7 +1847,7 @@ namespace lacewing {
18471847
struct relayserverinternal;
18481848
struct relayserver
18491849
{
1850-
static const int buildnum = 18;
1850+
static const int buildnum = 19;
18511851

18521852
void * internaltag, * tag = nullptr;
18531853

@@ -1985,7 +1985,7 @@ struct relayserver
19851985
::std::chrono::high_resolution_clock::time_point connectTime;
19861986
::std::chrono::steady_clock::time_point lasttcpmessagetime;
19871987
::std::chrono::steady_clock::time_point lastudpmessagetime; // UDP problem where unused connections are dropped by router, so must keep these separate
1988-
::std::chrono::steady_clock::time_point lastnonpingmessagetime; // For clients that go idle
1988+
::std::chrono::steady_clock::time_point lastchannelorpeermessagetime; // For clients that go idle
19891989
framereader reader;
19901990
std::vector<std::shared_ptr<channel>> channels;
19911991
std::string _name, _prevname;

Lacewing/RelayServer.cc

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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

942977
void 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;
22192259
lw_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
}

Lacewing/src/error.c

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,26 +118,26 @@ void lw_error_add (lw_error ctx, long error)
118118
{
119119
#ifdef _WIN32
120120

121-
char * message;
122-
123-
if (FormatMessageA
124-
(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
125-
0,
126-
error,
127-
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
128-
(char *) &message,
129-
1,
130-
0))
131-
{
132-
lw_error_addf (ctx, error < 0 ? "%s (%08X)" : "%s (%d)",
133-
message, error);
134-
}
135-
136-
LocalFree (message);
121+
char message[512];
122+
123+
if (FormatMessageA
124+
(FORMAT_MESSAGE_FROM_SYSTEM,
125+
0,
126+
error,
127+
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
128+
message,
129+
sizeof(message) / sizeof(*message),
130+
0))
131+
{
132+
lw_error_addf (ctx, error < 0 ? "%.*s (%08X)" : "%.*s (%d)",
133+
strlen(message) - 3, message, error);
134+
}
135+
136+
LocalFree (message);
137137

138138
#else
139139

140-
lw_error_addf (ctx, "%s", strerror (error));
140+
lw_error_addf (ctx, "%s (%ld)", strerror (error), error);
141141

142142
#endif
143143
}

0 commit comments

Comments
 (0)