--AqsLC8rIMeq19msA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I have found a bug in Net::Telnet - it only occurs infrequently, and
therefore is difficult to track down when it bites you.

The problem is this. Incoming telnet streams use \r\n as the line separator.
In normal operation (binmode¨▒se) Net::Telnet converts this to \n, which
means for example you can match end-of-line using $ in a regexp.

However, if the \r appears at the end of one TCP segment, and the \n at the
start of the next segment, and the kernel passes them in two separate read()
calls, then this translation doesn't work. Therefore sometimes you get \r\n
when you were expecting \n.

Here is a snippet of tcpdump where I suffered this problem in real life:

...
        0x0170:  3230 3920 7570 7469 6d65 2069 7320 3720  209.uptime.is.7.
        0x0180:  6d69 6e75 7465 730d 0a53 7973 7465 6d20  minutes..System.
        0x0190:  7265 7475 726e 6564 2074 6f20 524f 4d20  returned.to.ROM.
        0x01a0:  6279 2072 656c 6f61 6420 6174 2030 393a  by.reload.at.09:
        0x01b0:  3533 3a34 3820 5554 4320 5475 6520 4170  53:48.UTC.Tue.Ap
        0x01c0:  7220 3239 2032 3030 380d 0a53 7973 7465  r.29.2008..Syste
        0x01d0:  6d20 7265 7374 6172 7465 6420 6174 2030  m.restarted.at.0
        0x01e0:  393a 3534 3a33 3420 5554 4320 5475 6520  9:54:34.UTC.Tue.
        0x01f0:  4170 7220 3239 2032 3030 380d 0a53 7973  Apr.29.2008..Sys
        0x0200:  7465 6d20 696d 6167 6520 6669 6c65 2069  tem.image.file.i
        0x0210:  7320 2266 6c61 7368 3a63 3837 302d 6164  s."flash:c870-ad
        0x0220:  7669 7073 6572 7669 6365 736b 392d 6d7a  vipservicesk9-mz
        0x0230:  2e31 3234 2d31 352e 5431 2e62 696e 220d  .124-15.T1.bin".
11:02:06.106088 IP 10.2.10.188.36549 > 10.145.0.209.telnet: . ack 1904 win 8576
        0x0000:  4500 0028 3eb6 4000 4006 dbfa 0a02 0abc  E..(>.@. / .......
        0x0010:  0a91 00d1 8ec5 0017 34df 2b07 532b 1f71  ........4.+.S+.q
        0x0020:  5010 2180 0cd6 0000                      P.!.....
11:02:06.140684 IP 10.145.0.209.telnet > 10.2.10.188.36549: . 1904:2440(536) ack 88 win 4041
        0x0000:  4500 0240 b594 0000 fc06 e703 0a91 00d1  E.. / ............
        0x0010:  0a02 0abc 0017 8ec5 532b 1f71 34df 2b07  ........S+.q4.+.
        0x0020:  5010 0fc9 4d00 0000 0a4c 6173 7420 7265  P...M....Last.re
        0x0030:  6c6f 6164 2072 6561 736f 6e3a 2052 656c  load.reason:.Rel
        0x0040:  6f61 6420 436f 6d6d 616e 640d 0a0d 0a0d  oad.Command.....
...

I was matching the input against the regexp

    /^System image file is "(?:flash:)?(.*)"$/

to capture the firmware version, which worked 99% of the time, but failed
occasionally. In the above example, it only happened because the device
uptime was a single character (*7* minutes). When it increased to 10 minutes
all the characters shifted along one and the newline was translated properly
again!

I have been able to replicate the problem using the attached pair of
programs, which should run on two different machines connected via ethernet.
It may need some tweaking so that tcpdump shows 0d at the end of one packet
and 0a at the start of the next:

    tcpdump -i eth0 -n -s0 -X tcp port 12345

Under Linux I found that the TCP timestamp option was taking up 12 bytes, in
addition to the IP and TCP headers (40 bytes).

I haven't attempted to fix the code myself. Net::Telnet is sufficiently
complex that I'd rather not meddle :-) It appears that there are some
two and three-byte IAC sequences which could also suffer from this.

As a workaround you can set binmodeue and perform your own end-of-line
conversion, but this also affects the strings you *send*.

BTW, this is all under ruby-1.8.6p114. However I've looked at net/telnet.rb
from trunk, and it's identical apart from one extra gsub, and therefore I
believe it will have the same problem.

Regards,

Brian Candler.

--AqsLC8rIMeq19msA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="demo-server.rb"

require 'socket'
mtu  500   # standard ethernet
  
s_sock  CPServer.new('0.0.0.0',12345)
s_conn  _sock.accept
response   x" * (mtu - 40 - 12 - 10)  # IP/TCP hdr, timestamp, gap
response << "123456789\r"               # fill the gap
s_conn << response
sleep 1
s_conn << "\nxxxxxxxxxDONE\r\n"
s_conn.close

--AqsLC8rIMeq19msA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="demo-client.rb"

#!/usr/bin/ruby -w

ą╗gin
This demonstrates a problem with Net::Telnet. If the \r\n line terminator
is split between the end of one TCP segment and the beginning of the next,
then it is not translated to \n properly, and so /...$/ fails to match
within the string.
▀┼

require 'net/telnet'
host  172.31.131.190'  # INSERT SERVER IP ADDRESS HERE

c  et::Telnet.new(
	'Host' host,
	'Port' 12345
)
response  .waitfor(/DONE/)

raise "Mismatch?? #{response.inspect}" unless response /6789$/
puts "All seems OK"

--AqsLC8rIMeq19msA--