2ping-2.0/0000755000175000017500000000000011745113503011076 5ustar ryanryan2ping-2.0/README0000644000175000017500000000236511455146377012001 0ustar ryanryan2ping, a bi-directional ping utility By Ryan Finnie http://www.finnie.org/software/2ping/ ABOUT 2ping is a bi-directional ping utility. It uses 3-way pings (akin to TCP SYN, SYN/ACK, ACK) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs. INSTALLATION 2ping requires Perl 5.6.0 or newer. 2ping requires several Perl modules, all of which are in a standard Perl installation (though your distribution may split Perl into "base" and "standard" packages). Several non-standard packages are required for optional features, and can be installed from you distribution's package management system or CPAN. They are: IO::Socket::INET6 (for IPv6 support) Digest::SHA (for HMAC-SHA1 or HMAC-SHA256 auth support) Digest::CRC (for HMAC-CRC32 support) To "build" and install 2ping: make make test make install make clean /usr/local is the default Makefile prefix, use PREFIX to override. USAGE Please see the man page for full details, but in short, start a listener on the far end: 2ping --listen And run 2ping on the near end, connecting to the far end listener: 2ping $LISTENER Where "$LISTENER" is the name or IP address of the listener. 2ping-2.0/2ping.80000664000175000017500000003376011745113476012232 0ustar ryanryan.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is turned on, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .ie \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . nr % 0 . rr F .\} .el \{\ . de IX .. .\} .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "2PING 8" .TH 2PING 8 "2012-04-22" "" "" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" 2ping \- A bi\-directional ping client/listener .SH "SYNOPSIS" .IX Header "SYNOPSIS" \&\fB2ping\fR [\ \fBoptions\fR\ ] \fB\-\-listen\fR\ |\ \fIhost/IP\fR .SH "DESCRIPTION" .IX Header "DESCRIPTION" \&\fB2ping\fR is a bi-directional ping utility. It uses 3\-way pings (akin to \&\s-1TCP\s0 \s-1SYN\s0, \s-1SYN/ACK\s0, \s-1ACK\s0) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs. .PP To use 2ping, start a listener on a known stable network host. The relative network stability of the 2ping listener host should not be in question, because while 2ping can determine whether packet loss is occurring inbound or outbound relative to an endpoint, that will not help you determine the cause if both of the endpoints are in question. .PP Once the listener is started, start 2ping in client mode and tell it to connect to the listener. The ends will begin pinging each other and displaying network statistics. If packet loss occurs, 2ping will wait a few seconds (default 10, configurable with \-w) before comparing notes between the two endpoints to determine which direction the packet loss is occurring. .PP To quit 2ping on the client or listener ends, enter ^C, and a list of statistics will be displayed. To get a short inline display of statistics without quitting, send the process a \s-1QUIT\s0 signal (yes, that's the opposite of what you would think, but it's in line with the normal ping utility). .SH "OPTIONS" .IX Header "OPTIONS" \&\fBping\fR\-compatible options: .IP "\fB\-a\fR" 4 .IX Item "-a" Audible ping. .IP "\fB\-A\fR" 4 .IX Item "-A" Adaptive ping. A new client ping request is sent as soon as a client ping response is received. If a ping response is not received within the interval period, a new ping request is sent. Minimal interval is 200msec for not super-user. On networks with low rtt this mode is essentially equivalent to flood mode. .Sp \&\fB2ping\fR\-specific notes: This behavior is somewhat different to the nature of \fBping\fR's adaptive ping, but the result is roughly the same. .IP "\fB\-c\fR \fIcount\fR" 4 .IX Item "-c count" Stop after sending \fIcount\fR ping requests. .Sp \&\fB2ping\fR\-specific notes: This option behaves slightly differently from \fBping\fR. If both \fB\-c\fR and \fB\-w\fR are specified, satisfaction of \fB\-c\fR will cause an exit first. Also, internally, \fB2ping\fR exits just before sending \fIcount\fR+1 pings, to give time for the ping to complete. .IP "\fB\-f\fR" 4 .IX Item "-f" Flood ping. For every ping sent a period \*(L".\*(R" is printed, while for ever ping received a backspace is printed. This provides a rapid display of how many pings are being dropped. If interval is not given, it sets interval to zero and outputs pings as fast as they come back or one hundred times per second, whichever is more. Only the super-user may use this option with zero interval. .Sp \&\fB2ping\fR\-specific notes: Detected outbound/inbound loss responses are printed as \*(L">\*(R" and \*(L"<\*(R", respectively. Receive errors are printed as \*(L"E\*(R". Due to the asynchronous nature of \fB2ping\fR, successful responses (backspaces) may overwrite these loss and error characters. .IP "\fB\-i\fR \fIinterval\fR" 4 .IX Item "-i interval" Wait \fIinterval\fR seconds between sending each ping. The default is to wait for one second between each ping normally, or not to wait in flood mode. Only super-user may set interval to values less 0.2 seconds. .IP "\fB\-I\fR \fIinterface \s-1IP\s0\fR" 4 .IX Item "-I interface IP" Set source \s-1IP\s0 address. When pinging IPv6 link-local address this option is required. When in listener mode, this option may be specified multiple to bind to multiple \s-1IP\s0 addresses. When in client mode, this option may only be specified once, and all outbound pings will be bound to this source \s-1IP\s0. .Sp \&\fB2ping\fR\-specific notes: This option only takes an \s-1IP\s0 address, not a device name. Note that in listener mode, if the machine has an interface with multiple \s-1IP\s0 addresses and an request comes in via a sub \s-1IP\s0, the reply still leaves via the interface's main \s-1IP\s0. So this option must be used if you would like to respond via an interface's sub-IP. .IP "\fB\-l\fR \fIpreload\fR" 4 .IX Item "-l preload" If \fIpreload\fR is specified, \fB2ping\fR sends that many packets not waiting for reply. Only the super-user may select preload more than 3. .IP "\fB\-p\fR \fIpattern\fR" 4 .IX Item "-p pattern" You may specify up to 16 \*(L"pad\*(R" bytes to fill out the packets you send. This is useful for diagnosing data-dependent problems in a network. For example, \fB\-p ff\fR will cause the sent packet pad area to be filled with all ones. .Sp \&\fB2ping\fR\-specific notes: This pads the portion of the packet that does not contain the active payload data. If the active payload data is larger than the minimum packet size (\fB\-\-min\-packet\-size\fR=\fImin\fR), no padding will be sent. .IP "\fB\-q\fR" 4 .IX Item "-q" Quiet output. Nothing is displayed except the summary lines at startup time and when finished. .IP "\fB\-s\fR \fIpacketsize\fR" 4 .IX Item "-s packetsize" \&\fBping\fR compatibility, this will set \fB\-\-min\-packet\-size\fR to this plus 8 bytes. .IP "\fB\-v\fR" 4 .IX Item "-v" Verbose output. In \fB2ping\fR, this prints decodes of packets that are sent and received. .IP "\fB\-V\fR" 4 .IX Item "-V" Show version and exit. .IP "\fB\-w\fR \fIdeadline\fR" 4 .IX Item "-w deadline" Specify a timeout, in seconds, before \fB2ping\fR exits regardless of how many pings have been sent or received. Due to blocking, this may occur up to one second after the deadline specified. .Sp \&\fB2ping\fR\-specific notes: This option behaves slightly differently from \fBping\fR. If both \fB\-c\fR and \fB\-w\fR are specified, satisfaction of \fB\-c\fR will cause an exit first. .PP \&\fB2ping\fR\-specific options: .IP "\fB\-?\fR, \fB\-\-help\fR" 4 .IX Item "-?, --help" Print a synposis and exit. .IP "\fB\-6\fR, \fB\-\-ipv6\fR" 4 .IX Item "-6, --ipv6" Bind/connect as IPv6. .IP "\fB\-\-auth\fR=\fIkey\fR" 4 .IX Item "--auth=key" Set a shared key, send cryptographic hashes with each packet, and require cryptographic hashes from peer packets signed with the same shared key. .IP "\fB\-\-auth\-digest\fR=\fIdigest\fR" 4 .IX Item "--auth-digest=digest" When \fB\-\-auth\fR is used, specify the digest type to compute the cryptographic hash. Valid options are \fBhmac\-md5\fR (default), \fBhmac\-sha1\fR and \fBhmac\-sha256\fR. hmac\-md5 requires \fBDigest::MD5\fR, and the \s-1SHA\s0 digests require \fBDigest::SHA\fR. .IP "\fB\-\-debug\fR" 4 .IX Item "--debug" Print (lots of) debugging information. .IP "\fB\-\-inquire\-wait\fR=\fIsecs\fR" 4 .IX Item "--inquire-wait=secs" Wait at least \fIsecs\fR seconds before inquiring about a lost packet. Default is 10 seconds. \s-1UDP\s0 packets can arrive delayed or out of order, so it is best to give it some time before inquiring about a lost packet. .IP "\fB\-\-listen\fR" 4 .IX Item "--listen" Start as a listener. The listener will not send out ping requests at regular intervals, and will instead wait for the far end to initiate ping requests. A listener is required as the remote end for a client. .IP "\fB\-\-min\-packet\-size\fR=\fImin\fR" 4 .IX Item "--min-packet-size=min" Set the minimum total payload size to \fImin\fR bytes, default 128. If the payload is smaller than \fImin\fR bytes, padding will be added to the end of the packet. .IP "\fB\-\-max\-packet\-size\fR=\fImax\fR" 4 .IX Item "--max-packet-size=max" Set the maximum total payload size to \fImax\fR bytes, default 512, absolute minimum 64. If the payload is larger than \fImax\fR bytes, information will be rearranged and sent in future packets when possible. .IP "\fB\-\-no\-3way\fR" 4 .IX Item "--no-3way" Do not perform 3\-way pings. Used most often when combined with \fB\-\-listen\fR, as the listener is usually the one to determine whether a ping reply should become a 3\-way ping. .Sp Strictly speaking, a 3\-way ping is not necessary for determining directional packet loss between the client and the listener. However, the extra leg of the 3\-way ping allows for extra chances to determine packet loss more efficiently. Also, with 3\-way ping disabled, the listener will receive no client performance indicators, nor will the listener be able to determine directional packet loss that it detects. .IP "\fB\-\-no\-match\-packet\-size\fR" 4 .IX Item "--no-match-packet-size" When sending replies, 2ping will try to match the packet size of the received packet by adding padding if necessary, but will not exceed \fB\-\-max\-packet\-size\fR. \fB\-\-no\-match\-packet\-size\fR disabled this behavior, always setting the minimum to \fB\-\-min\-packet\-size\fR. .IP "\fB\-\-no\-send\-version\fR" 4 .IX Item "--no-send-version" Do not send the current running version of 2ping with each packet. .IP "\fB\-\-notice\fR=\fItext\fR" 4 .IX Item "--notice=text" Arbitrary notice text to send with each packet. If the remote peer supports it, this may be displayed to the user. .IP "\fB\-\-packet\-loss\fR=\fIout:in\fR" 4 .IX Item "--packet-loss=out:in" Simulate random packet loss outbound and inbound. For example, \fI25:10\fR means a 25% chance of not sending a packet, and a 10% chance of ignoring a received packet. A single number without colon separation means use the same percentage for both outbound and inbound. .IP "\fB\-\-port\fR=\fIport\fR" 4 .IX Item "--port=port" Use \s-1UDP\s0 port \fIport\fR. With \fB\-\-listen\fR, this is the port to bind as, otherwise this is the port to send to. Default is \s-1UDP\s0 port 15998. .IP "\fB\-\-stats\fR=\fIinterval\fR" 4 .IX Item "--stats=interval" Print a line of brief current statistics every \fIinterval\fR seconds. The same line can be printed on demand by sending \s-1SIGQUIT\s0 to the 2ping process. .SH "BUGS" .IX Header "BUGS" There are probably lots and lots and lots of unknown bugs. .PP By default, source \s-1IP\s0 logic doesn't work as expected, see \fB\-I\fR for details. There appears to be no way to peg the source \s-1IP\s0 of reply \s-1UDP\s0 packets to the destination of the packet that is being replied to. As a result, packets always go out the interface's main \s-1IP\s0 address if not specified manually. (Please, prove the author wrong.) .PP This manpage isn't finished yet, and may never be. .SH "AUTHOR" .IX Header "AUTHOR" \&\fB2ping\fR was written by Ryan Finnie . 2ping-2.0/2ping.spec0000644000175000017500000000176211745112236013001 0ustar ryanryanName: 2ping Version: 2.0 Release: 1%{?dist} Summary: A bi-directional ping utility Group: Applications/System License: GPLv2+ URL: http://www.finnie.org/software/2ping/ Source0: http://www.finnie.org/software/2ping/2ping-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch %description 2ping is a bi-directional ping utility. It uses 3-way pings (akin to TCP SYN, SYN/ACK, ACK) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs. %prep %setup -q %build make EXTRAVERSION=-$RPM_PACKAGE_RELEASE %install rm -rf $RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT %clean make clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) /usr/local/bin/2ping /usr/local/bin/2ping6 /usr/local/share/man/man8/2ping.8 /usr/local/share/man/man8/2ping6.8 %doc README %doc COPYING %changelog 2ping-2.0/2ping.8.html0000664000175000017500000003273311745113476013174 0ustar ryanryan 2ping - A bi-directional ping client/listener


