pax_global_header00006660000000000000000000000064126754167600014530gustar00rootroot0000000000000052 comment=cde83db8a8d1fef6dfdc1f6d1ecaee2b7f2b496e 2ping-3.2.1/000077500000000000000000000000001267541676000125525ustar00rootroot000000000000002ping-3.2.1/.gitignore000066400000000000000000000013101267541676000145350ustar00rootroot00000000000000MANIFEST # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ 2ping-3.2.1/.travis.yml000066400000000000000000000002341267541676000146620ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" install: - pip install argparse - pip install . script: - python setup.py test - python setup.py sdist 2ping-3.2.1/2ping000077500000000000000000000016101267541676000135150ustar00rootroot00000000000000#!/usr/bin/env python # 2ping - A bi-directional ping utility # Copyright (C) 2015 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. if __name__ == '__main__': import sys import twoping.cli sys.exit(twoping.cli.main()) 2ping-3.2.1/2ping.spec000066400000000000000000000017701267541676000144520ustar00rootroot00000000000000Name: 2ping Version: 3.2.1 Release: 1%{?dist} Summary: Bi-directional ping utility License: GPLv2+ URL: http://www.finnie.org/software/2ping Source0: http://www.finnie.org/software/%{name}/%{name}-%{version}.tar.gz BuildArch: noarch BuildRequires: python2-devel %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 %autosetup -n 2ping-%{version} %build %py2_build %install %py2_install install -d -m 0755 $RPM_BUILD_ROOT/usr/share/man/man1 install -m 0644 doc/2ping.1 $RPM_BUILD_ROOT/usr/share/man/man1/2ping.1 install -m 0644 doc/2ping.1 $RPM_BUILD_ROOT/usr/share/man/man1/2ping6.1 %check %{__python2} setup.py test %files %doc ChangeLog COPYING README %{python2_sitelib}/* %{_bindir}/2ping %{_bindir}/2ping6 %{_mandir}/man1/2ping.1* %{_mandir}/man1/2ping6.1* 2ping-3.2.1/2ping6000077500000000000000000000016101267541676000136030ustar00rootroot00000000000000#!/usr/bin/env python # 2ping - A bi-directional ping utility # Copyright (C) 2015 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. if __name__ == '__main__': import sys import twoping.cli sys.exit(twoping.cli.main()) 2ping-3.2.1/COPYING000066400000000000000000000431031267541676000136060ustar00rootroot00000000000000 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-3.2.1/ChangeLog000066400000000000000000000163161267541676000143330ustar00rootroot000000000000002ping 3.2.1 (2016-03-26) * Do not error out when non-ASCII notice text is received (only causes a remote denial of service crash when --debug is specified on the remote peer). 2ping 3.2.0 (2016-02-10) * Added --nagios, for Nagios-compatible output and status codes. * Added unit tests. * Added --send-time, which sends an extended segment containing the current wall time. * Added --send-monotonic-clock, which sends an extended segment containing a monotonically-incrementing counter, on supported platforms. * Added --send-random, which sends an extended segment containing random bytes. * Added -fuzz, which randomly fuzzes incoming packets (developer feature). * Fixed over-cautious handling of length limits when assembling extended segments. 2ping 3.1.0 (2015-11-16) * Best available poller for each platform (e.g. epoll on Linux, kqueue on BSD / OS X) is automatically used. * Old age timeout is set to a lower value on Win32 (1 second instead of 60), as KeyboardInterrupt does not interrupt select() on Win32. * Packet loss is now better visible in flood mode. * Adaptive mode now ramps up to EWMA faster. * Adaptive mode RTT predictions are now calculated per destination. * In client mode, statistics are now separated for each destination. * Added optional DNS SRV client support (requires dnspython). When given --srv, all SRV records for the 2ping UDP service of a host are pinged in parallel. * Investigation results are now sorted by sequence number. * Hostnames are displayed in statistics, if known. * 2ping will exit earlier if safe to do so (e.g. "-c 1" will not wait a full second if the ping is received immediately). * --port can now be given service names (as determined by the system resolver) instead of numeric ports. * System platform (Linux, Mach, etc) is sent in packets along with 2ping version. * Statistics use a more human-readable format (m, s, ms, etc). 2ping 3.0.1 (2015-10-29) * Fix peer_address on error when MSG_ERRQUEUE is not set * Documentation update 2ping 3.0.0 (2015-10-25) * Total rewrite from Perl to Python. * Multiple hostnames/addresses may be specified in client mode, and will be pinged in parallel. * Improved IPv6 support: * In most cases, specifying -4 or -6 is unnecessary. You should be able to specify IPv4 and/or IPv6 addresses and it will "just work". * IPv6 addresses may be specified without needing to add -6. * If a hostname is given in client mode and the hostname provides both AAAA and A records, the AAAA record will be chosen. This can be forced to one or another with -4 or -6. * If a hostname is given in listener mode with -I, it will be resolved to addresses to bind as. If the hostname provides both AAAA and A records, they will both be bound. Again, -4 or -6 can be used to restrict the bind. * IPv6 scope IDs (e.g. fe80::213:3bff:fe0e:8c08%eth0) may be used as bind addresses or destinations. * Better Windows compatibility. * ping(8)-compatible superuser restrictions (e.g. flood ping) have been removed, as 2ping is a scripted program using unprivileged sockets, and restrictions would be trivial to bypass. Also, the concept of a "superuser" is rather muddied these days. * Better timing support, preferring high-resolution monotonic clocks whenever possible instead of gettimeofday(). On Windows and OS X, monotonic clocks should always be available. On other Unix platforms, monotonic clocks should be available when using Python 2.7 * Long option names for ping(8)-compatible options (e.g. adaptive mode can be called as --adaptive in addition to -A). See 2ping --help for a full option list. 2ping 2.1.1 (2014-04-15) * Switch to Switch to ExtUtils::MakeMaker build system 2ping 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-3.2.1/MANIFEST.in000066400000000000000000000002401267541676000143040ustar00rootroot00000000000000include README include COPYING include ChangeLog include Makefile include *.md include doc/* include 2ping include 2ping.spec include 2ping6 include tests/*.py 2ping-3.2.1/Makefile000066400000000000000000000003731267541676000142150ustar00rootroot00000000000000all: build build: python setup.py build test: build python setup.py test install: build python setup.py install clean: python setup.py clean $(RM) -r build MANIFEST doc: README $(MAKE) -C doc README: README.md pandoc -s -t plain -o $@ $< 2ping-3.2.1/README000066400000000000000000000032501267541676000134320ustar00rootroot00000000000000 2PING - A BI-DIRECTIONAL PING UTILITY 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 Python 2.7 is recommended for 2ping. Python 2.6 is also supported, with the following notes: - The argparse module is also required. - Monotonic clock support is not available on Unix platforms, instead falling back to time.time() (which will likely be using gettimeofday()). To install: sudo python setup.py install Usage Please see the 2ping manpage for invocation options, 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. License 2ping - A bi-directional ping utility Copyright (C) 2015 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. 2ping-3.2.1/README.md000066400000000000000000000033061267541676000140330ustar00rootroot00000000000000# 2ping - A bi-directional ping utility 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 Python 2.7 is recommended for 2ping. Python 2.6 is also supported, with the following notes: * The argparse module is also required. * Monotonic clock support is not available on Unix platforms, instead falling back to `time.time()` (which will likely be using `gettimeofday()`). To install: sudo python setup.py install ## Usage Please see the 2ping manpage for invocation options, 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. ## License 2ping - A bi-directional ping utility Copyright (C) 2015 [Ryan Finnie](http://www.finnie.org/) 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. 2ping-3.2.1/doc/000077500000000000000000000000001267541676000133175ustar00rootroot000000000000002ping-3.2.1/doc/2ping-protocol-examples.py000066400000000000000000000223141267541676000203650ustar00rootroot00000000000000#!/usr/bin/env python # 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from twoping import packets def h(input): return ' '.join('{:02x}'.format(x) for x in input) print '### Example 1' print packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') print ' CLIENT: %s' % h(packet.dump()) print print '### Example 2' print packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x01') print ' SERVER: %s' % h(packet.dump()) print print '### Example 3' print packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x01') print ' SERVER: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 print ' CLIENT: %s' % h(packet.dump()) print print '### Example 4' print packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) print ' SERVER: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x03') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 print ' CLIENT: %s' % h(packet.dump()) print print '### Example 5' print packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) print ' SERVER: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x03') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 print ' CLIENT: %s' % h(packet.dump()) print print '### Example 6' print packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x03') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x03') print ' SERVER: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x04') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12823 print ' CLIENT: %s' % h(packet.dump()) print ' ... etc' packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x0a') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x02')) print ' CLIENT: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x06') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x0a') packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x02')) packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xb0\x02')) print ' SERVER: %s' % h(packet.dump()) packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x0b') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x06') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 13112 packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xb0\x02')) print ' CLIENT: %s' % h(packet.dump()) print 2ping-3.2.1/doc/2ping-protocol.md000066400000000000000000001013351267541676000165220ustar00rootroot00000000000000# 2ping protocol * Line protocol version: 3.1 * Document version: 20160210 ## Introduction The 2ping protocol is a bi-directional ping system. This allows for several features, such as 3-way ping (akin to SYN, SYN/ACK, ACK), and the ability to determine whether packet loss was inbound or outbound. This is accomplished by the client and server each keeping state tables of needed incoming and outgoing requests. If packet loss occurs without a complete network failure, the client and server can eventually resume communication and compare notes about which sides received what requests. ## Network transit 2ping operates over UDP, and is compatible with IPv4, IPv6 and possibly future Layer 3 protocols. The server/listener listens on port 15998. The client can use any high source port, and will send packets to the server on destination port 15998. The server will then respond to the client on the port the client originally used as a source port. The UDP message payload is currently completely binary data, and is designed with consistent and/or minimum space, consistent parsing, and forwards compatibility in mind. The protocol payload does not contain any IP-specific information, and is NAT/PAT safe. ## Message format The 2ping format is a variable length binary format, with the length determined by which opcodes are set. Opcode data lengths are in a standardized location, which allows for forward compatibility. A program utilizing the 2ping protocol can still parse a complete payload, even if the program does not understand a certain opcode. All data is in network byte format: octets are most significant bit first, and multiple-octet sequences (flags and integers capable of being larger than 8 bits) are most significant octet first. An exception is the order of opcode data segments; data for opcodes is stored in sequence according to which flags are set, starting with the least significant bit. | Field | Length | | ----- | ------ | | Magic number 0x3250 | 2 octets, required | | Checksum | 2 octets, field required, checksum optional | | Message ID | 6 octets, required | | Opcode flags | 2 octets, required | | Opcode data | Variable, zero or more octets, length depends on opcode flags | | Padding | Variable, optional | Thus, the minimum valid length of a 2ping payload is 12 octets (2-octet magic number + 2-octet checksum field + 6-octet message ID + 2-octet opcode flags with no flags enabled). 2 octets of opcode flags allow for up to 16 flags. The final flag (0x8000) is a container for an extended segment format, and allows for a nearly unlimited number of available segments. Padding may be added to a packet to bring it up to a desired minimum size. A 128 octet minimum packet size is recommended. Padding octets should be null character octets by default, but the contents of the padding is not important, as the packet parser should not use the contents of the padding for program operation. Zero or more opcode data segments are appended to form the opcode data area. Each opcode data segment is comprised of the following: | Field | Length | | ----- | ------ | | Segment data length (not including length header itself) | 2 octets, required | | Segment data | Variable, zero or more octets, determined by segment data length header above | An opcode data segment may be between zero and 65,535 octets long, plus 2 octets for the segment header. This layout is designed for forward compatibility with future protocol revisions. A 2ping implementation can simply stop processing once it reaches the last opcode flag it understands, or it can at least parse the opcodes it does not understand, as the opcode data segment header includes the length of the segment data -- using this information, the implementation can simply skip over the opcodes it does not understand. If there is a numeric gap in the understood opcodes of a 2ping implementation, it must at least check for the opcode and parse the opcode data segment in order to skip over it. For example, a 2ping implementation might understand 0x0001, 0x0002 and 0x0008, but not 0x0004, 0x0010 or 0x0020. To be able to parse the data segment for 0x0008, it must still check whether 0x0004 is set and seek to its segment data length header, determine the length of the segment data for 0x0004, and skip over it to begin parsing 0x0008. Due to the standardized length headers, this is possible without knowing what 0x0004 does. Once it is finished parsing 0x0008, it may stop checking opcode flags altogether. The minimum 2ping implementation must be able to understand and follow opcodes 0x0001 (reply requested) and 0x0002 (in reply to). Implementation of all other opcodes are optional (but highly recommended, as investigation opcodes are the core focus of 2ping). ## Message IDs Message IDs are 48 bits (6 octets) of data to identify a specific message. The message ID must be 48 bits of pseudo-random data; do not use incrementing numbers or a discernible pattern. Likewise, do not attempt to analyze or devise logic from the message IDs that a peer sends. Message IDs are unique within a socket's 5-tuple (local address, local port, peer address, peer port, protocol). For example, when implementing a listener, care should be taken to differentiate between message ID 0xF1E2D3C4B5A6 sent from peer 10.0.0.10, and message ID 0xF1E2D3C4B5A6 sent from peer 172.16.0.10, or even between the same message ID sent from the same host on different source ports. While the specification's random ID requirement and keyspace size makes the chance of a collision from two different peers unlikely, it is still possible. It is understandable that a 48-bit random unique ID is not easy to read from an end user's perspective. The implementation may therefore wish to keep a local mapping between message IDs and a more human-friendly identifier, such as an incrementing integer. ## Checksum The checksum allows for verification of packet integrity from unintentional transit errors. (If cryptographic verification is desired to prevent intentional tampering in transit, see opcode 0x0080 below.) While UDP includes its own checksum, it is optional in IPv4 and cannot be relied upon. (IPv6 makes the UDP checksum mandatory.) The 2ping protocol uses the following checksum method, as described in pseudocode: checksum = 0 if length(input) is not even: input.append(0) for (a, b) in input as ((int1, int2), (int3, int4), ...): checksum = checksum + (a << 8) + b checksum = ((checksum & 0xffff) + (checksum >> 16)) checksum = ~checksum & 0xffff if checksum == 0: checksum = 0xffff The input data is defined as the entire packet data payload, from the magic number to the end of the padding. The checksum field itself is zeroed for the purpose of the method input, and is replaced with the result of the method. An all zero transmitted checksum value means that the transmitter generated no checksum. The checksum field is required, though an implementation is not required to perform or verify checksums. However, if verification of checksums are performed, the verifier must be able to recognize the zero transmitted checksum value from a peer that did not compute a checksum. Checksum computation and verification attempts are strongly recommend for IPv4 packets, but should not be necessary for IPv6 packets, as verification is already performed at the UDPv6 level, and malformed packets should be discarded by the time they reach the userland. This specification originally specified a different checksum method (RFC 768's UDP checksum method). However, it was discovered that the only known 2ping implementation which computed checksums at the time was using an incorrect method, so the specification was changed to match the implementation to preserve compatibility. ## Opcodes Opcode data areas are only included when the corresponding opcode flag is set. Thus if no opcode flags are set, the opcode data area does not exist. If 0x0001 and 0x0020 flags are set, the entire opcode data area consists of the opcode header and segment data for 0x0001, followed by the opcode header and segment data for 0x0020. ### 0x0001 - Reply requested | Field | Length | | ----- | ------ | | No segment data | 0 octets | The sending end requests a reply from the receiving end. If the receiving end does not send a reply back to the sending end, the sending end will consider it a lost packet. Note: No segment data is part of this opcode, but as all used opcode segments must have a header, there will still be a 2-octet length header, signifying zero octets of data. ### 0x0002 - In reply to | Field | Length | | ----- | ------ | | Replied message ID | 6 octets, required | This opcode signifies the reply to a packet that requested a reply. The original sender's message ID is enclosed. ### 0x0004 - RTT enclosed | Field | Length | | ----- | ------ | | RTT in microseconds | 4 octets, required | If this packet is a reply packet and an RTT is enclosed, the packet being replied to was a successful ping, and the RTT is the round trip time of the previous operation, in microseconds. Up to 2^32-1 microseconds are possible, approximately 71 minutes. In a typical 3-way ping scenario, this is only sent in the third message in the sequence, from the client to the server. Thus the server will know both the server->client->server ping RTT, as well as the original client->server->client ping RTT. ### 0x0008 - Investigation complete, originally replied to as requested | Field | Length | | ----- | ------ | | Number of message IDs enclosed | 2 octets, required | | Message ID | 6 octets, optional | | Message ID... | 6 octets..., optional | This is a response to "0x0020 - Expected reply never received, please investigate", please continue reading below. The message IDs listed in this opcode are packets that the responder knows about, and had responded to. Since the requester never received the response, the requester can assume that the packet loss occurred inbound (relative to the requester). In theory, the number of message IDs enclosed may be between zero and 65,535, but limits on the total octet length of the opcode data area will make this impossible to achieve. In addition, UDP, IP and Ethernet length restrictions will further limit the number of message IDs that can be enclosed. The number of message IDs enclosed may legally be zero, but if that is the case, it's better to just not set the 0x0008 opcode flag. Investigation replies must not be unsolicited, and may only be sent in response to messages that requested a reply, and requested an investigation into specific message IDs. However, investigation responses by the requestee are not required, and can be ignored if packet payload space is limited or other problems arise. It is the responsibility of the requester to resend investigation requests if the requestee does not respond to them in its reply. ### 0x0010 - Investigation complete, request never received | Field | Length | | ----- | ------ | | Number of message IDs enclosed | 2 octets, required | | Message ID | 6 octets, optional | | Message ID... | 6 octets..., optional | This is a response to "0x0020 - Expected reply never received, please investigate", please continue reading below. The message IDs listed in this opcode are packets that the responder does not know about. Since the responder never received the request, the requester can assume that the packet loss occurred outbound (relative to the requester). In theory, the number of message IDs enclosed may be between zero and 65,535, but limits on the total octet length of the opcode data area will make this impossible to achieve. In addition, UDP, IP and Ethernet length restrictions will further limit the number of message IDs that can be enclosed. The number of message IDs enclosed may legally be zero, but if that is the case, it's better to just not set the 0x0010 opcode flag. Investigation replies must not be unsolicited, and may only be sent in response to messages that requested a reply, and requested an investigation into specific message IDs. However, investigation responses by the requestee are not required, and can be ignored if packet payload space is limited or other problems arise. It is the responsibility of the requester to resend investigation requests if the requestee does not respond to them in its reply. ### 0x0020 - Expected reply never received, please investigate | Field | Length | | ----- | ------ | | Number of message IDs enclosed | 2 octets, required | | Message ID | 6 octets, optional | | Message ID... | 6 octets..., optional | If the requester sends a packet to a remote party and indicates a reply is requested (0x0001), and a reply is never received, the requester can use this opcode to inquire about what happened to it. If replies to this inquiry come back in either "0x0008 - Investigation complete, originally replied to as requested" or "0x0010 - Investigation complete, request never received" opcodes (as explained above), the requester can use this information to determine whether the packet loss occurred inbound or outbound relative to the requester. In theory, the number of message IDs enclosed may be between zero and 65,535, but limits on the total octet length of the opcode data area will make this impossible to achieve. In addition, UDP, IP and Ethernet length restrictions will further limit the number of message IDs that can be enclosed. The number of message IDs enclosed may legally be zero, but if that is the case, it's better to just not set the 0x0020 opcode flag. Care should be taken not to begin inquiring too quickly. UDP packets may arrive delayed or out of order, so 10 seconds should be a good amount of time to wait before inquiring. The requester may send inquiries multiple times. The requestee is not required to respond to a specific inquiry immediately, as UDP payload space may be limited. Thus it is the responsibility of the requester to continue to send inquiries until it receives a response, so the requestee does not need to keep a state table of unreplied inquiries. ### 0x0040 - Courtesy message ID expiration | Field | Length | | ----- | ------ | | Number of message IDs enclosed | 2 octets, required | | Message ID | 6 octets, optional | | Message ID... | 6 octets..., optional | To facilitate bi-directional packet loss detection, it is necessary for each peer to maintain a set of state tables: messages expecting a reply and not yet received, and remote peer messages expecting a reply that the near end has replied to. Maintenance of the first cache is easy; simply remove a message ID once a reply or investigation has been received. However, the second case is trickier. In some cases, it may be possible to know if the far end has received an expected response and will not inquire about it. But in others, there may not be enough hints to be able to guarantee a peer will not ask for an investigation later. In particular, in a 3-way ping (assuming "Peer 1" initiated the ping), Peer 2 can figure out that the near end is satisfied with the response to the first leg, since Peer 1 sent the third leg to Peer 2, and so Peer 2 can remove the first leg's message ID from its cache. However, Peer 1 cannot tell if Peer 2 received the third leg (a response to the second leg), since a response to the third leg does not exist. But Peer 2 could in the future inquire about the response to the second leg sometime in the future. In this situation, it would be best to do a periodic cleanup of the state tables, but ideally the age of the message ID should be long enough (recommended default is 10 minutes), since it may take the peer awhile to request an investigation. In a session with one or two pings per second that's not a lot, but in a flood mode, thousands or millions of message IDs could get stored in the state table during that 10 minute period. To combat this, the courtesy opcode contains a list of message IDs that a peer is no longer concerned about, and can be removed from the remote peer's cache. Courtesy opcodes can be sent any time a packet is being sent from one peer to another (unlike investigation requests, which may only be sent when requesting a reply, or investigation results, which are in the immediate response to the investigation request). This opcode is optional (but recommended), and due to its opportunistic nature, it is not guaranteed the message will reach the remote peer. So it is still up to an implementation to do periodic maintenance on its caches, since it cannot rely on courtesy responses to prune its caches. Peers may send unsolicited messages with courtesy opcodes (a message not requesting a reply, not replying to another message, and containing the courtesy opcode), but in the normal course of operation, it should not be necessary. ### 0x0080 - Message authentication code (MAC) | Field | Length | | ----- | ------ | | Digest type index | 2 octets, required | | Computed hash value | Length depends on digest type, required | The message authentication code field is allowed to provide cryptographic data integrity and authentication of the remote side. This helps to prevent injection and replay attacks during transit. The payload data, combined with a shared secret key, is hashed and is verified by the other peer. The digest types supported are: | Index | Digest Type | Computed hash size | | ----- | ----------- | ------------------ | | 0 | Private / locally reserved | Variable | | 1 | HMAC-MD5 | 16 octets | | 2 | HMAC-SHA1 | 20 octets | | 3 | HMAC-SHA256 | 32 octets | | 4 | HMAC-CRC32 | 4 octets | The two peers must use the same key and digest type. If a peer is instructed to hash its messages, it must not accept replies that are not hashed with the same digest type and key. Index 0 may be used by clients for digest types not defined by this specification, and implementations must ignore index 0 hashes if a non-standard digest has not been defined locally. Additional digest types may be added to this specification in the future. The entire payload is hashed, from the magic number to the padding, inclusive. When computing the MAC hash, the hash value itself is filled with zeroed octets, and replaced with the computed hash. The MAC hash is computed before the payload checksum, therefore the checksum area must also be zeroed before the MAC hash is computed. Use of this opcode is optional and its parameters (whether to use hashing, the digest type and the shared secret) must be coordinated between the two sides ahead of time. An implementation is not required to implement all digest types. HMAC-CRC32 is included in this specification mostly as a joke, and a programming exercise for the reader. Please don't use this (and expect any sort of cryptographic data integrity). Endianness is not specified by CRC32; use network byte order (big endian). The HMAC specification requires aligning the key to the digest function's block size; CRC32 does not use a block size, so assume a block size of 64 bytes (same as MD5, SHA1 and SHA256). ### 0x0100 - Host processing latency | Field | Length | | ----- | ------ | | Delay in microseconds | 4 octets, required | The time between receiving a message packet and sending a reply packet may be non-trivial, due to host processing. This opcode can be used by an implementation, when sending a reply (0x0002), to inform a peer how much time progressed between a receive and a send. The peer may then subtract this latency time to better calculate network transit time. Up to 2^32-1 microseconds are possible, approximately 71 minutes. The delta should be computed from immediately after the original message packet has been received, to as soon as possible before the reply packet has been sent. Note that due to optional MAC hash and checksum calculation processing, which must be computed after the rest of the opcode data area, there still could be some host processing latency not accounted for in the reported latency time. ### 0x8000 - Extended segments This opcode data segment extends the 2ping format, allowing for a nearly unlimited number of available segments. Within the opcode segment data area is a series of sub-segments, referred to as extended segments, consisting of a 32-bit extID, a length of the extended segment data, and the extended segment data itself. Each part is built as so: | Field | Length | | ----- | ------ | | Extended segment ID | 4 octets, required | | Extended segment data length | 2 octets, required | | Extended segment data | Variable, zero or more octets, determined by extended segment data length header above | Multiple extended segment parts are chained together to form the data area of a 0x8000 opcode. Parsing this opcode's data area is similar to parsing the opcodes as a whole: read the extended segment ID and determine if it's a known ID. If so, parse its contents by reading the extended segment data length and extended segment data. If not, read the extended segment data length to determine how far to skip over the extended segment data. If no extended segments are to be included with the packet payload, do not set the opcode. ## Defined extended segments Extended segment IDs are 32 bits, allowing for approximately 4.2 billion different pieces of functionality. When defining a local extension, please choose a random ID to avoid the possibility of conflicting extensions. If the extension is useful to the public as a whole, please consider submitting it for inclusion in this specification. The following are registered extended segments. An implementation is not required to implement any extended segments (or the extended segment opcode itself, for that matter), but you are strongly encouraged to do so. ### 0x3250564e - Program version | Field | Length | | ----- | ------ | | Program version text | Variable length | The human-readable text version of the program or firmware generating the packet, with optional information such as architecture, etc. An example could be "Network Tools 3.0-1distro2 (x86_64-linux)". As the 2ping protocol is designed to be backwards and forwards compatible, this field must not be used by an implementation to determine functionality. It is recommended that this field be sent with every packet, but received segments should not be displayed to the user unless in a verbose/debug/etc mode. ### 0x2ff6ad68 - Random data | Field | Length | | ----- | ------ | | Flags | 2 octets, required | | Random data | Variable length | Random data as generated by the host's random number generator. The following flags may be present: * 0x0001 - Whether the data was generated by a hardware random number generator. * 0x0002 - Whether the data was generated by the operating system's random number generator. If this segment is to be used for cryptographic or trusted purposes, it should be combined with "0x0080 - Message authentication code (MAC)" to ensure end-to-end integrity. ### 0x64f69319 - Wall clock | Field | Length | | ----- | ------ | | Time in microseconds | 8 octets, required | Host time (wall clock) of the sender, in microseconds since 1970-01-01 00:00:00 UTC (Unix epoch). ### 0x771d8dfb - Monotonic clock | Field | Length | | ----- | ------ | | Generation ID | 2 octets, required | | Time in microseconds | 8 octets, required | Monotonic clock time, in microseconds since an epoch. The epoch is arbitrary; to avoid leaking the uptime of the host system/process, the epoch may be zeroed to a random offset, and the generation ID must be set to a random value at the same time as the epoch offset. A peer may compare two successive values by making sure the generation IDs match and the later time is greater than the earlier time. This segment must only be sent if the host is capable of using a monotonic, high-precision clock. ### 0xa837b44e - Notice text | Field | Length | | ----- | ------ | | Notice text | Variable length | Arbitrary text to be sent with the packet. This text should be defined by the user by a program flag, UI option, etc. It is designed to be human-readable; do not use this segment to pass machine-parsed data between the client and listener. Instead, if extended data transfer is desired between the client and listener, simply define a new extended segment ID. The implementation may display this text to the user, but it is not guaranteed the user will see it. ## Procedures In the following pseudocode examples, message IDs are represented as zero-padded incrementing numbers. This is for the purpose of identifying reply chains in these examples only. In real life, message IDs MUST be randomly-generated 48-bit (6 octet) identifiers. The program implementing the 2ping protocol may choose to locally associate a better identifier for the user (such as an incrementing integer), but the protocol message ID must be random and pseudo-unique. As mentioned above, the simplest 2ping packet is a 12-octet payload: 2 octets for the magic number (always 0x3250), 2 octets for the checksum field, 6 octets for the message ID, and 2 blank opcode flag octets: ### Example 1 CLIENT: 00000000a001, no opcodes Of course, this isn't very useful, and would be analogous to a NOOP. The simplest ping would require the "reply requested" opcode: ### Example 2 CLIENT: 00000000a001, reply requested SERVER: 00000000b001, in reply to 00000000a001 A 3-way ping further extends this. ### Example 3 CLIENT: 00000000a001, reply requested SERVER: 00000000b001, reply requested, in reply to 00000000a001 CLIENT: 00000000a002, in reply to 00000000b001, successful ping rtt 12345 µs The client successfully received the response from the server and was able to measure an RTT of 12345 µs. The client then sends a reply back to the server, referencing the original reply, and letting it know the RTT it measured. The server is then able to determine its RTT between the second and third leg (say, 11804 µs), and also know the RTT between the first and second leg. Now let's say the reply to the original client ping never came back. The client can start inquiring about whether the server saw the original request, and the server can provide info at the same time it is responding to a new ping request: ### Example 4 CLIENT: 00000000a001, reply requested CLIENT: 00000000a002, reply requested, did not receive reply to 00000000a001 SERVER: 00000000b002, reply requested, in reply to 00000000a002, received and replied to 00000000a001 CLIENT: 00000000a003, in reply to 00000000b002, successful ping rtt 12345 µs Now the client can tell that the server received the original request, and replied, which suggests inbound packet loss. Note from the example message IDs, the server most likely originally replied to 00000000a001 with 00000000b001. However, the specific message ID the server originally replied with is not important and not tracked, and hence is not indicated in the investigation reply, only that it was originally received and replied to. Conversely, for a request for reply that the server never actually received: ### Example 5 CLIENT: 00000000a001, reply requested CLIENT: 00000000a002, reply requested, did not receive reply to 00000000a001 SERVER: 00000000b001, reply requested, in reply to 00000000a002, never received 00000000a001 CLIENT: 00000000a003, in reply to 00000000b001, successful ping rtt 12345 µs This suggests outbound packet loss. As mentioned before, the client should not immediately start asking about replies never received. An actual sequence of events may look something like this: ### Example 6 CLIENT: 00000000a001, reply requested CLIENT: 00000000a002, reply requested CLIENT: 00000000a003, reply requested SERVER: 00000000b002, reply requested, in reply to 00000000a003 CLIENT: 00000000a004, in reply to 00000000b002, successful ping rtt 12823 µs ... etc CLIENT: 00000000a00a, reply requested, did not receive reply to 00000000a001 or 00000000a002 SERVER: 00000000b006, reply requested, in reply to 00000000a00a, received and replied to 00000000a001, never received 00000000a002, did not receive reply to 00000000b002 CLIENT: 00000000a00b, in reply to 00000000b006, successful ping rtt 13112 µs, received and replied to 00000000b002 The client waited a few seconds before asking about 00000000a001 and 00000000a002. The server replied that it knew about 00000000a001, but never received 00000000a002, indicating there is some packet loss both inbound and outbound. Also notice that in the same packet where the server replied with info about the client's lost packets inquiry, it also inquired about its own lost packet. In this case, the server never received the last segment of the 3-way ping, 00000000b002. The client then tells the server it did originally receive and respond to the packet in question. This raises an interesting point, that there is essentially no difference between a server and a client in 2ping. The "server" is expected to listen for the initial datagrams, and the "client" is expected to initiate ping requests at regular intervals. But if the "server" decides to randomly initiate a ping request of its own, the "client" is expected to respond appropriately, as a server would do. ## Reference packet dumps ### Example 1 CLIENT: 32 50 2d ae 00 00 00 00 a0 01 00 00 ### Example 2 CLIENT: 32 50 2d ad 00 00 00 00 a0 01 00 01 00 00 SERVER: 32 50 7d a4 00 00 00 00 b0 01 00 02 00 06 00 00 00 00 a0 01 ### Example 3 CLIENT: 32 50 2d ad 00 00 00 00 a0 01 00 01 00 00 SERVER: 32 50 7d a3 00 00 00 00 b0 01 00 03 00 00 00 06 00 00 00 00 a0 01 CLIENT: 32 50 4d 62 00 00 00 00 a0 02 00 06 00 06 00 00 00 00 b0 01 00 04 00 00 30 39 ### Example 4 CLIENT: 32 50 2d ad 00 00 00 00 a0 01 00 01 00 00 CLIENT: 32 50 8d 81 00 00 00 00 a0 02 00 21 00 00 00 08 00 01 00 00 00 00 a0 01 SERVER: 32 50 dd 8e 00 00 00 00 b0 02 00 0b 00 00 00 06 00 00 00 00 a0 02 00 08 00 01 00 00 00 00 a0 01 CLIENT: 32 50 4d 60 00 00 00 00 a0 03 00 06 00 06 00 00 00 00 b0 02 00 04 00 00 30 39 ### Example 5 CLIENT: 32 50 2d ad 00 00 00 00 a0 01 00 01 00 00 CLIENT: 32 50 8d 81 00 00 00 00 a0 02 00 21 00 00 00 08 00 01 00 00 00 00 a0 01 SERVER: 32 50 dd 87 00 00 00 00 b0 01 00 13 00 00 00 06 00 00 00 00 a0 02 00 08 00 01 00 00 00 00 a0 01 CLIENT: 32 50 4d 61 00 00 00 00 a0 03 00 06 00 06 00 00 00 00 b0 01 00 04 00 00 30 39 ### Example 6 CLIENT: 32 50 2d ad 00 00 00 00 a0 01 00 01 00 00 CLIENT: 32 50 2d ac 00 00 00 00 a0 02 00 01 00 00 CLIENT: 32 50 2d ab 00 00 00 00 a0 03 00 01 00 00 SERVER: 32 50 7d a0 00 00 00 00 b0 02 00 03 00 00 00 06 00 00 00 00 a0 03 CLIENT: 32 50 4b 81 00 00 00 00 a0 04 00 06 00 06 00 00 00 00 b0 02 00 04 00 00 32 17 ... etc CLIENT: 32 50 ed 6f 00 00 00 00 a0 0a 00 21 00 00 00 0e 00 02 00 00 00 00 a0 01 00 00 00 00 a0 02 SERVER: 32 50 8d 3b 00 00 00 00 b0 06 00 3b 00 00 00 06 00 00 00 00 a0 0a 00 08 00 01 00 00 00 00 a0 01 00 08 00 01 00 00 00 00 a0 02 00 08 00 01 00 00 00 00 b0 02 CLIENT: 32 50 9a 41 00 00 00 00 a0 0b 00 0e 00 06 00 00 00 00 b0 06 00 04 00 00 33 38 00 08 00 01 00 00 00 00 b0 02 ## Changelog ### 3.1 (20160210) * Added the following registered extended segments: * 0x2ff6ad68: Random data * 0x64f69319: Wall clock * 0x771d8dfb: Monotonic clock ### 3.0 (20151025) * Changed the checksum method from RFC 768 to a custom method with example pseudocode. This creates a functional incompatibility with previous versions of the specification. However, it was discovered that the only known 2ping implementation which computed checksums at the time was using an incorrect method, so the specification was changed to match the implementation to preserve compatibility. * Fixed a typo in the extended segment table, changing "Extended segment ID" from 8 octets to 4 octets. This clarifies the previous (correct) assertion that the segment ID is 32 bits (4 octets). * Populated checksums of example packet dumps. * Corrected the 5th client line of Example 6's packet dump (pseudocode version was correct, but example dump had 2 incorrect bytes added). * Adjusted wording of psuedocode examples to clarify "reply requested" opcode comes before "in reply to" opcode. ### 2.0 (20120422) * Protocol versions 1.0 and 2.0 are backwards and forwards compatible with each other. * Changed recommended default minimum packet size from 64 octets to 128 octets. * Added an extended segment container at opcode 0x8000. * Added the following registered extended segments: * 0x3250564e: Program version * 0xa837b44e: Notice text ### 1.0 (20101020) * Finalized initial release. ## Copyright Copyright (C) 2010 Ryan Finnie This text 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 text 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 text; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 2ping-3.2.1/doc/2ping.1000066400000000000000000000261171267541676000144270ustar00rootroot00000000000000.TH "2PING" "1" "" "" "2ping" .SH NAME .PP 2ping \- A bi\-directional ping utility .SH SYNOPSIS .PP 2ping [\f[I]options\f[]] \f[I]\-\-listen\f[] | host/IP .SH DESCRIPTION .PP \f[C]2ping\f[] 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. .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 \f[I]\-\-inquire\-wait\f[]) 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, enter ^\\ or send the process a QUIT signal. .SH OPTIONS .PP \f[C]ping\f[]\-compatible options (long option names are \f[C]2ping\f[]\-specific): .TP .B \-\-audible, \-a Audible ping. .RS .RE .TP .B \-\-adaptive, \-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. On networks with low rtt this mode is essentially equivalent to flood mode. .RS .RE .TP .B \-\-count=\f[I]count\f[], \-c \f[I]count\f[] Stop after sending \f[I]count\f[] ping requests. .RS .RE .TP .B \-\-flood, \-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. .RS .PP \f[C]2ping\f[]\-specific notes: Detected outbound/inbound loss responses are printed as ">" and "<", respectively. Receive errors are printed as "E". Due to the asynchronous nature of \f[C]2ping\f[], successful responses (backspaces) may overwrite these loss and error characters. .RE .TP .B \-\-interval=\f[I]interval\f[], \-i \f[I]interval\f[] Wait \f[I]interval\f[] seconds between sending each ping. The default is to wait for one second between each ping normally, or not to wait in flood mode. .RS .RE .TP .B \-\-interface\-address=\f[I]address\f[], \-I \f[I]address\f[] Set source IP address. 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. .RS .PP \f[C]2ping\f[]\-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\[aq]s main IP. So this option must be used if you would like to respond via an interface\[aq]s sub\-IP. .RE .TP .B \-\-preload=\f[I]count\f[], \-l \f[I]count\f[] If specified, \f[C]2ping\f[] sends that many packets not waiting for reply. .RS .RE .TP .B \-\-pattern=\f[I]hex_bytes\f[], \-p \f[I]hex_bytes\f[] 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, \f[I]\-\-pattern=ff\f[] will cause the sent packet pad area to be filled with all ones. .RS .PP \f[C]2ping\f[]\-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 (\f[I]\-\-min\-packet\-size\f[]), no padding will be sent. .RE .TP .B \-\-quiet, \-q Quiet output. Nothing is displayed except the summary lines at startup time and when finished. .RS .RE .TP .B \-\-packetsize\-compat=\f[I]bytes\f[], \-s \f[I]bytes\f[] \f[C]ping\f[] compatibility; this will set \f[I]\-\-min\-packet\-size\f[] to this plus 8 bytes. .RS .RE .TP .B \-\-verbose, \-v Verbose output. In \f[C]2ping\f[], this prints decodes of packets that are sent and received. .RS .RE .TP .B \-\-version, \-V Show version and exit. .RS .RE .TP .B \-\-deadline=\f[I]seconds\f[], \-w \f[I]seconds\f[] Specify a timeout, in seconds, before \f[C]2ping\f[] 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. .RS .RE .PP \f[C]2ping\f[]\-specific options: .TP .B \-\-help, \-h Print a synposis and exit. .RS .RE .TP .B \-\-ipv4, \-4 Limit binds to IPv4. In client mode, this forces resolution of dual\-homed hostnames to the IPv4 address. (Without \f[I]\-\-ipv4\f[] or \f[I]\-\-ipv6\f[], the first result will be used as specified by your operating system, usually the AAAA address on IPv6\-routable machines, or the A address on IPv4\-only machines.) In listener mode, this filters out any non\-IPv4 \f[I]\-\-interface\-address\f[] binds, either through hostname resolution or explicit passing. .RS .RE .TP .B \-\-ipv6, \-6 Limit binds to IPv6. In client mode, this forces resolution of dual\-homed hostnames to the IPv6 address. (Without \f[I]\-4\f[] or \f[I]\-6\f[], the first result will be used as specified by your operating system, usually the AAAA address on IPv6\-routable machines, or the A address on IPv4\-only machines.) In listener mode, this filters out any non\-IPv6 \f[I]\-\-interface\-address\f[] binds, either through hostname resolution or explicit passing. .RS .RE .TP .B \-\-auth=\f[I]key\f[] Set a shared key, send cryptographic hashes with each packet, and require cryptographic hashes from peer packets signed with the same shared key. .RS .RE .TP .B \-\-auth\-digest=\f[I]digest\f[] When \f[I]\-\-auth\f[] is used, specify the digest type to compute the cryptographic hash. Valid options are \f[C]hmac\-md5\f[] (default), \f[C]hmac\-sha1\f[] and \f[C]hmac\-sha256\f[]. .RS .RE .TP .B \-\-debug Print (lots of) debugging information. .RS .RE .TP .B \-\-fuzz=\f[I]percent\f[] Simulate corruption of incoming packets, with a \f[I]percent\f[] probability each bit will be flipped. After fuzzing, the packet checksum will be recalculated, and then the checksum itself will be fuzzed (but at a lower probability). .RS .RE .TP .B \-\-inquire\-wait=\f[I]secs\f[] Wait at least \f[I]secs\f[] 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. .RS .RE .TP .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. .RS .RE .TP .B \-\-min\-packet\-size=\f[I]min\f[] Set the minimum total payload size to \f[I]min\f[] bytes, default 128. If the payload is smaller than \f[I]min\f[] bytes, padding will be added to the end of the packet. .RS .RE .TP .B \-\-max\-packet\-size=\f[I]max\f[] Set the maximum total payload size to \f[I]max\f[] bytes, default 512, absolute minimum 64. If the payload is larger than \f[I]max\f[] bytes, information will be rearranged and sent in future packets when possible. .RS .RE .TP .B \-\-nagios=\f[I]wrta\f[],\f[I]wloss%\f[],\f[I]crta\f[],\f[I]closs%\f[] Produce output suitable for use in a Nagios check. If \f[I]\-\-count\f[] is not specified, defaults to 5 pings. A warning condition (exit code 1) will be returned if average RTT exceeds \f[I]wrta\f[] or ping loss exceeds \f[I]wloss%\f[]. A critical condition (exit code 2) will be returned if average RTT exceeds \f[I]crta\f[] or ping loss exceeds \f[I]closs%\f[]. .RS .RE .TP .B \-\-no\-3way Do not perform 3\-way pings. Used most often when combined with \f[I]\-\-listen\f[], as the listener is usually the one to determine whether a ping reply should become a 3\-way ping. .RS .PP 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. .RE .TP .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 \f[I]\-\-max\-packet\-size\f[]. \f[I]\-\-no\-match\-packet\-size\f[] disabled this behavior, always setting the minimum to \f[I]\-\-min\-packet\-size\f[]. .RS .RE .TP .B \-\-no\-send\-version Do not send the current running version of 2ping with each packet. .RS .RE .TP .B \-\-notice=\f[I]text\f[] Send arbitrary notice \f[I]text\f[] with each packet. If the remote peer supports it, this may be displayed to the user. .RS .RE .TP .B \-\-packet\-loss=\f[I]out:in\f[] Simulate random packet loss outbound and inbound. For example, \f[I]25:10\f[] 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. .RS .RE .TP .B \-\-port=\f[I]port\f[] Use UDP port \f[I]port\f[], either a numeric port number of a service name string. With \f[I]\-\-listen\f[], this is the port to bind as, otherwise this is the port to send to. Default is UDP port 15998. .RS .RE .TP .B \-\-send\-monotonic\-clock Send a monotonic clock value with each packet. Peer time (if sent by the peer) can be viewed with \f[I]\-\-verbose\f[]. Only supported if the system is capable of generating a monotonic clock. .RS .RE .TP .B \-\-send\-random=\f[I]bytes\f[] Send random data to the peer, up to \f[I]bytes\f[]. The number of bytes will be limited by other factors, up to \f[I]\-\-max\-packet\-size\f[]. If this data is to be used for trusted purposes, it should be combined with \f[I]\-\-auth\f[] for HMAC authentication. .RS .RE .TP .B \-\-send\-time Send the host time (wall clock) with each packet. Peer time (if sent by the peer) can be viewed with \f[I]\-\-verbose\f[]. .RS .RE .TP .B \-\-srv In client mode, causes hostnames to be looked up via DNS SRV records. If the SRV query returns multiple record targets, they will all be pinged in parallel; priority and weight are not considered. The record\[aq]s port will be used instead of \f[I]\-\-port\f[]. This functionality requires the dnspython module to be installed. .RS .RE .TP .B \-\-stats=\f[I]interval\f[] Print a line of brief current statistics every \f[I]interval\f[] seconds. The same line can be printed on demand by entering ^\\ or sending the QUIT signal to the 2ping process. .RS .RE .SH BUGS .PP None known, many assumed. .SH AUTHOR .PP \f[C]2ping\f[] was written by Ryan Finnie . .SH AUTHORS Ryan Finnie. 2ping-3.2.1/doc/2ping.md000066400000000000000000000241431267541676000146640ustar00rootroot00000000000000% 2PING(1) | 2ping % Ryan Finnie # NAME 2ping - A bi-directional ping utility # SYNOPSIS 2ping [*options*] *--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 *--inquire-wait*) 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, enter \^\\ or send the process a QUIT signal. # OPTIONS `ping`-compatible options (long option names are `2ping`-specific): --audible, -a : Audible ping. --adaptive, -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. On networks with low rtt this mode is essentially equivalent to flood mode. --count=*count*, -c *count* : Stop after sending *count* ping requests. --flood, -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. `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. --interval=*interval*, -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. --interface-address=*address*, -I *address* : Set source IP address. 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. --preload=*count*, -l *count* : If specified, `2ping` sends that many packets not waiting for reply. --pattern=*hex_bytes*, -p *hex_bytes* : 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, *--pattern=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*), no padding will be sent. --quiet, -q : Quiet output. Nothing is displayed except the summary lines at startup time and when finished. --packetsize-compat=*bytes*, -s *bytes* : `ping` compatibility; this will set *--min-packet-size* to this plus 8 bytes. --verbose, -v : Verbose output. In `2ping`, this prints decodes of packets that are sent and received. --version, -V : Show version and exit. --deadline=*seconds*, -w *seconds* : 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 options: --help, -h : Print a synposis and exit. --ipv4, -4 : Limit binds to IPv4. In client mode, this forces resolution of dual-homed hostnames to the IPv4 address. (Without *--ipv4* or *--ipv6*, the first result will be used as specified by your operating system, usually the AAAA address on IPv6-routable machines, or the A address on IPv4-only machines.) In listener mode, this filters out any non-IPv4 *--interface-address* binds, either through hostname resolution or explicit passing. --ipv6, -6 : Limit binds to IPv6. In client mode, this forces resolution of dual-homed hostnames to the IPv6 address. (Without *-4* or *-6*, the first result will be used as specified by your operating system, usually the AAAA address on IPv6-routable machines, or the A address on IPv4-only machines.) In listener mode, this filters out any non-IPv6 *--interface-address* binds, either through hostname resolution or explicit passing. --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`. --debug : Print (lots of) debugging information. --fuzz=*percent* : Simulate corruption of incoming packets, with a *percent* probability each bit will be flipped. After fuzzing, the packet checksum will be recalculated, and then the checksum itself will be fuzzed (but at a lower probability). --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. --nagios=*wrta*,*wloss%*,*crta*,*closs%* : Produce output suitable for use in a Nagios check. If *--count* is not specified, defaults to 5 pings. A warning condition (exit code 1) will be returned if average RTT exceeds *wrta* or ping loss exceeds *wloss%*. A critical condition (exit code 2) will be returned if average RTT exceeds *crta* or ping loss exceeds *closs%*. --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* : Send arbitrary notice *text* 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*, either a numeric port number of a service name string. With *--listen*, this is the port to bind as, otherwise this is the port to send to. Default is UDP port 15998. --send-monotonic-clock : Send a monotonic clock value with each packet. Peer time (if sent by the peer) can be viewed with *--verbose*. Only supported if the system is capable of generating a monotonic clock. --send-random=*bytes* : Send random data to the peer, up to *bytes*. The number of bytes will be limited by other factors, up to *--max-packet-size*. If this data is to be used for trusted purposes, it should be combined with *--auth* for HMAC authentication. --send-time : Send the host time (wall clock) with each packet. Peer time (if sent by the peer) can be viewed with *--verbose*. --srv : In client mode, causes hostnames to be looked up via DNS SRV records. If the SRV query returns multiple record targets, they will all be pinged in parallel; priority and weight are not considered. The record's port will be used instead of *--port*. This functionality requires the dnspython module to be installed. --stats=*interval* : Print a line of brief current statistics every *interval* seconds. The same line can be printed on demand by entering \^\\ or sending the QUIT signal to the 2ping process. # BUGS None known, many assumed. # AUTHOR `2ping` was written by Ryan Finnie \. 2ping-3.2.1/doc/Makefile000066400000000000000000000002221267541676000147530ustar00rootroot00000000000000MARKDOWN=2ping.md MAN=$(patsubst %.md,%.1,$(MARKDOWN)) all: man doc: man man: ${MAN} clean: rm -f ${MAN} %.1: %.md pandoc -s -t man -o $@ $< 2ping-3.2.1/setup.py000066400000000000000000000025401267541676000142650ustar00rootroot00000000000000#!/usr/bin/env python import os from setuptools import setup def read(filename): return open(os.path.join(os.path.dirname(__file__), filename)).read() setup( name='2ping', description='2ping a bi-directional ping utility', long_description=read('README'), version='3.2.1', license='GPLv2+', platforms=['Unix'], author='Ryan Finnie', author_email='ryan@finnie.org', url='http://www.finnie.org/software/2ping/', download_url='http://www.finnie.org/software/2ping/', packages=['twoping'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 'Natural Language :: English', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet', 'Topic :: System :: Networking', 'Topic :: Utilities', ], entry_points={ 'console_scripts': [ '2ping = twoping.cli:main', '2ping6 = twoping.cli:main', ], }, test_suite='tests', ) 2ping-3.2.1/tests/000077500000000000000000000000001267541676000137145ustar00rootroot000000000000002ping-3.2.1/tests/__init__.py000066400000000000000000000000001267541676000160130ustar00rootroot000000000000002ping-3.2.1/tests/test_best_poller.py000066400000000000000000000025461267541676000176460ustar00rootroot00000000000000#!/usr/bin/env python import unittest from twoping import best_poller import socket class TestBestPoller(unittest.TestCase): def test_register(self): poller = best_poller.best_poller() s = socket.socket() poller.register(s) self.assertEqual( poller.f_dict[s.fileno()], s ) def test_register_multiple(self): poller = best_poller.best_poller() s1 = socket.socket() s2 = socket.socket() poller.register(s1) poller.register(s2) self.assertEqual( (poller.f_dict[s1.fileno()], poller.f_dict[s2.fileno()]), (s1, s2) ) def test_register_idempotent(self): poller = best_poller.best_poller() s = socket.socket() poller.register(s) poller.register(s) self.assertEqual( len(poller.f_dict), 1 ) def test_available_pollers(self): self.assertTrue(len(best_poller.available_pollers()) > 0) def test_known_poller(self): self.assertTrue(isinstance( best_poller.best_poller(), ( best_poller.EpollPoller, best_poller.KqueuePoller, best_poller.PollPoller, best_poller.SelectPoller, ) )) if __name__ == '__main__': unittest.main() 2ping-3.2.1/tests/test_crc32.py000066400000000000000000000007531267541676000162460ustar00rootroot00000000000000#!/usr/bin/env python import unittest from twoping import crc32 import hmac class TestCRC32(unittest.TestCase): def test_crc32(self): c = crc32.new() c.update('Data to hash') self.assertEqual(bytearray(c.digest()), bytearray(b'\x44\x9e\x0a\x5c')) def test_hmac(self): h = hmac.new('Secret key', 'Data to hash', crc32) self.assertEqual(bytearray(h.digest()), bytearray(b'\x3c\xe1\xb6\xb9')) if __name__ == '__main__': unittest.main() 2ping-3.2.1/tests/test_monotonic_clock.py000066400000000000000000000020101267541676000204760ustar00rootroot00000000000000#!/usr/bin/env python import unittest from twoping import monotonic_clock class TestMonotonicClock(unittest.TestCase): def setUp(self): self.clock = monotonic_clock.clock self.clock_info = monotonic_clock.get_clock_info('clock') def test_clock(self): self.assertEqual(type(self.clock()), float) def test_monotonic(self): if not self.clock_info.monotonic: return time1 = self.clock() time2 = self.clock() self.assertTrue(time2 > time1) def test_known_clock(self): self.assertTrue(isinstance( self.clock.im_self, ( monotonic_clock.LinuxMonotonicCounter, monotonic_clock.FreeBSDMonotonicCounter, monotonic_clock.BSDMonotonicCounter, monotonic_clock.DarwinMonotonicCounter, monotonic_clock.Win32MonotonicCounter, monotonic_clock.SystemCounter, ) )) if __name__ == '__main__': unittest.main() 2ping-3.2.1/tests/test_packets.py000066400000000000000000000437001267541676000167630ustar00rootroot00000000000000#!/usr/bin/env python import unittest from twoping import packets class TestPacketsOpcodes(unittest.TestCase): def test_opcode_unknown(self): data = bytearray('Unknown data') opcode = packets.Opcode() opcode.load(data) self.assertEqual(opcode.id, None) self.assertEqual(opcode.dump(), data) def test_opcode_reply_requested(self): data = bytearray() opcode = packets.OpcodeReplyRequested() opcode.load(data) self.assertEqual(opcode.id, 0x0001) self.assertEqual(opcode.dump(), data) def test_opcode_in_reply_to(self): data = bytearray(b'\x01\x02\x03\x04\x05\x06') opcode = packets.OpcodeInReplyTo() opcode.load(data) self.assertEqual(opcode.id, 0x0002) self.assertEqual(opcode.dump(), data) def test_opcode_rtt_enclosed(self): data = bytearray(b'\x12\x34\x56\x78') opcode = packets.OpcodeRTTEnclosed() opcode.load(data) self.assertEqual(opcode.id, 0x0004) self.assertEqual(opcode.dump(), data) def test_opcode_investigation_seen(self): data = bytearray(b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16') opcode = packets.OpcodeInvestigationSeen() opcode.load(data) self.assertEqual(opcode.id, 0x0008) self.assertEqual(opcode.dump(), data) def test_opcode_investigation_unseen(self): data = bytearray(b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16') opcode = packets.OpcodeInvestigationUnseen() opcode.load(data) self.assertEqual(opcode.id, 0x0010) self.assertEqual(opcode.dump(), data) def test_opcode_investigate(self): data = bytearray(b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16') opcode = packets.OpcodeInvestigate() opcode.load(data) self.assertEqual(opcode.id, 0x0020) self.assertEqual(opcode.dump(), data) def test_opcode_courtesy_expiration(self): data = bytearray(b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16') opcode = packets.OpcodeCourtesyExpiration() opcode.load(data) self.assertEqual(opcode.id, 0x0040) self.assertEqual(opcode.dump(), data) def test_opcode_hmac(self): data = bytearray(b'\x00\x04\x01\x02\x03\x04') # The dump always includes a zeroed hash area data_out = bytearray(b'\x00\x04\x00\x00\x00\x00') opcode = packets.OpcodeHMAC() opcode.load(data) self.assertEqual(opcode.id, 0x0080) self.assertEqual(opcode.dump(), data_out) def test_opcode_host_latency(self): data = bytearray(b'\x12\x34\x56\x78') opcode = packets.OpcodeHostLatency() opcode.load(data) self.assertEqual(opcode.id, 0x0100) self.assertEqual(opcode.dump(), data) def test_opcode_extended(self): data = bytearray(b'\x32\x50\x56\x4e\x00\x12Test 2ping version') opcode = packets.OpcodeExtended() opcode.load(data) self.assertEqual(opcode.id, 0x8000) self.assertEqual(opcode.dump(), data) def test_extended_unknown(self): data = bytearray('Unknown extended data') opcode = packets.Extended() opcode.load(data) self.assertEqual(opcode.id, None) self.assertEqual(opcode.dump(), data) def test_extended_version(self): data = bytearray('Test 2ping version') opcode = packets.ExtendedVersion() opcode.load(data) self.assertEqual(opcode.id, 0x3250564e) self.assertEqual(opcode.dump(), data) def test_extended_notice(self): data = bytearray('Notice announcement') opcode = packets.ExtendedNotice() opcode.load(data) self.assertEqual(opcode.id, 0xa837b44e) self.assertEqual(opcode.dump(), data) def test_extended_wallclock_load(self): opcode = packets.ExtendedWallClock() opcode.load(bytearray(b'\x00\x03\x63\x73\xe7\x7a\xc2\x20')) self.assertEqual(opcode.id, 0x64f69319) self.assertEqual(opcode.time_us, int(953774386.102816 * 1000000)) def test_extended_wallclock_dump(self): opcode = packets.ExtendedWallClock() opcode.time_us = int(1454187789.993266 * 1000000) self.assertEqual(opcode.id, 0x64f69319) self.assertEqual(opcode.dump(), bytearray(b'\x00\x05\x2a\x93\x7a\xa8\xc5\x32')) def test_extended_monotonicclock_load(self): opcode = packets.ExtendedMonotonicClock() opcode.load(bytearray(b'\x7d\x67\x00\x03\x63\x73\xe7\x7a\xc2\x20')) self.assertEqual(opcode.id, 0x771d8dfb) self.assertEqual(opcode.generation, 32103) self.assertEqual(opcode.time_us, int(953774386.102816 * 1000000)) def test_extended_monotonicclock_dump(self): opcode = packets.ExtendedMonotonicClock() opcode.generation = 9311 opcode.time_us = int(1454187789.993266 * 1000000) self.assertEqual(opcode.id, 0x771d8dfb) self.assertEqual(opcode.dump(), bytearray(b'\x24\x5f\x00\x05\x2a\x93\x7a\xa8\xc5\x32')) def test_extended_random_load(self): random_data = bytearray(b'\xf1\xfd\xf8\x9c\xe3\x9a\x87\x14') opcode = packets.ExtendedRandom() opcode.load(bytearray(bytearray(b'\x00\x03') + random_data)) self.assertEqual(opcode.id, 0x2ff6ad68) self.assertEqual(opcode.is_hwrng, True) self.assertEqual(opcode.is_os, True) self.assertEqual(opcode.random_data, random_data) def test_extended_random_dump(self): random_data = bytearray(b'\xbc\xde\xdc\xe5\xa8\x9a\xbd\x14') opcode = packets.ExtendedRandom() opcode.is_hwrng = True opcode.random_data = random_data self.assertEqual(opcode.id, 0x2ff6ad68) self.assertEqual(opcode.dump(), bytearray(b'\x00\x01') + random_data) class TestPacketsReference(unittest.TestCase): ''' Test protocol reference packets These tests replicate the reference packets included as examples in the 2ping protocol specification. ''' def test_reference_1a(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xae\x00\x00\x00\x00\xa0\x01\x00\x00')) def test_reference_2a(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xad\x00\x00\x00\x00\xa0\x01\x00\x01\x00\x00')) def test_reference_2b(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x7d\xa4\x00\x00\x00\x00\xb0\x01\x00\x02\x00\x06\x00\x00\x00\x00\xa0\x01')) def test_reference_3a(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xad\x00\x00\x00\x00\xa0\x01\x00\x01\x00\x00')) def test_reference_3b(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x7d\xa3\x00\x00\x00\x00\xb0\x01\x00\x03\x00\x00\x00\x06\x00\x00\x00\x00\xa0\x01')) def test_reference_3c(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x4d\x62\x00\x00\x00\x00\xa0\x02\x00\x06\x00\x06\x00\x00\x00\x00\xb0\x01\x00\x04\x00\x00\x30\x39')) def test_reference_4a(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xad\x00\x00\x00\x00\xa0\x01\x00\x01\x00\x00')) def test_reference_4b(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x8d\x81\x00\x00\x00\x00\xa0\x02\x00\x21\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x01')) def test_reference_4c(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\xdd\x8e\x00\x00\x00\x00\xb0\x02\x00\x0b\x00\x00\x00\x06\x00\x00\x00\x00\xa0\x02\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x01')) def test_reference_4d(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x03') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x4d\x60\x00\x00\x00\x00\xa0\x03\x00\x06\x00\x06\x00\x00\x00\x00\xb0\x02\x00\x04\x00\x00\x30\x39')) def test_reference_5a(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xad\x00\x00\x00\x00\xa0\x01\x00\x01\x00\x00')) def test_reference_5b(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x8d\x81\x00\x00\x00\x00\xa0\x02\x00\x21\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x01')) def test_reference_5c(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\xdd\x87\x00\x00\x00\x00\xb0\x01\x00\x13\x00\x00\x00\x06\x00\x00\x00\x00\xa0\x02\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x01')) def test_reference_5d(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x03') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x01') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x4d\x61\x00\x00\x00\x00\xa0\x03\x00\x06\x00\x06\x00\x00\x00\x00\xb0\x01\x00\x04\x00\x00\x30\x39')) def test_reference_6a(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xad\x00\x00\x00\x00\xa0\x01\x00\x01\x00\x00')) def test_reference_6b(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xac\x00\x00\x00\x00\xa0\x02\x00\x01\x00\x00')) def test_reference_6c(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x03') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x2d\xab\x00\x00\x00\x00\xa0\x03\x00\x01\x00\x00')) def test_reference_6d(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x03') self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x7d\xa0\x00\x00\x00\x00\xb0\x02\x00\x03\x00\x00\x00\x06\x00\x00\x00\x00\xa0\x03')) def test_reference_6e(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x04') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x02') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12823 self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x4b\x81\x00\x00\x00\x00\xa0\x04\x00\x06\x00\x06\x00\x00\x00\x00\xb0\x02\x00\x04\x00\x00\x32\x17')) def test_reference_6f(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x0a') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x02')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\xed\x6f\x00\x00\x00\x00\xa0\x0a\x00\x21\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x00\xa0\x01\x00\x00\x00\x00\xa0\x02')) def test_reference_6g(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xb0\x06') packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xa0\x0a') packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x01')) packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xa0\x02')) packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(bytearray('\x00\x00\x00\x00\xb0\x02')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x8d\x3b\x00\x00\x00\x00\xb0\x06\x00\x3b\x00\x00\x00\x06\x00\x00\x00\x00\xa0\x0a\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x01\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x02\x00\x08\x00\x01\x00\x00\x00\x00\xb0\x02')) def test_reference_6h(self): packet = packets.Packet() packet.message_id = bytearray('\x00\x00\x00\x00\xa0\x0b') packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = bytearray('\x00\x00\x00\x00\xb0\x06') packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 13112 packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(bytearray('\x00\x00\x00\x00\xb0\x02')) self.assertEqual(packet.dump(), bytearray(b'\x32\x50\x9a\x41\x00\x00\x00\x00\xa0\x0b\x00\x0e\x00\x06\x00\x00\x00\x00\xb0\x06\x00\x04\x00\x00\x33\x38\x00\x08\x00\x01\x00\x00\x00\x00\xb0\x02')) if __name__ == '__main__': unittest.main() 2ping-3.2.1/tests/test_utils.py000066400000000000000000000032251267541676000164670ustar00rootroot00000000000000#!/usr/bin/env python import unittest from twoping import utils class TestUtils(unittest.TestCase): def test_twoping_checksum_hello(self): data = bytearray(b'Hello World') self.assertEqual(utils.twoping_checksum(data), 0xae31) def test_twoping_checksum_junk(self): data = bytearray(b'E\xc6\xca\x92\x10\x0e\xb1\xcf\x98\x88\x0bB\xc0\xf8X\xac\xd9\x81v\xc0E\x8co\x04\x9c\x0e\x93\xb3') + \ bytearray(b'I=8m\xb7\xd5\x86\xa7f,2\x15\xd9\x7f\xad>\x1a\xe09\x89J\xdd\x0b(\xa0\x07aGT\xc6P\x04-(NH<@\xc7\xdb') + \ bytearray(b'\x82\xfa\x0b\xab7n#^\x80\xab\xb9\xe5\x05\x14\xb1\xfcrT7\x8af\xf2Q\xa1w\xda\xd5\x88\xd58C10\xe7') + \ bytearray(b'\x1ez+\xc8\x14\rK|3\x9a\x9b\xc1\xc1\xc8\xc5\xfeZHD\x1b\x87\x9d$\xd9"}') self.assertEqual(utils.twoping_checksum(data), 0x4a06) def test_twoping_checksum_packet(self): data = bytearray(b'\x32\x50\x8d\x3b\x00\x00\x00\x00\xb0\x06\x00\x3b\x00\x00\x00\x06\x00\x00\x00\x00\xa0\x0a\x00') + \ bytearray(b'\x08\x00\x01\x00\x00\x00\x00\xa0\x01\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x02\x00\x08\x00\x01') + \ bytearray(b'\x00\x00\x00\x00\xb0\x02') data[2] = 0 data[3] = 0 self.assertEqual(utils.twoping_checksum(data), 0x8d3b) def test_twoping_checksum_iter(self): i = 65535 for x in range(256): for y in range(256): data = bytearray([x, y]) checksum = utils.twoping_checksum(data) self.assertEqual(checksum, i) i = i - 1 if i == 0: i = 65535 if __name__ == '__main__': unittest.main() 2ping-3.2.1/twoping/000077500000000000000000000000001267541676000142415ustar00rootroot000000000000002ping-3.2.1/twoping/__init__.py000066400000000000000000000014451267541676000163560ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2015 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. __version__ = '3.2.1' 2ping-3.2.1/twoping/args.py000066400000000000000000000225051267541676000155530ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from __future__ import print_function, division import sys import os import argparse from . import __version__ from .utils import _ def parse_args(argv=None): if argv is None: argv = sys.argv if argv[0].endswith('2ping6'): ipv6_default = True else: ipv6_default = False parser = argparse.ArgumentParser( description='2ping (%s)' % __version__, formatter_class=argparse.ArgumentDefaultsHelpFormatter, prog=os.path.basename(argv[0]), ) parser.add_argument( '--version', '-V', action='version', version=__version__, help=_('report the program version'), ) # Positionals parser.add_argument( 'host', type=str, default=None, nargs='*', help=_('host to ping'), ) # ping-compatible options parser.add_argument( '--audible', '-a', dest='audible', action='store_true', help=_('audible ping'), ) parser.add_argument( '--adaptive', '-A', dest='adaptive', action='store_true', help=_('adaptive RTT ping'), ) parser.add_argument( '--count', '-c', dest='count', type=int, help=_('number of pings to send'), ) parser.add_argument( '--flood', '-f', dest='flood', action='store_true', help=_('flood mode'), ) parser.add_argument( '--interval', '-i', dest='interval', type=float, default=1.0, help=_('seconds between pings'), metavar='SECONDS', ) parser.add_argument( '--interface-address', '-I', dest='interface_address', type=str, action='append', help=_('interface bind address'), metavar='ADDRESS', ) parser.add_argument( '--preload', '-l', dest='preload', type=int, default=1, help=_('number of pings to send at start'), metavar='COUNT', ) parser.add_argument( '--pattern', '-p', dest='pattern', type=str, help=_('hex pattern for padding'), metavar='HEX_BYTES', ) parser.add_argument( '--quiet', '-q', dest='quiet', action='store_true', help=_('quiet mode'), ) parser.add_argument( '--packetsize-compat', '-s', dest='packetsize_compat', type=int, help=_('packet size (ping compatible)'), metavar='BYTES', ) parser.add_argument( '--verbose', '-v', dest='verbose', action='store_true', help=_('verbose mode'), ) parser.add_argument( '--deadline', '-w', dest='deadline', type=float, help=_('maximum run time'), metavar='SECONDS', ) # 2ping options parser.add_argument( '--auth', type=str, help=_('HMAC authentication key'), metavar='KEY', ) parser.add_argument( '--auth-digest', type=str, default='hmac-md5', choices=['hmac-md5', 'hmac-sha1', 'hmac-sha256', 'hmac-crc32'], help=_('HMAC authentication digest'), metavar='DIGEST', ) parser.add_argument( '--debug', action='store_true', help=_('debug mode'), ) parser.add_argument( '--fuzz', type=float, help=_('incoming fuzz percentage'), metavar='PERCENT', ) parser.add_argument( '--inquire-wait', type=float, default=10.0, help=_('maximum time before loss inquiries'), metavar='SECONDS', ) parser.add_argument( '--ipv4', '-4', action='store_true', help=_('force IPv4'), ) parser.add_argument( '--ipv6', '-6', action='store_true', default=ipv6_default, help=_('force IPv6'), ) parser.add_argument( '--listen', action='store_true', help=_('listen mode'), ) parser.add_argument( '--max-packet-size', type=int, default=512, help=_('maximum packet size'), metavar='BYTES', ) parser.add_argument( '--min-packet-size', type=int, default=128, help=_('minimum packet size'), metavar='BYTES', ) parser.add_argument( '--nagios', type=str, help=_('nagios-compatible output'), metavar='WRTA,WLOSS%,CRTA,CLOSS%', ) parser.add_argument( '--no-3way', action='store_true', help=_('do not send 3-way pings'), ) parser.add_argument( '--no-match-packet-size', action='store_true', help=_('do not match packet size of peer'), ) parser.add_argument( '--no-send-version', action='store_true', help=_('do not send program version to peers'), ) parser.add_argument( '--notice', type=str, help=_('arbitrary notice text'), metavar='TEXT', ) parser.add_argument( '--packet-loss', type=str, help=_('percentage simulated packet loss'), metavar='OUT:IN', ) parser.add_argument( '--port', type=str, default='15998', help=_('port to connect / bind to'), ) parser.add_argument( '--send-monotonic-clock', action='store_true', help=_('send monotonic clock to peers'), ) parser.add_argument( '--send-random', type=int, help=_('send random data to peers'), metavar='BYTES', ) parser.add_argument( '--send-time', action='store_true', help=_('send wall clock time to peers'), ) parser.add_argument( '--stats', type=float, help=_('print recurring statistics'), metavar='SECONDS', ) parser.add_argument( '--srv', action='store_true', help=_('lookup SRV records in client mode'), ) # ping-compatible ignored options for opt in 'b|B|d|L|n|R|r|U'.split('|'): parser.add_argument( '-%s' % opt, action='store_true', dest='ignored_%s' % opt, help=argparse.SUPPRESS, ) for opt in 'F|Q|S|t|T|M|W'.split('|'): parser.add_argument( '-%s' % opt, type=str, default=None, dest='ignored_%s' % opt, help=argparse.SUPPRESS, ) args = parser.parse_args(args=argv[1:]) if (not args.listen) and (not args.host): parser.print_help() parser.exit() if args.nagios: args.quiet = True if not args.count: args.count = 5 nagios_opts = args.nagios.split(',') if len(nagios_opts) != 4: parser.error(_('Invalid limits')) ( args.nagios_warn_rta, args.nagios_warn_loss, args.nagios_crit_rta, args.nagios_crit_loss, ) = nagios_opts if args.nagios_warn_loss[-1:] != '%': parser.error(_('Invalid limits')) if args.nagios_crit_loss[-1:] != '%': parser.error(_('Invalid limits')) try: args.nagios_warn_loss = float(args.nagios_warn_loss[:-1]) args.nagios_crit_loss = float(args.nagios_crit_loss[:-1]) args.nagios_warn_rta = float(args.nagios_warn_rta) args.nagios_crit_rta = float(args.nagios_crit_rta) except ValueError as e: parser.error(e.message) if args.packetsize_compat: args.min_packet_size = args.packetsize_compat + 8 if args.max_packet_size < args.min_packet_size: parser.error(_('Maximum packet size must be at least minimum packet size')) if args.max_packet_size < 64: parser.error(_('Maximum packet size must be at least 64')) args.packet_loss_in = 0 args.packet_loss_out = 0 if args.packet_loss: if ':' in args.packet_loss: (v_out, v_in) = args.packet_loss.split(':', 1) try: args.packet_loss_out = float(v_out) args.packet_loss_in = float(v_in) except ValueError as e: parser.error(e.message) else: try: args.packet_loss_in = args.packet_loss_out = float(args.packet_loss) except ValueError as e: parser.error(_('Packet loss: {error}').format(error=e.message)) if args.pattern: if len(args.pattern) & 1: parser.error(_('Pattern must be full bytes')) if len(args.pattern) > 32: parser.error(_('Pattern must be 16 bytes or less')) args.pattern_bytearray = bytearray() for i in xrange(int(len(args.pattern) / 2)): a = args.pattern[(i*2):(i*2+2)] try: b = int(a, 16) except ValueError as e: parser.error(_('Pattern: {error}').format(error=e.message)) args.pattern_bytearray += bytearray([b]) else: args.pattern_bytearray = bytearray(1) hmac_id_map = { 'hmac-md5': 1, 'hmac-sha1': 2, 'hmac-sha256': 3, 'hmac-crc32': 4, } args.auth_digest_index = hmac_id_map[args.auth_digest] if args.debug: args.verbose = True return args 2ping-3.2.1/twoping/best_poller.py000077500000000000000000000110531267541676000171300ustar00rootroot00000000000000#!/usr/bin/env python # 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from __future__ import print_function import select import errno class EpollPoller(): poller_type = 'epoll' def __init__(self): self.poller = select.epoll() self.f_dict = {} def register(self, f): fileno = f.fileno() if fileno not in self.f_dict: self.poller.register(fileno, select.EPOLLIN) self.f_dict[fileno] = f def close(self): return self.poller.close() def poll(self, timeout): try: poll_res = self.poller.poll(timeout) except (select.error, IOError, OSError) as e: if e.args[0] not in (errno.EINTR,): raise return [] res = [] for i in poll_res: if i[0] in self.f_dict: res.append(self.f_dict[i[0]]) return res class KqueuePoller(): poller_type = 'kqueue' def __init__(self): self.poller = select.kqueue() self.kevents = [] self.f_dict = {} def register(self, f): fileno = f.fileno() if fileno not in self.f_dict: self.kevents.append(select.kevent( fileno, filter=select.KQ_FILTER_READ, flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE, )) self.f_dict[fileno] = f def close(self): return self.poller.close() def poll(self, timeout): try: poll_res = self.poller.control(self.kevents, 10, timeout) except (select.error, IOError, OSError) as e: if e.args[0] not in (errno.EINTR,): raise return [] res = [] for i in poll_res: if i.ident in self.f_dict: res.append(self.f_dict[i.ident]) return res class PollPoller(): poller_type = 'poll' def __init__(self): self.poller = select.poll() self.f_dict = {} def register(self, f): fileno = f.fileno() if fileno not in self.f_dict: self.poller.register(fileno, select.POLLIN) self.f_dict[fileno] = f def close(self): return self.poller.close() def poll(self, timeout): try: poll_res = self.poller.poll(timeout * 1000.0) except (select.error, IOError, OSError) as e: if e.args[0] not in (errno.EINTR,): raise return [] res = [] for i in poll_res: if i[0] in self.f_dict: res.append(self.f_dict[i[0]]) return res class SelectPoller(): poller_type = 'select' def __init__(self): self.f_dict = {} def register(self, f): self.f_dict[f.fileno()] = f def close(self): pass def poll(self, timeout): try: return select.select( self.f_dict.values(), [], [], timeout )[0] except (select.error, IOError, OSError) as e: if e.args[0] not in (errno.EINTR,): raise return [] def best_poller(): try: return EpollPoller() except AttributeError: pass try: return KqueuePoller() except AttributeError: pass try: return PollPoller() except AttributeError: pass return SelectPoller() def available_pollers(): available = [] for poller in [ EpollPoller, KqueuePoller, PollPoller, SelectPoller ]: try: available.append(poller()) except AttributeError: continue return available if __name__ == '__main__': available = available_pollers() print('Available pollers: %s' % ' '.join([p.poller_type for p in available])) poller = best_poller() print('Best poller: %s' % poller.poller_type) 2ping-3.2.1/twoping/cli.py000077500000000000000000001355571267541676000154050ustar00rootroot00000000000000#!/usr/bin/env python # 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from __future__ import print_function, division import random import socket import math import signal import sys import errno import time from . import __version__ from . import packets from . import monotonic_clock from . import best_poller from .args import parse_args from .utils import _, _pl, lazy_div, int_to_bytearray, bytearray_to_int, platform_info, twoping_checksum try: random_sys = random.SystemRandom() has_sysrandom = True except AttributeError: random_sys = random has_sysrandom = False try: import dns.resolver has_dns = True except ImportError: has_dns = False version_string = '2ping %s - %s' % (__version__, platform_info()) clock = monotonic_clock.clock clock_info = monotonic_clock.get_clock_info('clock') class SocketClass(): def __init__(self, sock): self.sock = sock # In-flight outbound messages. Added in the following conditions: # * Outbound packet with OpcodeReplyRequested sent. # Removed in following conditions: # * Inbound packet with OpcodeInReplyTo set to it. # * Inbound packet with it in OpcodeInvestigationSeen or OpcodeInvestigationUnseen. # * Cleanup after 10 minutes. # If it remains for more than <10> seconds, it it sent as part of # OpcodeInvestigate with the next outbound packet with OpcodeReplyRequested set. self.sent_messages = {} # Seen inbound messages. Added in the following conditions: # * Inbound packet with OpcodeReplyRequested set. # Referenced in the following conditions: # * Inbound packet with it in OpcodeInvestigate. # Removed in the following conditions: # * Inbound packet with it in OpcodeCourtesyExpiration. # * Cleanup after 2 minutes. self.seen_messages = {} # Courtesy messages waiting to be sent. Added in the following conditions: # * Inbound packet with OpcodeInReplyTo set. # Removed in the following conditions: # * Outbound packet where there is room to send it as part of OpcodeCourtesyExpiration. # * Cleanup after 2 minutes. self.courtesy_messages = {} # Current position of a peer tuple's incrementing ping integer. self.ping_positions = {} # Used during client mode for the host tuple to send UDP packets to. self.client_host = None # Statistics self.pings_transmitted = 0 self.pings_received = 0 self.packets_transmitted = 0 self.packets_received = 0 self.lost_outbound = 0 self.lost_inbound = 0 self.errors_received = 0 self.rtt_total = 0 self.rtt_total_sq = 0 self.rtt_count = 0 self.rtt_min = 0 self.rtt_max = 0 self.rtt_ewma = 0 self.next_send = 0 self.is_shutdown = False self.nagios_result = 0 def fileno(self): return self.sock.fileno() class TwoPing(): def __init__(self, args): now = clock() self.args = args self.time_start = now self.fake_time_epoch = random_sys.random() * (2**32) self.fake_time_generation = random_sys.randint(0, 65535) self.sock_classes = [] self.poller = best_poller.best_poller() self.pings_transmitted = 0 self.pings_received = 0 self.packets_transmitted = 0 self.packets_received = 0 self.lost_outbound = 0 self.lost_inbound = 0 self.errors_received = 0 self.rtt_total = 0 self.rtt_total_sq = 0 self.rtt_count = 0 self.rtt_min = 0 self.rtt_max = 0 self.rtt_ewma = 0 # Scheduled events self.next_cleanup = now + 60.0 self.next_stats = 0 if self.args.stats: self.next_stats = now + self.args.stats self.old_age_interval = 60.0 # On Windows, KeyboardInterrupt during select() will not be trapped until a socket event or timeout, so we should set # the timeout to a short value. if sys.platform.startswith(('win32', 'cygwin')): self.old_age_interval = 1.0 # Test for IPv6 functionality. self.has_ipv6 = True if not socket.has_ipv6: self.has_ipv6 = False else: # BSD jails seem to have has_ipv6 = True, but will throw "Protocol not supported" on bind. Test for this. try: socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) except socket.error as e: if e.errno == errno.EPROTONOSUPPORT: self.has_ipv6 = False else: raise if self.args.send_monotonic_clock and (not clock_info.monotonic): self.args.send_monotonic_clock = False def print_out(self, *args, **kwargs): '''Emulate Python 3's complete print() functionality''' if 'sep' not in kwargs: kwargs['sep'] = ' ' if 'end' not in kwargs: kwargs['end'] = '\n' if 'file' not in kwargs: kwargs['file'] = sys.stdout if 'flush' not in kwargs: kwargs['flush'] = False kwargs['file'].write(kwargs['sep'].join(args) + kwargs['end']) if kwargs['flush']: kwargs['file'].flush() def print_debug(self, *args, **kwargs): if not self.args.debug: return self.print_out(*args, **kwargs) def shutdown(self): self.print_stats() if self.args.nagios: sys.exit(self.nagios_result) sys.exit(0) def handle_socket_error(self, e, sock_class, peer_address=None): sock = sock_class.sock # Errors from the last send() can be trapped via IP_RECVERR (Linux only). self.errors_received += 1 sock_class.errors_received += 1 error_string = str(e) try: MSG_ERRQUEUE = 8192 (error_data, error_address) = sock.recvfrom(16384, MSG_ERRQUEUE) if self.args.quiet: pass elif self.args.flood: self.print_out('E', end='', flush=True) else: self.print_out('%s: %s' % (error_address[0], error_string)) except socket.error: if self.args.quiet: pass elif self.args.flood: self.print_out('E', end='', flush=True) else: if peer_address: self.print_out('%s: %s' % (peer_address[0], error_string)) else: self.print_out(error_string) def process_incoming_packet(self, sock_class): sock = sock_class.sock try: (data, peer_address) = sock.recvfrom(16384) except socket.error as e: self.handle_socket_error(e, sock_class) return socket_address = sock.getsockname() self.print_debug('Socket address: %s' % repr(socket_address)) self.print_debug('Peer address: %s' % repr(peer_address)) data = bytearray(data) # Simulate random packet loss. if self.args.packet_loss_in and (random.random() < (self.args.packet_loss_in / 100.0)): return # Simulate data corruption if self.args.fuzz: data = self.fuzz_packet(data) # Per-packet options. self.packets_received += 1 sock_class.packets_received += 1 calculated_rtt = None time_begin = clock() peer_tuple = (socket_address, peer_address, sock.type) # Preload state tables if the client has not been seen (or has been cleaned). if peer_tuple not in sock_class.seen_messages: sock_class.seen_messages[peer_tuple] = {} if peer_tuple not in sock_class.sent_messages: sock_class.sent_messages[peer_tuple] = {} if peer_tuple not in sock_class.courtesy_messages: sock_class.courtesy_messages[peer_tuple] = {} if peer_tuple not in sock_class.ping_positions: sock_class.ping_positions[peer_tuple] = 0 # Load/parse the packet. packet_in = packets.Packet() packet_in.load(data) if self.args.verbose: self.print_out('RECV: %s' % repr(packet_in)) # Verify HMAC if required. if self.args.auth: if packets.OpcodeHMAC.id not in packet_in.opcodes: self.errors_received += 1 sock_class.errors_received += 1 self.print_out(_('Auth required but not provided by {address}').format(address=peer_address[0])) return if packet_in.opcodes[packets.OpcodeHMAC.id].digest_index != self.args.auth_digest_index: self.errors_received += 1 sock_class.errors_received += 1 self.print_out( _('Auth digest type mismatch from {address} (expected {expected}, got {got})').format( address=peer_address[0], expected=self.args.auth_digest_index, got=packet_in.opcodes[packets.OpcodeHMAC.id].digest_index, ) ) return (test_begin, test_length) = packet_in.opcode_data_positions[packets.OpcodeHMAC.id] test_begin += 2 test_length -= 2 packet_in.opcodes[packets.OpcodeHMAC.id].key = bytearray(self.args.auth) test_data = data test_data[2:4] = bytearray(2) test_data[test_begin:(test_begin+test_length)] = bytearray(test_length) test_hash = packet_in.opcodes[packets.OpcodeHMAC.id].hash test_hash_calculated = packet_in.calculate_hash(packet_in.opcodes[packets.OpcodeHMAC.id], test_data) if test_hash_calculated != test_hash: self.errors_received += 1 sock_class.errors_received += 1 self.print_out( _('Auth hash failed from {address} (expected {expected}, got {got})').format( address=peer_address[0], expected=''.join('{hex:02x}'.format(hex=x) for x in test_hash_calculated), got=''.join('{hex:02x}'.format(hex=x) for x in test_hash), ) ) return # If this is in reply to one of our sent packets, it's a ping reply, so handle it specially. if packets.OpcodeInReplyTo.id in packet_in.opcodes: replied_message_id = packet_in.opcodes[packets.OpcodeInReplyTo.id].message_id replied_message_id_int = bytearray_to_int(replied_message_id) if replied_message_id_int in sock_class.sent_messages[peer_tuple]: (sent_time, _unused, ping_position) = sock_class.sent_messages[peer_tuple][replied_message_id_int] del(sock_class.sent_messages[peer_tuple][replied_message_id_int]) calculated_rtt = (time_begin - sent_time) * 1000 self.pings_received += 1 sock_class.pings_received += 1 self.update_rtts(sock_class, calculated_rtt) if self.args.quiet: pass elif self.args.flood: self.print_out('\x08', end='', flush=True) else: if self.args.audible: self.print_out('\x07', end='', flush=True) if packets.OpcodeRTTEnclosed.id in packet_in.opcodes: self.print_out( _('{bytes} bytes from {address}: ping_seq={seq} time={ms:0.03f} ms peertime={peerms:0.03f} ms').format( bytes=len(data), address=peer_tuple[1][0], seq=ping_position, ms=calculated_rtt, peerms=(packet_in.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us / 1000.0), ) ) else: self.print_out( _('{bytes} bytes from {address}: ping_seq={seq} time={ms:0.03f} ms').format( bytes=len(data), address=peer_tuple[1][0], seq=ping_position, ms=calculated_rtt, ) ) if ( (packets.OpcodeExtended.id in packet_in.opcodes) and (packets.ExtendedNotice.id in packet_in.opcodes[packets.OpcodeExtended.id].segments) ): notice = unicode(str(packet_in.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedNotice.id].text), 'UTF-8', 'replace') self.print_out(' ' + _('Peer notice: {notice}').format(notice=notice)) sock_class.courtesy_messages[peer_tuple][replied_message_id_int] = (time_begin, replied_message_id) # Check if any invesitgations results have come back. self.check_investigations(sock_class, peer_tuple, packet_in) # Process courtesy expirations if packets.OpcodeCourtesyExpiration.id in packet_in.opcodes: for message_id in packet_in.opcodes[packets.OpcodeCourtesyExpiration.id].message_ids: message_id_int = bytearray_to_int(message_id) if message_id_int in sock_class.seen_messages[peer_tuple]: del(sock_class.seen_messages[peer_tuple][message_id_int]) # If the peer requested a reply, prepare one. if packets.OpcodeReplyRequested.id in packet_in.opcodes: # Populate seen_messages. sock_class.seen_messages[peer_tuple][bytearray_to_int(packet_in.message_id)] = time_begin # Basic packet configuration. packet_out = self.base_packet() packet_out.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet_out.opcodes[packets.OpcodeInReplyTo.id].message_id = packet_in.message_id # If we are matching packet sizes of the peer, adjust the minimum if it falls between min_packet_size # and max_packet_size. if not self.args.no_match_packet_size: data_len = len(data) if (data_len <= self.args.max_packet_size) and (data_len >= self.args.min_packet_size): packet_out.min_length = data_len # 3-way pings already have the first roundtrip calculated. if calculated_rtt is not None: packet_out.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet_out.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = int(calculated_rtt * 1000) # Check for any investigations the peer requested. if packets.OpcodeInvestigate.id in packet_in.opcodes: for message_id in packet_in.opcodes[packets.OpcodeInvestigate.id].message_ids: if bytearray_to_int(message_id) in sock_class.seen_messages[peer_tuple]: if packets.OpcodeInvestigationSeen.id not in packet_out.opcodes: packet_out.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet_out.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(message_id) else: if packets.OpcodeInvestigationUnseen.id not in packet_out.opcodes: packet_out.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet_out.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(message_id) # If the packet_in is ReplyRequested but not InReplyTo, it is a second leg. Unless 3-way ping was # disabled, request a reply. if (packets.OpcodeInReplyTo.id not in packet_in.opcodes) and (not self.args.no_3way): packet_out.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() # Send any investigations we would like to know about. self.start_investigations(sock_class, peer_tuple, packet_out) # Any courtesy expirations we have waiting should be sent. if len(sock_class.courtesy_messages[peer_tuple]) > 0: packet_out.opcodes[packets.OpcodeCourtesyExpiration.id] = packets.OpcodeCourtesyExpiration() for (courtesy_time, courtesy_message_id) in sock_class.courtesy_messages[peer_tuple].values(): packet_out.opcodes[packets.OpcodeCourtesyExpiration.id].message_ids.append(courtesy_message_id) # Calculate the host latency as late as possible. packet_out.opcodes[packets.OpcodeHostLatency.id] = packets.OpcodeHostLatency() time_send = clock() packet_out.opcodes[packets.OpcodeHostLatency.id].delay_us = int((time_send - time_begin) * 1000000) # Dump the packet. dump_out = packet_out.dump() # Send the packet. self.sock_sendto(sock_class, dump_out, peer_address) self.packets_transmitted += 1 sock_class.packets_transmitted += 1 # If ReplyRequested is set, we care about its arrival. if packets.OpcodeReplyRequested.id in packet_out.opcodes: self.pings_transmitted += 1 sock_class.pings_transmitted += 1 sock_class.ping_positions[peer_tuple] += 1 sock_class.sent_messages[peer_tuple][bytearray_to_int(packet_out.message_id)] = ( time_send, packet_out.message_id, sock_class.ping_positions[peer_tuple] ) # Examine the sent packet. packet_out_examine = packets.Packet() packet_out_examine.load(dump_out) # Any courtesy expirations which had room in the sent packet should be forgotten. if packets.OpcodeCourtesyExpiration.id in packet_out_examine.opcodes: for courtesy_message_id in packet_out_examine.opcodes[packets.OpcodeCourtesyExpiration.id].message_ids: courtesy_message_id_int = bytearray_to_int(courtesy_message_id) if courtesy_message_id_int in sock_class.courtesy_messages[peer_tuple]: del(sock_class.courtesy_messages[peer_tuple][courtesy_message_id_int]) if self.args.verbose: self.print_out('SEND: %s' % repr(packet_out_examine)) # If we're in flood mode and this is a ping reply, send a new ping ASAP. if self.args.flood and (not self.args.listen) and (packets.OpcodeInReplyTo.id in packet_in.opcodes): sock_class.next_send = time_begin def sock_sendto(self, sock_class, data, address): sock = sock_class.sock # Simulate random packet loss. if self.args.packet_loss_out and (random.random() < (self.args.packet_loss_out / 100.0)): return # Send the packet. try: sock.sendto(data, address) except socket.error as e: self.handle_socket_error(e, sock_class, peer_address=address) def start_investigations(self, sock_class, peer_tuple, packet_check): if len(sock_class.sent_messages[peer_tuple]) == 0: return if packets.OpcodeInvestigate.id in packet_check.opcodes: iobj = packet_check.opcodes[packets.OpcodeInvestigate.id] else: iobj = None now = clock() for message_id_str in sock_class.sent_messages[peer_tuple]: (sent_time, message_id, _unused) = sock_class.sent_messages[peer_tuple][message_id_str] if now >= (sent_time + self.args.inquire_wait): if iobj is None: iobj = packets.OpcodeInvestigate() if message_id not in iobj.message_ids: iobj.message_ids.append(message_id) if iobj is not None: packet_check.opcodes[packets.OpcodeInvestigate.id] = iobj def check_investigations(self, sock_class, peer_tuple, packet_check): found = {} # Inbound if packets.OpcodeInvestigationSeen.id in packet_check.opcodes: for message_id in packet_check.opcodes[packets.OpcodeInvestigationSeen.id].message_ids: message_id_int = bytearray_to_int(message_id) if message_id_int not in sock_class.sent_messages[peer_tuple]: continue (_unused, _unused, ping_seq) = sock_class.sent_messages[peer_tuple][message_id_int] found[ping_seq] = ('inbound', peer_tuple[1][0]) del(sock_class.sent_messages[peer_tuple][message_id_int]) self.lost_inbound += 1 sock_class.lost_inbound += 1 # Outbound if packets.OpcodeInvestigationUnseen.id in packet_check.opcodes: for message_id in packet_check.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids: message_id_int = bytearray_to_int(message_id) if message_id_int not in sock_class.sent_messages[peer_tuple]: continue (_unused, _unused, ping_seq) = sock_class.sent_messages[peer_tuple][message_id_int] found[ping_seq] = ('outbound', peer_tuple[1][0]) del(sock_class.sent_messages[peer_tuple][message_id_int]) self.lost_outbound += 1 sock_class.lost_outbound += 1 if self.args.quiet: return # Print results for ping_seq in sorted(found): (loss_type, address) = found[ping_seq] if loss_type == 'inbound': if self.args.flood: self.print_out('<', end='', flush=True) else: self.print_out(_('Lost inbound packet from {address}: ping_seq={seq}').format( address=address, seq=ping_seq, )) else: if self.args.flood: self.print_out('>', end='', flush=True) else: self.print_out(_('Lost outbound packet to {address}: ping_seq={seq}').format( address=address, seq=ping_seq, )) def setup_listener(self): bound_addresses = [] if self.args.interface_address: interface_addresses = self.args.interface_address else: interface_addresses = ['0.0.0.0'] if self.has_ipv6: interface_addresses.append('::') for interface_address in interface_addresses: for l in socket.getaddrinfo( interface_address, self.args.port, socket.AF_UNSPEC, socket.SOCK_DGRAM, socket.IPPROTO_UDP ): if l in bound_addresses: continue if (l[0] == socket.AF_INET6) and (not self.args.ipv4) and self.has_ipv6: pass elif (l[0] == socket.AF_INET) and (not self.args.ipv6): pass else: continue sock = self.new_socket(l[0], l[1], l[4]) sock_class = SocketClass(sock) self.sock_classes.append(sock_class) self.poller.register(sock_class) bound_addresses.append(l) self.print_out(_('2PING listener ({address}): {min} to {max} bytes of data.').format( address=l[4][0], min=self.args.min_packet_size, max=self.args.max_packet_size, )) def setup_client(self): if self.args.srv: if not has_dns: raise socket.error('DNS SRV lookups not available; please install dnspython') hosts = [] for lookup in self.args.host: lookup_hosts_found = 0 self.print_debug('SRV lookup: %s' % lookup) try: res = dns.resolver.query('_2ping._udp.%s' % lookup, 'srv') except dns.exception.DNSException as e: raise socket.error('%s: %s' % (lookup, repr(e))) for rdata in res: self.print_debug('SRV result for %s: %s' % ( lookup, repr(rdata), )) if (str(rdata.target), rdata.port) in hosts: continue hosts.append((str(rdata.target), rdata.port)) lookup_hosts_found += 1 if lookup_hosts_found == 0: raise socket.error('%s: No SRV results' % lookup) else: hosts = [(x, self.args.port) for x in self.args.host] for (hostname, port) in hosts: try: self.setup_client_host(hostname, port) except socket.error as e: eargs = list(e.args) if len(eargs) == 1: eargs[0] = '%s: %s' % (hostname, eargs[0]) else: eargs[1] = '%s: %s' % (hostname, eargs[1]) raise socket.error(*eargs) def setup_client_host(self, hostname, port): host_info = None for l in socket.getaddrinfo( hostname, port, socket.AF_UNSPEC, socket.SOCK_DGRAM, socket.IPPROTO_UDP, socket.AI_CANONNAME, ): if (l[0] == socket.AF_INET6) and (not self.args.ipv4) and self.has_ipv6: host_info = l break elif (l[0] == socket.AF_INET) and (not self.args.ipv6): host_info = l break else: continue if host_info is None: raise socket.error('Name or service not known') bind_info = None if self.args.interface_address: h = self.args.interface_address[-1] else: if host_info[0] == socket.AF_INET6: h = '::' else: h = '0.0.0.0' for l in socket.getaddrinfo(h, 0, host_info[0], socket.SOCK_DGRAM, socket.IPPROTO_UDP): bind_info = l break if bind_info is None: raise socket.error(_('Cannot find suitable bind for {address}').format(address=host_info[4])) sock = self.new_socket(bind_info[0], bind_info[1], bind_info[4]) sock_class = SocketClass(sock) sock_class.client_host = host_info self.sock_classes.append(sock_class) self.poller.register(sock_class) if not self.args.nagios: self.print_out( _('2PING {hostname} ({address}): {min} to {max} bytes of data.').format( hostname=host_info[3], address=host_info[4][0], min=self.args.min_packet_size, max=self.args.max_packet_size, ) ) def fuzz_packet(self, packet): def fuzz_bytearray(data, pct): for p in range(len(data)): xor_byte = 0 for i in range(8): if random.random() < pct: xor_byte = xor_byte + (2 ** i) data[p] = data[p] ^ xor_byte return data fuzz_fraction = self.args.fuzz / 100.0 # Fuzz the entire packet packet[4:] = fuzz_bytearray(packet[4:], fuzz_fraction) # Fuzz the magic number, at a lower probability packet[0:2] = fuzz_bytearray(packet[0:2], fuzz_fraction / 10.0) # Fuzz the recalculated checksum itself, at a lower probability packet[2:4] = bytearray(2) packet[2:4] = fuzz_bytearray(int_to_bytearray(twoping_checksum(packet), 2), fuzz_fraction / 10.0) return packet def send_new_ping(self, sock_class, peer_address): sock = sock_class.sock socket_address = sock.getsockname() peer_tuple = (socket_address, peer_address, sock.type) if peer_tuple not in sock_class.sent_messages: sock_class.sent_messages[peer_tuple] = {} if peer_tuple not in sock_class.ping_positions: sock_class.ping_positions[peer_tuple] = 0 packet_out = self.base_packet() packet_out.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.start_investigations(sock_class, peer_tuple, packet_out) dump_out = packet_out.dump() now = clock() self.sock_sendto(sock_class, dump_out, peer_address) self.packets_transmitted += 1 sock_class.packets_transmitted += 1 self.pings_transmitted += 1 sock_class.pings_transmitted += 1 sock_class.ping_positions[peer_tuple] += 1 sock_class.sent_messages[peer_tuple][bytearray_to_int(packet_out.message_id)] = ( now, packet_out.message_id, sock_class.ping_positions[peer_tuple] ) packet_out_examine = packets.Packet() packet_out_examine.load(dump_out) if self.args.quiet: pass elif self.args.flood: self.print_out('.', end='', flush=True) if self.args.verbose: self.print_out('SEND: %s' % repr(packet_out_examine)) def update_rtts(self, sock_class, rtt): for c in (self, sock_class): c.rtt_total += rtt c.rtt_total_sq += (rtt ** 2) c.rtt_count += 1 if (rtt < c.rtt_min) or (c.rtt_min == 0): c.rtt_min = rtt if rtt > c.rtt_max: c.rtt_max = rtt if c.rtt_ewma == 0: c.rtt_ewma = rtt * 8.0 else: c.rtt_ewma += (rtt - (c.rtt_ewma / 8.0)) def sigquit_handler(self, signum, frame): self.print_stats(short=True) def stats_time(self, seconds): conversion = ( (1000, 'ms'), (60, 's'), (60, 'm'), (24, 'h'), (365, 'd'), (None, 'y'), ) out = '' rest = int(seconds * 1000) for (div, suffix) in conversion: if div is None: if(out): out = ' ' + out out = '%d%s%s' % (rest, suffix, out) break p = rest % div rest = int(rest / div) if p > 0: if(out): out = ' ' + out out = '%d%s%s' % (p, suffix, out) if rest == 0: break return out def print_stats(self, short=False): time_end = clock() if self.args.listen: self.print_stats_sock(time_end, short=short, sock_class=None) else: for sock_class in self.sock_classes: self.print_stats_sock(time_end, short=short, sock_class=sock_class) def print_stats_sock(self, time_end, short=False, sock_class=None): if sock_class is not None: stats_class = sock_class else: stats_class = self time_start = self.time_start pings_lost = stats_class.pings_transmitted - stats_class.pings_received lost_pct = lazy_div(pings_lost, stats_class.pings_transmitted) * 100 lost_undetermined = pings_lost - (stats_class.lost_outbound + stats_class.lost_inbound) outbound_pct = lazy_div(stats_class.lost_outbound, stats_class.pings_transmitted) * 100 inbound_pct = lazy_div(stats_class.lost_inbound, stats_class.pings_transmitted) * 100 undetermined_pct = lazy_div(lost_undetermined, stats_class.pings_transmitted) * 100 rtt_avg = lazy_div(float(stats_class.rtt_total), stats_class.rtt_count) rtt_ewma = stats_class.rtt_ewma / 8.0 rtt_mdev = math.sqrt( lazy_div(stats_class.rtt_total_sq, stats_class.rtt_count) - (lazy_div(stats_class.rtt_total, stats_class.rtt_count) ** 2) ) if self.args.listen: hostname = _('Listener') else: hostname = sock_class.client_host[3] if short: self.print_out('\x0d', end='', flush=True, file=sys.stderr) self.print_out(_pl( '{hostname}: {transmitted}/{received} ping, {loss}% loss ' + '({outbound}/{inbound}/{undetermined} out/in/undet), min/avg/ewma/max/mdev = ' + '{min:0.03f}/{avg:0.03f}/{ewma:0.03f}/{max:0.03f}/{mdev:0.03f} ms', '{hostname}: {transmitted}/{received} pings, {loss}% loss ' + '({outbound}/{inbound}/{undetermined} out/in/undet), min/avg/ewma/max/mdev = ' + '{min:0.03f}/{avg:0.03f}/{ewma:0.03f}/{max:0.03f}/{mdev:0.03f} ms', stats_class.pings_received ).format( hostname=hostname, transmitted=stats_class.pings_transmitted, received=stats_class.pings_received, loss=int(lost_pct), outbound=stats_class.lost_outbound, inbound=stats_class.lost_inbound, undetermined=lost_undetermined, min=stats_class.rtt_min, avg=rtt_avg, ewma=rtt_ewma, max=stats_class.rtt_max, mdev=rtt_mdev, ), file=sys.stderr) elif self.args.nagios: if (lost_pct >= self.args.nagios_crit_loss) or (rtt_avg >= self.args.nagios_crit_rta): self.nagios_result = 2 nagios_result_text = 'CRITICAL' elif (lost_pct >= self.args.nagios_warn_loss) or (rtt_avg >= self.args.nagios_warn_rta): self.nagios_result = 1 nagios_result_text = 'WARNING' else: self.nagios_result = 0 nagios_result_text = 'OK' self.print_out(_( '2PING {result} - Packet loss = {loss}%, RTA = {avg:0.03f} ms' ).format( result=nagios_result_text, loss=int(lost_pct), avg=rtt_avg, ) + ( '|rta={avg:0.06f}ms;{avgwarn:0.06f};{avgcrit:0.06f};0.000000 ' + 'pl={loss}%;{losswarn};{losscrit};0' ).format( avg=rtt_avg, loss=int(lost_pct), avgwarn=self.args.nagios_warn_rta, avgcrit=self.args.nagios_crit_rta, losswarn=int(self.args.nagios_warn_loss), losscrit=int(self.args.nagios_crit_loss), )) else: self.print_out('') self.print_out('--- %s ---' % _('{hostname} 2ping statistics').format(hostname=hostname)) self.print_out(_pl( '{transmitted} ping transmitted, {received} received, {loss}% ping loss, time {time}', '{transmitted} pings transmitted, {received} received, {loss}% ping loss, time {time}', stats_class.pings_transmitted ).format( transmitted=stats_class.pings_transmitted, received=stats_class.pings_received, loss=int(lost_pct), time=self.stats_time(time_end - time_start), )) self.print_out(_pl( '{outbound} outbound ping loss ({outboundpct}%), {inbound} inbound ({inboundpct}%), ' + '{undetermined} undetermined ({undeterminedpct}%)', '{outbound} outbound ping losses ({outboundpct}%), {inbound} inbound ({inboundpct}%), ' + '{undetermined} undetermined ({undeterminedpct}%)', stats_class.lost_outbound ).format( outbound=stats_class.lost_outbound, outboundpct=int(outbound_pct), inbound=stats_class.lost_inbound, inboundpct=int(inbound_pct), undetermined=lost_undetermined, undeterminedpct=int(undetermined_pct), )) self.print_out(_('rtt min/avg/ewma/max/mdev = {min:0.03f}/{avg:0.03f}/' + '{ewma:0.03f}/{max:0.03f}/{mdev:0.03f} ms').format( min=stats_class.rtt_min, avg=rtt_avg, ewma=rtt_ewma, max=stats_class.rtt_max, mdev=rtt_mdev, )) self.print_out(_pl( '{transmitted} raw packet transmitted, {received} received', '{transmitted} raw packets transmitted, {received} received', stats_class.packets_transmitted ).format( transmitted=stats_class.packets_transmitted, received=stats_class.packets_received, )) def run(self): self.print_debug('Clock: %s, value: %f' % (clock_info, clock())) self.print_debug('Poller: %s' % self.poller.poller_type) if hasattr(signal, 'SIGQUIT'): signal.signal(signal.SIGQUIT, self.sigquit_handler) try: if self.args.listen: self.setup_listener() else: self.setup_client() except (socket.error, socket.gaierror) as e: self.print_out(str(e)) return 1 try: self.loop() except KeyboardInterrupt: self.shutdown() return def base_packet(self): packet_out = packets.Packet() if (not self.args.no_send_version) or (self.args.notice): packet_out.opcodes[packets.OpcodeExtended.id] = packets.OpcodeExtended() if not self.args.no_send_version: packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedVersion.id] = packets.ExtendedVersion() packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedVersion.id].text = version_string if self.args.send_time: packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedWallClock.id] = packets.ExtendedWallClock() packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedWallClock.id].time_us = int(time.time() * 1000000) if self.args.send_monotonic_clock: packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedMonotonicClock.id] = packets.ExtendedMonotonicClock() packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedMonotonicClock.id].generation = self.fake_time_generation packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedMonotonicClock.id].time_us = int((clock() - self.time_start + self.fake_time_epoch) * 1000000) if self.args.send_random: random_data = bytearray([random_sys.randint(0, 255) for x in xrange(self.args.send_random)]) packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedRandom.id] = packets.ExtendedRandom() packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedRandom.id].is_hwrng = False packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedRandom.id].is_os = has_sysrandom packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedRandom.id].random_data = random_data if self.args.notice: packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedNotice.id] = packets.ExtendedNotice() packet_out.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedNotice.id].text = self.args.notice if self.args.auth: packet_out.opcodes[packets.OpcodeHMAC.id] = packets.OpcodeHMAC() packet_out.opcodes[packets.OpcodeHMAC.id].key = bytearray(self.args.auth) packet_out.opcodes[packets.OpcodeHMAC.id].digest_index = self.args.auth_digest_index packet_out.padding_pattern = self.args.pattern_bytearray packet_out.min_length = self.args.min_packet_size packet_out.max_length = self.args.max_packet_size return packet_out def scheduled_cleanup(self): self.print_debug('Cleanup') for sock_class in self.sock_classes: self.scheduled_cleanup_sock_class(sock_class) def scheduled_cleanup_sock_class(self, sock_class): now = clock() for peer_tuple in sock_class.sent_messages.keys(): for message_id_int in sock_class.sent_messages[peer_tuple].keys(): if now > (sock_class.sent_messages[peer_tuple][message_id_int][0] + 120.0): del(sock_class.sent_messages[peer_tuple][message_id_int]) self.print_debug('Cleanup: Removed sent_messages %s %d' % (repr(peer_tuple), message_id_int)) if len(sock_class.sent_messages[peer_tuple]) == 0: del(sock_class.sent_messages[peer_tuple]) self.print_debug('Cleanup: Removed sent_messages empty %s' % repr(peer_tuple)) for peer_tuple in sock_class.seen_messages.keys(): for message_id_int in sock_class.seen_messages[peer_tuple].keys(): if now > (sock_class.seen_messages[peer_tuple][message_id_int] + 600.0): del(sock_class.seen_messages[peer_tuple][message_id_int]) self.print_debug('Cleanup: Removed seen_messages %s %d' % (repr(peer_tuple), message_id_int)) if len(sock_class.seen_messages[peer_tuple]) == 0: del(sock_class.seen_messages[peer_tuple]) self.print_debug('Cleanup: Removed seen_messages empty %s' % repr(peer_tuple)) for peer_tuple in sock_class.courtesy_messages.keys(): for message_id_int in sock_class.courtesy_messages[peer_tuple].keys(): if now > (sock_class.courtesy_messages[peer_tuple][message_id_int][0] + 120.0): del(sock_class.courtesy_messages[peer_tuple][message_id_int]) self.print_debug('Cleanup: Removed courtesy_messages %s %d' % (repr(peer_tuple), message_id_int)) if len(sock_class.courtesy_messages[peer_tuple]) == 0: del(sock_class.courtesy_messages[peer_tuple]) self.print_debug('Cleanup: Removed courtesy_messages empty %s' % repr(peer_tuple)) def new_socket(self, family, type, bind): sock = socket.socket(family, type) try: import IN sock.setsockopt(socket.IPPROTO_IP, IN.IP_RECVERR, int(True)) except (ImportError, AttributeError, socket.error): pass if family == socket.AF_INET6: try: sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, int(True)) except (AttributeError, socket.error): pass sock.bind(bind) self.print_debug('Bound to: %s' % repr((family, type, bind))) return sock def loop(self): while True: now = clock() if now >= self.next_cleanup: self.scheduled_cleanup() self.next_cleanup = now + 60.0 if not self.args.listen: for sock_class in self.sock_classes: if sock_class.is_shutdown: continue if now >= sock_class.next_send: if self.args.count and (sock_class.pings_transmitted >= self.args.count): sock_class.is_shutdown = True continue if (sock_class.pings_transmitted == 0) and (self.args.preload > 1): for i in xrange(self.args.preload): self.send_new_ping(sock_class, sock_class.client_host[4]) else: self.send_new_ping(sock_class, sock_class.client_host[4]) sock_class.next_send = now + self.args.interval if self.args.flood: next_send = now + 0.01 for sock_class in self.sock_classes: if next_send < sock_class.next_send: sock_class.next_send = next_send next_wakeup = now + self.old_age_interval next_wakeup_reason = 'old age' for sock_class in self.sock_classes: if (not self.args.listen) and (sock_class.next_send < next_wakeup): next_wakeup = sock_class.next_send next_wakeup_reason = 'send' if self.args.stats: if now >= self.next_stats: self.print_stats(short=True) self.next_stats = now + self.args.stats if self.next_stats < next_wakeup: next_wakeup = self.next_stats next_wakeup_reason = 'stats' if self.args.deadline: time_deadline = self.time_start + self.args.deadline if now >= time_deadline: self.shutdown() if time_deadline < next_wakeup: next_wakeup = time_deadline next_wakeup_reason = 'deadline' if self.next_cleanup < next_wakeup: next_wakeup = self.next_cleanup next_wakeup_reason = 'cleanup' if next_wakeup < now: next_wakeup = now next_wakeup_reason = 'time travel' self.print_debug('Next wakeup: %s (%s)' % ((next_wakeup - now), next_wakeup_reason)) for sock_class in self.poller.poll(next_wakeup - now): try: self.process_incoming_packet(sock_class) except Exception as e: self.print_out(_('Exception: {error}').format(error=str(e))) if self.args.debug: raise if self.args.adaptive and sock_class.rtt_ewma: target = sock_class.rtt_ewma / 8.0 / 1000.0 sock_class.next_send = now + target if ( self.args.count and (sock_class.pings_transmitted >= self.args.count) and (sock_class.pings_transmitted == sock_class.pings_received) ): sock_class.is_shutdown = True all_shutdown = True for sock_class in self.sock_classes: if not sock_class.is_shutdown: all_shutdown = False break if all_shutdown: self.shutdown() def main(): args = parse_args() t = TwoPing(args) return(t.run()) if __name__ == '__main__': sys.exit(main()) 2ping-3.2.1/twoping/crc32.py000077500000000000000000000044531267541676000155400ustar00rootroot00000000000000#!/usr/bin/env python # 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from __future__ import print_function import binascii import copy digest_size = 4 class CRC32(): digest_size = 4 block_size = 64 _crc = 0 def __init__(self, buf=None): if buf is not None: self.update(buf) def copy(self): return copy.copy(self) def update(self, buf): self._crc = binascii.crc32(bytes(buf), self._crc) def clear(self): self._crc = 0 def digest(self): i = self._crc & 0xffffffff out = bytearray() while i >= 256: out.insert(0, i & 0xff) i = i >> 8 out.insert(0, i) out_len = len(out) if out_len < 4: out = bytearray(4 - out_len) + out return out def hexdigest(self): return ''.join('{hex:02x}'.format(hex=x) for x in self.digest()) def new(buf=None): return CRC32(buf) if __name__ == '__main__': import sys files = sys.argv[1:] if len(files) == 0: if hasattr(sys.stdin, 'buffer'): stdin = sys.stdin.buffer else: stdin = sys.stdin c = new() for buf in stdin.readlines(): c.update(buf) print(c.hexdigest()) else: for file in files: with open(file, 'rb') as f: c = new() for buf in f.readlines(): c.update(buf) if len(files) > 1: print('%s\t%s' % (c.hexdigest(), file)) else: print(c.hexdigest()) 2ping-3.2.1/twoping/monotonic_clock.py000077500000000000000000000171761267541676000200120ustar00rootroot00000000000000#!/usr/bin/env python # Monotonic clock # Copyright (C) 2015 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. from __future__ import print_function, division import sys import time import ctypes import ctypes.util class GettimeMonotonicCounter(): adjustable = False implementation = None monotonic = True resolution = 0.0 libraries = ('c',) class timespec_long(ctypes.Structure): _fields_ = ( ('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long), ) class timespec_int(ctypes.Structure): _fields_ = ( ('tv_sec', ctypes.c_int), ('tv_nsec', ctypes.c_long), ) def __init__(self): if not hasattr(self, 'clocks'): raise NotImplementedError() # We need to know the size of time_t, but can only reliably figure # it out in Python 2.7. try: import sysconfig except ImportError: raise RuntimeError('Cannot figure out timespec struct') sizeof_time_t = sysconfig.get_config_var('SIZEOF_TIME_T') sizeof_long = sysconfig.get_config_var('SIZEOF_LONG') sizeof_int = sysconfig.get_config_var('SIZEOF_INT') if sizeof_time_t == sizeof_long: self.timespec = self.timespec_long elif sizeof_time_t == sizeof_int: self.timespec = self.timespec_int else: raise RuntimeError('Cannot figure out timespec struct') self.clock_gettime = None for library_name in self.libraries: library_location = ctypes.util.find_library(library_name) if library_location is None: continue try: dll = ctypes.CDLL(library_location) except OSError: continue try: self.clock_gettime = dll.clock_gettime except AttributeError: continue break if self.clock_gettime is None: raise RuntimeError( 'Cannot find suitable library for clock_gettime' ) clock_getres = dll.clock_getres self.clock_id = None for (clock_id, clock_name, clock_monotonic) in self.clocks: res = self.timespec() if clock_getres(clock_id, ctypes.pointer(res)) != 0: continue self.resolution = res.tv_sec + (res.tv_nsec / 1e09) self.clock_id = clock_id self.implementation = 'clock_gettime(%s)' % clock_name self.monotonic = clock_monotonic break if self.clock_id is None: raise RuntimeError( 'Cannot find suitable clock for clock_gettime' ) def clock(self): tp = self.timespec() self.clock_gettime(self.clock_id, ctypes.pointer(tp)) return tp.tv_sec + (tp.tv_nsec / 1e09) class LinuxMonotonicCounter(GettimeMonotonicCounter): # http://linux.die.net/man/3/clock_gettime libraries = ('rt', 'c') clocks = ( (4, 'CLOCK_MONOTONIC_RAW', True), (1, 'CLOCK_MONOTONIC', True), (0, 'CLOCK_REALTIME', False), ) class FreeBSDMonotonicCounter(GettimeMonotonicCounter): # https://www.freebsd.org/cgi/man.cgi?query=clock_gettime clocks = ( (11, 'CLOCK_MONOTONIC_PRECISE', True), (4, 'CLOCK_MONOTONIC', True), (9, 'CLOCK_REALTIME_PRECISE', False), (0, 'CLOCK_REALTIME', False), ) class BSDMonotonicCounter(GettimeMonotonicCounter): # http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man2/clock_gettime.2 # https://www.daemon-systems.org/man/clock_gettime.2.html clocks = ( (3, 'CLOCK_MONOTONIC', True), (0, 'CLOCK_REALTIME', False), ) class DarwinMonotonicCounter(): # https://developer.apple.com/library/mac/qa/qa1398/ adjustable = False implementation = 'mach_absolute_time()' monotonic = True resolution = 1e-09 timebase = 0.0 class mach_timebase_info_data_t(ctypes.Structure): _fields_ = ( ('numer', ctypes.c_uint32), ('denom', ctypes.c_uint32), ) def __init__(self): library = ctypes.util.find_library('c') dll = ctypes.CDLL(library) self.mach_absolute_time = dll.mach_absolute_time self.mach_absolute_time.restype = ctypes.c_uint64 sTimebaseInfo = self.mach_timebase_info_data_t() dll.mach_timebase_info(ctypes.byref(sTimebaseInfo)) self.timebase = float(sTimebaseInfo.numer) / sTimebaseInfo.denom def clock(self): return self.mach_absolute_time() * self.timebase / 1e09 class Win32MonotonicCounter(): # https://msdn.microsoft.com/en-gb/library/windows/desktop/dn553408.aspx adjustable = False implementation = 'QueryPerformanceCounter()' monotonic = True resolution = 0.0 LARGE_INTEGER = ctypes.c_int64 BOOL = ctypes.c_int frequency = 0.0 def __init__(self): dll = ctypes.windll.kernel32 self.QueryPerformanceCounter = dll.QueryPerformanceCounter self.QueryPerformanceCounter.restype = self.BOOL QueryPerformanceFrequency = dll.QueryPerformanceFrequency QueryPerformanceFrequency.restype = self.BOOL frequency = self.LARGE_INTEGER() QueryPerformanceFrequency(ctypes.byref(frequency)) self.frequency = float(frequency.value) self.resolution = 1.0 / self.frequency def clock(self): count = self.LARGE_INTEGER() self.QueryPerformanceCounter(ctypes.byref(count)) return count.value / self.frequency class SystemCounter(): adjustable = True implementation = 'time()' monotonic = False resolution = 1.0 def clock(self): return time.time() class SimpleNamespace(): def __init__(self, **kwargs): for (k, v) in kwargs.items(): setattr(self, k, v) def __repr__(self): return 'namespace(%s)' % (', '.join( ['%s=%s' % (k, repr(v)) for (k, v) in self.__dict__.items()] )) try: if sys.platform.startswith('linux'): _counter = LinuxMonotonicCounter() elif sys.platform.startswith(('freebsd', 'dragonfly')): _counter = FreeBSDMonotonicCounter() elif 'bsd' in sys.platform: _counter = BSDMonotonicCounter() elif sys.platform.startswith('darwin'): _counter = DarwinMonotonicCounter() elif sys.platform.startswith(('win32', 'cygwin')): _counter = Win32MonotonicCounter() else: _counter = SystemCounter() except RuntimeError: # Fall back to built-in counter as a last resort _counter = SystemCounter() clock = _counter.clock def get_clock_info(clock): if clock != 'clock': raise ValueError('unknown clock') return SimpleNamespace( adjustable=_counter.adjustable, implementation=_counter.implementation, monotonic=_counter.monotonic, resolution=_counter.resolution, ) if __name__ == '__main__': print(get_clock_info('clock')) for _ in range(5): print('%0.050f' % clock()) 2ping-3.2.1/twoping/packets.py000066400000000000000000000362031267541676000162510ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from __future__ import print_function, division import random import hmac import time from . import crc32 from .utils import twoping_checksum, int_to_bytearray, bytearray_to_int import hashlib class Extended(): id = None def __init__(self): self.data = bytearray() def __repr__(self): if self.id is None: return '' % len(self.data) else: id_hex = ''.join(['%02x' % x for x in int_to_bytearray(self.id, 2)]) return '' % (id_hex, len(self.data)) def load(self, data): self.data = data def dump(self): return self.data class ExtendedText(Extended): def __init__(self): self.text = bytearray() def __repr__(self): return '' % str(self.text) def load(self, data): self.text = data def dump(self, max_length=None): if (max_length is not None) and (max_length < len(self.text)): return None return self.text class ExtendedVersion(ExtendedText): id = 0x3250564e def __repr__(self): return '' % str(self.text) class ExtendedNotice(ExtendedText): id = 0xa837b44e def __repr__(self): return '' % str(self.text) class ExtendedWallClock(Extended): id = 0x64f69319 def __init__(self): self.time_us = 0 def __repr__(self): return '' % time.strftime('%c', time.gmtime(self.time_us / 1000000.0)) def load(self, data): self.time_us = bytearray_to_int(data[0:8]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 8): return None return int_to_bytearray(self.time_us, 8) class ExtendedMonotonicClock(Extended): id = 0x771d8dfb def __init__(self): self.generation = 0 self.time_us = 0 def __repr__(self): return '' % ((self.time_us / 1000000.0), self.generation) def load(self, data): self.generation = bytearray_to_int(data[0:2]) self.time_us = bytearray_to_int(data[2:10]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 10): return None return int_to_bytearray(self.generation, 2) + int_to_bytearray(self.time_us, 8) class ExtendedRandom(Extended): id = 0x2ff6ad68 def __init__(self): self.is_hwrng = False self.is_os = False self.random_data = bytearray() def __repr__(self): return '' % ( repr(self.random_data), len(self.random_data), repr(self.is_hwrng), repr(self.is_os), ) def load(self, data): flags = bytearray_to_int(data[0:2]) self.is_hwrng = bool(flags & 0x0001) self.is_os = bool(flags & 0x0002) self.random_data = data[2:] def dump(self, max_length=None): random_data = self.random_data if len(random_data) == 0: return None if max_length is not None: if max_length < 3: return None if max_length < (len(random_data) - 2): random_data = random_data[0:max_length-2] flags = 0 if self.is_hwrng: flags = flags | 0x0001 if self.is_os: flags = flags | 0x0002 return int_to_bytearray(flags, 2) + random_data class Opcode(): id = None def __init__(self): self.data = bytearray() def __repr__(self): if self.id is None: return '' % len(self.data) else: id_hex = ''.join(['%02x' % x for x in int_to_bytearray(self.id, 2)]) return '' % (id_hex, len(self.data)) def load(self, data): self.data = data def dump(self): return self.data class OpcodeReplyRequested(Opcode): id = 0x0001 def __init__(self): pass def __repr__(self): return '' def load(self, data): pass def dump(self, max_length=None): return bytearray() class OpcodeInReplyTo(Opcode): id = 0x0002 def __init__(self): self.message_id = bytearray() def __repr__(self): message_id_hex = ''.join(['%02x' % x for x in self.message_id]) return '' % message_id_hex def load(self, data): self.message_id = data[0:6] def dump(self, max_length=None): if (max_length is not None) and (max_length < 6): return None return self.message_id class OpcodeRTTEnclosed(Opcode): id = 0x0004 def __init__(self): self.rtt_us = 0 def __repr__(self): return '' % self.rtt_us def load(self, data): self.rtt_us = bytearray_to_int(data[0:4]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 4): return None return int_to_bytearray(self.rtt_us, 4) class OpcodeMessageIDList(Opcode): def __init__(self): self.message_ids = [] def __repr__(self): ids = ['0x%s' % ''.join(['%02x' % x for x in y]) for y in self.message_ids] return '' % (', '.join(ids), len(self.message_ids)) def load(self, data): self.message_ids = [] pos = 2 for i in xrange(bytearray_to_int(data[0:2])): self.message_ids.append(data[pos:pos+6]) pos += 6 def dump(self, max_length=None): if (max_length is not None): if (max_length < 8): return None output_ids = self.message_ids[0:int((max_length - 2) / 6)] else: output_ids = self.message_ids out = int_to_bytearray(len(output_ids), 2) for i in output_ids: out += i return out class OpcodeInvestigationSeen(OpcodeMessageIDList): id = 0x0008 def __repr__(self): ids = ['0x%s' % ''.join(['%02x' % x for x in y]) for y in self.message_ids] return '' % (', '.join(ids), len(self.message_ids)) class OpcodeInvestigationUnseen(OpcodeMessageIDList): id = 0x0010 def __repr__(self): ids = ['0x%s' % ''.join(['%02x' % x for x in y]) for y in self.message_ids] return '' % (', '.join(ids), len(self.message_ids)) class OpcodeInvestigate(OpcodeMessageIDList): id = 0x0020 def __repr__(self): ids = ['0x%s' % ''.join(['%02x' % x for x in y]) for y in self.message_ids] return '' % (', '.join(ids), len(self.message_ids)) class OpcodeCourtesyExpiration(OpcodeMessageIDList): id = 0x0040 def __repr__(self): ids = ['0x%s' % ''.join(['%02x' % x for x in y]) for y in self.message_ids] return '' % (', '.join(ids), len(self.message_ids)) class OpcodeHMAC(Opcode): id = 0x0080 def __init__(self): self.key = bytearray() self.digest_index = None self.hash = bytearray() self.digest_map = { 1: (hashlib.md5, 16), 2: (hashlib.sha1, 20), 3: (hashlib.sha1, 30), 4: (crc32, 4), } def __repr__(self): return '' def load(self, data): self.digest_index = bytearray_to_int(data[0:2]) self.hash = data[2:] def dump(self, max_length=None): if self.digest_index is not None: (hasher, size) = self.digest_map[self.digest_index] return int_to_bytearray(self.digest_index, 2) + bytearray(size) return None class OpcodeHostLatency(Opcode): id = 0x0100 def __init__(self): self.delay_us = 0 def __repr__(self): return '' % self.delay_us def load(self, data): self.delay_us = bytearray_to_int(data[0:4]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 4): return None return int_to_bytearray(self.delay_us, 4) class OpcodeExtended(Opcode): id = 0x8000 def __init__(self): self.segments = {} self.segment_data_positions = {} def __repr__(self): return '' % repr(sorted(self.segments.values(), key=lambda x: x.id)) def load(self, data): self.segments = {} self.segment_data_positions = {} pos = 0 known_segments = ( ExtendedVersion, ExtendedNotice, ExtendedMonotonicClock, ExtendedWallClock, ExtendedRandom, ) while pos < len(data): flag = bytearray_to_int(data[pos:pos+4]) pos += 4 segment_data_length = bytearray_to_int(data[pos:pos+2]) pos += 2 self.segment_data_positions[flag] = (pos, segment_data_length) segment_handler = None for seg in known_segments: if flag == seg.id: segment_handler = seg break if segment_handler is None: segment_handler = Extended segment_handler.id = flag self.segments[flag] = segment_handler() self.segments[flag].load(data[pos:(pos+segment_data_length)]) pos += segment_data_length def dump(self, max_length=None): if (max_length is not None) and (max_length < 6): return None out = bytearray() pos = 0 for segment in self.segments.values(): if max_length is None: segment_max_length = None else: segment_max_length = max_length - pos - 6 segment_data = segment.dump(max_length=segment_max_length) if segment_data is None: continue out += int_to_bytearray(segment.id, 4) pos += 4 out += int_to_bytearray(len(segment_data), 2) pos += 2 out += segment_data pos += len(segment_data) if len(out) == 0: return None return out class Packet(): def __repr__(self): return '' % ( ''.join(['%02x' % x for x in self.message_id]), repr(sorted(self.opcodes.values(), key=lambda x: x.id)) ) def __init__(self): self.message_id = bytearray() self.opcodes = {} self.min_length = 0 self.max_length = 1024 self.padding_pattern = bytearray(1) self.opcode_data_positions = {} def load(self, data): magic_number = data[0:2] if magic_number != bytearray((0x32, 0x50)): raise Exception('Invalid magic number') checksum = bytearray_to_int(data[2:4]) if checksum: if twoping_checksum(data[0:2] + bytearray(2) + data[4:]) != checksum: raise Exception('Invalid checksum') self.message_id = data[4:10] opcode_flags = bytearray_to_int(data[10:12]) self.opcodes = {} pos = 12 known_opcodes = ( OpcodeReplyRequested, OpcodeInReplyTo, OpcodeRTTEnclosed, OpcodeInvestigationSeen, OpcodeInvestigationUnseen, OpcodeInvestigate, OpcodeCourtesyExpiration, OpcodeHMAC, OpcodeHostLatency, OpcodeExtended, ) for flag in (2 ** x for x in xrange(16)): if not opcode_flags & flag: continue opcode_data_length = bytearray_to_int(data[pos:pos+2]) pos += 2 self.opcode_data_positions[flag] = (pos, opcode_data_length) opcode_handler = None for oc in known_opcodes: if flag == oc.id: opcode_handler = oc break if opcode_handler is None: opcode_handler = Opcode opcode_handler.id = flag self.opcodes[flag] = opcode_handler() self.opcodes[flag].load(data[pos:(pos+opcode_data_length)]) pos += opcode_data_length def dump(self): auth_pos_begin = 0 auth_pos_end = 0 if not self.message_id: self.message_id = bytearray([random.randint(0, 255) for x in xrange(6)]) opcode_datas = {} packet_length = 12 for flag in ( OpcodeHMAC.id, OpcodeReplyRequested.id, OpcodeInReplyTo.id, OpcodeRTTEnclosed.id, OpcodeInvestigationSeen.id, OpcodeInvestigationUnseen.id, OpcodeInvestigate.id, OpcodeHostLatency.id, OpcodeCourtesyExpiration.id, OpcodeExtended.id, ): if flag not in self.opcodes: continue if (packet_length + 2) > self.max_length: break res = self.opcodes[flag].dump(max_length=(self.max_length - packet_length - 2)) if res is None: continue opcode_datas[flag] = res res_len = len(res) packet_length += res_len + 2 opcode_flags = 0 opcode_data = bytearray() packet_length = 12 for flag in sorted(opcode_datas.keys()): res = opcode_datas[flag] res_len = len(res) if flag == OpcodeHMAC.id: auth_pos_begin = packet_length + 4 auth_pos_end = auth_pos_begin + (res_len - 2) opcode_flags = opcode_flags | flag opcode_data += int_to_bytearray(res_len, 2) opcode_data += res packet_length += res_len + 2 out = bytearray((0x32, 0x50)) + bytearray(2) + self.message_id + int_to_bytearray(opcode_flags, 2) + opcode_data if len(out) < self.min_length: target_padding = self.min_length - len(out) padding = (self.padding_pattern * int(target_padding / len(self.padding_pattern) + 1))[0:target_padding] out += padding if (OpcodeHMAC.id in self.opcodes) and auth_pos_begin: out[auth_pos_begin:auth_pos_end] = self.calculate_hash(self.opcodes[OpcodeHMAC.id], out) out[2:4] = int_to_bytearray(twoping_checksum(out), 2) return out def calculate_hash(self, opcode, payload): (hasher, size) = opcode.digest_map[opcode.digest_index] return bytearray(hmac.new(opcode.key, payload, hasher).digest()) 2ping-3.2.1/twoping/utils.py000066400000000000000000000040441267541676000157550ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2015 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. from __future__ import print_function, division import platform import gettext _ = gettext.translation('2ping', fallback=True).ugettext _pl = gettext.translation('2ping', fallback=True).ungettext def twoping_checksum(d): checksum = 0 for i in xrange(len(d)): if i & 1: checksum += d[i] else: checksum += d[i] << 8 checksum = ((checksum >> 16) + (checksum & 0xffff)) checksum = ((checksum >> 16) + (checksum & 0xffff)) checksum = ~checksum & 0xffff if checksum == 0: checksum = 0xffff return checksum def lazy_div(n, d): if d == 0: return 0 return n / d def int_to_bytearray(i, minimum=1): out = bytearray() while i >= 256: out.insert(0, i & 0xff) i = i >> 8 out.insert(0, i) out_len = len(out) if out_len < minimum: out = bytearray(minimum - out_len) + out return out def bytearray_to_int(b): out = 0 for x in b: out = (out << 8) + x return out def platform_info(): out = platform.system() try: linux_distribution = platform.linux_distribution() if linux_distribution[0]: out += ' (%s)' % linux_distribution[0] except: pass out += ' %s' % platform.machine() return out