[comp.bugs.4bsd] What happens when someone says they want a TCP MSS=0?

esj@manatee.cis.ufl.edu (Eric S. Johnson) (07/29/89)

(BSD box below means any BSDish machine with the Van Jacobson
TCP routines posted to comp.bugs.4bsd.ucb-fixes last year)

We have a PC here which runs a TCP/IP package which comes from
a 3 letter company. When used to FTP to a BSD box, a get command
causes it to reply to the SYN (sent from BSD box) on FTP-DATA with
a SYN/ACK TCP packet with a option of MSS=0. To me this means the 
PC and its TCP/IP software is broken. 

But... BSD, upon receipt of this SYN/ACK, option MSS=0 packet, proceeds
to start sending continuous ACK packets, with no data (the third part of
the handshake). This continual ACKing will lock up the UNIX box in question,
and wreck havoc on gateways between PC and victim.

A quick look through tcp_output.c shows these code segments to be 
the *apparent* cause:

tcp_output(tp)
	register struct tcpcb *tp;
{
.
.
.
again:
	sendalot = 0;
.
.
.
	if (len > tp->t_maxseg) {
		len = tp->t_maxseg;
		sendalot = 1;
	}
.
.
.
	if (sendalot)
		goto again;
	return (0);
}


I am not a BSD TCP/IP wiz, but if len is >0 when some dopey PC has
requested a tcp mss=0, then packet flooding is what would be expected
from this code eh? Not terribly robust.

So, if I am misunderstanding the cause of the problem, please correct me.
And in any case, how do I fix it?

Ej

dls@mace.cc.purdue.edu (David L Stevens) (07/29/89)

	IP says that any Internet host must be able to receive packets of at
least 576 octets. So any technology using TCP/IP has to allow packets of at
least 576 - 60 (IP headers, max) - 20 (TCP headers, no opts) = 496 octets of
TCP data. Without IP options, it's 516, or 512 to pick a nicer number.
	Though I don't believe RFC 793 says so, packets with MSS of 0, or
really anything less than 496, should be dropped along with implementations
that generate them. To run IP, the machine has to have hardware and buffer
space for packets of at least that size and the window management takes care
of times when load is the problem.
	Note that small MSS's reduce the data/header ratio and thus the
effective throughput, so choosing the largest MSS that IP requires (496)
as the minimum is the best you can do without knowing that the underlying
hardware on the other end can do more.
	Silently enforcing it is a simple "mss = MAX(mss, 496)" in tcp_mss();
sending a RST segment and dropping the packet would be better, though.
-- 
					+-DLS  (dls@mace.cc.purdue.edu)

mogul@decwrl.dec.com (Jeffrey Mogul) (07/29/89)

In article <2809@mace.cc.purdue.edu> dls@mace.cc.purdue.edu (David L Stevens) writes:
>	IP says that any Internet host must be able to receive packets of at
>least 576 octets. So any technology using TCP/IP has to allow packets of at
>least 576 - 60 (IP headers, max) - 20 (TCP headers, no opts) = 496 octets of
>TCP data. Without IP options, it's 516, or 512 to pick a nicer number.

Although any IP host has to be able to receive 576-byte datagrams, I
believe it is legal for a TCP to ask the sender to send smaller ones.
However, I think there is a lower limit on the MSS, because it makes
no sense to set it smaller than 1 byte.

>	Though I don't believe RFC 793 says so, packets with MSS of 0, or
>really anything less than 496, should be dropped along with implementations
>that generate them.

If the MSS is > 0 (and not too large), then the packet is legal and it
should be obeyed.  Owners of implementations that send tiny MSS values
will find out soon enough (as I did a few years ago when we forgot to
byteswap the MSS value before sending it!); the performance impact is
too obvious to overlook.  If the MSS is 0, then this is probably
worth doing something about.  The robustness principle implies that
the TCP receiving this option should do something close to what the
sender means ... so I would probably interpret this as asking for
the minimal "reasonable" MSS, which is 1 byte.  Alternatively, one
could treat this as a bogus value, and use the default.  Perhaps the
"Host Requirements" RFC will say.  Anyway, it does look like it is
wrong to simply accept the MSS=0.

>	Note that small MSS's reduce the data/header ratio and thus the
>effective throughput, so choosing the largest MSS that IP requires (496)
>as the minimum is the best you can do without knowing that the underlying
>hardware on the other end can do more.

On the contrary; there are situations when setting the MSS too large
is counterproductive.  There are still links in the Internet with
MTUs < 256 bytes (!), and over such links it is probably optimal
to use a rather small MSS if there is a high chance that fragments
might be dropped.  4.3BSD-derived systems include code to do this
if the small-MTU network is directly attached to the host.

>	Silently enforcing it is a simple "mss = MAX(mss, 496)" in tcp_mss();
>sending a RST segment and dropping the packet would be better, though.

This almost certainly violates the robustness principle.  Unfortunately,
there is no way for TCP to indicate "bogus value in packet" analogous
to the ICMP parameter problem message (which I don't believe can be
used here).

-Jeff

alan@cunixc.cc.columbia.edu (Alan Crosswell) (08/01/89)

[I sent this to 4bsd-bugs and have opened an S.O. with Sun as well]

Date: Thu, 13 Jul 89 19:20:06 EDT
From: Alan Crosswell <alan@curta.cc.columbia.edu>
To: 4bsd-bugs@berkeley.edu
Cc: unixsys@cunixc.cc.columbia.edu
Reply-To: unixsys@cunixc.cc.columbia.edu
Subject: VJ TCP gets zero divide panic (maxseg option value not sanity-checked)

Index:	sys/netinet/tcp_timer.c 4.3BSD+VJ TCP

Description:
	Our Ultrix 2.0 system which we have merged Van Jacobsen TCP into
	panics with an integer divide by zero arithmetic fault in tcp_timer.c
	where (struct tcpcb) t_maxseg is used as a divisor.  This only
	occurs rarely and is apparently tickled by a SunOS 4.0 bug which
	causes it to occasionally present a max seg of zero.  SunOS 4.0
	apparently suffers from this same bug (3 Suns and a Vax all got
	zero divide panics around the same time last night:-).  This also
	has been tickled by MIT PCIP before.
Repeat-By:
	Run SunOS 4.0 in conjunction with VJ TCP hosts and wait for a
	retransmission timeout on your LAN.  Cisco terminal servers on
	the same LAN (interacting with the Suns) will occassionally
	log an error message like the following to indicate that bad segsizes
	are being presented:
		TCP: bad segsize option 0 from 128.59.40.141
		Process= "IP Input", pid= 4, level=0
	May also be occasionally repeated using PCIP FTP and doing a large
	number of transfers using mget or mput.
Fix:
	In tcp_input.c, add a sanity check for maxseg <= 0 (less than 0
	because PCIP was sending negative maxseg due to some signed
	vs. unsigned bug; equal 0 to prevent zero divide)

		case TCPOPT_MAXSEG:
			if (optlen != 4)
				continue;
			if (!(ti->ti_flags & TH_SYN))
				continue;
			tp->t_maxseg = *(u_short *)(cp + 2);
			tp->t_maxseg = ntohs((u_short)tp->t_maxseg);
			if (tp->t_maxseg <= 0) {
				printf("tcp_input: t_maxseg <= 0 (src %x)\n",
				       ti->ti_src);
				tp->t_maxseg = tcp_mss(tp);
			} else
				tp->t_maxseg = MIN(tp->t_maxseg, tcp_mss(tp));
			break;
		}
	}
	(void) m_free(om);
}