NAME

2ping - A bi-directional ping client/listener


SYNOPSIS

2pingoptions ] --listen | host/IP


DESCRIPTION

2ping is a bi-directional ping utility. It uses 3-way pings (akin to TCP SYN, SYN/ACK, ACK) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs.

To use 2ping, start a listener on a known stable network host. The relative network stability of the 2ping listener host should not be in question, because while 2ping can determine whether packet loss is occurring inbound or outbound relative to an endpoint, that will not help you determine the cause if both of the endpoints are in question.

Once the listener is started, start 2ping in client mode and tell it to connect to the listener. The ends will begin pinging each other and displaying network statistics. If packet loss occurs, 2ping will wait a few seconds (default 10, configurable with -w) before comparing notes between the two endpoints to determine which direction the packet loss is occurring.

To quit 2ping on the client or listener ends, enter ^C, and a list of statistics will be displayed. To get a short inline display of statistics without quitting, send the process a QUIT signal (yes, that's the opposite of what you would think, but it's in line with the normal ping utility).


OPTIONS

ping-compatible options:

-a

Audible ping.

-A

Adaptive ping. A new client ping request is sent as soon as a client ping response is received. If a ping response is not received within the interval period, a new ping request is sent. Minimal interval is 200msec for not super-user. On networks with low rtt this mode is essentially equivalent to flood mode.

2ping-specific notes: This behavior is somewhat different to the nature of ping's adaptive ping, but the result is roughly the same.

-c count

Stop after sending count ping requests.

2ping-specific notes: This option behaves slightly differently from ping. If both -c and -w are specified, satisfaction of -c will cause an exit first. Also, internally, 2ping exits just before sending count+1 pings, to give time for the ping to complete.

-f

Flood ping. For every ping sent a period "." is printed, while for ever ping received a backspace is printed. This provides a rapid display of how many pings are being dropped. If interval is not given, it sets interval to zero and outputs pings as fast as they come back or one hundred times per second, whichever is more. Only the super-user may use this option with zero interval.

2ping-specific notes: Detected outbound/inbound loss responses are printed as ">" and "<", respectively. Receive errors are printed as "E". Due to the asynchronous nature of 2ping, successful responses (backspaces) may overwrite these loss and error characters.

-i interval

Wait interval seconds between sending each ping. The default is to wait for one second between each ping normally, or not to wait in flood mode. Only super-user may set interval to values less 0.2 seconds.

-I interface IP

Set source IP address. When pinging IPv6 link-local address this option is required. When in listener mode, this option may be specified multiple to bind to multiple IP addresses. When in client mode, this option may only be specified once, and all outbound pings will be bound to this source IP.

2ping-specific notes: This option only takes an IP address, not a device name. Note that in listener mode, if the machine has an interface with multiple IP addresses and an request comes in via a sub IP, the reply still leaves via the interface's main IP. So this option must be used if you would like to respond via an interface's sub-IP.

-l preload

If preload is specified, 2ping sends that many packets not waiting for reply. Only the super-user may select preload more than 3.

-p pattern

You may specify up to 16 "pad" bytes to fill out the packets you send. This is useful for diagnosing data-dependent problems in a network. For example, -p ff will cause the sent packet pad area to be filled with all ones.

2ping-specific notes: This pads the portion of the packet that does not contain the active payload data. If the active payload data is larger than the minimum packet size (--min-packet-size=min), no padding will be sent.

-q

Quiet output. Nothing is displayed except the summary lines at startup time and when finished.

-s packetsize

ping compatibility, this will set --min-packet-size to this plus 8 bytes.

-v

Verbose output. In 2ping, this prints decodes of packets that are sent and received.

-V

Show version and exit.

-w deadline

Specify a timeout, in seconds, before 2ping exits regardless of how many pings have been sent or received. Due to blocking, this may occur up to one second after the deadline specified.

2ping-specific notes: This option behaves slightly differently from ping. If both -c and -w are specified, satisfaction of -c will cause an exit first.

2ping-specific options:

-?, --help

Print a synposis and exit.

-6, --ipv6

Bind/connect as IPv6.

--auth=key

Set a shared key, send cryptographic hashes with each packet, and require cryptographic hashes from peer packets signed with the same shared key.

--auth-digest=digest

When --auth is used, specify the digest type to compute the cryptographic hash. Valid options are hmac-md5 (default), hmac-sha1 and hmac-sha256. hmac-md5 requires Digest::MD5, and the SHA digests require Digest::SHA.

--debug

Print (lots of) debugging information.

--inquire-wait=secs

Wait at least secs seconds before inquiring about a lost packet. Default is 10 seconds. UDP packets can arrive delayed or out of order, so it is best to give it some time before inquiring about a lost packet.

--listen

Start as a listener. The listener will not send out ping requests at regular intervals, and will instead wait for the far end to initiate ping requests. A listener is required as the remote end for a client.

--min-packet-size=min

Set the minimum total payload size to min bytes, default 128. If the payload is smaller than min bytes, padding will be added to the end of the packet.

--max-packet-size=max

Set the maximum total payload size to max bytes, default 512, absolute minimum 64. If the payload is larger than max bytes, information will be rearranged and sent in future packets when possible.

--no-3way

Do not perform 3-way pings. Used most often when combined with --listen, as the listener is usually the one to determine whether a ping reply should become a 3-way ping.

Strictly speaking, a 3-way ping is not necessary for determining directional packet loss between the client and the listener. However, the extra leg of the 3-way ping allows for extra chances to determine packet loss more efficiently. Also, with 3-way ping disabled, the listener will receive no client performance indicators, nor will the listener be able to determine directional packet loss that it detects.

--no-match-packet-size

When sending replies, 2ping will try to match the packet size of the received packet by adding padding if necessary, but will not exceed --max-packet-size. --no-match-packet-size disabled this behavior, always setting the minimum to --min-packet-size.

--no-send-version

Do not send the current running version of 2ping with each packet.

--notice=text

Arbitrary notice text to send with each packet. If the remote peer supports it, this may be displayed to the user.

--packet-loss=out:in

Simulate random packet loss outbound and inbound. For example, 25:10 means a 25% chance of not sending a packet, and a 10% chance of ignoring a received packet. A single number without colon separation means use the same percentage for both outbound and inbound.

--port=port

Use UDP port port. With --listen, this is the port to bind as, otherwise this is the port to send to. Default is UDP port 15998.

--stats=interval

Print a line of brief current statistics every interval seconds. The same line can be printed on demand by sending SIGQUIT to the 2ping process.


BUGS

There are probably lots and lots and lots of unknown bugs.

By default, source IP logic doesn't work as expected, see -I for details. There appears to be no way to peg the source IP of reply UDP packets to the destination of the packet that is being replied to. As a result, packets always go out the interface's main IP address if not specified manually. (Please, prove the author wrong.)

This manpage isn't finished yet, and may never be.


AUTHOR

2ping was written by Ryan Finnie <ryan@finnie.org>.

