jcs
/subtext
/amendments
/573
telnet: Speed up initial IAC negotiation to avoid delay after connect
Extract output IAC escaping from telnet_output so telnet_output_iac
can call it to put data into node->obuf without actually flushing it
to the network. Since the initial IAC negotiation will have a bunch
of back and forth, this lets us queue up a bunch of outgoing data
that we can flush once.
Also avoid sending DO NEWENV unless we're talking to the trusted
proxy, since we only need that to receive REMOTE_ADDR. Otherwise
we'll waste time and packets receiving junk like the telnet user's
DISPLAY or XAUTHORITY variables.
jcs made amendment 573 11 months ago
--- telnet.c Mon Nov 27 21:17:05 2023
+++ telnet.c Sat Dec 2 20:26:57 2023
@@ -100,7 +100,6 @@ struct telnet_node {
unsigned short ibuflen;
unsigned short ibufoff;
unsigned short sending_iac;
- unsigned short escaped_obuflen;
short iac_state;
unsigned char iac_sb[128];
short sb_len;
@@ -142,6 +141,7 @@ static const char * vt100_terms[] = {
void telnet_deinit(void);
void telnet_setup(struct session *session);
void telnet_listen_on_node(struct telnet_node *node);
+short telnet_escape_output(struct session *session);
void telnet_output_iac(struct session *session, const char *iacs,
size_t len);
void telnet_print_busy(struct telnet_node *node);
@@ -424,11 +424,20 @@ telnet_setup(struct session *session)
IAC, DO, IAC_NAWS, /* send your NAWS */
IAC, DO, IAC_TTYPE, /* send your TTYPE */
IAC, DO, IAC_NEWENV, /* send your NEWENV vars */
-
- /* IAC, DO, IAC_LINEMODE, */
};
+ struct telnet_node *node = (struct telnet_node *)session->cookie;
+ size_t size = sizeof(iacs);
- telnet_output_iac(session, iacs, sizeof(iacs));
+ if (!node->from_trusted_proxy)
+ /* we only care about NEWENV when we need REMOTE_ADDR */
+ size -= 3;
+
+ telnet_output_iac(session, iacs, size);
+ session_flush(session);
+
+ /* see if we can quickly read and parse any incoming IAC */
+ telnet_input(session);
+ session_flush(session);
}
short
@@ -492,7 +501,7 @@ telnet_input(struct session *session)
if (error) {
session_logf(session, "TCP read failed (%d), closing connection",
error);
- session->ending = 1;
+ session->ending = true;
return 0;
}
@@ -686,8 +695,7 @@ telnet_input(struct session *session)
if (tspeed > 0) {
if (tspeed > 57600)
tspeed = 57600;
- node->session->tspeed =
- node->session->log.tspeed =
+ node->session->tspeed = node->session->log.tspeed =
(unsigned short)tspeed;
}
break;
@@ -716,6 +724,8 @@ telnet_input(struct session *session)
telnet_output_iac(session, iac_out, 3);
break;
case IAC_NEWENV:
+ if (!node->from_trusted_proxy)
+ break;
case IAC_TTYPE:
case IAC_TSPEED:
iac_out[1] = SB;
@@ -787,102 +797,125 @@ telnet_input(struct session *session)
node->ibuflen -= used;
}
+ if (node->obuflen)
+ session_flush(session);
+
return rlen;
}
short
-telnet_output(struct session *session)
+telnet_escape_output(struct session *session)
{
struct telnet_node *node = (struct telnet_node *)session->cookie;
- short error;
unsigned char c;
- unsigned long now;
+ unsigned long added = 0;
if (session->obuflen == 0 || session->ending)
return 0;
- if (node->send_pb.ioResult > 0)
- /* previous _TCPSend has not completed yet */
- return 0;
-
-process_result:
- if (node->tcp_wds[0].length) {
- if (node->tcp_wds[0].length != node->escaped_obuflen &&
- node->escaped_obuflen != node->obuflen)
- warn("bug: tried to send %d (escaped from %d) but TCP says "
- "we sent %d, how much to shift out?", node->escaped_obuflen,
- node->obuflen, node->tcp_wds[0].length);
-
- /* previous _TCPSend completed, shift out those bytes */
- if (session->obuflen < node->obuflen) {
- warn("bogus obuflen %d", session->obuflen);
- session->obuflen = 0;
- } else
- session->obuflen -= node->obuflen;
- if (session->obuflen > 0)
- memmove(session->obuf, session->obuf + node->obuflen,
- session->obuflen);
-
- node->tcp_wds[0].length = 0;
-
- if (session->obuflen == 0)
- return node->obuflen;
- }
-
if (session->obuflen == 1 && session->obuf[0] != IAC) {
- node->obuf[0] = session->obuf[0];
- node->escaped_obuflen = node->obuflen = 1;
+ node->obuf[node->obuflen++] = session->obuf[0];
+ session->obuflen = 0;
} else if (session->direct_output) {
- memcpy(node->obuf, session->obuf, session->obuflen);
- node->escaped_obuflen = node->obuflen = session->obuflen;
+ added = MIN(session->obuflen, sizeof(node->obuf) - node->obuflen);
+ memcpy(node->obuf + node->obuflen, session->obuf, added);
+ node->obuflen += added;
} else {
- /* copy obuf to node buffer, escaping IACs */
- for (node->escaped_obuflen = 0, node->obuflen = 0;
- node->obuflen < session->obuflen &&
- node->escaped_obuflen < sizeof(node->obuf) - 1;
- node->obuflen++) {
- c = session->obuf[node->obuflen];
+ /* copy session obuf to node buffer, escaping IACs */
+ for (added = 0; added < session->obuflen; added++) {
+ if (node->obuflen >= sizeof(node->obuf) - 1)
+ break;
+
+ c = session->obuf[added];
if (!node->sending_iac && c == IAC) {
- node->obuf[node->escaped_obuflen++] = IAC;
- node->obuf[node->escaped_obuflen++] = IAC;
+ node->obuf[node->obuflen++] = IAC;
+ node->obuf[node->obuflen++] = IAC;
} else
- node->obuf[node->escaped_obuflen++] = c;
+ node->obuf[node->obuflen++] = c;
}
}
- if (node->escaped_obuflen > sizeof(node->obuf)) {
- warn("bogus obuflen %d > %ld", node->escaped_obuflen,
- sizeof(node->obuf));
- session->ending = true;
- session_close(session);
+ if (added > 0) {
+ if (added < session->obuflen)
+ memmove(session->obuf, session->obuf + added,
+ session->obuflen - added);
+
+ session->obuflen -= added;
+ }
+
+ return added;
+}
+
+short
+telnet_output(struct session *session)
+{
+ struct telnet_node *node = (struct telnet_node *)session->cookie;
+ unsigned long now = Ticks;
+ short error, ret = 0;
+
+ if (session->obuflen != 0)
+ telnet_escape_output(session);
+
+ if (node->obuflen == 0 || session->ending)
return 0;
+
+ /* wait for previous _TCPSend to complete */
+ while (node->send_pb.ioResult > 0) {
+ if (uthread_current == NULL)
+ /* this was an opportunistic flush anyway, no sense waiting */
+ return 0;
+
+ uthread_yield();
+
+ if (session->ending)
+ return 0;
}
+
+process_result:
+ if (node->tcp_wds[0].length) {
+ /* shift out old data */
+ if (node->obuflen < node->tcp_wds[0].length)
+ panic("data in tcp_wds > obuflen");
+
+ if (node->obuflen > node->tcp_wds[0].length) {
+ memmove(node->obuf, node->obuf + node->tcp_wds[0].length,
+ node->obuflen - node->tcp_wds[0].length);
+ node->obuflen -= node->tcp_wds[0].length;
+ } else
+ node->obuflen = 0;
+ node->tcp_wds[0].length = 0;
+
+ if (node->obuflen == 0)
+ return ret;
+ }
+
/*
* _TCPSend only knows how many wds pointers were passed in when it
* reads the next one and its pointer is zero (or size is zero?)
*/
node->tcp_wds[0].ptr = (Ptr)&node->obuf;
- node->tcp_wds[0].length = node->escaped_obuflen;
+ node->tcp_wds[0].length = node->obuflen;
node->tcp_wds[1].ptr = 0;
node->tcp_wds[1].length = 0;
+
+ ret = node->tcp_wds[0].length;
- now = Ticks;
error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil,
true);
if (error) {
warn("TCPSend[%d] failed: %d", node->id, error);
session->ending = true;
}
-
+
/* if we can send in less than 500ms, avoid a uthread switch */
while (Ticks - now <= 30) {
if (node->send_pb.ioResult <= 0)
goto process_result;
}
- return 0;
+ return ret;
}
void
@@ -890,13 +923,14 @@ telnet_output_iac(struct session *session, const char
{
struct telnet_node *node = (struct telnet_node *)session->cookie;
- session_flush(session);
if (session->ending)
return;
+
+ telnet_escape_output(session);
node->sending_iac = 1;
session_output(session, iacs, len);
- session_flush(session);
+ telnet_escape_output(session);
node->sending_iac = 0;
}