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 about 1 year 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;
 }