pax_global_header00006660000000000000000000000064131414744350014520gustar00rootroot0000000000000052 comment=bc8b972b18cf7524afaa992b9101a75bfe0da544 2ping-4.1/000077500000000000000000000000001314147443500124035ustar00rootroot000000000000002ping-4.1/.gitignore000066400000000000000000000013101314147443500143660ustar00rootroot00000000000000MANIFEST # 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-4.1/.travis.yml000066400000000000000000000002531314147443500145140ustar00rootroot00000000000000language: python python: - "3.4" - "3.5" - "3.6" install: - pip3 install . script: - python3 setup.py test - python3 setup.py check - python3 setup.py sdist 2ping-4.1/2ping000077500000000000000000000016161314147443500133540ustar00rootroot00000000000000#!/usr/bin/env python3 # 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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-4.1/2ping.spec000066400000000000000000000017701314147443500143030ustar00rootroot00000000000000Name: 2ping Version: 4.1 Release: 1%{?dist} Summary: Bi-directional ping utility License: GPLv2+ URL: https://www.finnie.org/software/2ping Source0: https://www.finnie.org/software/%{name}/%{name}-%{version}.tar.gz BuildArch: noarch BuildRequires: python3-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 %py3_build %install %py3_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 %{__python3} setup.py test %files %doc ChangeLog COPYING README %{python3_sitelib}/* %{_bindir}/2ping %{_bindir}/2ping6 %{_mandir}/man1/2ping.1* %{_mandir}/man1/2ping6.1* 2ping-4.1/2ping6000077500000000000000000000016161314147443500134420ustar00rootroot00000000000000#!/usr/bin/env python3 # 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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-4.1/COPYING000066400000000000000000000431031314147443500134370ustar00rootroot00000000000000 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-4.1/ChangeLog000066400000000000000000000202471314147443500141620ustar00rootroot000000000000002ping 4.1 (2017-08-06) * Fixed --fuzz CRC function. * Added --encrypt option for shared-secret encrypted packets. * Added --listen --all-interfaces option for automatically binding to all interface IPs (requires Python netifaces module). * Simplified monotonic_clock functionality, relying on Python 3 for most functionality, reducing the possibility of platform bugs. * Minor fixes and unit test suite improvements. 2ping 4.0.1 (2017-07-22) * Fixed unit tests causing test failure in certain conditions. 2ping 4.0 (2017-07-22) * Rewrite from Python 2 to Python 3 (3.4 or higher). * Fixed hmac-sha256 handling, added hmac-sha512. * --nagios will now work when combined with --deadline, in addition to --count. * Added Wireshark Lua dissector and sample capture. * Added battery level (ExtID 0x88a1f7c7). Note that while 2ping recognizes the new option in incoming packets, it currently does not have the capability to send battery levels. * Minor fixes. 2ping 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-4.1/MANIFEST.in000066400000000000000000000002641314147443500141430ustar00rootroot00000000000000include README include COPYING include ChangeLog include Makefile include *.md include doc/* include 2ping include 2ping.spec include 2ping6 include tests/*.py include wireshark/* 2ping-4.1/Makefile000066400000000000000000000004561314147443500140500ustar00rootroot00000000000000PYTHON := python3 PANDOC := pandoc all: 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-4.1/README000066400000000000000000000032301314147443500132610ustar00rootroot00000000000000 2PING - A BI-DIRECTIONAL PING UTILITY https://www.finnie.org/software/2ping/ About 2ping is a bi-directional ping utility. It uses 3-way pings (akin to TCP SYN, SYN/ACK, ACK) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs. Installation 2ping requires Python 3 version 3.4 or higher. To install: sudo python3 setup.py install Python 3 stdlib is the only requirement for base functionality, but 2ping can utilize the following modules if available: - dnspython for --srv - netifaces for --listen --all-interfaces - pycrypto for --encrypt 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) 2010-2017 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-4.1/README.md000066400000000000000000000034621314147443500136670ustar00rootroot00000000000000# 2ping - A bi-directional ping utility https://www.finnie.org/software/2ping/ ## About 2ping is a bi-directional ping utility. It uses 3-way pings (akin to TCP SYN, SYN/ACK, ACK) and after-the-fact state comparison between a 2ping listener and a 2ping client to determine which direction packet loss occurs. ## Installation 2ping requires Python 3 version 3.4 or higher. To install: sudo python3 setup.py install Python 3 stdlib is the only requirement for base functionality, but 2ping can utilize the following modules if available: * [dnspython](https://pypi.python.org/pypi/dnspython) for --srv * [netifaces](https://pypi.python.org/pypi/netifaces) for --listen --all-interfaces * [pycrypto](https://pypi.python.org/pypi/pycrypto) for --encrypt ## 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) 2010-2017 [Ryan Finnie](https://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-4.1/doc/000077500000000000000000000000001314147443500131505ustar00rootroot000000000000002ping-4.1/doc/2ping-protocol-examples.py000066400000000000000000000217351314147443500202240ustar00rootroot00000000000000#!/usr/bin/env python3 # 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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 = b'\x00\x00\x00\x00\xa0\x01' print(' CLIENT: {}'.format(h(packet.dump()))) print() print('### Example 2') print() packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xb0\x01' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\x00\x00\x00\x00\xa0\x01' print(' SERVER: {}'.format(h(packet.dump()))) print() print('### Example 3') print() packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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 = b'\x00\x00\x00\x00\xa0\x01' print(' SERVER: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\x00\x00\x00\x00\xb0\x01' packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 print(' CLIENT: {}'.format(h(packet.dump()))) print() print('### Example 4') print() packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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(b'\x00\x00\x00\x00\xa0\x01') print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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 = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x01') print(' SERVER: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x03' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\x00\x00\x00\x00\xb0\x02' packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 print(' CLIENT: {}'.format(h(packet.dump()))) print() print('### Example 5') print() packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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(b'\x00\x00\x00\x00\xa0\x01') print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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 = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x01') print(' SERVER: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x03' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\x00\x00\x00\x00\xb0\x01' packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12345 print(' CLIENT: {}'.format(h(packet.dump()))) print() print('### Example 6') print() packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x03' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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 = b'\x00\x00\x00\x00\xa0\x03' print(' SERVER: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x04' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\x00\x00\x00\x00\xb0\x02' packet.opcodes[packets.OpcodeRTTEnclosed.id] = packets.OpcodeRTTEnclosed() packet.opcodes[packets.OpcodeRTTEnclosed.id].rtt_us = 12823 print(' CLIENT: {}'.format(h(packet.dump()))) print(' ... etc') packet = packets.Packet() packet.message_id = b'\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(b'\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x02') print(' CLIENT: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\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 = b'\x00\x00\x00\x00\xa0\x0a' packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(b'\x00\x00\x00\x00\xb0\x02') print(' SERVER: {}'.format(h(packet.dump()))) packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x0b' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\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(b'\x00\x00\x00\x00\xb0\x02') print(' CLIENT: {}'.format(h(packet.dump()))) print() 2ping-4.1/doc/2ping-protocol.md000066400000000000000000001104641314147443500163560ustar00rootroot00000000000000# 2ping protocol * Line protocol version: 4.0 * Document version: 20170806 ## 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 | | 5 | HMAC-SHA512 | 64 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, SHA256 and SHA512). ### 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. ### 0x0200 - Encrypted packet | Field | Length | | ----- | ------ | | Method index | 2 octets, required | | Encrypted data | Variable, required | This opcode acts as a container for a shared-secret encrypted 2ping packet. A complete valid 2ping packet, from magic number and checksum through optional padding, is encrypted using a known method and contained in this opcode in a stub 2ping packet. The stub packet, which is what is sent along the the wire, is also a valid 2ping packet, but contains only a single opcode, 0x0200. When the encrypted payload in the stub packet is decrypted on the receiving end, the decrypted packet is used in place of the stub packet and is parsed normally. The stub packet's message ID is not used and should be pseudo-random. Implementations must not re-use the encrypted packet's message ID for the unencrypted stub's message ID. When one end specifies it is using encryption, it must not accept replies from the other end if the message is not encrypted, the method index does not match, or the encrypted payload does not decrypt to a valid 2ping packet. As of this protocol revision, a single method is specified, HKDF-AES256-CBC (index 1). Implementations may use a locally-reserved method by specifying index 0, as long as both ends have implemented the method. #### Method index 1 - HKDF-AES256-CBC * Shared secret * HKDF (RFC 5869) key derivation function, extract + expand rounds * Input key material: shared secret, encoded as UTF-8 if needed * Salt: 16 octet (128 bit) per-message pseudo-random value * Output key length: 32 octets (256 bits) * Expand round info value: 0xd889ac93aceba1f398d0c69bc8c6a7aa + 8 octet (64 bit) per-session pseudo-random value (concatenated) * AES encryption * Cipher mode: AES-CBC * Key: 32 octet (256 bit) output of HKDF extract + expand rounds above * Initialization vector (IV): Same as HKDF salt above * Input: Complete unencrypted 2ping packet The session ID, IV/salt and the result of the AES encryption are concatenated to form the encrypted data field. To decrypt, extract the session ID from the first 8 octets of the encrypted data field, the IV/salt from the following 16 octets, and use the method to AES decrypt the remaining octets. AES has a 128 bit block size, which data must be padded to when encrypting. As trailing padding is a core feature of 2ping, no special padding method is required. The implementation only needs to make sure the unencrypted packet length is a multiple of 16 octets before encryption. ### 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. The opcode data area must include a maximum of one instance of any individual extended segment. 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. ### 0x88a1f7c7 - Battery levels | Field | Length | | ----- | ------ | | Number of batteries enclosed | 2 octets, required | | Battery ID | 2 octets, optional | | Battery level | 2 octets, optional | | Battery ID... | 2 octets..., optional | | Battery level... | 2 octets..., optional | If the host is a device which includes batteries, this may be used to report their levels. Multiple batteries may be reported using different battery IDs. The level is indicated as a percentage between 0x0000 (completely empty) and 0xffff (completely full). The number of batteries enclosed may legally be zero, but if that is the case, it's better to just not include the 0x88a1f7c7 segment. ### 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 ### 4.0 (20170806) * Added opcode 0x0200 - Encrypted packet ### 3.2 (20170722) * Added HMAC-SHA512 (index 5) to 0x0080 MAC digest types. * Added the following registered extended segments: * 0x88a1f7c7: Battery levels * Clarified that only one instance of a specific ExtID may be present in a packet. ### 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-4.1/doc/2ping.1000066400000000000000000000274571314147443500142700ustar00rootroot00000000000000.\" Automatically generated by Pandoc 1.17.2 .\" .TH "2PING" "1" "" "" "2ping" .hy .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 either this option or \f[I]\-\-all\-interfaces\f[] 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 \-\-all\-interfaces In listener mode, listen on all possible interface addresses. If used, this will override any addresses given by \f[I]\-\-interface\-address\f[]. This functionality requires the netifaces module to be installed. .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[], \f[C]hmac\-sha256\f[] and \f[C]hmac\-sha512\f[]. .RS .RE .TP .B \-\-debug Print (lots of) debugging information. .RS .RE .TP .B \-\-encrypt=\f[I]key\f[] Set a shared key, encrypt 2ping packets, and require encrypted packets from peers encrypted with the same shared key. Requires the PyCrypto module. .RS .RE .TP .B \-\-encrypt\-method=\f[I]method\f[] When \f[I]\-\-encrypt\f[] is used, specify the method used to encrypt packets. Valid options are \f[C]hkdf\-aes256\-cbc\f[] (default). .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-4.1/doc/2ping.md000066400000000000000000000252741314147443500145230ustar00rootroot00000000000000% 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 either this option or *--all-interfaces* 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. --all-interfaces : In listener mode, listen on all possible interface addresses. If used, this will override any addresses given by *--interface-address*. This functionality requires the netifaces module to be installed. --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`, `hmac-sha256` and `hmac-sha512`. --debug : Print (lots of) debugging information. --encrypt=*key* : Set a shared key, encrypt 2ping packets, and require encrypted packets from peers encrypted with the same shared key. Requires the PyCrypto module. --encrypt-method=*method* : When *--encrypt* is used, specify the method used to encrypt packets. Valid options are `hkdf-aes256-cbc` (default). --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-4.1/doc/Makefile000066400000000000000000000002221314147443500146040ustar00rootroot00000000000000MARKDOWN=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-4.1/setup.py000066400000000000000000000025441314147443500141220ustar00rootroot00000000000000#!/usr/bin/env python3 import os import sys from setuptools import setup assert(sys.version_info > (3, 4)) 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='4.1', license='GPLv2+', platforms=['Unix'], author='Ryan Finnie', author_email='ryan@finnie.org', url='https://www.finnie.org/software/2ping/', download_url='https://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 :: 3 :: Only', 'Topic :: Internet', 'Topic :: System :: Networking', 'Topic :: Utilities', ], entry_points={ 'console_scripts': [ '2ping = twoping.cli:main', '2ping6 = twoping.cli:main', ], }, test_suite='tests', ) 2ping-4.1/tests/000077500000000000000000000000001314147443500135455ustar00rootroot000000000000002ping-4.1/tests/__init__.py000066400000000000000000000000001314147443500156440ustar00rootroot000000000000002ping-4.1/tests/test_best_poller.py000066400000000000000000000026611314147443500174750ustar00rootroot00000000000000#!/usr/bin/env python3 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 ) s.close() 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) ) s1.close() s2.close() 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 ) s.close() 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-4.1/tests/test_cli.py000066400000000000000000000110471314147443500157300ustar00rootroot00000000000000#!/usr/bin/env python3 import unittest import os import subprocess import time import signal import random from twoping import monotonic_clock try: from Crypto.Cipher import AES has_aes = True except ImportError: has_aes = False @unittest.skipUnless(hasattr(os, 'fork'), 'CLI tests require os.fork()') class BaseTestCLI(unittest.TestCase): bind_address = '127.0.0.1' port = None settle_time = 3 child_pid = 0 twoing_binary = '/usr/bin/2ping' listener_opts = [] @classmethod def setUpClass(self): dir_path = os.path.dirname(os.path.realpath(__file__)) self.twoping_binary = os.path.join(os.path.split(dir_path)[0], '2ping') (self.child_pid, self.port) = self.fork_listener(self, self.listener_opts) @classmethod def tearDownClass(self): if self.child_pid: os.kill(self.child_pid, signal.SIGINT) def fork_listener(self, extra_opts=None): if self.port: port = self.port else: port = random.randint(49152, 65535) opts = [ '2ping', '--listen', '--quiet', '--no-3way', '--interface-address={}'.format(self.bind_address), '--port={}'.format(port), ] if extra_opts: opts += extra_opts child_pid = os.fork() if child_pid == 0: devnull_f = open(os.devnull, 'r') os.dup2(devnull_f.fileno(), 0) os.dup2(devnull_f.fileno(), 1) os.dup2(devnull_f.fileno(), 2) os.execv(self.twoping_binary, opts) time.sleep(self.settle_time) return((child_pid, port)) def run_listener_client(self, client_opts, listener_opts=None): if listener_opts is None: listener_opts = [] client_base_opts = [ self.twoping_binary, self.bind_address, '--port={}'.format(self.port), '--debug', '--nagios=1000,5%,1000,5%', ] if not (('--adaptive' in client_opts) or ('--flood' in client_opts)): client_base_opts.append('--count=1') if not ('--count=1' in client_opts): client_base_opts.append('--interval=5') try: subprocess.check_output(client_base_opts + client_opts) except subprocess.CalledProcessError as e: self.fail(e.output) class TestCLIStandard(BaseTestCLI): def test_notice(self): self.run_listener_client(['--notice=Notice text']) @unittest.skipUnless( ( os.environ.get('LANG') and ('UTF-8' in os.environ.get('LANG')) ), 'UTF-8 environment required' ) def test_notice_utf8(self): self.run_listener_client(['--notice=UTF-8 \u2603']) def test_random(self): self.run_listener_client(['--send-random=32']) def test_time(self): self.run_listener_client(['--send-time']) @unittest.skipUnless(monotonic_clock.get_clock_info('clock').monotonic, 'Monotonic clock required') def test_monotonic_clock(self): self.run_listener_client(['--send-monotonic-clock']) def test_adaptive(self): self.run_listener_client(['--adaptive', '--deadline=3']) def test_flood(self): self.run_listener_client(['--flood', '--deadline=3']) class TestCLIHMACMD5(BaseTestCLI): listener_opts = ['--auth-digest=hmac-md5', '--auth=rBgRpBfRbF4DkwFQXncz'] def test_hmac(self): self.run_listener_client(self.listener_opts) class TestCLIHMACSHA1(BaseTestCLI): listener_opts = ['--auth-digest=hmac-sha1', '--auth=qnzTCJHnZXdrxRZ8JjQw'] def test_hmac(self): self.run_listener_client(self.listener_opts) class TestCLIHMACSHA256(BaseTestCLI): listener_opts = ['--auth-digest=hmac-sha256', '--auth=cc8G2Ssbq4WZRq7H7d5L'] def test_hmac(self): self.run_listener_client(self.listener_opts) class TestCLIHMACSHA512(BaseTestCLI): listener_opts = ['--auth-digest=hmac-sha512', '--auth=sjk3kqzcSV3XfHJWNstn'] def test_hmac(self): self.run_listener_client(self.listener_opts) class TestCLIHMACCRC32(BaseTestCLI): listener_opts = ['--auth-digest=hmac-crc32', '--auth=mc82kJwtXFlhqQSCKptQ'] def test_hmac(self): self.run_listener_client(self.listener_opts) @unittest.skipUnless(has_aes, 'PyCrypto required') class TestCLIEncryptAES256(BaseTestCLI): listener_opts = ['--encrypt-method=hkdf-aes256-cbc', '--auth=S49HVbnJd3fBdDzdMVVw'] def test_encrypt(self): self.run_listener_client(self.listener_opts) if __name__ == '__main__': unittest.main() 2ping-4.1/tests/test_crc32.py000066400000000000000000000007031314147443500160720ustar00rootroot00000000000000#!/usr/bin/env python3 import unittest from twoping import crc32 import hmac class TestCRC32(unittest.TestCase): def test_crc32(self): c = crc32.new() c.update(b'Data to hash') self.assertEqual(c.digest(), b'\x44\x9e\x0a\x5c') def test_hmac(self): h = hmac.new(b'Secret key', b'Data to hash', crc32) self.assertEqual(h.digest(), b'\x3c\xe1\xb6\xb9') if __name__ == '__main__': unittest.main() 2ping-4.1/tests/test_monotonic_clock.py000066400000000000000000000010221314147443500203310ustar00rootroot00000000000000#!/usr/bin/env python3 import unittest from twoping import monotonic_clock clock = monotonic_clock.clock clock_info = monotonic_clock.get_clock_info('clock') class TestMonotonicClock(unittest.TestCase): def test_clock(self): self.assertEqual(type(clock()), float) @unittest.skipUnless(clock_info.monotonic, 'Monotonic clock required') def test_monotonic(self): time1 = clock() time2 = clock() self.assertGreaterEqual(time2, time1) if __name__ == '__main__': unittest.main() 2ping-4.1/tests/test_packets.py000066400000000000000000000451071314147443500166170ustar00rootroot00000000000000#!/usr/bin/env python3 import unittest from twoping import packets try: from Crypto.Cipher import AES has_aes = True except ImportError: has_aes = False class TestPacketsOpcodes(unittest.TestCase): def test_opcode_unknown(self): data = b'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 = b'' opcode = packets.OpcodeReplyRequested() opcode.load(data) self.assertEqual(opcode.id, 0x0001) self.assertEqual(opcode.dump(), data) def test_opcode_in_reply_to(self): data = 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 = 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 = 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 = 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 = 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 = 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 = b'\x00\x04\x01\x02\x03\x04' # The dump always includes a zeroed hash area data_out = 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 = b'\x12\x34\x56\x78' opcode = packets.OpcodeHostLatency() opcode.load(data) self.assertEqual(opcode.id, 0x0100) self.assertEqual(opcode.dump(), data) def test_opcode_encrypted(self): data = b'\x00\x01\xaa\xb1\xc0\x0f\x0f\x83\xd2\xc4x\xfe\xa1\xe2\x10by\xee"Rp\xf1\x93\xee\xe98G\xea\x11\xd1\xc9\x80=\xe3' opcode = packets.OpcodeEncrypted() opcode.load(data) self.assertEqual(opcode.id, 0x0200) self.assertEqual(opcode.dump(), data) def test_opcode_extended(self): data = 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 = b'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 = b'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 = b'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(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(), b'\x00\x05\x2a\x93\x7a\xa8\xc5\x32') def test_extended_monotonicclock_load(self): opcode = packets.ExtendedMonotonicClock() opcode.load(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(), b'\x24\x5f\x00\x05\x2a\x93\x7a\xa8\xc5\x32') def test_extended_random_load(self): random_data = b'\xf1\xfd\xf8\x9c\xe3\x9a\x87\x14' opcode = packets.ExtendedRandom() opcode.load(b'\x00\x03' + random_data) self.assertEqual(opcode.id, 0x2ff6ad68) self.assertTrue(opcode.is_hwrng) self.assertTrue(opcode.is_os) self.assertEqual(opcode.random_data, random_data) def test_extended_random_dump(self): random_data = 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(), b'\x00\x01' + random_data) def test_extended_batteries_load(self): opcode = packets.ExtendedBatteryLevels() opcode.load(b'\x00\x02\x00\x00\xff\xff\x00\x01\xce\xa3') self.assertEqual(opcode.id, 0x88a1f7c7) self.assertEqual(opcode.batteries[0], 65535) self.assertEqual(opcode.batteries[1], 52899) def test_extended_batteries_dump(self): opcode = packets.ExtendedBatteryLevels() opcode.batteries = { 0: 65535, 1: 52899, } self.assertEqual(opcode.id, 0x88a1f7c7) self.assertEqual(opcode.dump(), b'\x00\x02\x00\x00\xff\xff\x00\x01\xce\xa3') @unittest.skipUnless(has_aes, 'PyCrypto required') def test_encrypted_encrypt_decrypt(self): key = b'Secret key' iv = b'2\xf0J/\xb3x\xe3\xf3s+J\x8c\x02t\xca\x0e' minimal_packet_data = b'2P\xda\x0a\x0e\xa5[\xe2\x89\x1d\x00\x00\x00\x00\x00\x00' opcode = packets.OpcodeEncrypted() opcode.session = b'z\xe3\xcb\xdfeK\x86\x96' opcode.iv = iv opcode.method_index = 1 opcode.encrypt(minimal_packet_data, key) self.assertEqual(opcode.decrypt(key), minimal_packet_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 = b'\x00\x00\x00\x00\xa0\x01' self.assertEqual(packet.dump(), b'\x32\x50\x2d\xae\x00\x00\x00\x00\xa0\x01\x00\x00') def test_reference_2a(self): packet = packets.Packet() packet.message_id = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xb0\x01' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\x00\x00\x00\x00\xa0\x01' self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\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 = b'\x00\x00\x00\x00\xa0\x01' self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\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(), 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 = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\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(b'\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), 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 = b'\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 = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x03' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\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(), 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 = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\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(b'\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), 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 = b'\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 = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x01') self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x03' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\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(), 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 = b'\x00\x00\x00\x00\xa0\x01' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x02' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x03' packet.opcodes[packets.OpcodeReplyRequested.id] = packets.OpcodeReplyRequested() self.assertEqual(packet.dump(), 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 = b'\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 = b'\x00\x00\x00\x00\xa0\x03' self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x04' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\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(), 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 = b'\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(b'\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x02') self.assertEqual(packet.dump(), 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 = b'\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 = b'\x00\x00\x00\x00\xa0\x0a' packet.opcodes[packets.OpcodeInvestigationSeen.id] = packets.OpcodeInvestigationSeen() packet.opcodes[packets.OpcodeInvestigationSeen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x01') packet.opcodes[packets.OpcodeInvestigationUnseen.id] = packets.OpcodeInvestigationUnseen() packet.opcodes[packets.OpcodeInvestigationUnseen.id].message_ids.append(b'\x00\x00\x00\x00\xa0\x02') packet.opcodes[packets.OpcodeInvestigate.id] = packets.OpcodeInvestigate() packet.opcodes[packets.OpcodeInvestigate.id].message_ids.append(b'\x00\x00\x00\x00\xb0\x02') self.assertEqual(packet.dump(), 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 = b'\x00\x00\x00\x00\xa0\x0b' packet.opcodes[packets.OpcodeInReplyTo.id] = packets.OpcodeInReplyTo() packet.opcodes[packets.OpcodeInReplyTo.id].message_id = b'\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(b'\x00\x00\x00\x00\xb0\x02') self.assertEqual(packet.dump(), 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-4.1/tests/test_utils.py000066400000000000000000000031211314147443500163130ustar00rootroot00000000000000#!/usr/bin/env python3 import unittest from twoping import utils class TestUtils(unittest.TestCase): def test_twoping_checksum_hello(self): data = b'Hello World' self.assertEqual(utils.twoping_checksum(data), 0xae31) def test_twoping_checksum_junk(self): data = b'E\xc6\xca\x92\x10\x0e\xb1\xcf\x98\x88\x0bB\xc0\xf8X\xac\xd9\x81v\xc0E\x8co\x04\x9c\x0e\x93\xb3' + \ b'I=8m\xb7\xd5\x86\xa7f,2\x15\xd9\x7f\xad>\x1a\xe09\x89J\xdd\x0b(\xa0\x07aGT\xc6P\x04-(NH<@\xc7\xdb' + \ b'\x82\xfa\x0b\xab7n#^\x80\xab\xb9\xe5\x05\x14\xb1\xfcrT7\x8af\xf2Q\xa1w\xda\xd5\x88\xd58C10\xe7' + \ 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' + b'\x08\x00\x01\x00\x00\x00\x00\xa0\x01\x00\x08\x00\x01\x00\x00\x00\x00\xa0\x02\x00\x08\x00\x01' + 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 = bytes([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-4.1/twoping/000077500000000000000000000000001314147443500140725ustar00rootroot000000000000002ping-4.1/twoping/__init__.py000066400000000000000000000014501314147443500162030ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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__ = '4.1' 2ping-4.1/twoping/args.py000066400000000000000000000241051314147443500154020ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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. 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 ({})'.format(__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( '--all-interfaces', action='store_true', help=_('listen on all possible interface IPs'), ) 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', 'hmac-sha512'], help=_('HMAC authentication digest'), metavar='DIGEST', ) parser.add_argument( '--debug', action='store_true', help=_('debug mode'), ) parser.add_argument( '--encrypt', type=str, help=_('Encryption key'), metavar='KEY', ) parser.add_argument( '--encrypt-method', type=str, default='hkdf-aes256-cbc', choices=['hkdf-aes256-cbc'], help=_('Encryption method'), metavar='METHOD', ) 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( '-{}'.format(opt), action='store_true', dest='ignored_{}'.format(opt), help=argparse.SUPPRESS, ) for opt in 'F|Q|S|t|T|M|W'.split('|'): parser.add_argument( '-{}'.format(opt), type=str, default=None, dest='ignored_{}'.format(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) and (not args.deadline): 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_bytes = b'' for i in range(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_bytes += bytes([b]) else: args.pattern_bytes = b'\x00' hmac_id_map = { 'hmac-md5': 1, 'hmac-sha1': 2, 'hmac-sha256': 3, 'hmac-crc32': 4, 'hmac-sha512': 5, } args.auth_digest_index = hmac_id_map[args.auth_digest] if args.encrypt: try: from Crypto.Cipher import AES except ImportError: parser.error(_('Python crypto module required for encryption')) encrypt_id_map = { 'hkdf-aes256-cbc': 1, } args.encrypt_method_index = encrypt_id_map[args.encrypt_method] if args.debug: args.verbose = True return args 2ping-4.1/twoping/best_poller.py000077500000000000000000000110271314147443500167620ustar00rootroot00000000000000#!/usr/bin/env python3 # 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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. 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: {}'.format(' '.join([p.poller_type for p in available]))) poller = best_poller() print('Best poller: {}'.format(poller.poller_type)) 2ping-4.1/twoping/cli.py000077500000000000000000001511071314147443500152230ustar00rootroot00000000000000#!/usr/bin/env python3 # 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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. 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, npack, nunpack, 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 try: import netifaces has_netifaces = True except ImportError: has_netifaces = False assert(sys.version_info > (3, 4)) version_string = '2ping {} - {}'.format(__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 10 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 # Current session of a 5-tuple for encrypted sessions self.encrypted_sessions = {} # 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 self.session = b'' 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): print(*args, **kwargs) 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('{}: {}'.format(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('{}: {}'.format(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: {}'.format(repr(socket_address))) self.print_debug('Peer address: {}'.format(repr(peer_address))) # 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 if peer_tuple not in sock_class.encrypted_sessions: sock_class.encrypted_sessions[peer_tuple] = None # Load/parse the packet. packet_in = packets.Packet() packet_in.load(data) if self.args.verbose: self.print_out('RECV: {}'.format(repr(packet_in))) # Decrypt packet if needed. if self.args.encrypt: if packets.OpcodeEncrypted.id not in packet_in.opcodes: self.errors_received += 1 sock_class.errors_received += 1 self.print_out(_('Encryption required but not provided by {address}').format(address=peer_address[0])) return if packet_in.opcodes[packets.OpcodeEncrypted.id].method_index != self.args.encrypt_method_index: self.errors_received += 1 sock_class.errors_received += 1 self.print_out( _('Encryption method mismatch from {address} (expected {expected}, got {got})').format( address=peer_address[0], expected=self.args.encrypt_method_index, got=packet_in.opcodes[packets.OpcodeEncrypted.id].method_index, ) ) return data = packet_in.opcodes[packets.OpcodeEncrypted.id].decrypt(self.args.encrypt.encode('UTF-8')) encrypted_packet_in = packet_in packet_in = packets.Packet() packet_in.load(data) if sock_class.encrypted_sessions[peer_tuple] is None: sock_class.encrypted_sessions[peer_tuple] = ( time_begin, [], encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].session, ) if encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].session != sock_class.encrypted_sessions[peer_tuple][2]: self.errors_received += 1 sock_class.errors_received += 1 self.print_out( _('Encryption session mismatch from {address} (expected {expected}, got {got})').format( address=peer_address[0], expected=repr(sock_class.encrypted_sessions[peer_tuple][2]), got=repr(encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].session), ) ) return if encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].iv in sock_class.encrypted_sessions[peer_tuple][1]: self.errors_received += 1 sock_class.errors_received += 1 self.print_out( _('Repeated IV {iv} from {address}, discarding').format( iv=repr(encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].iv), address=peer_address[0], ) ) return sock_class.encrypted_sessions[peer_tuple][1].append(encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].iv) if self.args.verbose: self.print_out('DECR: {}'.format(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 = self.args.auth.encode('UTF-8') test_data = bytearray(data) test_data[2:4] = b'\x00\x00' test_data[test_begin:(test_begin+test_length)] = bytes(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 = nunpack(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 = packet_in.opcodes[packets.OpcodeExtended.id].segments[packets.ExtendedNotice.id].text 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 = nunpack(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][nunpack(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 nunpack(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() # If enabled, encrypt the packet and wrap it in a stub packet. if self.args.encrypt: encrypted_packet = packets.Packet() encrypted_packet.opcodes[packets.OpcodeEncrypted.id] = packets.OpcodeEncrypted() encrypted_packet.opcodes[packets.OpcodeEncrypted.id].method_index = self.args.encrypt_method_index encrypted_packet.opcodes[packets.OpcodeEncrypted.id].session = encrypted_packet_in.opcodes[packets.OpcodeEncrypted.id].session encrypted_packet.opcodes[packets.OpcodeEncrypted.id].encrypt(dump_out, self.args.encrypt.encode('UTF-8')) sock_out = encrypted_packet.dump() else: sock_out = dump_out # Send the packet. self.sock_sendto(sock_class, sock_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][nunpack(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 = nunpack(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: if self.args.encrypt: self.print_out('SEND (encrypted): {}'.format(repr(packet_out_examine))) else: self.print_out('SEND: {}'.format(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 = nunpack(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 = nunpack(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.all_interfaces: if not has_netifaces: raise socket.error('All interface addresses not available; please install netifaces') addrs = set() for iface in netifaces.interfaces(): iface_addrs = netifaces.ifaddresses(iface) if (self.args.ipv4 or (not self.args.ipv4 and not self.args.ipv6)) and (netifaces.AF_INET in iface_addrs): addrs.update([f['addr'] for f in iface_addrs[netifaces.AF_INET] if 'addr' in f]) if self.has_ipv6 and (self.args.ipv6 or (not self.args.ipv4 and not self.args.ipv6)) and (netifaces.AF_INET6 in iface_addrs): addrs.update([f['addr'] for f in iface_addrs[netifaces.AF_INET6] if 'addr' in f]) interface_addresses = list(addrs) elif 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: {}'.format(lookup)) try: res = dns.resolver.query('_2ping._udp.{}'.format(lookup), 'srv') except dns.exception.DNSException as e: raise socket.error('{}: {}'.format(lookup, repr(e))) for rdata in res: self.print_debug('SRV result for {}: {}'.format( 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('{}: No SRV results'.format(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] = '{}: {}'.format(hostname, eargs[0]) else: eargs[1] = '{}: {}'.format(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 sock_class.session = bytes([random_sys.randint(0, 255) for x in range(8)]) 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 packet = bytearray(packet) 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] = b'\x00\x00' packet[2:4] = fuzz_bytearray(bytearray(npack(twoping_checksum(packet), 2)), fuzz_fraction / 10.0) return bytes(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() # If enabled, encrypt the packet and wrap it in a stub packet. if self.args.encrypt: encrypted_packet = packets.Packet() encrypted_packet.opcodes[packets.OpcodeEncrypted.id] = packets.OpcodeEncrypted() encrypted_packet.opcodes[packets.OpcodeEncrypted.id].method_index = self.args.encrypt_method_index encrypted_packet.opcodes[packets.OpcodeEncrypted.id].session = sock_class.session encrypted_packet.opcodes[packets.OpcodeEncrypted.id].encrypt(dump_out, self.args.encrypt.encode('UTF-8')) sock_out = encrypted_packet.dump() else: sock_out = dump_out now = clock() self.sock_sendto(sock_class, sock_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][nunpack(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: if self.args.encrypt: self.print_out('SEND (encrypted): {}'.format(repr(packet_out_examine))) else: self.print_out('SEND: {}'.format(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 = '{}{}{}'.format(rest, suffix, out) break p = rest % div rest = int(rest / div) if p > 0: if(out): out = ' ' + out out = '{}{}{}'.format(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('--- {} ---'.format(_('{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: {}, value: {:f}'.format(clock_info, clock())) self.print_debug('Poller: {}'.format(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 = bytes([random_sys.randint(0, 255) for x in range(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 = self.args.auth.encode('UTF-8') packet_out.opcodes[packets.OpcodeHMAC.id].digest_index = self.args.auth_digest_index packet_out.padding_pattern = self.args.pattern_bytes packet_out.min_length = self.args.min_packet_size packet_out.max_length = self.args.max_packet_size if self.args.encrypt: packet_out.align_length = 16 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 tuple(sock_class.sent_messages.keys()): for message_id_int in tuple(sock_class.sent_messages[peer_tuple].keys()): if now > (sock_class.sent_messages[peer_tuple][message_id_int][0] + 600.0): del(sock_class.sent_messages[peer_tuple][message_id_int]) self.print_debug('Cleanup: Removed sent_messages {} {}'.format(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 {}'.format(repr(peer_tuple))) for peer_tuple in tuple(sock_class.seen_messages.keys()): for message_id_int in tuple(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 {} {}'.format(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 {}'.format(repr(peer_tuple))) for peer_tuple in tuple(sock_class.courtesy_messages.keys()): for message_id_int in tuple(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 {} {}'.format(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 {}'.format(repr(peer_tuple))) for peer_tuple in tuple(sock_class.encrypted_sessions.keys()): if now > (sock_class.encrypted_sessions[peer_tuple][0] + 600.0): del(sock_class.encrypted_sessions[peer_tuple]) self.print_debug('Cleanup: Removed encrypted_sessions {}'.format(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: {}'.format(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 range(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: {} ({})'.format((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-4.1/twoping/crc32.py000077500000000000000000000044101314147443500153620ustar00rootroot00000000000000#!/usr/bin/env python3 # 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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. 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(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('{}\t{}'.format(c.hexdigest(), file)) else: print(c.hexdigest()) 2ping-4.1/twoping/monotonic_clock.py000077500000000000000000000053561314147443500176400ustar00rootroot00000000000000#!/usr/bin/env python3 # Monotonic clock # Copyright (C) 2010-2017 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. import sys import time import types class BuiltinMonotonicCounter(): clock = None def __init__(self): # Unix + CLOCK_MONOTONIC_RAW if hasattr(time, 'clock_gettime') and hasattr(time, 'CLOCK_MONOTONIC_RAW'): self.info = time.get_clock_info('monotonic') self.info.implementation = 'clock_gettime(CLOCK_MONOTONIC_RAW)' def _clock(): return time.clock_gettime(time.CLOCK_MONOTONIC_RAW) self.clock = _clock self.clock() return # All other platforms for mode in ('perf_counter', 'monotonic', 'clock'): try: self.info = time.get_clock_info(mode) except ValueError: continue if not self.info.monotonic: continue if self.info.resolution > 1e-06: continue self.clock = getattr(time, mode) self.clock() return raise NotImplementedError() class SystemCounter(): info = types.SimpleNamespace( adjustable=True, implementation='time()', monotonic=False, resolution=1.0, ) def clock(self): return time.time() try: _counter = BuiltinMonotonicCounter() except RuntimeError: # Fall back to time.time() as a last resort _counter = SystemCounter() clock = _counter.clock monotonic = _counter.clock def get_clock_info(clock): if clock not in ('clock', 'monotonic'): raise ValueError('unknown clock') return _counter.info if __name__ == '__main__': if len(sys.argv) > 1: for _ in range(int(sys.argv[1])): print('{:.50f}'.format(clock())) else: print(get_clock_info('clock')) previous = 0.0 for _ in range(10): current = clock() if current >= previous: print('{:.50f}'.format(current)) else: print('{:.50f} (!!!)'.format(current)) 2ping-4.1/twoping/packets.py000066400000000000000000000466241314147443500161120ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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. import random import hmac import time from . import crc32 from .utils import twoping_checksum, npack, nunpack from math import ceil import hashlib try: from Crypto.Cipher import AES has_aes = True except ImportError: has_aes = False class Extended(): id = None def __init__(self): self.data = b'' def __repr__(self): if self.id is None: return ''.format(len(self.data)) else: id_hex = ''.join(['{:02x}'.format(x) for x in npack(self.id, 4)]) return ''.format(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 = str() def __repr__(self): return ''.format(self.text) def load(self, data): self.text = data.decode('UTF-8') def dump(self, max_length=None): text_bytes = self.text.encode('UTF-8') if (max_length is not None) and (max_length < len(text_bytes)): return None return text_bytes class ExtendedVersion(ExtendedText): id = 0x3250564e def __repr__(self): return ''.format(self.text) class ExtendedNotice(ExtendedText): id = 0xa837b44e def __repr__(self): return ''.format(self.text) class ExtendedWallClock(Extended): id = 0x64f69319 def __init__(self): self.time_us = 0 def __repr__(self): return ''.format(time.strftime('%c', time.gmtime(self.time_us / 1000000.0))) def load(self, data): self.time_us = nunpack(data[0:8]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 8): return None return npack(self.time_us, 8) class ExtendedMonotonicClock(Extended): id = 0x771d8dfb def __init__(self): self.generation = 0 self.time_us = 0 def __repr__(self): return ''.format((self.time_us / 1000000.0), self.generation) def load(self, data): self.generation = nunpack(data[0:2]) self.time_us = nunpack(data[2:10]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 10): return None return npack(self.generation, 2) + npack(self.time_us, 8) class ExtendedRandom(Extended): id = 0x2ff6ad68 def __init__(self): self.is_hwrng = False self.is_os = False self.random_data = b'' def __repr__(self): return ''.format( repr(self.random_data), len(self.random_data), repr(self.is_hwrng), repr(self.is_os), ) def load(self, data): flags = nunpack(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 npack(flags, 2) + random_data class ExtendedBatteryLevels(Extended): id = 0x88a1f7c7 def __init__(self): self.batteries = {} def __repr__(self): return ''.format( len(self.batteries), ', '.join( ['{}: {:0.03f}%'.format(x, (self.batteries[x] / 65535.0 * 100.0)) for x in sorted(self.batteries)] ), ) def load(self, data): self.batteries = {} pos = 2 for i in range(nunpack(data[0:2])): battery_id = nunpack(data[pos:pos+2]) battery_level = nunpack(data[pos+2:pos+4]) self.batteries[battery_id] = battery_level pos += 4 def dump(self, max_length=None): if (max_length is not None): if (max_length < 6): return None batteries = {} for i in sorted(self.batteries.keys())[0:int((max_length - 2) / 4)]: batteries[i] = self.batteries[i] else: batteries = self.batteries out = npack(len(batteries), 2) for i in batteries: out += npack(i, 2) out += npack(batteries[i], 2) return out class Opcode(): id = None def __init__(self): self.data = b'' def __repr__(self): if self.id is None: return ''.format(len(self.data)) else: id_hex = ''.join(['{:02x}'.format(x) for x in npack(self.id, 2)]) return ''.format(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 b'' class OpcodeInReplyTo(Opcode): id = 0x0002 def __init__(self): self.message_id = b'' def __repr__(self): message_id_hex = ''.join(['{:02x}'.format(x) for x in self.message_id]) return ''.format(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 ''.format(self.rtt_us) def load(self, data): self.rtt_us = nunpack(data[0:4]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 4): return None return npack(self.rtt_us, 4) class OpcodeMessageIDList(Opcode): def __init__(self): self.message_ids = [] def __repr__(self): ids = ['0x{}'.format(''.join(['{:02x}'.format(x) for x in y]) for y in self.message_ids)] return ''.format(', '.join(ids), len(self.message_ids)) def load(self, data): self.message_ids = [] pos = 2 for i in range(nunpack(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 = npack(len(output_ids), 2) for i in output_ids: out += i return out class OpcodeInvestigationSeen(OpcodeMessageIDList): id = 0x0008 def __repr__(self): ids = ['0x{}'.format(''.join(['{:02x}'.format(x) for x in y])) for y in self.message_ids] return ''.format(', '.join(ids), len(self.message_ids)) class OpcodeInvestigationUnseen(OpcodeMessageIDList): id = 0x0010 def __repr__(self): ids = ['0x{}'.format(''.join(['{:02x}'.format(x) for x in y])) for y in self.message_ids] return ''.format(', '.join(ids), len(self.message_ids)) class OpcodeInvestigate(OpcodeMessageIDList): id = 0x0020 def __repr__(self): ids = ['0x{}'.format(''.join(['{:02x}'.format(x) for x in y])) for y in self.message_ids] return ''.format(', '.join(ids), len(self.message_ids)) class OpcodeCourtesyExpiration(OpcodeMessageIDList): id = 0x0040 def __repr__(self): ids = ['0x{}'.format(''.join(['{:02x}'.format(x) for x in y])) for y in self.message_ids] return ''.format(', '.join(ids), len(self.message_ids)) class OpcodeHMAC(Opcode): id = 0x0080 def __init__(self): self.key = b'' self.digest_index = None self.hash = b'' self.digest_map = { 1: (hashlib.md5, 16, 'HMAC-MD5'), 2: (hashlib.sha1, 20, 'HMAC-SHA1'), 3: (hashlib.sha256, 32, 'HMAC-SHA256'), 4: (crc32, 4, 'HMAC-CRC32'), 5: (hashlib.sha512, 64, 'HMAC-SHA512'), } def __repr__(self): if self.digest_index is not None: return '<{}: 0x{}>'.format( self.digest_map[self.digest_index][2], ''.join(['{:02x}'.format(x) for x in self.hash]), ) return '' def load(self, data): self.digest_index = nunpack(data[0:2]) self.hash = data[2:] def dump(self, max_length=None): if self.digest_index is not None: (hasher, size, hasher_name) = self.digest_map[self.digest_index] return npack(self.digest_index, 2) + bytes(size) return None class OpcodeHostLatency(Opcode): id = 0x0100 def __init__(self): self.delay_us = 0 def __repr__(self): return ''.format(self.delay_us) def load(self, data): self.delay_us = nunpack(data[0:4]) def dump(self, max_length=None): if (max_length is not None) and (max_length < 4): return None return npack(self.delay_us, 4) class OpcodeEncrypted(Opcode): id = 0x0200 def __init__(self): self.hkdf_info = b'\xd8\x89\xac\x93\xac\xeb\xa1\xf3\x98\xd0\xc6\x9b\xc8\xc6\xa7\xaa' self.method_index = None self.encrypted = b'' self.session = b'' self.iv = None self.method_map = { 1: ('HKDF-AES256-CBC',), } def __repr__(self): if self.method_index is not None: return '<{} (Session {}, IV {}, {} bytes)>'.format( self.method_map[self.method_index][0], repr(self.session), repr(self.iv), len(self.encrypted), ) return '' def load(self, data): self.method_index = nunpack(data[0:2]) if self.method_index == 1: self.session = data[2:10] self.iv = data[10:26] self.encrypted = data[26:] else: self.encrypted = data[2:] def dump(self, max_length=None): if self.method_index == 1: return npack(self.method_index, 2) + self.session + self.iv + self.encrypted return None def encrypt(self, unencrypted, key): if not has_aes: return None if self.method_index is None: return None if self.method_index == 1: if self.iv is None: self.iv = bytes([random.randint(0, 255) for x in range(16)]) aeskey = self.hkdf(32, key, salt=self.iv, info=(self.hkdf_info + self.session), digestmod=hashlib.sha256) aes_e = AES.new(aeskey, AES.MODE_CBC, self.iv) self.encrypted = aes_e.encrypt(unencrypted) else: return None def decrypt(self, key): if not has_aes: return None if self.method_index is None: return None if self.method_index == 1: aeskey = self.hkdf(32, key, salt=self.iv, info=(self.hkdf_info + self.session), digestmod=hashlib.sha256) aes_d = AES.new(aeskey, AES.MODE_CBC, self.iv) return aes_d.decrypt(self.encrypted) def hkdf(self, length, ikm, salt=b'', info=b'', digestmod=None): if digestmod is None: digestmod = hashlib.sha256 prk = hmac.new(salt, ikm, digestmod).digest() hash_len = len(prk) t = b'' okm = b'' for i in range(ceil(length / hash_len)): t = hmac.new(prk, t + info + bytes([1+i]), digestmod).digest() okm += t return okm[:length] class OpcodeExtended(Opcode): id = 0x8000 def __init__(self): self.segments = {} self.segment_data_positions = {} def __repr__(self): return ''.format(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, ExtendedBatteryLevels, ) while pos < len(data): flag = nunpack(data[pos:pos+4]) pos += 4 segment_data_length = nunpack(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 = b'' 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 += npack(segment.id, 4) pos += 4 out += npack(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 ''.format( ''.join(['{:02x}'.format(x) for x in self.message_id]), repr(sorted(self.opcodes.values(), key=lambda x: x.id)) ) def __init__(self): self.message_id = b'' self.opcodes = {} self.min_length = 0 self.max_length = 1024 self.align_length = 0 self.padding_pattern = b'\x00' self.opcode_data_positions = {} def load(self, data): magic_number = data[0:2] if magic_number != b'\x32\x50': raise Exception('Invalid magic number') checksum = nunpack(data[2:4]) if checksum: if twoping_checksum(data[0:2] + b'\x00\x00' + data[4:]) != checksum: raise Exception('Invalid checksum') self.message_id = data[4:10] opcode_flags = nunpack(data[10:12]) self.opcodes = {} pos = 12 known_opcodes = ( OpcodeReplyRequested, OpcodeInReplyTo, OpcodeRTTEnclosed, OpcodeInvestigationSeen, OpcodeInvestigationUnseen, OpcodeInvestigate, OpcodeCourtesyExpiration, OpcodeHMAC, OpcodeHostLatency, OpcodeEncrypted, OpcodeExtended, ) for flag in (2 ** x for x in range(16)): if not opcode_flags & flag: continue opcode_data_length = nunpack(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 = bytes([random.randint(0, 255) for x in range(6)]) opcode_datas = {} packet_length = 12 for flag in ( OpcodeEncrypted.id, 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 = b'' 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 += npack(res_len, 2) opcode_data += res packet_length += res_len + 2 out = bytearray(b'\x32\x50\x00\x00' + self.message_id + npack(opcode_flags, 2) + opcode_data) target_length = len(out) if len(out) < self.min_length: target_length = self.min_length if self.align_length and (target_length % self.align_length): target_length += self.align_length - (target_length % self.align_length) if len(out) < target_length: target_padding = target_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] = npack(twoping_checksum(out), 2) return bytes(out) def calculate_hash(self, opcode, payload): (hasher, size, hasher_name) = opcode.digest_map[opcode.digest_index] return hmac.new(opcode.key, payload, hasher).digest() 2ping-4.1/twoping/utils.py000066400000000000000000000037651314147443500156170ustar00rootroot00000000000000# 2ping - A bi-directional ping utility # Copyright (C) 2010-2017 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. import platform import gettext _ = gettext.translation('2ping', fallback=True).gettext _pl = gettext.translation('2ping', fallback=True).ngettext def twoping_checksum(d): checksum = 0 for i in range(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 npack(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 bytes(out) def nunpack(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 += ' ({})'.format(linux_distribution[0]) except: pass out += ' {}'.format(platform.machine()) return out 2ping-4.1/wireshark/000077500000000000000000000000001314147443500144025ustar00rootroot000000000000002ping-4.1/wireshark/2ping.lua000066400000000000000000000602111314147443500161240ustar00rootroot00000000000000local twoping = Proto("2ping","2ping Protocol") local mac_digests = { [0] = "Private", [1] = "HMAC-MD5", [2] = "HMAC-SHA1", [3] = "HMAC-SHA256", [4] = "HMAC-CRC32", [5] = "HMAC-SHA512" } local encrypted_methods = { [0] = "Private", [1] = "HKDF-AES256-CBC" } local extended_ids = { [0x3250564e] = "Program version", [0x2ff6ad68] = "Random data", [0x64f69319] = "Wall clock", [0x771d8dfb] = "Monotonic clock", [0x88a1f7c7] = "Battery levels", [0xa837b44e] = "Notice text" } local pf_magic_number = ProtoField.new ("Magic number", "2ping.magic_number", ftypes.UINT16, nil, base.HEX) local pf_checksum = ProtoField.new ("Checksum", "2ping.checksum", ftypes.UINT16, nil, base.HEX) local pf_message_id = ProtoField.new ("Message ID", "2ping.message_id", ftypes.ETHER) local pf_opcode_flags = ProtoField.new ("Opcode flags", "2ping.opcode_flags", ftypes.UINT16, nil, base.HEX) local pf_opcode_flag_reply_requested = ProtoField.bool ("2ping.opcode_flags.reply_requested", "Reply requested", 16, nil, 0x0001) local pf_opcode_flag_in_reply_to = ProtoField.bool ("2ping.opcode_flags.in_reply_to", "In reply to", 16, nil, 0x0002) local pf_opcode_flag_rtt_enclosed = ProtoField.bool ("2ping.opcode_flags.rtt_enclosed", "RTT enclosed", 16, nil, 0x0004) local pf_opcode_flag_investigation_replied = ProtoField.bool ("2ping.opcode_flags.investigation_replied", "Investigation (replied)", 16, nil, 0x0008) local pf_opcode_flag_investigation_lost = ProtoField.bool ("2ping.opcode_flags.investigation_lost", "Investigation (lost)", 16, nil, 0x0010) local pf_opcode_flag_investigation_request = ProtoField.bool ("2ping.opcode_flags.investigation_request", "Investigation request", 16, nil, 0x0020) local pf_opcode_flag_courtesy_expiration = ProtoField.bool ("2ping.opcode_flags.courtesy_expiration", "Courtesy expiration", 16, nil, 0x0040) local pf_opcode_flag_mac = ProtoField.bool ("2ping.opcode_flags.mac", "Message authentication code", 16, nil, 0x0080) local pf_opcode_flag_host_latency = ProtoField.bool ("2ping.opcode_flags.host_latency", "Host processing latency", 16, nil, 0x0100) local pf_opcode_flag_encrypted = ProtoField.bool ("2ping.opcode_flags.encrypted", "Encrypted packet", 16, nil, 0x0200) local pf_opcode_flag_extended = ProtoField.bool ("2ping.opcode_flags.extended", "Extended segments", 16, nil, 0x8000) local pf_segment_length = ProtoField.new ("Length", "2ping.segment.length", ftypes.UINT16) local pf_reply_requested = ProtoField.new ("Reply requested", "2ping.reply_requested", ftypes.NONE) local pf_in_reply_to = ProtoField.new ("In reply to", "2ping.in_reply_to", ftypes.STRING) local pf_in_reply_to_message_id = ProtoField.new ("Message ID", "2ping.in_reply_to.message_id", ftypes.ETHER) local pf_rtt_enclosed = ProtoField.new ("RTT enclosed", "2ping.rtt_enclosed", ftypes.UINT32) local pf_rtt_enclosed_rtt = ProtoField.new ("RTT (μs)", "2ping.rtt_enclosed.rtt", ftypes.UINT32) local pf_investigation_replied = ProtoField.new ("Investigation (replied)", "2ping.investigation_replied", ftypes.STRING) local pf_investigation_replied_count = ProtoField.new ("Message ID count", "2ping.investigation_replied.count", ftypes.UINT16) local pf_investigation_replied_message_id = ProtoField.new ("Message ID", "2ping.investigation_replied.message_id", ftypes.ETHER) local pf_investigation_lost = ProtoField.new ("Investigation (lost)", "2ping.investigation_lost", ftypes.STRING) local pf_investigation_lost_count = ProtoField.new ("Message ID count", "2ping.investigation_lost.count", ftypes.UINT16) local pf_investigation_lost_message_id = ProtoField.new ("Message ID", "2ping.investigation_lost.message_id", ftypes.ETHER) local pf_investigation_request = ProtoField.new ("Investigation request", "2ping.investigation_request", ftypes.STRING) local pf_investigation_request_count = ProtoField.new ("Message ID count", "2ping.investigation_request.count", ftypes.UINT16) local pf_investigation_request_message_id = ProtoField.new ("Message ID", "2ping.investigation_request.message_id", ftypes.ETHER) local pf_courtesy_expiration = ProtoField.new ("Courtesy expiration", "2ping.courtesy_expiration", ftypes.STRING) local pf_courtesy_expiration_count = ProtoField.new ("Message ID count", "2ping.courtesy_expiration.count", ftypes.UINT16) local pf_courtesy_expiration_message_id = ProtoField.new ("Message ID", "2ping.courtesy_expiration.message_id", ftypes.ETHER) local pf_mac = ProtoField.new ("Message authentication code", "2ping.mac", ftypes.STRING) local pf_mac_digest = ProtoField.uint16 ("2ping.mac.digest", "Digest", base.DEC, mac_digests) local pf_mac_hash = ProtoField.new ("Hash", "2ping.mac.hash", ftypes.BYTES) local pf_host_latency = ProtoField.new ("Host processing latency", "2ping.host_latency", ftypes.UINT32) local pf_host_latency_delay = ProtoField.new ("Delay (μs)", "2ping.host_latency.delay", ftypes.UINT32) local pf_encrypted = ProtoField.new ("Encrypted packet", "2ping.encrypted", ftypes.STRING) local pf_encrypted_method = ProtoField.uint16 ("2ping.encrypted.method", "Method", base.DEC, encrypted_methods) local pf_encrypted_data = ProtoField.new ("Data", "2ping.encrypted.data", ftypes.BYTES) local pf_unknown = ProtoField.new ("Unknown", "2ping.unknown", ftypes.STRING) local pf_unknown_data = ProtoField.new ("Data", "2ping.unknown.data", ftypes.BYTES) local pf_extended = ProtoField.new ("Extended segments", "2ping.extended", ftypes.STRING) local pf_extended_count = ProtoField.new ("Count", "2ping.extended.count", ftypes.UINT16) local pf_extended_id = ProtoField.uint32 ("2ping.extended.id", "ID", base.HEX, extended_ids) local pf_version = ProtoField.new ("Program version", "2ping.version", ftypes.STRING) local pf_version_text = ProtoField.new ("Text", "2ping.version.text", ftypes.STRING) local pf_notice = ProtoField.new ("Notice text", "2ping.notice", ftypes.STRING) local pf_notice_text = ProtoField.new ("Text", "2ping.notice.text", ftypes.STRING) local pf_random = ProtoField.new ("Random data", "2ping.random", ftypes.STRING) local pf_random_data = ProtoField.new ("Data", "2ping.random.data", ftypes.BYTES) local pf_random_flag_hardware = ProtoField.bool ("2ping.random.hardware", "Hardware RNG", 16, nil, 0x0001) local pf_random_flag_os = ProtoField.bool ("2ping.random.os", "Operating system RNG", 16, nil, 0x0002) local pf_wallclock = ProtoField.new ("Wall clock", "2ping.wallclock", ftypes.ABSOLUTE_TIME) local pf_wallclock_time = ProtoField.new ("Time", "2ping.wallclock.time", ftypes.ABSOLUTE_TIME) local pf_monotonic = ProtoField.new ("Monotonic clock", "2ping.monotonic", ftypes.ABSOLUTE_TIME) local pf_monotonic_generation = ProtoField.new ("Generation", "2ping.monotonic.generation", ftypes.UINT16) local pf_monotonic_time = ProtoField.new ("Time", "2ping.monotonic.time", ftypes.ABSOLUTE_TIME) local pf_battery_levels = ProtoField.new ("Battery levels", "2ping.battery_levels", ftypes.STRING) local pf_battery_levels_count = ProtoField.new ("Battery count", "2ping.battery_levels.count", ftypes.UINT16) local pf_battery_levels_id = ProtoField.new ("Battery ID", "2ping.battery_levels.id", ftypes.UINT16) local pf_battery_levels_level = ProtoField.new ("Battery level", "2ping.battery_levels.level", ftypes.UINT16) local pf_padding = ProtoField.new ("Padding", "2ping.padding", ftypes.BYTES) twoping.fields = { pf_magic_number, pf_checksum, pf_message_id, pf_opcode_flags, pf_opcode_flag_reply_requested, pf_opcode_flag_in_reply_to, pf_opcode_flag_rtt_enclosed, pf_opcode_flag_investigation_replied, pf_opcode_flag_investigation_lost, pf_opcode_flag_investigation_request, pf_opcode_flag_courtesy_expiration, pf_opcode_flag_mac, pf_opcode_flag_host_latency, pf_opcode_flag_encrypted, pf_opcode_flag_extended, pf_segment_length, pf_reply_requested, pf_in_reply_to, pf_in_reply_to_message_id, pf_rtt_enclosed, pf_rtt_enclosed_rtt, pf_investigation_replied, pf_investigation_replied_count, pf_investigation_replied_message_id, pf_investigation_lost, pf_investigation_lost_count, pf_investigation_lost_message_id, pf_investigation_request, pf_investigation_request_count, pf_investigation_request_message_id, pf_courtesy_expiration, pf_courtesy_expiration_count, pf_courtesy_expiration_message_id, pf_mac, pf_mac_digest, pf_mac_hash, pf_host_latency, pf_host_latency_delay, pf_encrypted, pf_encrypted_method, pf_encrypted_data, pf_unknown, pf_unknown_data, pf_extended, pf_extended_count, pf_extended_segment, pf_extended_id, pf_version, pf_version_text, pf_notice, pf_notice_text, pf_random, pf_random_data, pf_random_flag_hardware, pf_random_flag_os, pf_wallclock, pf_wallclock_time, pf_monotonic, pf_monotonic_generation, pf_monotonic_time, pf_battery_levels, pf_battery_levels_count, pf_battery_levels_id, pf_battery_levels_level, pf_padding, } local reply_requested_field = Field.new("2ping.opcode_flags.reply_requested") local in_reply_to_field = Field.new("2ping.opcode_flags.in_reply_to") local rtt_enclosed_field = Field.new("2ping.opcode_flags.rtt_enclosed") local investigation_replied_field = Field.new("2ping.opcode_flags.investigation_replied") local investigation_lost_field = Field.new("2ping.opcode_flags.investigation_lost") local investigation_request_field = Field.new("2ping.opcode_flags.investigation_request") local courtesy_expiration_field = Field.new("2ping.opcode_flags.courtesy_expiration") local mac_field = Field.new("2ping.opcode_flags.mac") local host_latency_field = Field.new("2ping.opcode_flags.host_latency") local encrypted_field = Field.new("2ping.opcode_flags.encrypted") local extended_field = Field.new("2ping.opcode_flags.extended") local function shift_opcode_data(buf) local data_length = buf:range(0,2):uint() local opcode_range = buf:range(0,data_length+2) local data_range = opcode_range:range(0,0) if data_length > 0 then data_range = opcode_range:range(2) end local remaining_buf = buf:range(0,0) if data_length+2 < buf:len() then remaining_buf = buf:range(data_length+2) end return opcode_range, data_range, remaining_buf end local function process_common_message_id_list(tree, opcode_range, data_range, pf, pf_count, pf_message_id) local num_message_ids = data_range:range(0,2):uint() local local_tree = tree:add(pf, opcode_range) local_tree:add(pf_segment_length, opcode_range:range(0,2)) local_tree:add(pf_count, data_range:range(0,2)) local local_pos = 2 for i=1,num_message_ids,1 do local_tree:add(pf_message_id, data_range:range(local_pos,6)) local_pos = local_pos + 6 end if num_message_ids == 1 then local_tree:append_text(tostring(data_range:range(2):ether())) else local_tree:append_text(num_message_ids) local_tree:append_text(" IDs") end end function twoping.dissector(tvbuf,pktinfo,root) pktinfo.cols.protocol:set("2PING") local pktlen = tvbuf:reported_length_remaining() local tree = root:add(twoping, tvbuf:range(0,pktlen)) tree:add(pf_magic_number, tvbuf:range(0,2)) tree:add(pf_checksum, tvbuf:range(2,2)) tree:add(pf_message_id, tvbuf:range(4,6)) local flagrange = tvbuf:range(10,2) local flag_tree = tree:add(pf_opcode_flags, flagrange) local opcode_remaining = tvbuf:range(12) flag_tree:add(pf_opcode_flag_reply_requested, flagrange) if reply_requested_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local reply_requested_tree = tree:add(pf_reply_requested, opcode_range) reply_requested_tree:add(pf_segment_length, opcode_range:range(0,2)) end flag_tree:add(pf_opcode_flag_in_reply_to, flagrange) if in_reply_to_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local in_reply_to_tree = tree:add(pf_in_reply_to, opcode_range, tostring(data_range:ether())) in_reply_to_tree:add(pf_segment_length, opcode_range:range(0,2)) in_reply_to_tree:add(pf_in_reply_to_message_id, data_range) end flag_tree:add(pf_opcode_flag_rtt_enclosed, flagrange) if rtt_enclosed_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local rtt_enclosed_tree = tree:add(pf_rtt_enclosed, opcode_range, data_range:uint(), nil, "μs") rtt_enclosed_tree:add(pf_segment_length, opcode_range:range(0,2)) rtt_enclosed_tree:add(pf_rtt_enclosed_rtt, data_range) end flag_tree:add(pf_opcode_flag_investigation_replied, flagrange) if investigation_replied_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf process_common_message_id_list(tree, opcode_range, data_range, pf_investigation_replied, pf_investigation_replied_count, pf_investigation_replied_message_id) end flag_tree:add(pf_opcode_flag_investigation_lost, flagrange) if investigation_lost_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf process_common_message_id_list(tree, opcode_range, data_range, pf_investigation_lost, pf_investigation_lost_count, pf_investigation_lost_message_id) end flag_tree:add(pf_opcode_flag_investigation_request, flagrange) if investigation_request_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf process_common_message_id_list(tree, opcode_range, data_range, pf_investigation_request, pf_investigation_request_count, pf_investigation_request_message_id) end flag_tree:add(pf_opcode_flag_courtesy_expiration, flagrange) if courtesy_expiration_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf process_common_message_id_list(tree, opcode_range, data_range, pf_courtesy_expiration, pf_courtesy_expiration_count, pf_courtesy_expiration_message_id) end flag_tree:add(pf_opcode_flag_mac, flagrange) if mac_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local mac_tree = tree:add(pf_mac, opcode_range, tostring(data_range:range(2):bytes()):lower()) mac_tree:add(pf_segment_length, opcode_range:range(0,2)) mac_tree:add(pf_mac_digest, data_range:range(0,2)) mac_tree:add(pf_mac_hash, data_range:range(2)) end flag_tree:add(pf_opcode_flag_host_latency, flagrange) if host_latency_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local host_latency_tree = tree:add(pf_host_latency, opcode_range, data_range:uint(), nil, "μs") host_latency_tree:add(pf_segment_length, opcode_range:range(0,2)) host_latency_tree:add(pf_host_latency_delay, data_range) end flag_tree:add(pf_opcode_flag_encrypted, flagrange) if encrypted_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local encrypted_tree = tree:add(pf_encrypted, opcode_range, tostring(data_range:range(2):bytes()):lower()) encrypted_tree:add(pf_segment_length, opcode_range:range(0,2)) encrypted_tree:add(pf_encrypted_method, data_range:range(0,2)) encrypted_tree:add(pf_encrypted_data, data_range:range(2)) end for bitpos=5,1,-1 do if flagrange:bitfield(bitpos,1) == 1 then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local unknown_tree = tree:add(pf_unknown, opcode_range, tostring(data_range:bytes()):lower(), nil, data_range:len(), "bytes") unknown_tree:add(pf_segment_length, opcode_range:range(0,2)) unknown_tree:add(pf_unknown_data, data_range) end end flag_tree:add(pf_opcode_flag_extended, flagrange) if extended_field()() then local opcode_range, data_range, remaining_buf = shift_opcode_data(opcode_remaining) opcode_remaining = remaining_buf local extended_tree = tree:add(pf_extended, opcode_range) extended_tree:add(pf_segment_length, opcode_range:range(0,2)) local extended_range = data_range local num_extended_segments = 0 while( extended_range:len() > 0 ) do num_extended_segments = num_extended_segments + 1 local id_range = extended_range:range(0,4) local length_range = extended_range:range(4,2) local extended_id = id_range:uint() local extended_length = length_range:uint() local segment_range = extended_range:range(0,extended_length+6) local segment_data_range = segment_range:range(6) if extended_range:len() <= extended_length+6 then extended_range = extended_range:range(0,0) else extended_range = extended_range:range(extended_length+6) end if extended_id == 0x3250564e then local version_text = segment_data_range:string() local extended_segment_tree = tree:add(pf_version, segment_range, version_text) extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_version_text, segment_data_range) elseif extended_id == 0xa837b44e then local notice_text = segment_data_range:string() local extended_segment_tree = tree:add(pf_notice, segment_range, notice_text) extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_notice_text, segment_data_range) elseif extended_id == 0x2ff6ad68 then local extended_segment_tree = tree:add(pf_random, segment_range, tostring(segment_data_range:range(2):bytes()):lower(), nil, segment_data_range:len()-2, "bytes") extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_random_flag_hardware, segment_data_range:range(0,2)) extended_segment_tree:add(pf_random_flag_os, segment_data_range:range(0,2)) extended_segment_tree:add(pf_random_data, segment_data_range:range(2)) elseif extended_id == 0x64f69319 then local usecs = segment_data_range:range(0,8):uint64() local secs = (usecs / 1000000):tonumber() local nsecs = (usecs % 1000000):tonumber() * 1000 local nstime = NSTime.new(secs, nsecs) local extended_segment_tree = tree:add(pf_wallclock, segment_range, nstime) extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_wallclock_time, segment_data_range:range(0,8), nstime) elseif extended_id == 0x771d8dfb then local usecs = segment_data_range:range(2,8):uint64() local secs = (usecs / 1000000):tonumber() local nsecs = (usecs % 1000000):tonumber() * 1000 local nstime = NSTime.new(secs, nsecs) local extended_segment_tree = tree:add(pf_monotonic, segment_range, nstime) extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_monotonic_generation, segment_data_range:range(0,2)) extended_segment_tree:add(pf_monotonic_time, segment_data_range:range(2,8), nstime) elseif extended_id == 0x88a1f7c7 then local num_batteries = segment_data_range:range(0,2):uint() local extended_segment_tree = tree:add(pf_battery_levels, segment_range, "") extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_battery_levels_count, segment_data_range:range(0,2)) local local_pos = 2 for i=1,num_batteries,1 do extended_segment_tree:add(pf_battery_levels_id, segment_data_range:range(local_pos,2)) extended_segment_tree:add(pf_battery_levels_level, segment_data_range:range(local_pos+2,2)) local_pos = local_pos + 4 end if num_batteries == 1 then extended_segment_tree:append_text(tostring(segment_data_range:range(4,2):uint())) else extended_segment_tree:append_text(num_batteries) extended_segment_tree:append_text(" batteries") end else local extended_segment_tree = tree:add(pf_unknown, segment_range, tostring(segment_data_range:bytes()):lower(), nil, segment_data_range:len(), "bytes") extended_segment_tree:add(pf_extended_id, id_range) extended_segment_tree:add(pf_segment_length, length_range) extended_segment_tree:add(pf_unknown_data, segment_data_range) end end local segments_count_tree = extended_tree:add(pf_extended_count, data_range, num_extended_segments) segments_count_tree:set_generated() extended_tree:append_text(num_extended_segments) if num_extended_segments == 1 then extended_tree:append_text(" segment") else extended_tree:append_text(" segments") end end if opcode_remaining:len() > 0 then tree:add(pf_padding, opcode_remaining) end local info_append = "" if reply_requested_field()() or in_reply_to_field()() then if reply_requested_field()() and in_reply_to_field()() then info_append = info_append .. " [RR,IRT]" elseif reply_requested_field()() then info_append = info_append .. " [RR]" elseif in_reply_to_field()() then info_append = info_append .. " [IRT]" end end if investigation_request_field()() or investigation_replied_field()() or investigation_lost_field()() then local inv_append = " [" local inv_printed = false if investigation_request_field()() then if inv_printed then inv_append = inv_append .. "," end inv_append = inv_append .. "?" inv_printed = true end if investigation_replied_field()() then if inv_printed then inv_append = inv_append .. "," end inv_append = inv_append .. ">" inv_printed = true end if investigation_lost_field()() then if inv_printed then inv_append = inv_append .. "," end inv_append = inv_append .. "<" inv_printed = true end inv_append = inv_append .. "]" info_append = info_append .. inv_append end info_append = info_append .. " ID=" .. tostring(tvbuf:range(4,6):ether()) pktinfo.cols.info:append(info_append) end local function heur_dissect_twoping(tvbuf,pktinfo,root) if tvbuf:len() < 12 then return false end if tvbuf:range(0,2):uint() ~= 0x3250 then return false end twoping.dissector(tvbuf,pktinfo,root) pktinfo.conversation = twoping return true end udp_table = DissectorTable.get("udp.port") udp_table:add(15998,twoping) twoping:register_heuristic("udp",heur_dissect_twoping) 2ping-4.1/wireshark/2ping.pcap000066400000000000000000000757161314147443500163060ustar00rootroot00000000000000òM+JXkE.@@  >~$2P Àp_RA{k/h'IJj6dC1>w  հ@+7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64M+JXmE/@@  >~$2P {٣;G{\X3bvk/hLUjAj ɆdC1>w b?7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64M+JXoEǀ0@@  >~$2P};(;{٣68\I߮'_}Ek/hM!\n}P"dC1>w  հ@7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64N+JXnE@@k  >~$2PeFZC`^YWqsfk/hGk!u⁤ZdC1M\w  հOE7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64N+JXpEÀ@@c  >~$2PrC7jgZ{٣88\ ?Ok/h}kӬlP~ldC1M^w b7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64O+JX[rE@@  >~$2P7 UKΠ]]'РߠMk/hKۭ!!WdC1\w  հ^7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64P+JXxE@@"  >~$2PR7T/pz ,k/hC>kσ):nлdC1kmw  հmԦ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64P+JXzE@@!  >~$2PCXr$R7T/pVh!J{˚UYk/hE{iЯZ W,dC1kw b7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64R+JXE@@[  >~$2PBB0gmY#&Nk/hR?,p4SdC1xw  հb7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64R+JX,E@@Z  >~$2P=NlB0gXY9}Z Jk/h8 S* JdC1{ow bҠ7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64S+JX&EZ@@  >~$2Phf+us)%%>k/h,Ry:Aqc dC1w  հ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64S+JX+E[@@  >~$2PHhf+58݈vl2ׂk/hs V0kdC1w b*7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64S+JXHEDŽ\@@  >~$2PGˁHlhf+%,a@k/hͰ)FPneH:dC1w  հY7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64T+JXzE@@8  >~$2P&ԫl0.&BBhk/h@ZXB$dC1w  հ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64T+JXȍEÄ@@0  >~$2P\?٣فԫl0Hs.*_%6f@k/hp,ahTrIdC1w b-M7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64T+JXEDŽ@@+  >~$2Pe?٣Oԫl0RENWk/h~$2PE"l{˳2x\B1k/hᜫȡcHVdC1Ew  հ.7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64U+JXpEÄ@@'  >~$2PěE:=8"l?٣OYM/m% k/h;xU+~dC1Gw bm7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64U+JXEDŽ@@"  >~$2P'%~$2P o/29̨PVsS&;͏k/h疬osdm}㽢dC1NJUw  հs¨7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64V+JXEÅ@@m  >~$2P5+Zpo/29E:=8o‡ f)Tk k/hV>*dC1njvw bר7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64V+JXEDž@@h  >~$2P[ց+Zpio/29OG1q} UBk/huq|?ȡIdC1ǎw  հx7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64W+JXcEN@@  >~$2Pt$ Y10¯xk/h'@~ _dC1!w  հظ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64W+JX^EÆO@@  >~$2P Tt$+ZpAw?56t%Ck/hӳNhg 8s[dC1w bE7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64W+JXoEdžP@@  >~$2P?ɋTt$eCN.Э;Gk/h`x̛%dC1w  հu7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64X+JXE@@C  >~$2PmI9Zh .w+k/hʭ K [3"TbEdC1w  հ^7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64X+JXGE׆@@'  >~$2PPSI9ZC7jgTe'S6{2k/h"Vl7D-dC1w b.;|7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64X+JXE׆@@&  >~$2P#YGSC7jgI95[} ypsއ&k/hVGѱ@7~EKbdC1w  հ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Y+JXE@@[  >~$2PFAR$7 UKeY!۔S k/hOC&7F()0vAdC1Vw  հ@37NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Y+JXđEӇ@@C  >~$2PI⇨3AR$7 UKC7jgS^Z3 5-Ek/h@AIۍER^WJ"dC1Xw b=~X7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Y+JXEׇ@@>  >~$2PYG%⇨3C7jgS^AR$:jM`\͵Wبek/h+!.UdC1[Aw  հDʨ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Z+JXvE@@  >~$2PS=K!㴜>k/h\2(usddC1w  ձ-7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Z+JX*E͈@@  >~$2PF;uXԁS=Xr$⇨3rDq)rj k/h UEgdݥdC1Qw bL7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Z+JXEш@@  >~$2PJ\6٩uXuXr$S=#m#xk/h\lhv`dC1w  ձa7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64[+JXE@@  >~$2P^x2*SvTA<|>k/hOGO$?tj=dC1Ow  ձ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64[+JXE͈@@  >~$2Pc߁^x2*SvTuXR҄9̞¢7k/h)18FaSŢ{NQAidC1w b\ /7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64[+JX1E͈@@  >~$2P#5|\c^x2*Sv1.JZpk/hkMe=dC1 w  ձЩ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64\+JXϙE@@  >~$2P^X'4;܏_?$k*k/h<9鸲T.̚dC1#'w  ձ%W7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64\+JXE͉@@q  >~$2PɎdX'4lc@T]XeD+0k/h(|rLZzJcJdC1#)w bkO7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64\+JX(Eщ@@l  >~$2PɎdlX'4ɭaC⿨1ąmk/hJ_0 dC1#,:w  ձ%ߨ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64]+JX͜E@@w  >~$2PS6cրԊ⪬3-*n/k/h~7%@ү*dC12lw  ձ4V7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64]+JXEÉ@@o  >~$2P$n6cɎdrɇ88y-xFt-k/h8pdC12o(w bzɨ7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64]+JX,Elj@@j  >~$2P~^_z6cxxZB9QGfk/hw9@`2)svdC12qmw  ձ4[7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64^+JX6ET@@  >~$2PRM*^"Gh MCS k/h3#$=^GvdC1A\w  ձC7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64^+JXuEÊU@@  >~$2P`bRM*^YDz8 mk/hJ4xq+6dC1Aw bܕ7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64^+JXŪENJV@@  >~$2P.Hؒ bRM*^YizT^PFrk/hUa AͪǝdC1Aw  ձC7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64_+JXE@@  >~$2PHD|{ZaS kB+pQk/h}iX-dC1Paw  ձR!7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64_+JXԤEË@@  >~$2P!#9, CHD|{Zb#ukjD*7f5^u+k/hX'h 5qi(dC1Pw bF7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64_+JXENj@@  >~$2PMj#;9, CwHD|{Z-"vсciLb,k/h5H(r=VdC1Pw  ձR峨7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64`+JXWE@@m  >~$2P[^8#ZjF|ik/hm +ed[}h|M޷dC1`~$2P-(a^89, CzDT!-k/hZR~`^dC1`>lw bd)7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64`+JXENj@@`  >~$2P9|kD'(ar^8'"-bt)Bzk/hhvY .Ή|ˑ0dC1`@w  ձb*7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64c+JXoE@@t  >~$2PȅS{ܰ䃚N~k/h@3w*GdC1 w  ձ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64c+JXE͍@@b  >~$2Pl1kSS(a.N^LZQ+Μ@2k/hn{8fBt/jKdC1Iw b:!7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64d+JXPE/@@  >~$2P3xѥ>viY"@i]k/hFo dC1W+w  ձA7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64d+JXrEÎ0@@  >~$2PCG6J1Zxѥ>SBH1+k/hO9TP45WdC1Yiw bK7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64d+JXEю1@@  >~$2Pf-fEp6J1Z}Sxѥ>ۛVTw*Ihk/hn?0M`-%ydC1[w  ձE7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64f+JX*En@@  >~$2P8s>F40_k/h .D?j1dC1rw  ձq7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64f+JXFEÎo@@  >~$2Pͽ끣8sS&|>NQ=-k/ho0>mEl4dC1w b 7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64f+JXEюp@@  >~$2P{M S8sX?k/hx\#C?NR#[zdC1)w  ձ)7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64g+JXE@@  >~$2P+H fƀ8N8 l%5}k/htV$)IdޑdC1)w  ձ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64g+JX+EÏ@@  >~$2P N f~$2P 뢈N` fB^@N:AoA\k/h&[-aTjdC13w  ձ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64h+JXVEP@@  >~$2P[P'I@G( QPk/hkE璀2BdC1ow  ձY7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64h+JXlEQ@@  >~$2PJ|.on[P'*˷Kc\\+k/h8u*q?ldC1qpw b"u7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64h+JXEǏR@@  >~$2Puw|.on}[P'Oq9"05|@k/hgFrQ?dC1sqw  ձ]7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64i+JXNE@@r  >~$2Pk>%0gr!":Ck/h^t-C3dC1&w  ձC7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64j+JXE@@  >~$2P'ε}F=Eer7k/heF6<2ar/dC1w  ձ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64j+JXE@@  >~$2Ps(3˻nε}p6nz=m<k/h4PZ k_dC1w bA%)7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64j+JXEǐ @@  >~$2PL΁3˻ncε}&V<ۻf]8k/hp5oidC1!w  ձE7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64k+JXfE@@{  >~$2P [U`ik*KLR&,nq|翖k/hzۚ M۳cdC1Aw  ղ +7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64k+JXE͐@@i  >~$2Pwd|dVҁ[U`ik*KLR3˻n6npldX5cYk/htgdC1Dcw bPj7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64k+JXE͐@@h  >~$2P$hd|dV[U`ik ٢Jk/h|CĕtW8"dC1GFw  ղ 1v7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64l+JXE@@H  >~$2Pj(& -p& [xDr;SJm_rk/hs(ӗ)dC1Dw  ղr|7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64l+JXE͐@@6  >~$2PHj(& -p& [d|dV,oUC+Hk/hqJw]؞ ⺑.>dC1w b_7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64l+JXE͐@@5  >~$2P^ `1qj(& v٪:s[k/hwklvxz?dC1w  ղwǨ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64m+JXIE@@  >~$2PCVI(Ǩ,~hok/h'D d>b@dC1&hw  ղ(7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64m+JXUE͑@@q  >~$2PvY.VI(l1kjVM+X k/hP[W_.V]ndC1&Ћw bn7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64m+JXeEё@@l  >~$2P+j3Y.ul1kVI(,āz]k/hv՝ΰX^RL6ydC1&Vw  ղ(7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64p+JXiE`@@  >~$2P> ^2v-MIK,(ik/hH.[IP+jW6BdC1T=w  ղV7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64q+JX"E@@v  >~$2P,65'€2vC6[rX! /[ik/h+3CK!ldC1cJw  ղe׻7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64r+JX,EL@@  >~$2Pf_M2v!d^>C/B~$2PFl9'_M2v6J1ZNF!km._3k/hUA2܇ 3/k}dC1s:w ba7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64r+JXEדN@@  >~$2P)?l9'6J1ZN_M tP sMZ3Qjk/h}y9^{D!_pdC1s=w  ղu'}7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64s+JX[E@@7  >~$2PPAd%0jdrC$k/h{D%z~+/AdC1~ w  ղh7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64s+JXEד@@  >~$2Pe6<PAd%0,l9'2^8`` X`k/hꗠ\u(%8l~$2PP8 Ӂ6<,PAdL@Tak/h;8FmzdC1w  ղt7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64t+JXE@@(  >~$2P /B}x]1.OESaTrk/h>}cN!TUotdC1w  ղU7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64t+JXEÓ@@  >~$2P shD/B}6<zԥs`z, k/hH %NnMUXNdC1 w b7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64t+JXEǓ@@  >~$2PRz߭shD/B}oޝM=Ahk/hE8xvbVdC1Ƅw  ղ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64u+JXE@@a  >~$2PƢo,:р?}xeuůQork/hKjv{#*ZdC1w  ղ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64u+JXEÔ@@Y  >~$2PhױIo,:shDHL Od~̋_ k/hjjKI!HUzYdC1 Lw b/ƨ7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64u+JX/Eǔ@@T  >~$2P+\ױIgo,:PHLF Z.Dk/h w ;*  GE2dC1 nw  ղ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64v+JXE@@  >~$2P lܴw0@bk/h5 yᯗu\dC1Kw  ղ67NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64v+JX.EÕ@@z  >~$2P Dg< lܴױImRZ m9>Vi=k/htQ9PZ>D.dC1Nww bt7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64v+JXEǕ@@u  >~$2PVԒ<%߁g< lܴ7u|`~$2PMPO>:۫#v/hcՄk/h 6pd4"dC1w  ղ|7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64w+JXEÖK@@  >~$2PJu\PO>g<9J *i i k/hz ꦑTTO#YdC1;w bը7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64w+JXEǖL@@  >~$2PtOJJu\^PO>+e@) 6Wkhgk/h_wߊ_ןdC1\w  ղ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64x+JXE@@~  >~$2PkX$]_נ^:k/h_3c]x+qm%dC1w  ղпt7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64x+JX E×@@~  >~$2PBIW)X$]_zb[7rN2Qfk/hT@ª`ςwUdC1w b7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64x+JX@ EǗ @@~  >~$2PEq3IW)sXOɉ枅EQGYk/h9.kgIҩǝ]dC1>w  ղ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64z+JXE.@@~  >~$2P45:򒀡 ^v_PԂ_,Pk/hQ*Z:dC1bw  ղMC7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64z+JXE/@@~  >~$2PJ I): ^vݢgc/IW)-oR i&LAk/h5p iL3xdC1hw b5Ө7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64z+JXy E0@@~  >~$2PbZǁ I)ݢgc/:Ά(8pB;fDk/h#X#ykdC1o w  ղYШ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64{+JX_E@@~T  >~$2PrmM65'539Zy-"k/hA3ҍ.+dC1w  ղ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64{+JXwEח@@~8  >~$2P35%bmM65'߭Xybz I))y?;|Eֵk/h5a$r)y$OdC1#w bDԨ7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64{+JXEח@@~7  >~$2PZVp°V5%b߭XybzmMYt,Yb4[k/h]4!V䷝$ۛzdC1mw  ղ*7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64}+JXE@@}s  >~$2Pǎ!TA]r5czltk/hoYI7OdC15w  ճ 7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64}+JX$EØ@@}k  >~$2P_zdǎ!T5%bsxLB%M k/h7mL<؂8dC1;^w bcb,7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64}+JX*Eǘ@@}f  >~$2PAa axڣzdJǎ!TS dE䱌~k/hÙW?3 dC1AHw  ճ,%7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64~+JXE@@}  >~$2P $DJG"D-Lk/hVLX8$ù.UdC1*xw  ճ,cҨ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64~+JX!EÙ@@}  >~$2P^,tl $zdov8vm=¯k/hu`CIeL În2dC1*{w bre7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64~+JX6#EǙ@@}  >~$2P?w,tll $4MV  =k/hDD C5, dC1*}w  ճ,h7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX#E@@}  >~$2P_5}F$^fG3#X$Wfk/hd}% 7vS#^dC19w  ճ;7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX)EÙ@@}  >~$2Pq5v6}F,tl^0_u Ձ k/h|FiyEcOdC19w b7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX,EǙ@@}  >~$2PQ̦%5v6D}FipI*!k/hڗʩ瘱?} OdC190w  ճ;7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JXY#E@@|p  >~$2P\DTw2$AwQكDk/hvQD`2dC1I;w  ճJ%7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64ǀ+JXC%E@@|o  >~$2PDZ {*DTw<Žf; k/hd,=T|rdC1IGw b+%7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX%E@@|V  >~$2PJJ+5Jw(ڂ1B[Ik/h 'c]\dC1XFw  ճZ17NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64ǁ+JX'EÙ@@|N  >~$2P 7+Ju\J’i7ni^9k/hkV^%Bf1dC1XIw bo7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JXE*Eљ@@|?  >~$2P41]bm7Ju\+xTO`E^k/h"e@|ݞ.ۘ5ʙdC1XK}w  ճZ6p7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX0ER@@{  >~$2P nIzt[+*Q7 0&k/hަث=pvdC1vw  ճxʨ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64ǃ+JX3E͚S@@{  >~$2P0.!MnIzt[+7uzlmg l9k/h:YG.^dC1vؖw b7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX8E͚T@@{  >~$2P%.!MhnIztE\iYTZ 6Xk/hDNR2˕ۣV.ldC1vw  ճxߨ7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Ȅ+JX45E"@@z  >~$2PRHė+M9MKÎǯ4@qk/h(X`~"vdC1Cw  ճ^7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64DŽ+JXy:EÛ#@@z  >~$2P)*eė+.!MH)DS{=k/h0 5ҡ dC1 w bG7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX1>EǛ$@@z  >~$2PW X]*eFė+lšׄSG?M@k/hS \ -(wfLSfedC1%w  ճר7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX5E@@z  >~$2P{Ρ<>'? k/hf7uvudC1_-w  ճJH7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Dž+JX8EÜ@@z  >~$2PK˭{Ρ*eU@}MXuAڢN׳k/hӗ\`mX8dC1buw b݉7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JXP;Eǜ@@z  >~$2P>&K˭{Ρ2Nj9hozk/h--@YPorT,dC1ew  ճP7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX5E@@yI  >~$2P cHo\ybHA%n`cfk/hFBE.FdC1'w  ճG7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64dž+JX7E͜@@y7  >~$2PgywcHo\ybK˭9x*kd<$C8k/h`4KTdC1Ww bk7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX%:E͜@@y6  >~$2Pk^yw|cHo\9c_^k/h˹n eݓM>dC1w  ճè7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64ȇ+JX8E@@y  >~$2PP. ųFbÑ&! tk/hpdƒ1Q2dC1)w  ճR7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64LJ+JX:EÝ@@y  >~$2PRLuzP. ųFywͻ[9f 9:5 k/h}{Nv2I4'dC1Iw be7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX<Eǝ@@y  >~$2P !?@RLuzjP. ųF*dmWE'wLk/hcʣ4ҚQEdC1tw  ճ֝7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JX2;Eg@@x  >~$2Pz6BMjx),lk/h.?H@ ÁVdC1,w  ճB7NClient2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64Lj+JXK=EÝh@@x  >~$2PD{yh߁6BMRLuzc'{Nhd k/hwRxWWJ5dC1.1w b UW7NServer2PVN#2ping 3.2.1 - Linux (Ubuntu) x86_64+JXt?Eǝi@@x  >~$2P̋-pjyh6BM,hcܖk