2ping-2.0/src/0000775000175000017500000000000011744674152011702 5ustar ryanryan2ping-2.0/src/2ping.pl0000755000175000017500000020663311745112226013257 0ustar ryanryan#!/usr/bin/perl -w ######################################################################## # 2ping, a bi-directional ping utility # Copyright (C) 2010 Ryan Finnie # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. ######################################################################## my $VERSION = '2.0'; my $EXTRAVERSION = '#EXTRAVERSION#'; use warnings; use strict; use Getopt::Long; use Pod::Usage; use IO::Select; use IO::Socket::INET; use Time::HiRes qw/time/; # IO::Socket::INET6 may be loaded below # Digest::MD5 may be loaded below # Digest::SHA may be loaded below # Digest::CRC may be loaded below # "use constant {}" doesn't seem available in Perl 5.6, everything else # works in 5.6, so we may as well make it backwards compatible to that. use constant OPCODE_SENDREPLY => 1; use constant OPCODE_REPLYTO => 2; use constant OPCODE_RTT => 4; use constant OPCODE_LOSTFOUNDS => 8; use constant OPCODE_LOSTNOTFOUNDS => 16; use constant OPCODE_LOSTPACKETS => 32; use constant OPCODE_COURTESIES => 64; use constant OPCODE_HASH => 128; use constant OPCODE_DELAY => 256; use constant OPCODE_EXTENDED => 32768; use constant EXTENDED_ID_VERSION => 0x3250564e; use constant EXTENDED_ID_NOTICE => 0xa837b44e; use constant LEN_MAGIC_NUMBER => 2; use constant LEN_CHECKSUM => 2; use constant LEN_MESSAGE_ID => 6; use constant LEN_OPCODES => 2; use constant LEN_OPCODE_HEADER_LEN => 2; use constant LEN_ARRAY_CNT => 2; use constant LEN_RTT => 4; use constant LEN_DIGEST_TYPE => 2; use constant LEN_DELAY => 4; use constant LEN_EXTENDED_ID => 4; use constant LEN_EXTENDED_LEN => 2; my $versionstring = sprintf('2ping %s%s', $VERSION, ($EXTRAVERSION eq ('#'.'EXTRAVERSION'.'#') ? '' : $EXTRAVERSION) ); ######################################################################## # Command line option parsing ######################################################################## my( $opt_ignoreopt, $opt_ignoreopt_val, $opt_help, $opt_listen, $opt_debug, $opt_interval, $opt_packetloss, $opt_inqwait, $opt_minpacket, $opt_maxpacket, $opt_port, $opt_ipv6, @opt_intaddrs, $opt_flood, $opt_audible, $opt_verbose, $opt_compat_packetsize, $opt_pad_pattern, $opt_quiet, $opt_version, $opt_adaptive, $opt_count, $opt_deadline, $opt_3way, $opt_preload, $opt_auth, $opt_authdigest, $opt_stats, $opt_notice, $opt_sendversion, $opt_matchpacketsize, ); $opt_preload = 1; $opt_sendversion = 1; $opt_3way = 1; $opt_matchpacketsize = 1; Getopt::Long::Configure("bundling"); my($result) = GetOptions( 'b|B|d|L|n|R|r|U' => \$opt_ignoreopt, 'F|Q|S|t|T|M|W=s' => \$opt_ignoreopt_val, 'a' => \$opt_audible, 'A' => \$opt_adaptive, 'c=i' => \$opt_count, 'f' => \$opt_flood, 'i=f' => \$opt_interval, 'I=s' => \@opt_intaddrs, 'l=i' => \$opt_preload, 'p=s' => \$opt_pad_pattern, 'q' => \$opt_quiet, 's=i' => \$opt_compat_packetsize, 'v' => \$opt_verbose, 'V' => \$opt_version, 'w=f' => \$opt_deadline, 'help|?' => \$opt_help, 'auth=s' => \$opt_auth, 'auth-digest=s' => \$opt_authdigest, 'debug' => \$opt_debug, 'inquire-wait=f' => \$opt_inqwait, 'ipv6|6' => \$opt_ipv6, 'listen' => \$opt_listen, 'min-packet-size=i' => \$opt_minpacket, 'max-packet-size=i' => \$opt_maxpacket, '3way!' => \$opt_3way, 'packet-loss=s' => \$opt_packetloss, 'port=i' => \$opt_port, 'stats=f' => \$opt_stats, 'notice=s' => \$opt_notice, 'send-version!' => \$opt_sendversion, 'match-packet-size!' => \$opt_matchpacketsize, ); if($opt_version) { print "$versionstring\n"; exit; } # If called as "2ping6", assume -6 if($0 =~ /[\/\\^]2ping6$/) { $opt_ipv6 = 1; } if(((scalar @ARGV == 0) && !$opt_listen) || $opt_help) { print STDERR "$versionstring\n"; print STDERR "Copyright (C) 2010 Ryan Finnie \n"; print STDERR "\n"; pod2usage(2); } my($authhashf); my($authhashint); my($authhashlen); if($opt_auth) { $opt_authdigest = 'hmac-md5' unless($opt_authdigest); unless(grep $_ eq $opt_authdigest, qw/hmac-md5 hmac-sha1 hmac-sha256 hmac-crc32/) { print STDERR "2ping: Invalid hash algorithm specified. Valid alogorithms: hmac-md5 hmac-sha1 hmac-sha256 hmac-crc32\n"; exit 2; } if($opt_authdigest eq "hmac-md5") { require Digest::MD5; $authhashf = \&Digest::MD5::md5; $authhashint = 1; $authhashlen = 16; } elsif($opt_authdigest eq "hmac-crc32") { require Digest::CRC; $authhashf = \&crc32_bin; $authhashint = 4; $authhashlen = 4; } else { require Digest::SHA; if($opt_authdigest eq "hmac-sha1") { $authhashf = \&Digest::SHA::sha1; $authhashint = 2; $authhashlen = 20; } elsif($opt_authdigest eq "hmac-sha256") { $authhashf = \&Digest::SHA::sha256; $authhashint = 3; $authhashlen = 32; } } } if(($opt_preload < 1) || ($opt_preload > 65536)) { print STDERR "2ping: bad preload value, should be 1..65536\n"; exit 2; } if(($opt_preload > 3) && ($> > 0)) { print STDERR "2ping: cannot set preload to value > 3\n"; exit 2; } # "Real flood" is sending as fast as possible. If the user specified both # flood and an interval, it's not really flood mode, just a shortened # output mode. Otherwise, send as fast as the replies come in, or 100 # times per second, whichever is greater. my($opt_realflood) = 0; if($opt_flood && !$opt_interval) { if($> > 0) { print STDERR "2ping: cannot flood; minimal interval, allowed for user, is 200ms\n"; exit 2; } $opt_interval = 0.01; $opt_realflood = 1; } if($opt_adaptive && !$opt_interval && ($> > 0)) { $opt_interval = 0.2; } $opt_interval = 1 unless $opt_interval; if(($> > 0) && ($opt_interval < 0.2)) { print STDERR "2ping: cannot flood; minimal interval, allowed for user, is 200ms\n"; exit 2; } # Default time to wait before inquiring about lost packets. $opt_inqwait = 10 unless $opt_inqwait; # -s: ping compatibility. Set $opt_minpacket to this plus 8. $opt_minpacket = $opt_compat_packetsize + 8 if(!$opt_minpacket && $opt_compat_packetsize); # Default minimum/maximum packet sizes. Absolute minimum maximum (if that # makes sense) is 64. $opt_minpacket = 128 unless $opt_minpacket; $opt_maxpacket = 512 unless $opt_maxpacket; $opt_maxpacket = 64 if($opt_maxpacket < 64); $opt_minpacket = $opt_maxpacket if($opt_minpacket > $opt_maxpacket); # Default UDP port (IANA-registered port for 2ping) $opt_port = 15998 unless $opt_port; # Build a pad pattern from user input. # This could probably be cleaned up. my($pad_pattern); if($opt_pad_pattern) { unless($opt_pad_pattern =~ /^[0-9A-Fa-f]+$/) { die("2ping: patterns must be specified as hex digits.\n"); } my($i) = 0; my($pattern_in) = $opt_pad_pattern; while(length($pattern_in) > 0) { my($hexbyte); if(length($pattern_in) == 1) { $hexbyte = $pattern_in . '0'; $pattern_in = ''; } else { $hexbyte = substr($pattern_in, 0, 2); $pattern_in = substr($pattern_in, 2); } $pad_pattern .= chr(hex($hexbyte)); $i++; last if $i == 16; } } if($pad_pattern) { printf("PATTERN: 0x%*v02x\n", '', $pad_pattern); } else { $pad_pattern = chr(0); } # Config simulated packet loss. my($opt_packetloss_out) = 0; my($opt_packetloss_in) = 0; if($opt_packetloss) { if($opt_packetloss =~ /^(\d+):(\d+)$/) { $opt_packetloss_out = $1; $opt_packetloss_in = $2; } elsif($opt_packetloss =~ /^(\d+)$/) { $opt_packetloss_out = $opt_packetloss; $opt_packetloss_in = $opt_packetloss; } } # Set up signals $SIG{ALRM} = \&processsigalrm; $SIG{INT} = \&processsigint; $SIG{QUIT} = \&processsigquit; ######################################################################## # Global variables ######################################################################## # Script execution start time my($starttime) = time; # Incrementing index count, by peer session (ping_seq=1, etc) my(%cntbypeer) = (); # Total raw packets sent my($packetsout) = 0; # Total raw packets received my($packetsin) = 0; # Total ping requests sent my($pingsout) = 0; # Total ping requests received successfully my($pingsin) = 0; # Total errors (ICMP errors, invalid checksum, etc) my($errors) = 0; # RTT sum in ms ($pingsinrttsum / $pingsin = avg rtt) my($pingsinrttsum) = 0; # Sum of squares of RTTs, for quick stddev calculation my($pingsinrttsumsq) = 0; # Exponentially weighted moving average my($pingsinewma) = 0; # Maximum single RTT computed my($pingsinrttmax) = 0; # Minimum single RTT computed my($pingsinrttmin) = 0; # Confirmed outbound pings lost my($outlost) = 0; # Confirmed inbound pings lost my($inlost) = 0; # Sent message ID info hash that we expect replies to my(%msginfo) = (); # Received message ID info hash that we responded to my(%msgreplyinfo) = (); # IO::Socket objects my(@socks); # Last time %msginfo and %msgreplyinfo were cleaned up my($lastcleanup) = time; # Last time a packet was received my($lastreply) = 0; # Last time a scheduled interval was run (usually results in a new ping # being sent, but may not in flood/adaptive mode) my($lastsched) = time; # Last time a stats line was printed my($laststats) = time; ######################################################################## # Socket setup ######################################################################## # Many systems don't have IO::Socket::INET6, so don't try to load it # unless needed. if($opt_ipv6) { require IO::Socket::INET6; } # Turn off STDOUT buffering. STDOUT->autoflush(1); if($opt_listen) { my(@working_opt_intaddrs) = @opt_intaddrs; if((scalar @working_opt_intaddrs) == 0) { push(@working_opt_intaddrs, undef); } foreach my $opt_intaddr (@working_opt_intaddrs) { my($sock); my($is_ipv6) = $opt_ipv6; if($opt_ipv6 && $opt_intaddr =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { $is_ipv6 = 0; } my $sockerr = ''; my $sockaddlerr = ''; if($is_ipv6) { $sock = IO::Socket::INET6->new( Domain => 10, # AF_INET6 LocalAddr => ($opt_intaddr ? $opt_intaddr : undef), LocalPort => $opt_port, Proto => 'udp' ); unless($sock) { $sockerr = $!; $sockaddlerr = $@; } } else { $sock = IO::Socket::INET->new( Domain => 2, # AF_INET LocalAddr => ($opt_intaddr ? $opt_intaddr : undef), LocalPort => $opt_port, Proto => 'udp' ); unless($sock) { $sockerr = $!; $sockaddlerr = $@; } } die sprintf("Could not create socket: %s%s\n", $sockerr, ($sockaddlerr ? " ($sockaddlerr)" : "")) unless $sock; binmode($sock, ':raw'); printf("2PING listener (%s): %d to %d bytes of data.\n", $sock->sockhost, $opt_minpacket, $opt_maxpacket); push(@socks, $sock); } } else { my($opt_intaddr) = undef; if((scalar @opt_intaddrs) > 1) { $opt_intaddr = $opt_intaddrs[0]; } foreach my $to (@ARGV) { my($sock); my($is_ipv6) = $opt_ipv6; # If -6 is specified and an IPv4-like argument is given, use the IPv4 # stack instead. if($opt_ipv6 && $to =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { $is_ipv6 = 0; } my $sockerr = ''; my $sockaddlerr = ''; if($is_ipv6) { $sock = IO::Socket::INET6->new( Domain => 10, # AF_INET6 LocalAddr => ($opt_intaddr ? $opt_intaddr : undef), PeerPort => $opt_port, PeerAddr => $to, Proto => 'udp', ); unless($sock) { $sockerr = $!; $sockaddlerr = $@; } } else { $sock = IO::Socket::INET->new( Domain => 2, # AF_INET LocalAddr => ($opt_intaddr ? $opt_intaddr : undef), PeerPort => $opt_port, PeerAddr => $to, Proto => 'udp', ); unless($sock) { $sockerr = $!; $sockaddlerr = $@; } } die sprintf("Could not create socket: %s%s\n", $sockerr, ($sockaddlerr ? " ($sockaddlerr)" : "")) unless $sock; printf("2PING %s (%s): %d to %d bytes of data.\n", $to, $sock->peerhost, $opt_minpacket, $opt_maxpacket); binmode($sock, ':raw'); push(@socks, $sock); my(%peer) = ( 'sock' => $sock, ); $peer{'peerhost'} = $peer{'sock'}->peerhost; $peer{'peeraddr'} = $peer{'sock'}->peeraddr; $peer{'peerport'} = $peer{'sock'}->peerport; $peer{'peertuple'} = $peer{'sock'}->sockaddr . $peer{'sock'}->sockport . $peer{'sock'}->peeraddr . $peer{'sock'}->peerport . $peer{'sock'}->protocol; $peer{'peername'} = $peer{'sock'}->peername; # Immediately send a new ping to start out in client mode. for(my $i = 1; $i <= $opt_preload; $i++) { sendpacket(\%peer, 1); } } } ######################################################################## # Main loop ######################################################################## my($iosel) = IO::Select->new(); foreach my $sock (@socks) { $iosel->add($sock); } while(1) { # Schedule the IO::Select timeout for either: # Listener: Next cleanup time (every 60 seconds) # Client: Next interval time (every second unless specified) my($now) = time; my($waittime); if($opt_listen) { $waittime = 60 - ($now - $lastcleanup); } else { $waittime = $opt_interval - ($now - $lastsched); } if($opt_stats) { my($waittime_stats) = $opt_stats - ($now - $laststats); $waittime = $waittime_stats if($waittime_stats < $waittime); } $waittime = 0 if($waittime < 0); my(@canread) = $iosel->can_read($waittime); foreach my $sock (@canread) { processincomingpacket($sock); } # We're in the timeout for whatever reason. Do some housekeeping. # Cleanup occurs every 60 seconds. if(($now - $lastcleanup) >= 60) { debug("Cleanup run\n"); # IDs we sent that have not been purged, and are older than 10 # minutes should be purged. foreach my $testmsgid (keys %msginfo) { my $delta = $now - $msginfo{$testmsgid}->{'time'}; debug(sprintf(" msginfo: %*v02x is %d sec old\n", '', $testmsgid, $delta)); if($delta > 600) { debug(sprintf("msginfo: Deleting %*v02x (expired)\n", '', $testmsgid)); delete($msginfo{$testmsgid}); } } # IDs we replied to that are older than 2 minutes should be purged. foreach my $testmsgid (keys %msgreplyinfo) { my $delta = $now - $msgreplyinfo{$testmsgid}->{'time'}; debug(sprintf(" msgreplyinfo: %*v02x is %d sec old\n", '', $testmsgid, $delta)); if($delta > 120) { debug(sprintf("msgreplyinfo: Deleting %*v02x (expired)\n", '', $testmsgid)); delete($msgreplyinfo{$testmsgid}); } } $lastcleanup = $now; } # If we've reached the deadline (maximum program execution time), # go ahead and exit. if($opt_deadline && (($now - $starttime) >= $opt_deadline)) { gracefulexit(); } # Print occasional stats if specified if($opt_stats && (($now - $laststats) >= $opt_stats)) { shortstats(); $laststats = $now; } # Should we send a new ping? Several ways this can happen: # 1. Normal mode, and interval has been reached. # 2. Flood mode, and interval (which most likely has been set very # low because of flood mode) has passed without a reply. # 3. Adaptive mode, and interval has passed without a reply. if(!$opt_listen && (($now - $lastsched) >= $opt_interval)) { $lastsched = $now; if($opt_adaptive || $opt_realflood) { if(($now - $lastreply) > $opt_interval) { sendpacket_all(); } } else { sendpacket_all(); } } } # Take a socket (that we know is waiting by now), recv() it, send it on # to be parsed, then process its logic. sub processincomingpacket { my($payload); my($recvtime); my(%peer) = ( 'sock' => $_[0], ); $packetsin++; if($peer{'sock'}->recv($payload, 16384)) { $recvtime = time; if(($opt_packetloss_in > 0) && (rand() < ($opt_packetloss_in / 100))) { return; } $lastreply = $recvtime; } else { return(parseerror(sprintf("From %s: %s\n", $peer{'sock'}->peerhost, $!))); } $peer{'peerhost'} = $peer{'sock'}->peerhost; $peer{'peeraddr'} = $peer{'sock'}->peeraddr; $peer{'peerport'} = $peer{'sock'}->peerport; $peer{'peertuple'} = $peer{'sock'}->sockaddr . $peer{'sock'}->sockport . $peer{'sock'}->peeraddr . $peer{'sock'}->peerport . $peer{'sock'}->protocol; $peer{'peername'} = $peer{'sock'}->peername; my($peerid, $peersendreply, $peerreplyto, $peerrtt, $peerresendsref, $peerresendsnotfoundref, $peeroldidsref, $peercourtesiesref, $peerdelay, $peerextoptionsref) = processpacket($payload, \%peer); return unless $peerid; my(@peerresendsnotfound) = @{$peerresendsnotfoundref}; my(@peerresends) = @{$peerresendsref}; my(@peeroldids) = @{$peeroldidsref}; my(@peercourtesies) = @{$peercourtesiesref}; my(%peerextoptions) = %{$peerextoptionsref}; debug(sprintf("msginfo count: %d\n", scalar keys(%msginfo))); foreach my $i (keys %msginfo) { debug(sprintf(" %*v02x\n", '', $i)); } debug(sprintf("msgreplyinfo count: %d\n", scalar keys(%msgreplyinfo))); foreach my $i (keys %msgreplyinfo) { debug(sprintf(" %*v02x\n", '', $i)); } my(@resends); my(@resendsnotfound); if($peersendreply) { $msgreplyinfo{$peer{'peertuple'} . $peerid} = { 'time' => time }; if(scalar(@peeroldids) > 0) { foreach my $peeroldid (@peeroldids) { if($msgreplyinfo{$peer{'peertuple'} . $peeroldid}) { push(@resends, $peeroldid); } else { push(@resendsnotfound, $peeroldid); } } } } foreach my $peercourtesy (@peercourtesies) { if($msgreplyinfo{$peer{'peertuple'} . $peercourtesy}) { debug(sprintf("msgreplyinfo: Deleting %*v02x (courtesy)\n", '', ($peer{'peertuple'} . $peercourtesy))); delete($msgreplyinfo{$peer{'peertuple'} . $peercourtesy}); } } if($peerreplyto && $msginfo{$peer{'peertuple'} . $peerreplyto}) { my(%thismsginfo) = %{$msginfo{$peer{'peertuple'} . $peerreplyto}}; my($rtt) = (($recvtime - $thismsginfo{'time'}) * 1000); $msginfo{$peer{'peertuple'} . $peerreplyto}->{'courtesy'} = 1; # If they want a reply, send it as early as possible. if($peersendreply) { # If $peersendreply is set (and we already know $peerreplyto is # set), then we know the peer was the 2nd leg of a 3-way ping, and # is requesting the 3rd leg. So we shouldn't expect a reply # ourselves. sendpacket(\%peer, 0, $peerid, $rtt, \@resends, \@resendsnotfound, $recvtime, length($payload)); } if(scalar(@peerresends) > 0) { foreach my $peerresend (@peerresends) { if($msginfo{$peer{'peertuple'} . $peerresend}) { if(!$opt_quiet) { if($opt_flood) { print "<"; } else { printf("Lost inbound packet from %s: ping_seq=%d\n", $peer{'peerhost'}, $msginfo{$peer{'peertuple'} . $peerresend}->{'idx'}); } } $inlost++; $msginfo{$peer{'peertuple'} . $peerresend}->{'courtesy'} = 1; } } } if(scalar(@peerresendsnotfound) > 0) { foreach my $peerresendnotfound (@peerresendsnotfound) { if($msginfo{$peer{'peertuple'} . $peerresendnotfound}) { if(!$opt_quiet) { if($opt_flood) { print ">"; } else { printf("Lost outbound packet to %s: ping_seq=%d\n", $peer{'peerhost'}, $msginfo{$peer{'peertuple'} . $peerresendnotfound}->{'idx'}); } } $outlost++; $msginfo{$peer{'peertuple'} . $peerresendnotfound}->{'courtesy'} = 1; } } } # If this is the third leg of a 3-way, we should be able to determine # if we don't need reply info anymore, and delete it. If the peer is # sending courtesy info though, most likely it's already deleted. if($thismsginfo{'replyto'} && $msgreplyinfo{$peer{'peertuple'} . $thismsginfo{'replyto'}}) { debug(sprintf("msgreplyinfo: Deleting %*v02x\n", '', ($peer{'peertuple'} . $thismsginfo{'replyto'}))); delete($msgreplyinfo{$peer{'peertuple'} . $thismsginfo{'replyto'}}); } # Print the ping result if(!$opt_quiet) { if($opt_flood) { print chr(8) . ' ' . chr(8); } else { printf("%d bytes from %s: ping_seq=%d time=%0.03f ms%s%s\n", length($payload), $peer{'peerhost'}, $thismsginfo{'idx'}, $rtt, ($peerrtt ? sprintf(' peertime=%0.03f ms', $peerrtt) : ''), ($opt_audible ? chr(7) : '') ); if($peerextoptions{'notice'}) { printf(" Peer notice: %s\n", $peerextoptions{'notice'}); } } } $pingsinrttsum += $rtt; $pingsinrttsumsq += $rtt ** 2; $pingsinewma = ($pingsinewma ? ($pingsinewma + ($rtt - ($pingsinewma / 8))) : ($rtt * 8)); $pingsinrttmax = $rtt if($rtt > $pingsinrttmax); $pingsinrttmin = $rtt if(($rtt < $pingsinrttmin) || !$pingsinrttmin); $pingsin++; # If it's an adaptive or flood ping, send a new ping out. if(!$opt_listen && (($opt_adaptive && ($> == 0)) || $opt_realflood)) { sendpacket(\%peer, 1); } } elsif($peersendreply) { # Peer has requested a reply. if($peerreplyto || (!$peerreplyto && !$opt_3way)) { # Send back a normal ping reply. # If $peerreplyto was set and it wasn't caught above, that must mean # we somehow didn't know info about the packet (old expired?). # Still, they requested a reply, so we must reply. # Either the peer's response was 2nd leg of a 3-way ping (it had a # reply-to), or it's the 1st leg of a ping and config explicitly # disables 3-way pings. Either way, do not request a reply. sendpacket(\%peer, 0, $peerid, 0, \@resends, \@resendsnotfound, $recvtime, length($payload)); } else { # Request a reply, this was the 1st leg of a 3-way ping, which we # will continue to make a 2nd and request a 3rd. sendpacket(\%peer, 1, $peerid, 0, \@resends, \@resendsnotfound, $recvtime, length($payload)); } } } # In certain cases (scheduled times, SIGALRM, etc), we want to send out # a ping to all sockets. Loop through each socket entry and send out a # ping. Note: in listener mode, the last socket peer could be # seconds/days/years old. Oops. sub sendpacket_all { foreach my $sock (@socks) { # Sockets in listener mode that haven't received a packet yet will # not have a peer definition, and can't send(), so ignore them. next unless $sock->peeraddr; my(%peer) = ( 'sock' => $sock, ); $peer{'peerhost'} = $peer{'sock'}->peerhost; $peer{'peeraddr'} = $peer{'sock'}->peeraddr; $peer{'peerport'} = $peer{'sock'}->peerport; $peer{'peertuple'} = $peer{'sock'}->sockaddr . $peer{'sock'}->sockport . $peer{'sock'}->peeraddr . $peer{'sock'}->peerport . $peer{'sock'}->protocol; $peer{'peername'} = $peer{'sock'}->peername; sendpacket(\%peer, 1); } } # Build a 2ping packet and send it out sub sendpacket { my %peer = %{$_[0]}; # Peer info object my $sendreply = $_[1]; # Whether the remote side should send a reply (true/false) my $replyto = $_[2]; # Message ID being replied to my $rtt = $_[3]; # RTT reported (3rd leg) my @resends = ($_[4] ? @{$_[4]} : ()); # List of investigated IDs (received) my @resendsnotfound = ($_[5] ? @{$_[5]} : ()); # List of investigated IDs (never received) my $recvtime = $_[6]; # Time the recv() happened my $recvpayloadlen = $_[7]; # Length of the received payload # Generate a random message ID my($msgid) = ''; for(my $i = 1; $i <= LEN_MESSAGE_ID; $i++) { $msgid .= chr(int(rand(256))); } # $outbuff: Opcode data area (everything between opcode flags and # padding, exclusive) my $outbuff = ''; # %outbuffs: Opcode data area segments, indexed by opcode flag (used to # build $outbuff) my %outbuffs; # $outbufflen: Working length of what $outbuff will eventually be my $outbufflen; # $opcodeflags: 32-bit opcode flag area my $opcodeflags = 0; # If we're beyond the deadline or count period, only send the packet if # we're not expecting a reply back. if($opt_deadline && $sendreply && ((time - $starttime) >= $opt_deadline)) { gracefulexit(); } if($opt_count && $sendreply && ($pingsout >= $opt_count)) { gracefulexit(); } # $vbuff is a built verbose output line my($vbuff) = ''; if($opt_verbose) { $vbuff = sprintf('msgid=%*v02x', '', $msgid); } # 128: MAC hashing # We want room for this as soon as possible, so we're doing it now. # The actual hash will be filled in at the end. if($opt_auth) { $opcodeflags |= OPCODE_HASH; $outbuffs{OPCODE_HASH()} = pack('n', $authhashlen + LEN_DIGEST_TYPE) . pack('n', $authhashint) . (chr(0) x $authhashlen); $outbufflen += LEN_OPCODE_HEADER_LEN + $authhashlen + LEN_DIGEST_TYPE; } # 1: Reply expected if($sendreply) { $opcodeflags |= OPCODE_SENDREPLY; $outbuffs{OPCODE_SENDREPLY()} = pack('n', 0); $outbufflen += LEN_OPCODE_HEADER_LEN; if($opt_verbose) { $vbuff .= ' sendreply=yes'; } } # 2: Replying to this Message ID if($replyto) { $opcodeflags |= OPCODE_REPLYTO; $outbuffs{OPCODE_REPLYTO()} = pack('n', LEN_MESSAGE_ID) . $replyto; $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_MESSAGE_ID; if($opt_verbose) { $vbuff .= sprintf(' replyto=%*v02x', '', $replyto); } } # 256: RTT delay if($recvtime) { $opcodeflags |= OPCODE_DELAY; $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_DELAY; } # 4: RTT included (3rd leg) if($rtt) { $opcodeflags |= OPCODE_RTT; my($rttmicro) = int($rtt * 1000); $outbuffs{OPCODE_RTT()} = pack('n', LEN_RTT) . pack('N', $rttmicro); $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_RTT; if($opt_verbose) { $vbuff .= sprintf(' rtt=%0.03f', $rtt); } } # 8: Investigation complete, found these IDs if((scalar(@resends) > 0) && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + LEN_MESSAGE_ID) <= $opt_maxpacket)) { my($tmpoutbuff) = ''; $opcodeflags |= OPCODE_LOSTFOUNDS; my($cnt) = 0; foreach my $resend (@resends) { if((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff) + LEN_MESSAGE_ID) <= $opt_maxpacket) { $tmpoutbuff .= $resend; $cnt++; if($opt_verbose) { if($cnt == 1) { $vbuff .= sprintf(' invfound=%*v02x', '', $resend); } else { $vbuff .= sprintf(',%*v02x', '', $resend); } } } } $outbuffs{OPCODE_LOSTFOUNDS()} = pack('n', LEN_ARRAY_CNT + length($tmpoutbuff)) . pack('n', $cnt) . $tmpoutbuff; $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff); } # 16: Investigation complete, didn't fing these IDs if((scalar(@resendsnotfound) > 0) && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + LEN_MESSAGE_ID) <= $opt_maxpacket)) { my($tmpoutbuff) = ''; $opcodeflags |= OPCODE_LOSTNOTFOUNDS; my($cnt) = 0; foreach my $resendnotfound (@resendsnotfound) { if((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff) + LEN_MESSAGE_ID) <= $opt_maxpacket) { $tmpoutbuff .= $resendnotfound; $cnt++; if($opt_verbose) { if($cnt == 1) { $vbuff .= sprintf(' invnotfound=%*v02x', '', $resendnotfound); } else { $vbuff .= sprintf(',%*v02x', '', $resendnotfound); } } } } $outbuffs{OPCODE_LOSTNOTFOUNDS()} = pack('n', LEN_ARRAY_CNT + length($tmpoutbuff)) . pack('n', $cnt) . $tmpoutbuff; $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff); } # 32: Investigation requests my(@validoldas) = (); if($sendreply && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT) <= $opt_maxpacket)) { foreach my $testmsgid (keys %msginfo) { next unless($msginfo{$testmsgid}->{'peer'} eq $peer{'peertuple'}); next if($msginfo{$testmsgid}->{'courtesy'}); if($msginfo{$testmsgid}->{'time'} < (time() - $opt_inqwait)) { push(@validoldas, $msginfo{$testmsgid}->{'id'}); } } } if($sendreply && (scalar(@validoldas) > 0) && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + LEN_MESSAGE_ID) <= $opt_maxpacket)) { fisher_yates_shuffle(\@validoldas); my($tmpoutbuff) = ''; $opcodeflags |= OPCODE_LOSTPACKETS; my($cnt) = 0; foreach my $olda (@validoldas) { if((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff) + LEN_MESSAGE_ID) <= $opt_maxpacket) { $tmpoutbuff .= $olda; $cnt++; if($opt_verbose) { if($cnt == 1) { $vbuff .= sprintf(' invreq=%*v02x', '', $olda); } else { $vbuff .= sprintf(',%*v02x', '', $olda); } } } } $outbuffs{OPCODE_LOSTPACKETS()} .= pack('n', LEN_ARRAY_CNT + length($tmpoutbuff)) . pack('n', $cnt) . $tmpoutbuff; $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff); } # 64: Courtesies my(@courtesies) = (); if((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT) <= $opt_maxpacket) { foreach my $testmsgid (keys %msginfo) { next unless($msginfo{$testmsgid}->{'peer'} eq $peer{'peertuple'}); next unless($msginfo{$testmsgid}->{'courtesy'}); push(@courtesies, $msginfo{$testmsgid}->{'id'}); debug(sprintf("msginfo: Deleting %*v02x (courtesy sent)\n", '', $testmsgid)); delete($msginfo{$testmsgid}); } } if((scalar(@courtesies) > 0) && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + LEN_MESSAGE_ID) <= $opt_maxpacket)) { my($tmpoutbuff) = ''; $opcodeflags |= OPCODE_COURTESIES; my($cnt) = 0; foreach my $courtesy (@courtesies) { if((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff) + LEN_MESSAGE_ID) <= $opt_maxpacket) { $tmpoutbuff .= $courtesy; $cnt++; if($opt_verbose) { if($cnt == 1) { $vbuff .= sprintf(' courtesy=%*v02x', '', $courtesy); } else { $vbuff .= sprintf(',%*v02x', '', $courtesy); } } } } $outbuffs{OPCODE_COURTESIES()} .= pack('n', LEN_ARRAY_CNT + length($tmpoutbuff)) . pack('n', $cnt) . $tmpoutbuff; $outbufflen += LEN_OPCODE_HEADER_LEN + LEN_ARRAY_CNT + length($tmpoutbuff); } # Extended options. Only build the opcode block if any extended # options are set. my($buildextended) = 0; my(%extendedout) = (); # Extended 0xa837b44e: Notice text if($opt_notice && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_EXTENDED_ID + LEN_EXTENDED_LEN + length($opt_notice)) <= $opt_maxpacket)) { $buildextended = 1; $extendedout{EXTENDED_ID_NOTICE()} = $opt_notice; $outbufflen += LEN_EXTENDED_ID + LEN_EXTENDED_LEN + length($opt_notice); $vbuff .= sprintf(' notice="%s"', $opt_notice); } # Extended 0x3250564e: Program version if($opt_sendversion && ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen + LEN_OPCODE_HEADER_LEN + LEN_EXTENDED_ID + LEN_EXTENDED_LEN + length($versionstring)) <= $opt_maxpacket)) { $buildextended = 1; $extendedout{EXTENDED_ID_VERSION()} = $versionstring; $outbufflen += LEN_EXTENDED_ID + LEN_EXTENDED_LEN + length($versionstring); $vbuff .= sprintf(' version="%s"', $versionstring); } # If any extended options are set, build the opcode block. if($buildextended) { $opcodeflags |= OPCODE_EXTENDED; my $extendedoutbuff = ''; foreach my $extid (sort { $a <=> $b } keys %extendedout) { $extendedoutbuff .= pack('N', $extid) . pack('n', length($extendedout{$extid})) . $extendedout{$extid}; } $outbufflen += LEN_OPCODE_HEADER_LEN; $outbuffs{OPCODE_EXTENDED()} .= pack('n', length($extendedoutbuff)) . $extendedoutbuff; $vbuff .= ' extended=yes'; } # Determine how small the packet should be. my($minpacket); if($opt_matchpacketsize && $recvpayloadlen) { if($recvpayloadlen < $opt_maxpacket) { $minpacket = $recvpayloadlen; } else { $minpacket = $opt_minpacket; } } else { $minpacket = $opt_minpacket; } # If there is room left, build padding according to the pattern. my($outpad) = ''; my($pad) = 0; if((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen) < $minpacket) { $pad = ($minpacket - ((LEN_MAGIC_NUMBER + LEN_CHECKSUM + LEN_MESSAGE_ID + LEN_OPCODES + $outbufflen))); $outpad = substr(($pad_pattern x int(($pad / length($pad_pattern)) + 1)), 0, $pad); if($opt_verbose) { $vbuff .= sprintf(' pad=%d', $pad); } } my($sendtime) = time; # If a delay is being sent, calculate it as late as possible here if($recvtime) { my($delaymicro) = ($sendtime - $recvtime) * 1000000; $outbuffs{OPCODE_DELAY()} = pack('n', LEN_DELAY) . pack('N', $delaymicro); if($opt_verbose) { $vbuff .= sprintf(' delay=%0.03f', $delaymicro/1000); } } my $hashpos; # Loop through %outbuffs in numeric (opcode) order, assembling $outbuff foreach my $i (sort { $a <=> $b } keys %outbuffs) { $outbuff .= $outbuffs{$i}; # If this is the hash area, record it for later use if($i == OPCODE_HASH) { $hashpos = length($outbuff) - $authhashlen; } } # Build the hash if($opt_auth) { $outbuff = substr($outbuff, 0, $hashpos) . hmac($authhashf, '2P' . pack('n', 0) . $msgid . pack('n', $opcodeflags) . $outbuff . $outpad, $opt_auth) . substr($outbuff, $hashpos + $authhashlen); } # # Fuzzing test # my($fuzzdata) = pack('n', $opcodeflags) . $outbuff; # if(rand() >= .5) { # my($rpos) = int(rand(length($fuzzdata))); # $fuzzdata = substr($fuzzdata, 0, $rpos) . chr(ord(substr($fuzzdata, $rpos, 1)) ^ 2**int(rand(8))) . substr($fuzzdata, $rpos+1); # } else { # my($olen) = length($fuzzdata); # $fuzzdata = ''; # for(my $i = 0; $i < $olen; $i++) { # $fuzzdata .= chr(int(rand(256))); # } # } # $opcodeflags = unpack('n', substr($fuzzdata, 0, 2)); # $outbuff = substr($fuzzdata, 2); # Build a checksum my($checksum); $checksum = ip_checksum('2P' . pack('n', 0) . $msgid . pack('n', $opcodeflags) . $outbuff . $outpad, 1); if($opt_verbose) { $vbuff .= sprintf(' cksum=%04x', $checksum); } my($finalpkt) = '2P' . pack('n', $checksum) . $msgid . pack('n', $opcodeflags) . $outbuff . $outpad; # # Random checksum errors # if(rand() > .5) { # my($rpos) = int(rand(length($finalpkt)/2))*2; # $finalpkt = substr($finalpkt, 0, $rpos) . pack('n', abs(unpack('n', substr($finalpkt, $rpos, 2))-1)) . substr($finalpkt, $rpos+2); # } # Send it! (Unless we're simulating packet loss) if(!(($opt_packetloss_out > 0) && (rand() < ($opt_packetloss_out / 100)))) { $peer{'sock'}->send($finalpkt, 0, $peer{'peername'}); } $packetsout++; # If we're expecting a reply, record it for future use if($sendreply) { unless($cntbypeer{$peer{'peertuple'}}) { $cntbypeer{$peer{'peertuple'}} = 0; } $cntbypeer{$peer{'peertuple'}}++; $msginfo{$peer{'peertuple'} . $msgid} = { 'id' => $msgid, 'idx' => $cntbypeer{$peer{'peertuple'}}, 'peer' => $peer{'peertuple'}, 'time' => $sendtime, 'replyto' => $replyto, 'courtesy' => 0, }; } debug(sprintf("Sent: %*v02x\n", ' ', $finalpkt)); if($opt_verbose) { printf("SEND: %d bytes to %s:%s: %s\n", length($finalpkt), $peer{'sock'}->peerhost, $peer{'sock'}->peerport, $vbuff); } if($sendreply) { print '.' if($opt_flood && !$opt_quiet); $pingsout++; } return($msgid); } sub processpacket { my($payload) = $_[0]; my(%peer) = %{$_[1]}; my($payloadlen) = length($payload); debug(sprintf("Received: %*v02x\n", ' ', $payload)); # $vbuff is a built verbose output line my($vbuff) = ''; # Magic number my($pos) = 0; return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MAGIC_NUMBER)); my($magic) = substr($payload, $pos, LEN_MAGIC_NUMBER); $pos += LEN_MAGIC_NUMBER; unless($magic eq '2P') { return(parseerror(sprintf("Invalid magic number from %s: expected %*v02x, received %*v02x\n", $peer{'peerhost'}, '', '2P', '', $magic))); } # Checksum return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_CHECKSUM)); my($payload_checksum) = unpack('n', substr($payload, $pos, LEN_CHECKSUM)); $pos += LEN_CHECKSUM; $vbuff .= sprintf('cksum=%04x', $payload_checksum) if $opt_verbose; if($payload_checksum > 0) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos)); my($test_checksum) = ip_checksum('2P' . pack('n', 0) . substr($payload, $pos), 1); unless($payload_checksum == $test_checksum) { return(parseerror(sprintf("Invalid checksum from %s: expected %04x, received %04x\n", $peer{'peerhost'}, $test_checksum, $payload_checksum))); } } # Message ID return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MESSAGE_ID)); my($peerid) = substr($payload, $pos, LEN_MESSAGE_ID); $pos += LEN_MESSAGE_ID; $vbuff .= sprintf(' msgid=%*v02x', '', $peerid) if $opt_verbose; return unless(length($peerid) == LEN_MESSAGE_ID); # Opcode flags return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODES)); my($opcodeflags) = unpack('n', substr($payload, $pos, LEN_OPCODES)); $pos += LEN_OPCODES; # Don't accept a packet if we were expecting a hash if($opt_auth && !($opcodeflags & OPCODE_HASH)) { return(parseerror(sprintf("Auth hash expected from %s but not found: msgid=%*v02x\n", $peer{'peerhost'}, '', $peerid))); } # 1: Peer reply expected my($peersendreply) = 0; if($opcodeflags & OPCODE_SENDREPLY) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; if($opblocklen > 0) { return(parseerror(sprintf("Packet error from %s found: SENDREPLY: length expected 0, received %d\n", $peer{'peerhost'}, $opblocklen))); } $peersendreply = 1; $vbuff .= ' sendreply=yes' if $opt_verbose; } # 2: Peer replying to this Message ID my($peerreplyto); if($opcodeflags & OPCODE_REPLYTO) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; unless($opblocklen == LEN_MESSAGE_ID) { return(parseerror(sprintf("Packet error from %s found: REPLYTO: length expected %d, received %d\n", $peer{'peerhost'}, LEN_MESSAGE_ID, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MESSAGE_ID)); $peerreplyto = substr($payload, $pos, LEN_MESSAGE_ID); $pos += LEN_MESSAGE_ID; $vbuff .= sprintf(' replyto=%*v02x', '', $peerreplyto) if $opt_verbose; } # 4: Peer RTT included (3rd leg) my($peerrtt); if($opcodeflags & OPCODE_RTT) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; unless($opblocklen == LEN_RTT) { return(parseerror(sprintf("Packet error from %s found: RTT: length expected %d, received %d\n", $peer{'peerhost'}, LEN_RTT, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_RTT)); $peerrtt = unpack('N', substr($payload, $pos, LEN_RTT)) / 1000; $pos += LEN_RTT; $vbuff .= sprintf(' rtt=%0.03f', $peerrtt) if $opt_verbose; } # 8: Peer investigation complete, found these IDs my(@peerresends); if($opcodeflags & OPCODE_LOSTFOUNDS) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; if($opblocklen < LEN_ARRAY_CNT) { return(parseerror(sprintf("Packet error from %s found: LOSTFOUNDS: length expected >= %d, received %d\n", $peer{'peerhost'}, LEN_ARRAY_CNT, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_ARRAY_CNT)); my($found) = unpack('n', substr($payload, $pos, LEN_ARRAY_CNT)); $pos += LEN_ARRAY_CNT; unless($opblocklen == ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT)) { return(parseerror(sprintf("Packet error from %s found: LOSTFOUNDS: length expected %d, received %d\n", $peer{'peerhost'}, ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT), $opblocklen))); } for(my $mi = 0; $mi < $found; $mi++) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MESSAGE_ID)); my($peerresend) = substr($payload, $pos, LEN_MESSAGE_ID); push(@peerresends, $peerresend); $pos += LEN_MESSAGE_ID; if($opt_verbose) { if($mi == 0) { $vbuff .= sprintf(' invfound=%*v02x', '', $peerresend); } else { $vbuff .= sprintf(',%*v02x', '', $peerresend); } } } } # 16: Peer investigation complete, didn't fing these IDs my(@peerresendsnotfound); if($opcodeflags & OPCODE_LOSTNOTFOUNDS) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; if($opblocklen < LEN_ARRAY_CNT) { return(parseerror(sprintf("Packet error from %s found: LOSTNOTFOUNDS: length expected >= %d, received %d\n", $peer{'peerhost'}, LEN_ARRAY_CNT, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_ARRAY_CNT)); my($found) = unpack('n', substr($payload, $pos, LEN_ARRAY_CNT)); $pos += LEN_ARRAY_CNT; unless($opblocklen == ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT)) { return(parseerror(sprintf("Packet error from %s found: LOSTNOTFOUNDS: length expected %d, received %d\n", $peer{'peerhost'}, ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT), $opblocklen))); } for(my $mi = 0; $mi < $found; $mi++) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MESSAGE_ID)); my($peerresendnotfound) = substr($payload, $pos, LEN_MESSAGE_ID); push(@peerresendsnotfound, $peerresendnotfound); $pos += LEN_MESSAGE_ID; if($opt_verbose) { if($mi == 0) { $vbuff .= sprintf(' invnotfound=%*v02x', '', $peerresendnotfound); } else { $vbuff .= sprintf(',%*v02x', '', $peerresendnotfound); } } } } # 32: Peer investigation requests my(@peeroldids); if($opcodeflags & OPCODE_LOSTPACKETS) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; if($opblocklen < LEN_ARRAY_CNT) { return(parseerror(sprintf("Packet error from %s found: LOSTPACKETS: length expected >= %d, received %d\n", $peer{'peerhost'}, LEN_ARRAY_CNT, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_ARRAY_CNT)); my($found) = unpack('n', substr($payload, $pos, LEN_ARRAY_CNT)); $pos += LEN_ARRAY_CNT; unless($opblocklen == ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT)) { return(parseerror(sprintf("Packet error from %s found: LOSTPACKETS: length expected %d, received %d\n", $peer{'peerhost'}, ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT), $opblocklen))); } for(my $mi = 0; $mi < $found; $mi++) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MESSAGE_ID)); my($peeroldid) = substr($payload, $pos, LEN_MESSAGE_ID); push(@peeroldids, $peeroldid); $pos += LEN_MESSAGE_ID; if($opt_verbose) { if($mi == 0) { $vbuff .= sprintf(' invreq=%*v02x', '', $peeroldid); } else { $vbuff .= sprintf(',%*v02x', '', $peeroldid); } } } } # 64: Peer courtesies my(@peercourtesies); if($opcodeflags & OPCODE_COURTESIES) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; if($opblocklen < LEN_ARRAY_CNT) { return(parseerror(sprintf("Packet error from %s found: COURTESIES: length expected >= %d, received %d\n", $peer{'peerhost'}, LEN_ARRAY_CNT, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_ARRAY_CNT)); my($found) = unpack('n', substr($payload, $pos, LEN_ARRAY_CNT)); $pos += LEN_ARRAY_CNT; unless($opblocklen == ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT)) { return(parseerror(sprintf("Packet error from %s found: COURTESIES: length expected %d, received %d\n", $peer{'peerhost'}, ((LEN_MESSAGE_ID * $found) + LEN_ARRAY_CNT), $opblocklen))); } for(my $mi = 0; $mi < $found; $mi++) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_MESSAGE_ID)); my($peercourtesy) = substr($payload, $pos, LEN_MESSAGE_ID); push(@peercourtesies, $peercourtesy); $pos += LEN_MESSAGE_ID; if($opt_verbose) { if($mi == 0) { $vbuff .= sprintf(' courtesy=%*v02x', '', $peercourtesy); } else { $vbuff .= sprintf(',%*v02x', '', $peercourtesy); } } } } # 128: Peer MAC hashing if($opcodeflags & OPCODE_HASH) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; if($opt_auth) { if($opblocklen < LEN_DIGEST_TYPE) { return(parseerror(sprintf("Packet error from %s found: HASH: length expected >= %d, received %d\n", $peer{'peerhost'}, LEN_ARRAY_CNT, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_DIGEST_TYPE)); my($digesttype) = unpack('n', substr($payload, $pos, LEN_DIGEST_TYPE)); $pos += LEN_DIGEST_TYPE; unless($digesttype == $authhashint) { return(parseerror(sprintf("Hash digest mismatch from %s\n", $peer{'peerhost'}))); } my($hashpos) = $pos; return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, $authhashlen)); my($payload_hash) = substr($payload, $pos, $authhashlen); $pos += $authhashlen; $vbuff .= sprintf(' hash=%*v02x', '', $payload_hash) if $opt_verbose; return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, (LEN_MAGIC_NUMBER + LEN_DIGEST_TYPE), ($hashpos - (LEN_MAGIC_NUMBER + LEN_DIGEST_TYPE)))); my($test_hash) = hmac($authhashf, '2P' . pack('n', 0) . substr($payload, (LEN_MAGIC_NUMBER + LEN_DIGEST_TYPE), ($hashpos - (LEN_MAGIC_NUMBER + LEN_DIGEST_TYPE))) . (chr(0) x $authhashlen) . substr($payload, $pos), $opt_auth); unless($payload_hash eq $test_hash) { return(parseerror(sprintf("Invalid hash from %s: expected %*v02x, received %*v02x\n", $peer{'peerhost'}, '', $test_hash, '', $payload_hash))); } } else { $pos += $opblocklen; } } # 256: Peer RTT delay my($peerdelay); if($opcodeflags & OPCODE_DELAY) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; unless($opblocklen == LEN_DELAY) { return(parseerror(sprintf("Packet error from %s found: DELAY: length expected %d, received %d\n", $peer{'peerhost'}, LEN_DELAY, $opblocklen))); } return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_DELAY)); $peerdelay = unpack('N', substr($payload, $pos, LEN_DELAY)) / 1000; $pos += LEN_DELAY; $vbuff .= sprintf(' delay=%0.03f', $peerdelay) if $opt_verbose; } # Skip over unknown (undefined at the time of this writing) opcodes foreach my $unknownflag (qw/512 1024 2048 4096 8192 16384/) { if($opcodeflags & $unknownflag) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, $opblocklen)); $pos += $opblocklen; $vbuff .= sprintf(' unknown_opcode(%04x)=%dB', $unknownflag, $opblocklen) if $opt_verbose; } } # 32768: Extended format my %extoptions = (); if($opcodeflags & OPCODE_EXTENDED) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_OPCODE_HEADER_LEN)); $vbuff .= ' extended=yes'; my($opblockpos) = 0; my($opblocklen) = unpack('n', substr($payload, $pos, LEN_OPCODE_HEADER_LEN)); $pos += LEN_OPCODE_HEADER_LEN; while($opblockpos < $opblocklen) { return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_EXTENDED_ID)); my($extid) = unpack('N', substr($payload, $pos, LEN_EXTENDED_ID)); $pos += LEN_EXTENDED_ID; $opblockpos += LEN_EXTENDED_ID; return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, LEN_EXTENDED_LEN)); my($extlen) = unpack('n', substr($payload, $pos, LEN_EXTENDED_LEN)); $pos += LEN_EXTENDED_LEN; $opblockpos += LEN_EXTENDED_LEN; return(parseerror(sprintf("Packet received from %s: attempt to read beyond packet\n", $peer{'peerhost'}))) unless(readbytestest($payloadlen, $pos, $extlen)); if($extid == EXTENDED_ID_VERSION) { my($extdata) = substr($payload, $pos, $extlen); $pos += $extlen; $opblockpos += $extlen; $vbuff .= sprintf(' version="%s"', $extdata) if $opt_verbose; } elsif($extid == EXTENDED_ID_NOTICE) { my($extdata) = substr($payload, $pos, $extlen); $pos += $extlen; $opblockpos += $extlen; $vbuff .= sprintf(' notice="%s"', $extdata) if $opt_verbose; $extoptions{'notice'} = $extdata; } else { $pos += $extlen; $opblockpos += $extlen; $vbuff .= sprintf(' unknown_ext(%08x)=%dB', $extid, $extlen) if $opt_verbose; } } } # Peer padding $vbuff .= sprintf(' pad=%d', (length($payload) - $pos)) if $opt_verbose; if($opt_verbose) { printf("RECV: %d bytes from %s:%s: %s\n", length($payload), $peer{'peerhost'}, $peer{'peerport'}, $vbuff); } return($peerid, $peersendreply, $peerreplyto, $peerrtt, \@peerresends, \@peerresendsnotfound, \@peeroldids, \@peercourtesies, $peerdelay, \%extoptions); } # Test for reading beyond the end of the packet sub readbytestest { my($packetlen) = shift; my($pos) = shift; my($readlen) = shift; if(defined $readlen) { if(($pos + $readlen) > $packetlen) { return(0); } return(1); } else { if($pos > $packetlen) { return(0); } return(1); } } # Process a packet error sub parseerror { my($msg) = shift; if(!$opt_quiet) { if($opt_flood) { print "E"; } else { print $msg; } } $errors++; return; } # Print statistics and exit sub gracefulexit { print "\n"; printf("--- %s 2ping statistics ---\n", ($opt_listen ? 'Listener' : $ARGV[0])); printf( "%d pings transmitted, %d received,%s %d%% ping loss, time %dms\n", $pingsout, $pingsin, (($errors > 0) ? sprintf(' +%d errors,', $errors) : ''), lazydiv(($pingsout-$pingsin), $pingsout)*100, (time-$starttime)*1000 ); printf( "%d outbound ping losses (%d%%), %d inbound (%d%%), %d undetermined (%d%%)\n", $outlost, lazydiv($outlost, $pingsout)*100, $inlost, lazydiv($inlost, $pingsout)*100, $pingsout-$pingsin-$outlost-$inlost, lazydiv(($pingsout-$pingsin-$outlost-$inlost), $pingsout)*100 ); printf( "rtt min/avg/ewma/max/mdev = %0.03f/%0.03f/%0.03f/%0.03f/%0.03f ms\n", $pingsinrttmin, lazydiv($pingsinrttsum, $pingsin), $pingsinewma / 8, $pingsinrttmax, sqrt(lazydiv($pingsinrttsumsq, $pingsin) - (lazydiv($pingsinrttsum, $pingsin) ** 2)) ); printf("%d raw packets transmitted, %d received\n", $packetsout, $packetsin); exit(0); } # ALRM - manually sends a new ping sub processsigalrm { # Really ALRM shouldn't be allowed in listener mode since it sends an # outgoing packet to whoever the last peer happened to be (if any at # all), but it's useful in debugging. sendpacket_all(); } # INT - exits with summary sub processsigint { gracefulexit(); } # QUIT - doesn't actually quit, prints a 1-line summary sub processsigquit { shortstats(); } # Print one line of statistics without exiting sub shortstats { printf("%d/%d pings, %d%% loss (%d/%d/%d out/in/undet), min/avg/max/ewma/mdev = %0.03f/%0.03f/%0.03f/%0.03f/%0.03f ms\n", $pingsout, $pingsin, lazydiv(($pingsout-$pingsin), $pingsout)*100, $outlost, $inlost, $pingsout-$pingsin-$outlost-$inlost, $pingsinrttmin, lazydiv($pingsinrttsum, $pingsin), $pingsinewma / 8, $pingsinrttmax, sqrt(lazydiv($pingsinrttsumsq, $pingsin) - (lazydiv($pingsinrttsum, $pingsin) ** 2)) ); } # Output text only if $opt_debug is on sub debug { if($opt_debug) { my $msg = shift; print("DEBUG: $msg"); } } # Division, replacing divide-by-zero with zero sub lazydiv { my($a) = shift; my($b) = shift; return 0 if($b == 0); return($a / $b) } # fisher_yates_shuffle( \@array ) : generate a random permutation # of @array in place sub fisher_yates_shuffle { my $array = shift; my $i; for ($i = @$array; --$i; ) { my $j = int rand ($i+1); next if $i == $j; @$array[$i,$j] = @$array[$j,$i]; } } # Generate a IP-style checksum of a packet, with an optional # UDP-style mode adjustment. sub ip_checksum { my($d) = shift; # Data my($rfc768) = shift; # RFC768 mode my($checksum) = 0; my($l) = length($d); # Input size is calculated on an even number. if($l % 2) { $l++; $d .= chr(0); } # Add the value of the packet. for(my $i = 0; $i < $l; $i += 2) { $checksum += unpack('n', substr($d, $i, 2)); } # To avoid overloading 32 bits on $checksum, you should limit the # packet to 16MiB. If this is not possible (flying car IPv6 future?), # you should check for large values within the loop above and # end-around carry as needed. $checksum = ($checksum >> 16) + ($checksum & 0xffff); # Calculate the ones' complement. $checksum = ~(($checksum >> 16) + $checksum) & 0xffff; # RFC768 mode: if the end result is zero, set to all ones per RFC768. # (UDPv4 checksum is optional, and lack of a checksum is signified by # zero value. Checksums are required in IP (header), TCP, and UDPv6.) $checksum = 0xffff if(($checksum == 0) && $rfc768); return($checksum); } # Generate an HMAC hash sub hmac { my($f) = shift; my($message) = shift; my($key) = shift; my($blocksize) = shift || 64; $key = &$f($key) if(length($key) > $blocksize); $key .= (chr(0) x ($blocksize - length($key))) if(length($key) < $blocksize); my($o_key_pad) = $key ^ (chr(0x5c) x $blocksize); my($i_key_pad) = $key ^ (chr(0x36) x $blocksize); return &$f($o_key_pad . &$f($i_key_pad . $message)); } # Generate a binary CRC32 checksum (Digest::CRC::crc32 outputs an int) sub crc32_bin { my($data) = shift; return pack('N', Digest::CRC::crc32($data)); } __END__ =head1 NAME 2ping - A bi-directional ping client/listener =head1 SYNOPSIS B<2ping> S<[ B ]> S | I> =head1 DESCRIPTION B<2ping> is a bi-directional ping utility. It uses 3-way pings (akin to TCP SYN, SYN/ACK, ACK) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs. To use 2ping, start a listener on a known stable network host. The relative network stability of the 2ping listener host should not be in question, because while 2ping can determine whether packet loss is occurring inbound or outbound relative to an endpoint, that will not help you determine the cause if both of the endpoints are in question. Once the listener is started, start 2ping in client mode and tell it to connect to the listener. The ends will begin pinging each other and displaying network statistics. If packet loss occurs, 2ping will wait a few seconds (default 10, configurable with -w) before comparing notes between the two endpoints to determine which direction the packet loss is occurring. To quit 2ping on the client or listener ends, enter ^C, and a list of statistics will be displayed. To get a short inline display of statistics without quitting, send the process a QUIT signal (yes, that's the opposite of what you would think, but it's in line with the normal ping utility). =head1 OPTIONS B-compatible options: =over =item B<-a> Audible ping. =item B<-A> Adaptive ping. A new client ping request is sent as soon as a client ping response is received. If a ping response is not received within the interval period, a new ping request is sent. Minimal interval is 200msec for not super-user. On networks with low rtt this mode is essentially equivalent to flood mode. B<2ping>-specific notes: This behavior is somewhat different to the nature of B's adaptive ping, but the result is roughly the same. =item B<-c> I Stop after sending I ping requests. B<2ping>-specific notes: This option behaves slightly differently from B. If both B<-c> and B<-w> are specified, satisfaction of B<-c> will cause an exit first. Also, internally, B<2ping> exits just before sending I+1 pings, to give time for the ping to complete. =item B<-f> Flood ping. For every ping sent a period "." is printed, while for ever ping received a backspace is printed. This provides a rapid display of how many pings are being dropped. If interval is not given, it sets interval to zero and outputs pings as fast as they come back or one hundred times per second, whichever is more. Only the super-user may use this option with zero interval. B<2ping>-specific notes: Detected outbound/inbound loss responses are printed as ">" and "<", respectively. Receive errors are printed as "E". Due to the asynchronous nature of B<2ping>, successful responses (backspaces) may overwrite these loss and error characters. =item B<-i> I Wait I seconds between sending each ping. The default is to wait for one second between each ping normally, or not to wait in flood mode. Only super-user may set interval to values less 0.2 seconds. =item B<-I> I Set source IP address. When pinging IPv6 link-local address this option is required. When in listener mode, this option may be specified multiple to bind to multiple IP addresses. When in client mode, this option may only be specified once, and all outbound pings will be bound to this source IP. B<2ping>-specific notes: This option only takes an IP address, not a device name. Note that in listener mode, if the machine has an interface with multiple IP addresses and an request comes in via a sub IP, the reply still leaves via the interface's main IP. So this option must be used if you would like to respond via an interface's sub-IP. =item B<-l> I If I is specified, B<2ping> sends that many packets not waiting for reply. Only the super-user may select preload more than 3. =item B<-p> I You may specify up to 16 "pad" bytes to fill out the packets you send. This is useful for diagnosing data-dependent problems in a network. For example, B<-p ff> will cause the sent packet pad area to be filled with all ones. B<2ping>-specific notes: This pads the portion of the packet that does not contain the active payload data. If the active payload data is larger than the minimum packet size (B<--min-packet-size>=I), no padding will be sent. =item B<-q> Quiet output. Nothing is displayed except the summary lines at startup time and when finished. =item B<-s> I B compatibility, this will set B<--min-packet-size> to this plus 8 bytes. =item B<-v> Verbose output. In B<2ping>, this prints decodes of packets that are sent and received. =item B<-V> Show version and exit. =item B<-w> I Specify a timeout, in seconds, before B<2ping> exits regardless of how many pings have been sent or received. Due to blocking, this may occur up to one second after the deadline specified. B<2ping>-specific notes: This option behaves slightly differently from B. If both B<-c> and B<-w> are specified, satisfaction of B<-c> will cause an exit first. =back B<2ping>-specific options: =over =item B<-?>, B<--help> Print a synposis and exit. =item B<-6>, B<--ipv6> Bind/connect as IPv6. =item B<--auth>=I Set a shared key, send cryptographic hashes with each packet, and require cryptographic hashes from peer packets signed with the same shared key. =item B<--auth-digest>=I When B<--auth> is used, specify the digest type to compute the cryptographic hash. Valid options are B (default), B and B. hmac-md5 requires B, and the SHA digests require B. =item B<--debug> Print (lots of) debugging information. =item B<--inquire-wait>=I Wait at least I seconds before inquiring about a lost packet. Default is 10 seconds. UDP packets can arrive delayed or out of order, so it is best to give it some time before inquiring about a lost packet. =item B<--listen> Start as a listener. The listener will not send out ping requests at regular intervals, and will instead wait for the far end to initiate ping requests. A listener is required as the remote end for a client. =item B<--min-packet-size>=I Set the minimum total payload size to I bytes, default 128. If the payload is smaller than I bytes, padding will be added to the end of the packet. =item B<--max-packet-size>=I Set the maximum total payload size to I bytes, default 512, absolute minimum 64. If the payload is larger than I bytes, information will be rearranged and sent in future packets when possible. =item B<--no-3way> Do not perform 3-way pings. Used most often when combined with B<--listen>, as the listener is usually the one to determine whether a ping reply should become a 3-way ping. Strictly speaking, a 3-way ping is not necessary for determining directional packet loss between the client and the listener. However, the extra leg of the 3-way ping allows for extra chances to determine packet loss more efficiently. Also, with 3-way ping disabled, the listener will receive no client performance indicators, nor will the listener be able to determine directional packet loss that it detects. =item B<--no-match-packet-size> When sending replies, 2ping will try to match the packet size of the received packet by adding padding if necessary, but will not exceed B<--max-packet-size>. B<--no-match-packet-size> disabled this behavior, always setting the minimum to B<--min-packet-size>. =item B<--no-send-version> Do not send the current running version of 2ping with each packet. =item B<--notice>=I Arbitrary notice text to send with each packet. If the remote peer supports it, this may be displayed to the user. =item B<--packet-loss>=I Simulate random packet loss outbound and inbound. For example, I<25:10> means a 25% chance of not sending a packet, and a 10% chance of ignoring a received packet. A single number without colon separation means use the same percentage for both outbound and inbound. =item B<--port>=I Use UDP port I. With B<--listen>, this is the port to bind as, otherwise this is the port to send to. Default is UDP port 15998. =item B<--stats>=I Print a line of brief current statistics every I seconds. The same line can be printed on demand by sending SIGQUIT to the 2ping process. =back =head1 BUGS There are probably lots and lots and lots of unknown bugs. By default, source IP logic doesn't work as expected, see B<-I> for details. There appears to be no way to peg the source IP of reply UDP packets to the destination of the packet that is being replied to. As a result, packets always go out the interface's main IP address if not specified manually. (Please, prove the author wrong.) This manpage isn't finished yet, and may never be. =head1 AUTHOR B<2ping> was written by Ryan Finnie . =cut 2ping-2.0/COPYING0000644000175000017500000004310311441564473012143 0ustar ryanryan GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. 2ping-2.0/ChangeLog0000644000175000017500000000666611745112174012671 0ustar ryanryan2ping 2.0 (2012-04-22) * Updated to support 2ping protocol 2.0 * Protocol 1.0 and 2.0 are backwards and forwards compatible with each other * Added support for extended segments * Added extended segment support for program version and notice text * Changed default minimum packet size from 64 to 128 bytes * Added peer reply packet size matching support, turned on by default * Added extra error output for socket errors (such as hostname not found) * Added extra version support for downstream distributions * Removed generation of 2ping6 symlinks at "make all" time (symlinks are still generated during "make install" in the destination tree 2ping 1.2.3 (2012-01-01) * Fixed ewma report (was always showing the last rtt) * Fixed the various brown paper bag stuff I did in 1.2.1 and 1.2.2 while I rediscovered the magical journey that is git 2ping 1.2 (2011-12-24) * Added exponentially-weighted moving average (ewma) and moving standard drviation (mdev) statistics to the summary display 2ping 1.1 (2011-04-05) * Host processing delays sent by the peer are no longer considered when calculating RTT * Changed ID expiration (for which no courtesty was received) time from 10 minutes to 2 minutes * Manpage fix: correct UDP port number listed * Added an RPM spec file 2ping 1.0 (2010-10-20) * Protocol now "finished", 2ping is now "stable"! * Removed the sample initscript * Small Makefile and documentation changes 2ping 0.9.1 (2010-10-09) * Version bumped to 0.9.1 to signify a stable standardization is close * Changed the default UDP port from 58277 to 15998 (IANA-registered port) * Host processing latency is now subtracted where possible (protocol extension, backwards compatible) * Minor code cleanup * 0.9.0 (unreleased) was a Brown Paper Bag commit; typo in ChangeLog fixed 2ping 0.0.3 (2010-10-03) * Large cleanup and documentation push -- code is now "acceptable" * Fixed calculation of opcode data area lengths on some opcodes; implementation now incompatible with 0.0.2 * Added more checks against malformed packets; 2ping no longer produces produces Perl warnings when fuzzing * Added a preload (-l) option, mimicking ping's -l functionality * Added a 2ping6 symlink; 2ping will now assume -6 if called as 2ping6 * Added a message authentication code (MAC) option with a pre-shared key (--auth=key), allowing for message authentication and verification while in transit * Added a timed interval of brief statistics output (--stats=int) * STDOUT buffering is disabled in all modes now * Added compatibility down to Perl 5.6.0 * Cleaned up distribution tarball, added a Makefile * Changed man section from 1 to 8 2ping 0.0.2 (2010-09-07) * Fixed potential endianness issues * Added packet checksum field, in a fixed position near the beginning of the packet (PROTOCOL NOW INCOMPATIBLE WITH 0.0.1 RELEASE) * Added state table cleanup notification between peers, which will keep memory usage down in longer flood ping situations (protocol opcode added) * Added support for multiple binds in listen mode (specify -I IP multiple times) * Added support for multiple peers in client mode (specify multiple IP arguments) * Added additional packet error checks * Misc code cleanup and documentation (not yet to my satisfaction, but it's a start) 2ping 0.0.1 (2010-08-29) * Initial release 2ping-2.0/Makefile0000644000175000017500000000310311744752410012540 0ustar ryanryanPREFIX := /usr/local all: 2ping 2ping: src/2ping.pl perl -pe 's%#EXTRAVERSION#%$(EXTRAVERSION)%g' $< >$@ chmod 0755 $@ # Docs are shipped pre-compiled doc: 2ping.8 2ping.8.html 2ping.8: 2ping pod2man -c '' -r '' -s 8 $< >$@ 2ping.8.html: 2ping pod2html $< >$@ rm -f pod2htmd.tmp pod2htmi.tmp test: @perl -MGetopt::Long -e 'print "Getopt::Long is installed.\n";' @perl -MPod::Usage -e 'print "Pod::Usage is installed.\n";' @perl -MIO::Select -e 'print "IO::Select is installed.\n";' @perl -MIO::Socket::INET -e 'print "IO::Socket::INET is installed.\n";' @perl -MTime::HiRes -e 'print "Time::HiRes is installed.\n";' @perl -MIO::Socket::INET6 -e 'print "IO::Socket::INET6 is installed.\n";' 2>/dev/null || echo 'IO::Socket::INET6 is not installed (but optional).' @perl -MDigest::MD5 -e 'print "Digest::MD5 is installed.\n";' 2>/dev/null || echo 'Digest::MD5 is not installed (but optional).' @perl -MDigest::SHA -e 'print "Digest::SHA is installed.\n";' 2>/dev/null || echo 'Digest::SHA is not installed (but optional).' @perl -MDigest::CRC -e 'print "Digest::CRC is installed.\n";' 2>/dev/null || echo 'Digest::CRC is not installed (but optional).' @echo 'All tests complete.' install: all install -d -m 0755 $(DESTDIR)$(PREFIX)/bin install -d -m 0755 $(DESTDIR)$(PREFIX)/share/man/man8 install -m 0755 2ping $(DESTDIR)$(PREFIX)/bin ln -sf 2ping $(DESTDIR)$(PREFIX)/bin/2ping6 install -m 0644 2ping.8 $(DESTDIR)$(PREFIX)/share/man/man8 ln -sf 2ping.8 $(DESTDIR)$(PREFIX)/share/man/man8/2ping6.8 distclean: clean clean: rm -f 2ping doc-clean: rm -f 2ping.8 2ping.8.html