diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3d1068 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# .gitignore +*.o +*.pyc + +Makefile +Makefile.in +aclocal.m4 +confdefs.h +config.* +configure +confinc +confmf +conftest.* +stamp-h1* + +/build-aux/compile +/build-aux/depcomp +/build-aux/install-sh +/build-aux/missing +/build-aux/test-driver + +/autom4te.cache/ +/.deps/ +/packet/.deps/ +/packet/.dirstamp +/packet/testpacket.py.log +/packet/testpacket.py.trs +/test-suite.log +/ChangeLog +/INSTALL +/mtr +/mtr-packet +/mtr-packet-listen +/mtr.8 +/mtr-packet.8 +/test/.deps/ +/test/.dirstamp +/ui/.deps/ +/ui/.dirstamp + +/test/*.py.log +/test/*.py.trs + +/mtr-*.tar.gz diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d4c2a1f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,69 @@ + + Matt Kimball is the primary author of mtr. + + Roger Wolff is currently maintaining mtr. + + + Bug reports and feature requests should be sent as described in + the README file. + + Thanks to everyone who has provided feedback on mtr. + + Thanks especially to those of you who have sent code: + (Reverse alphabetical order, and sometimes I just add people at + the end... ) + + Bohdan Vlasyuk + Evgeniy Tretyak + John Thacker + Juha Takala + David Sward + David Stone + Andrew Stesin + Greg Stark + Robert Sparks + Mike Simons + Aaron Scarisbrick + Craig Milo Rogers + Antonio Querubin + Russell Nelson + Davin Milun + Josh Martin + Alexander V. Lukyanov + Charles Levert + Bertrand Leconte + Anand Kumria + Olav Kvittem + Adam Kramer + Philip Kizer + Simon Kirby + Sami Kerola + Christophe Kalt + Steve Kann + Brett Johnson + Roland Illig + Damian Gryski + Rob Foehl + Mircea Damian + Cougar + Travis Cross + Brian Casey + Andrew Brown + Bill Bogstad + Marc Bejarano + Moritz Barsnick + Thomas Klausner + Roderick Groesbeek + Kyle J. McKay + Joseph Carter + Thales + "Min" + Vaibhav Bajpai + Jürgen Schönwälder + + and anyone who has slipped through the cracks of my mail file. + +Authors: If you want your Email mentioned here, send it to me. + If you don't want your Email mentioned here, tell me. + + -- REW diff --git a/BSDCOPYING b/BSDCOPYING new file mode 100644 index 0000000..c8560bc --- /dev/null +++ b/BSDCOPYING @@ -0,0 +1,30 @@ +Portions of this software have the following copyright. + +-- + +Copyright (c) 1991, 1993 + The Regents of the University of California. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + 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. diff --git a/FORMATS b/FORMATS new file mode 100644 index 0000000..3e64e17 --- /dev/null +++ b/FORMATS @@ -0,0 +1,103 @@ + +The "split" format is for a separating the gui from the main program. +The main program can be installed setuid, and you don't want to link a +gui-library with a setuid program. + + +The split format is: + + + + +The "raw" format is: + +hostline|xmitline|pingline|dnsline|timestampline + +hostline: +h + +xmitline: +x + +pingline: +p + +dnsline: +d + +timestampline: +t + + +Timestampline is not yet implemented. Need to find out how to do +ICMP timestamping first. :-) + + +Someone suggested to put the following text here. As to context: Some +people are wondering why mtr sometimes reports hosts beyond the +destination host. + + +The FINAL host will occasionally be mentioned at position n, n+1, n+2 +etc. + +You know traceroute, right? It sends a packet, waits for the reply to +come back and when it comes back, it sends the next packet. + +If say hosts 5-8 do not send "time exceeded" packets, you'll wait a +4*3 = twelve seconds extra before you get any results on hosts 9 and +further. MTR doesn't work like that. + +In theory we could send out a probe for host 1-40 all at once. But +this would pose an unnecessary burden on the network. So what we do, +is we send out probes for a max of 5 hosts beyond where we've seen a +reply. So in the example above, we'd see a reply from router at +position 4, then we'd send out 5-9 (and because the max-host is now at +9, we'll send them out at 1s/9 = 111ms intervals). When the reply from +host 9 comes back, we'll start probing for host 10-15 (at about 60ms +intervals). But suppose the network delay up to host 9 is already 200ms +and suppose our destination host is at position 11. Then by the time +the packet from host 11 comes back, we'll already have sent probe +packets for position 12, 13, and 14! Those will come back as +"destination reached" and be reported by the "raw" mode. + +Curses mode will stop showing hosts with position numbers beyond the +first reply of the destination host. It could gather the information +about replies to packets sent as probes FURTHER than it actually is +into the line displayed at its true position, but it doesn't (yet). + +In fact the above example is almost completely true: + +% mtr -r -n -c 2 152.179.99.218 | tail -5 + 13.|-- 144.232.18.238 0.0% 2 94.8 95.4 94.8 96.0 0.8 + 14.|-- 152.63.16.182 0.0% 2 95.1 95.5 95.1 95.8 0.5 + 15.|-- 152.63.64.106 0.0% 2 163.9 163.9 163.9 164.0 0.1 + 16.|-- 152.63.50.89 50.0% 2 163.7 163.7 163.7 163.7 0.0 + 17.|-- 152.179.99.218 50.0% 2 168.2 168.2 168.2 168.2 0.0 +% mtr -l -c 2 152.179.99.218 | grep -v "^[dp]" |tail -7 +h 10 144.232.1.41 +h 11 144.232.4.96 +h 16 152.179.99.218 +h 17 152.179.99.218 +h 18 152.179.99.218 +h 12 144.232.18.238 +h 13 152.63.16.182 + +As you can see we get the reply from the destination host at position +16 AFTER we've sent probes for position 17 and 18. When those come +back, they are reported. That's what raw mode does. It reports the raw +information. + +If you write a backend for the raw mode, it's up to you to +filter/display the results. + +h 10 144.232.1.41 +h 11 144.232.4.96 +h 12 144.232.18.238 +h 13 152.63.16.182 +h 14 152.63.64.106 +h 15 152.63.50.89 +h 16 152.179.99.218 +h 17 152.179.99.218 +h 18 152.179.99.218 + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..c0709ca --- /dev/null +++ b/Makefile.am @@ -0,0 +1,158 @@ +EXTRA_DIST = \ + BSDCOPYING \ + SECURITY \ + build-aux/mtr.bat \ + img/mtr_icon.xpm + $(TEST_FILES) + +sbin_PROGRAMS = mtr mtr-packet +TESTS = \ + test/cmdparse.py \ + test/param.py \ + test/probe.py + +TEST_FILES = \ + test/cmdparse.py \ + test/mtrpacket.py \ + test/param.py \ + test/probe.py \ + test/lint.sh +EXTRA_DIST += $(TEST_FILES) + +PATHFILES = +CLEANFILES = $(PATHFILES) +EXTRA_DIST += $(PATHFILES:=.in) + +# +# We would use % pattern matching here, but that is a GNU make +# extension and doesn't work on FreeBSD. +# +mtr-packet.8: $(srcdir)/man/mtr-packet.8.in + $(AM_V_GEN) $(srcdir)/build-aux/mangen.sh "$(VERSION)" \ + $(srcdir)/man/mtr-packet.8.in $@ + +mtr.8: $(srcdir)/man/mtr.8.in + $(AM_V_GEN) $(srcdir)/build-aux/mangen.sh "$(VERSION)" \ + $(srcdir)/man/mtr.8.in $@ + +$(PATHFILES): Makefile + +dist_man_MANS = mtr.8 mtr-packet.8 +PATHFILES += man/mtr.8 man/mtr-packet.8 + +install-exec-hook: + `setcap cap_net_raw+ep $(DESTDIR)$(sbindir)/mtr-packet` \ + || chmod u+s $(DESTDIR)$(sbindir)/mtr-packet + +mtr_SOURCES = ui/mtr.c ui/mtr.h \ + ui/net.c ui/net.h \ + ui/cmdpipe.c ui/cmdpipe.h \ + ui/dns.c ui/dns.h \ + ui/raw.c ui/raw.h \ + ui/split.c ui/split.h \ + ui/display.c ui/display.h \ + ui/report.c ui/report.h \ + ui/select.c ui/select.h \ + ui/utils.c ui/utils.h \ + packet/cmdparse.c packet/cmdparse.h \ + ui/mtr-curses.h \ + img/mtr_icon.xpm \ + ui/mtr-gtk.h + +if WITH_ERROR +mtr_SOURCES += \ + portability/error.h \ + portability/error.c +endif + +if WITH_GETOPT +mtr_SOURCES += \ + portability/getopt.h \ + portability/getopt.c \ + portability/getopt1.c +endif + +if WITH_IPINFO +mtr_SOURCES += ui/asn.c ui/asn.h +endif + +if WITH_CURSES +mtr_SOURCES += ui/curses.c +endif + +if WITH_GTK +mtr_SOURCES += ui/gtk.c +endif + +mtr_INCLUDES = $(GLIB_CFLAGS) -I$(top_builddir) -I$(top_srcdir) +mtr_CFLAGS = $(GTK_CFLAGS) $(NCURSES_CFLAGS) +mtr_LDADD = $(GTK_LIBS) $(NCURSES_LIBS) $(RESOLV_LIBS) + + +mtr_packet_SOURCES = \ + portability/queue.h \ + packet/packet.c \ + packet/cmdparse.c packet/cmdparse.h \ + packet/command.c packet/command.h \ + packet/platform.h \ + packet/probe.c packet/probe.h \ + packet/protocols.h \ + packet/timeval.c packet/timeval.h \ + packet/wait.h + +mtr_packet_LDADD = $(CAP_LIBS) + + +if CYGWIN + +mtr_packet_SOURCES += \ + packet/command_cygwin.c packet/command_cygwin.h \ + packet/probe_cygwin.c packet/probe_cygwin.h \ + packet/wait_cygwin.c +mtr_packet_LDADD += -lcygwin -liphlpapi -lws2_32 + +dist_windows_aux = \ + $(srcdir)/build-aux/mtr.bat \ + $(srcdir)/AUTHORS \ + $(srcdir)/BSDCOPYING \ + $(srcdir)/COPYING \ + $(srcdir)/README \ + $(srcdir)/NEWS + +distwindir = $(distdir)-win-$(host_cpu) + +# Bundle necessary files for a Windows binary distribution +distdir-win: $(dist_windows_aux) mtr.exe mtr-packet.exe + rm -fr $(distwindir) + mkdir -p $(distwindir) $(distwindir)/bin $(distwindir)/terminfo + cp $(dist_windows_aux) -t $(distwindir) + cp mtr.exe mtr-packet.exe -t $(distwindir)/bin + ldd mtr.exe | grep -v cygdrive | awk '{ print $$3 }' | xargs cp -t $(distwindir)/bin + cp `find /usr/share/terminfo -name cygwin | xargs dirname` -r $(distwindir)/terminfo + +# Zip up a Windows binary distribution +dist-windows-bin: distdir-win + rm -f $(distwindir).zip + zip -rq $(distwindir).zip $(distwindir) + rm -fr $(distwindir) + +else # if CYGWIN + +check_PROGRAMS = mtr-packet-listen + +mtr_packet_SOURCES += \ + packet/command_unix.c packet/command_unix.h \ + packet/construct_unix.c packet/construct_unix.h \ + packet/deconstruct_unix.c packet/deconstruct_unix.h \ + packet/probe_unix.c packet/probe_unix.h \ + packet/wait_unix.c + +mtr_packet_listen_SOURCES = \ + test/packet_listen.c + +endif # if CYGWIN + + +if BUILD_BASH_COMPLETION +dist_bashcompletion_DATA = bash-completion/mtr +endif diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..4cc42b1 --- /dev/null +++ b/NEWS @@ -0,0 +1,772 @@ +WHAT'S NEW? + +The new release script will do a "git shortlog" to add +the commit messages here. + +#NEW_STUFF_HERE this is a tag my script looks for. +V0.92 + added a few arguments to calls added by fmazu. Allows it to compile. + +V0.91 + only made the tag point to the proper commit. --REW + script now handles that situation (aborted release script) better. + +V0.90 + only fixed the release script. Should now contain fmaxullo's + patch. --rew + fmazullo (1): + Add AS number to json output + +V0.89 + only made the tag point to the proper commit. --REW + +V0.88 + Antonio Querubin (3): + Merge remote-tracking branch 'origin/master' into newdns + Need to error check getnameinfo(). + Merge remote-tracking branch 'origin/master' into newdns + + David Hill (1): + include for fd_set + + Jakub Wilk (1): + Fix typos + + Joe Bruggeman (2): + Replace all tabs tabs in net.c with spaces + cleanup the if blocks in net.c to improve readability + + Jürgen Weigert (1): + Mention + and - keys in the man page + + Kacper Michajłow (2): + Relax mtr-packet search rules. + Add missing errno.h include. + + Matt Kimball (20): + Added mtr-packet subprocess + test: Fix mtr-packet tests for Python 3 + cmdline: multiple host names dropped all but one host (issue #168) + mtr-packet: IPv6 support + mtr-packet: UDP probe support + mtr-packet: packet customization options (size, fill, mark, tos) + mtr-packet: TCP and SCTP probes + mtr-packet: MPLS decoding and local UDP port usage + mtr-packet: allow local address binding + Merge branch mtr-packet into 'master' + mtr-packet: drop capabilities + using BSD's linked lists for probes + build: moved front-end source into ui subdir + build: use AC_CHECK_LIB for ncurses, rather than pkg-tool + mtr-packet: Fall back to IPv4 only support if IPv6 sockets fail to open + build: if linking with ncurses fails, try curses (for NetBSD) + build: Fix Solaris build issues + build: fix compiler warnings when for OpenBSD, NetBSD and Solaris + mtr-packet: Report probe status on host unreachable (Cygwin) + cleanup: Fix #ifdef structure which confuses 'ident' + cleanup: reindented C source with GNU indent + + Narthorn (2): + Initialize dns process before opening display + Add displaymode 2 back in + + R.E. Wolff (19): + Merge branch 'newdns' of https://github.com/traviscross/mtr into newdns + Merge branch 'newdns' + fixed double printout of start time, issue 131 + Updated NEWS as in v0.87.1 + format sent and rcvd fields correctly for big numbers #66 + increased default unknownhosts #92 #132 #130 (I give in). + Merge branch 'master' of github.com:traviscross/mtr + fixed no-gtk build bug introduced with e2d898cc + more cleanup + Partial reverse of 6bb5b6b3b. + re-initialize ipinfo_no and -max. Fixes #161. + Merge branch 'master' of github.com:traviscross/mtr + fixed dynamic DNS on/off switch. Fixed #160 + header alignment issue found&fixed by meingtsla. Fixes #164 + Merge branch 'master' of github.com:traviscross/mtr + asn fix from meingtsla, fixes #163. Pong! + put ifdefs around IPV6 only part. Fixes #184 + More whitespace mangling for consistency in net.c + The release script bumped the version number + + Roger Wolff (22): + New DNS works for IPV4.... + moved towards IPV6 compatibilty... + removed the include mess... + merged antonios's bufsize fixes + Merge branch 'master' of github.com:traviscross/mtr into newdns + AQ: Added include for redhat, and fixed salen for BSD + removed last debug output from dns.c + One more patch to fix a getnameinfo corruption problem. -- AQ + + Rogier Wolff (5): + removed AC check for features newdns doesn't use + Fixed pull #133 another way.... + fixed #27 and #35 where the fix was tested a long time ago. + fixed #141 compile without SCTP if not available + fixed typo. + + Sami Kerola (122): + warnings: remove unnecessary file + usage: add short and long options and descriptions to usage() + warnings: stop variable shadowing + dns: remove unnecessary dns_events() function + posix: replace bzero() and index() with modern equivelants + warnings: stop reassigning a value before the old one has been used + warnings: remove code that cannot be reached + warnings: fix printf data types + cleanup: remove unnecessary null check + build-sys; do not use subdirectory object + man: use url macro to urls and fix reference manual notations + build-sys: default to ,/configure --enable-silent-rules + warnings: do not take abs() when data type is unsigned + warnings: mark unused function input variables + warnings: fix couple unsigned vs signed variable comparisions + warnings: multiply timeval seconds only when the value is small + warnings: fix some missed unsigned vs signed variable comparisions + comment: add value range note to initialization + cast: do not downgrade to float when double should be used + warnings: remove dead code + build-sys: fix make distcheck + build-sys: remove old dist Makefile kludge + build-sys: use build version script from gnulib + build-sys: improve configure.am + build-sys: require automake 1.11.6 or newer + warnings: fix unused variable when ./configure --without-gtk is used + readability: always use EXIT_* definitions from stdlib.h + cleanup: remove unnecessary function + warnigns: add void to functions that do not take any arguments + build-sys: fix --without-ipinfo regressions + build-sys: fix ./configure --disable-ipv6 + warnings: fix --disable-ipv6 --without-ipinfo compilation warnings + build-sys: check pkg-config availability + build-sys: use pkg-config to find gtk+-2.0 + build-sys: use pkg-config to find ncurses + build-sys: get rid of double negative ipinfo autotools settings + cleanup: remove NO_SPLIT preprocessor check + build-sys: simplify finding resolver library + build-sys: remove unused autoconf check values + cleanup: remove obsolete herror() function + usage: reflect ./configure choices in available command line options + cleanup: remove preprocessor missing functions go-arounds + usage: be careful when parsing numeric user input + usage: use error(3) error-reporting function + cleanup: move max port number to be a define in net.h + build-sys: use system getopt_long() when it is available + build-sys: tell function locality explicitly + portability: fix float max check from values.h + portability: MacOS does not have error() function + portability: fix MacOS libresolv usage + data types: set static strings to be read-only + cleanup: remove redundant redeclaration + data types: move variable declaration from header to .c file + data types: check with smatch everything is in resonable scope + warnings: fix use of uninitialized warning + data types: get rid of all globals that are easy to remove + usability: fix --mark documentation + docs: make manual page versioning automatic + data types: move global data to control structures + data types: make control structure smaller + data types: move rest of the global variables to control structures + crash fix: make --xml not to dump core + warnings: correct function pointer prototype argument + warnings: do not use zero as NULL + warnings: avoid vla when malloc() is more appropriate + usability: print usage() if unknown options are used + cleanup: use definition for a magic value appearing twice in code + cleanup: remove commented out includes in dns.c + cleanup: avoid duplicating stdint.h + cleanup: use ICMP definitions from linux/icmp.h when possible + cleanup: move generic utility functions to a separate file + reliability: ensure string copy results to a null determined string + reliability: further removal of unsave string operation + reliability: always check malloc() return value + reliability: always check strdup() return value + reliability: check writing to stdout and stderr was successful + usability: use ISO-8601 timestamp + posix: do not use time(2) input argument + usability: add bash-completion file + bug fix: long option --gracetime is correct, --graceperiod is not + performance: use fewer printw() calls to center text + cleanup: merge two trim functions to one + crash fix: add ctl structure to gtk Pause_clicked() handler + crash fix: never return const string as address + crash fix: ctl->iiwidth_len was not initialized correctly + cleanup: make unused and const attributes to look the same + performance: make get_iiwidth() to be const function + cleanup: remove more/bottom labels header separation from mpls + cleanup: set variable only if it is used + cleanup: correct display_offset variable usage + cleanup: remove message duplicate + performance: set few variables read-only + docs: add Sami Kerola to authors + performance: make reset in net.c more effective + portability: fix bsd build + warnings: ensure printf will not overflow + misc: improve random initialization + net: fix net_reopen() initialization + warnings: fix warnings when everything possible is turned on + curses: simplify format_number() + curses: use switch case in mtr_curses_keyaction() + cleanup: remove dead code + style: convert c++ comment style to c style + display: avoid unnecessary switch case clauses + curses: convert magic numbers to an enum list + data types: move variables from a file to a function scope + cleanup: move file scope variables to the beginning of file + data types: move names list away from global scope + cleanup: move definitions and struct declarations to mtr.h + cleanup: clarify preprocessor nesting + build-sys: use proper check to find if time_t is defined + build-sys: enable all system extensions + regression: fix --displaymode=2 argument + user interface: do not allow out of range --ipinfo arguments + cleanup: use single logic to handle conditional options + docs: add very basic --sctp documentation to manual page + docs: improve mtr-packet(8) manual page + build-sys: update .gitignore file + smatch: extern keyword is needed only in header + smatch: fix couple warnings + build-sys: update .gitignore file + docs: FSF moved back in 2005 + + Vlad Glagolev (1): + respect theme foreground color + + aquerubin (5): + Correct psize for IPv6. + Merge updates from branch 'master' into newdns + Merge branch 'master' into newdns + Merge branch 'newdns' of https://github.com/aquerubin/mtr into newdns + Fix standard deviation calculation. + + rewolff (22): + + +V0.87 +Antonio Querubin (1): + Use setcap instead of setuid when installing the binary. + +Baptiste Jonglez (4): + Allow enabling IP info and ASN lookup from the curses interface + Document the -y option in the manpage + Cosmetic cleanup of the option-parsing code + Fix wrap-around bug when displaying IP info (-y option) + +Danek Duvall (1): + Fix issue #76: rationalize the discovery of a terminal handling library + +Gareth Randall (6): + Corrected the "without gtk" reference to "./configure --without-gtk", + Filled in some of the missing man page sections. + Remove a warning message at compile time. + Fix typos and update mailing list references. + Add a section about granting limited security capabilities. + State that Github is the preferred way to report bugs. + +Guo Yixuan (1): + Raw output: add x for a ping-packet-sent event. + +Hajimu UMEMOTO (1): + Add aslookup support to gtk interface + +Jakub Wilk (1): + Fix typos. + +Kris Coward (1): + Added --displaymode option + +Narthorn (1): + curses: Fix background transparency in terminal + +Nikolai R Kristiansen (1): + Add support for JSON as report output format + +R.E. Wolff (9): + explanation of the version numbers in NEWS. + Merge branch 'master' of github.com:traviscross/mtr + removed warning about IPV6 socket when IPV6 is not available at runtime + fix for printing space field in XML. + modified name of timeout variable to prevent warning on solaris. + changed the name of the ping timeout timer from 'tag' to 'ping timeout timer' + net.c fix from AQ. + issue 128: compile should be in .gitignore + The release script bumped the version number + added use-default-colors... + +Theo Baschak (1): + Update asn.c - 32bit asn widths + +Tobias Rittweiler (5): + Fix typo in csv_close() that prevented any of the data columns from being printed. + --csv: Don't print spaces in columns. + --csv: Print a header line as the first line which names all columns. + asn.h: Guard against being included twice. + Fix setting length field of UDP header to broken value on BSD systems. + +Vojtech Kurka (1): + Fixed behaviour of Pause button + +aquerubin (3): + Correct psize for IPv6. + Fix Avg and Best column order to match column headers in GTK display. + Update Tony's email address in the GTK credits. + +penyu (1): + add max-unknown option + +russor (10): + allow setting local and remote port for UDP probing + fix checksum for odd sized packets + set the local address for display if it was bound + automatically set udp address if needed + fix improper aliasing + fix placement of zeros when running alternate udp checksum + endian neutral placement of alternate checksum + copy odd byte into a 16-bit temp value; used bit-sized types for calrity + correct checksum calculation when adding the overflow overflows + add option to set graceperiod + +swordfeng (3): + Add SCTP support (same way with tcp) + remove comment + fix sctp header structure + +======= +#this is a tag my script looks for. +#NEW_STUFF_HERE +V0.87 + Antonio Querubin (1): + Use setcap instead of setuid when installing the binary. + + Baptiste Jonglez (4): + Allow enabling IP info and ASN lookup from the curses interface + Document the -y option in the manpage + Cosmetic cleanup of the option-parsing code + Fix wrap-around bug when displaying IP info (-y option) + + Danek Duvall (1): + Fix issue #76: rationalize the discovery of a terminal handling library + + Gareth Randall (6): + Corrected the "without gtk" reference to "./configure --without-gtk" + Filled in some of the missing man page sections. + Remove a warning message at compile time. + Fix typos and update mailing list references. + Add a section about granting limited security capabilities. + State that Github is the preferred way to report bugs. + + Guo Yixuan (1): + Raw output: add x for a ping-packet-sent event. + + Hajimu UMEMOTO (1): + Add aslookup support to gtk interface + + Jakub Wilk (1): + Fix typos. + + Kris Coward (1): + Added --displaymode option + + Narthorn (1): + curses: Fix background transparency in terminal + + Nikolai R Kristiansen (1): + Add support for JSON as report output format + + R.E. Wolff (9): + explanation of the version numbers in NEWS. + Merge branch 'master' of github.com:traviscross/mtr + removed warning about IPV6 socket when IPV6 is not available at runtime + fix for printing space field in XML. + modified name of timeout variable to prevent warning on solaris. + changed the name of the ping timeout timer from 'tag' to 'ping timeout timer' + net.c fix from AQ. + issue 128: compile should be in .gitignore + The release script bumped the version number + + Rogier Wolff (1): + added use-default-colors... + + Theo Baschak (1): + Update asn.c - 32bit asn widths + + Tobias Rittweiler (5): + Fix typo in csv_close() that prevented any of the data columns from being printed. + --csv: Don't print spaces in columns. + --csv: Print a header line as the first line which names all columns. + asn.h: Guard against being included twice. + Fix setting length field of UDP header to broken value on BSD systems. + + Vojtech Kurka (1): + Fixed behaviour of Pause button + + aquerubin (3): + Correct psize for IPv6. + Fix Avg and Best column order to match column headers in GTK display. + Update Tony's email address in the GTK credits. + + penyu (1): + add max-unknown option + + russor (10): + allow setting local and remote port for UDP probing + fix checksum for odd sized packets + set the local address for display if it was bound + automatically set udp address if needed + fix improper aliasing + fix placement of zeros when running alternate udp checksum + endian neutral placement of alternate checksum + copy odd byte into a 16-bit temp value; used bit-sized types for calrity + correct checksum calculation when adding the overflow overflows + add option to set graceperiod + + swordfeng (3): + Add SCTP support (same way with tcp) + remove comment + fix sctp header structure + + +V0.86 Fixed default hostname logic. + Fix for NetBSD: 64bit time_t -- Thomas Klausner + Fix unnecessary runtime dependency on glib from VSYakovetsky through + Thomas + Inverted IPINFO define in the code. Removes double negatives. + -- Vladimir Yakovetsky, REW. + Fixed failure on IPv4 only systems when IPv6 was available at + compile time. -- REW. + Fixed (longstanding) bug that mtr used 100% cpu when paused. + Cosmetic changes from Richard Hartman. + +V0.85 Fixed asn support. (better compile time detection of required features) + support for multiple hostnames. (fixed in 0.86) + support for TCP probes + +V0.84 Fix some glib things by Thomas. + +V0.83 Move to github. Mostly done by Travis. + +Author: Travis Cross + Add autotools bootstrap script + Update README for building from git repository + Cleanup whitespace in the NEWS file + Resolve -Wunused-but-set-variable warnings + Resolve -Wnull-dereference clang warning + Add -z / --show-ip support +Author: R.E. Wolff (mostly from patches by others) + some running patches + Made report wide switch properly to displayreport mode. Bug #780647 + fixed gtk field order. Bug #701513 + added aslookup patch from bug #701514 + added some extra clarifications to the SECURITY file. + enable ipv6 resolvers. By Antonio Querubin. Fixes bug #752583 + + V0.82 Removed old Changelog file appended at the end as oldest + changes. + 2011-03-28 Mark Kamichoff + Enable decoding of ICMP extensions for MPLS for curses and + report interfaces. Use the -e flag or press 'e' to enable it. + + V0.81 Moved to git. Testing git... + + V0.80 Some compilation fixes for BSD by Jeremy Chadwick + + + V0.78/0.79 some compilation fixes for BSD&others by + Thomas Klausner + + V0.76 display load sharing hosts in --raw output. + added about button in gui. + + v0.75 Feelgood patch to move sprintf to snprintf. People might think + that sprintf might cause a buffer overflow. Now it's clean. + cut-paste patches: you can now copy an intermediate host to the + clipboard. + + v0.74 Martin Pels' patch to allow UDP probes. + KES reported a build problem. Turns out I need to install gtk-1.2 + on my development system, otherwise my release script causes the + build to break. + changed some docs to advertise the new mailing list. + added documentation for the Mac OS X compilation problem. + added -Wno-pointer-sign to the compiler options. + Nico Lichtmaier's cleanup-gtk patch. (now mtr uses a more modern + dialect of gtk). + + v0.73 Some security patches. Although MTR drops privileges as soon + as possible after opening the sockets, it still had some + sprintf calls, which have now been converted into snprintf. + + v0.72 Fix signed/unsigned bug in IPV6 part + improved random packet size behaviour. --REW + + v0.71 Some IPV6 fixes, introduce packet size cmdline option. + (was already present as a cmdline argument) + + v0.70 Antinio submitted a cumulative patch containing some + nice improvements. He also submitted an automake patch + that causes mtr to no longer compile on my system. I + refuse to have mtr "in the dark" that I can't test-compile + the dist. + + v0.69 make distclean should now also remove "rej" files. + Antonio Querubin: update getopt.h . More cleanups using + new infrastructure. + rcw: Fixed IPV6 support: When compiled in an IPV6-supporting + environment, but when the kernel doesn't support IPV6, mtr would + fail to start. + + v0.68 included some old patches. + included patch from Antonio Querubin for better IPV6 support + restructured some more whitespace. + added mtr.h where "global" things should go. Not finished + moving things around, but now that the infrastructure is there, + it should be easy. + + v0.67 Bad keyboarding by REW caused this one out the door. Sorry. + No changes. + + v0.66 Through the Debian bugtracking system a bug report and + fix was sent my way, that deals with stupid optimization + trying to save some 768 bytes of memory, sacrificing "it + works" on a different architecture... (default char signedness) + + v0.65 Dancer Vesperman noted that mtr no longer traces past + a section of non-responding hosts. Apparently I added + a line in net.c that didn't make sense in mtr-0.56. I + can't find the reason for adding that line, so someone + who thinks (s)he needs it, should holler. + + v0.64 Philippe suggests to do the time_t thingy before socket.h. + Apparently, MAC OS X doesn't compile socket.h otherwise. + + v0.63 Suggestion by RCW: Add -lm at line 70 of Configure.in. + On my system no ill effects ensued, so this version released + so that he can test if it still works on his system. + + Let me add that it's stupid that I have to specify that this + this program now requires Automake version 1.5 to build, where + Automake was intended to make software independent of different + versions of build software! + + For those concerned about the above statement: If you're just + trying to compile and use MTR, there is no need for automake. + Just when you're messing with the configure and build system of + mtr is automake a tool you need. + + v0.62 Apparently someone changed gethostbyname into gethostbyname2 + in mtr.c in an attempt to add IPV6 support. For systems without + ipv6 support, the old gethostbyname should be used! Linux + has the call even if you don't enable IPV6. Thanks Gary (rsub) + + v0.61 Attempt to get/print the local IP address. Now shows as + 0.0.0.0 :-( Hints and tips appreciated! -- REW + Lots of blank space reformatting. + moved the interface address setting to net.c (where it + belongs). + + v0.60 John Thacker submitted a surprisingly simple patch to + enable linking against GTK2. (up to 2.4.0) + + v0.59 Josh Martin suggested to add some bounds checking to + the dynamic field code. This caused me to delve in, and + rewrite some things. Now 50 lines of code less, but cleaner + code. :-) + + v0.58 I don't remember. Forgot to update this. :-( Check the + patch. + + v0.57 Lots of whitespace cleanups. And a DNS fix: Don't do DNS + lookups in raw mode with -n specified. + + v0.56 Fixed compile warnings. Now compiles with -Wall. If your + compiler finds things mine didn't feel free to shout. + + v0.55 Cleanup patch. I'm going to do some maintenance on MTR, + but I want to be able to say: Can you see which version + fixed/broke things for you, so you're going to see a + bunch of new releases soon. + + v0.54 Added "scrolling" patch from Roland Illig, to allow + scrolling in text mode. I've always wanted this...... + + v0.53 Added fix for raw mode. + + v0.52 Mostly cleanups from Brett Johnson on MacOS X. It may + clean up some compilation problems on MacOS X as well. + + v0.51 Fixed the bug introduced by the previous select loop fix... + Thanks Evgeniy + + v0.50 Make "interface address" option work. + Changes to "select" loop to allow window resizes (select + interruption) to work. Thanks Mike! + + v0.49 Fix compilation problems on several platforms. + + v0.48 Draw names in red (GTK) or bold (Curses) if host doesn't + respond. + + v0.47 Fixed a (believed-) non-exploitable buffer overflow. + Thanks Damian. + + v0.46 Included patch to be able to specify outgoing interface + address. + + v0.45 People are pressuring me to release new versions with their + changes. That's fine. Now this version just adds dynamic + switching between numeric / dns names, and some minor + stuff I forgot. This release serves as a code-sync-release. + new version with even more new stuff in about two weeks! + I'm afraid I don't know how to fix the MacOS-X compilation + problems in the source. Help wanted... + + v0.44 David Stone adds the "last" column to the gtk version. + + v0.43 Compile fixes. + + v0.41 Added afr's patch to allow disabling of gtk without Robn's hack. + Made report mode report the newly added extra resolution. + + v0.40 Fixed some problems with HPUX and SunOS. + Included Olav Kvittem's patch to do packetsize option. + Made the timekeeping in micro seconds. + + v0.39 Forgot the parentheses around the previous fix... :-( + + v0.38 fixed some dubious code in dns.c (noted by someone's lint) + + v0.37 Added Bill Bogstad's "show the local host & time" patch. + Added R. Sparks' show-last-ping patch, submitted by Philip Kizer. + + v0.36 Added Craigs change-the-interval-on-the-fly patch. + Added Moritz Barsnick's "do something sensible if host not found" + patch. + Some cleanup of both Craigs and Moritz' patches. + + v0.35 Added Craig Milo Rogers pause/resume for GTK patch. + Added Craig Milo Rogers cleanup of "reset". (restart at the beginning) + Net_open used to send a first packet. After that the display-driver + got a chance to distort the timing by taking its time to + initialize. + + v0.34 Added Matt's nifty "use the icmp unreachables to do the timing" patch. + Added Steve Kann's pause/resume patch. + + v0.33 Fixed the Linux glibc resolver problems. + Fixed the off-by-one problem with -c option. + + v0.32 Fixed the FreeBSD bug detection stuff. + + v0.31 Fixed a few documentation issues. -- Matt + Changed the autoconf stuff to find the resolver library on + Solaris. -- REW + Cleaned up the autoconf.in file a bit. -- Matt. + + v0.30 Fixed a typo in the changelog (NEWS) entry for 0.27. :-) + added use of "MTR_OPTIONS" environment variable for defaults. + + v0.29 Lots of stuff. + Neato overview display by David Sward. + FreeBSD does wrong in the kernel the same that Solaris/x86 (see + note for 0.27 does right. It forces mtr to send bad packets.... + Adjusted "not too much at once" algorithm. Now probing + continues as long as not more than 5 hosts are unknown. + Returning packets usually allow us to do the first sweep + in one go. + + v0.28 DNS lookups are now suppressed if you don't want them. + + v0.27 + Fixed bug that showed up on Solaris/x86. + GTK mainloop now runs as it's supposed to. + + v0.26 + Added "-n" flag for numeric output. + fixed IP numbers displaying backwards. + GTK mainloop now runs at 10 packets per second. + - That's too much if there are only 3 hosts + - that's too little if there are 20 hosts. + -> Someone tell me how to change the "ping-timeout" + callback time in gtk. Can't find it in the docs. + The default for "hostname" is now "localhost" so that + you can start mtr without any arguments and later + fill in the host you want to trace to. + + v0.25 + Included two "raw" formats. One for separating GUI from + the setuid program, and one suitable for later parsing and + displaying. Volunteers wanted to separate the GTK + backend. Thanks to Bertrand Leconte for contributing + the format that's now called "split". + + v0.24 + Fixed number of probes. Accidentally was counted per + packet sent instead of per round of packets. + + v0.23 + Fixed Sparc alignment problem with statmalloc + + v0.22 + Roger has take over maintenance. + mtr now uses an "int" to pass options to the kernel. + Makes things work on Solaris and *BSD I'm told. + mtr doesn't fire off a flurry of packets when a new + second comes around. Instead they are spaced evenly + around the whole second. This allows people with a + relatively slow first link to do meaningful measurements + of whatever is behind that. + + v0.21 + mtr now drops root permissions after it acquires the raw + sockets it needs. + mtr should be a bit happier about building under SCO and + Solaris. + Fixed the problem with packets arriving after a reset. + + v0.20 + The build process for mtr now uses automake. + Fixed a build problem for Irix. + Now uses non-blocking DNS code, so mtr can attempt + to do reverse lookup on multiple hosts at once. + Fewer packets are sent out each cycle, so mtr + doesn't hog quite so much bandwidth. + + v0.19 + Fixed a type-o in curses.c + + v0.18 + Fixed the network code to work properly under FreeBSD. + Hopefully this will fix some other operating systems too. + Also, fixed a build problem and the DNS hanging bug. + + v0.17 + Fixed the configure script to always like with the math + library. Added an icon. + + v0.16 + Added one #include to select.c. Some people were unable + to build mtr without this line. + + v0.15 + Both the build process and the networking code have + been cleaned up and reorganized. mtr now builds + cleanly with GTK+ 0.99.8. + +--- Below is the contents of the old "Changelog file" that annoyed some +people as it didn't contain any recent changes/news. + +2002-03-06 Cougar + + If hop doesn't respond, draw its name in red (GTK) or bold (curses) + +2002-02-09 bodq + + Added --address option to bind to given IP address + +2001-04-15 root + + Added this file so that automake won't complain. + + Commented out the test for res_init in configure.in; + it does not work for GLIBC2 systems (e.g., RedHat 7+). + + Fixed the subordinate CHECK_LIBS on the test for res_mkquery, + so that they test for res_mkquery, not res_init. + diff --git a/README b/README new file mode 100644 index 0000000..1ff5c0f --- /dev/null +++ b/README @@ -0,0 +1,101 @@ +WHAT IS MTR? + + mtr combines the functionality of the 'traceroute' and 'ping' programs + in a single network diagnostic tool. + + As mtr starts, it investigates the network connection between the host + mtr runs on and a user-specified destination host. After it + determines the address of each network hop between the machines, + it sends a sequence of ICMP ECHO requests to each one to determine the + quality of the link to each machine. As it does this, it prints + running statistics about each machine. + + mtr is distributed under the GNU General Public License version 2. + See the COPYING file for details. + +INSTALLING + + If you're building this from a tarball, compiling mtr should be as + simple as: + + make + + It should first call the "configure" script and then run "make" again + with the makefile that "configure" just generated. + + If you're building from the git repository, you'll need to run: + + ./bootstrap.sh && ./configure && make + + After compiling, install: + + make install + + Note that mtr-packet must be suid-root because it requires access to + raw IP sockets. See SECURITY for security information. + + Older versions used to require a non-existent path to GTK for a + correct build of a non-gtk version while GTK was installed. This is + no longer necessary. ./configure --without-gtk should now work. + If it doesn't, try "make WITHOUT_X11=YES" as the make step. + + On Solaris, you'll need to use GNU make to build. + (Use 'gmake' rather than 'make'.) + + On Solaris (and possibly other systems) the "gtk" library may be + installed in a directory where the dynamic linker refuses to look when + a binary is setuid. Roman Shterenzon reports that adding + -Wl,-rpath=/usr/lib + to the commandline will work if you are using gnu LD. He tells me that + you're out of luck when you use the sun LD. That's not quite true, as + you can move the gtk libraries to /usr/lib instead of leaving them in + /usr/local/lib. (when the ld tells you that /usr/local/lib is untrusted + and /usr/lib is trusted, and you trust the gtk libs enough to want them + in a setuid program, then there is something to say for moving them + to the "trusted" directory.) + + Building on MacOS should not require any special steps. + +BUILDING FOR WINDOWS + + Building for Windows requires Cygwin. To obtain Cygwin, see + https://cygwin.com/install.html. When installing Cygwin, select + the 'lynx' package for installation. lynx is required by apt-cyg. + + Next, install apt-cyg for easy installation of the remaining + components. See https://github.com/transcode-open/apt-cyg. + + Install the packages required for building: + + apt-cyg install automake pkg-config make gcc-core libncurses-devel + + Build as under Unix: + + ./bootstrap.sh && ./configure && make + + Finally, install the built binaries: + + make install + +WHERE CAN I GET THE LATEST VERSION OR MORE INFORMATION? + + mtr is now hosted on github. + https://github.com/traviscross/mtr + + See the mtr web page at + http://www.BitWizard.nl/mtr/ + + Bug reports and feature requests should be submitted to the Github + bug tracking system. + + Patches can be submitted by cloning the Github repository and issuing + a pull request, or by email to me. Please use unified diffs. Usually + the diff is sort of messy, so please check that the diff is clean and + doesn't contain too much of your local stuff (for example, I don't + want/need the "configure" script that /your/ automake made for you). + + (There used to be a mailinglist, but all it got was spam. So + when the server was upgraded, the mailing list died.) + +-- REW + diff --git a/SECURITY b/SECURITY new file mode 100644 index 0000000..82c697c --- /dev/null +++ b/SECURITY @@ -0,0 +1,66 @@ +SECURITY ISSUES RELATED TO MTR + +mtr invokes a sub-process, mtr-packet, which requires extra privileges +to send custom packets, and there are security implications from +granting this. + +There are several different ways to provide the privileges: + +1. Add limited privileges on systems that support this. (Preferred.) +2. Run mtr as the root user. +3. Make mtr-packet a setuid-root binary. + +Details: + +1. Add limited privileges on systems that support this. + +Some operating systems allow binaries to be run with only the subset +of security privileges that are actually needed. + +Linux: +On Linux, privileges are known as capabilities. The only additional +capability that mtr-packet needs is cap_net_raw. To give this +capability to the mtr-packet binary, run the following command as root: + +# setcap cap_net_raw+ep mtr-packet + + +2. Run mtr as the root user. + +You can limit mtr usage to the root user by not putting a setuid bit +on the mtr-packet binary. In that case, the security implications are +minimal. + + +3. Make mtr-packet a setuid-root binary. + +The mtr-packet binary can be made setuid-root, which is what "make install" +does by default. + +When mtr-packet is installed as suid-root, some concern over security is +justified. mtr-packet does the following two things after it is launched: + +* mtr-packet open sockets for sending raw packets and for receiving + ICMP packets. +* mtr-packet drops root privileges by setting the effective uid to + match uid or the user calling mtr. +* If capabilities support is available, mtr-packet drops all privileged + capabilities. + +See main() in packet.c and init_net_state_privileged() in probe_unix.c +for the details of this process. + +This should limit the possibilities of using mtr to breach system security. +The worst case scenario is as follows: + +Due to some oversight in the mtr-packet code, a malicious user is able to +overrun one of mtr-packets's internal buffers with binary code that is +eventually executed. The malicious user is still not able to read +from or write to any system files other than those normally accessible +by the user running mtr. The only privileges gained are access to the raw +socket, which would allow the malicious user to listen to all ICMP packets +arriving at the system, and to send forged packets with arbitrary contents. + + +If you have further questions or comments about security issues, +please see the README file for details on how to submit them. diff --git a/TODO b/TODO new file mode 100644 index 0000000..0673b24 --- /dev/null +++ b/TODO @@ -0,0 +1,128 @@ + +Hi everyone, + +This is the "todo" file for mtr. I just realized that some people +might think that this is all in MY queue to implement. That is not +true: This is the "for everybody" todo list. Feel free to pick a +"project" and implement something off this list. + +Students: Feel free to take up one of these as a programming exercise +for one of your courses. + +Everybody: If you want to start on something, contact me first, so +that the effort isn't wasted by someone who finishes just a tad +earlier. I'll happily provide "coaching" to anyone who wants to +implement something on this list. That way we get the design of +these things the way I like them. This should result in a better +maintainable mtr. + +Oh, Feel free to provide suggestions for this list. + + +-- REW + +---------------------------------------------------------------------- + + +- Stuff to implement: + + - Allow mtr to log the return packets, for later analysis. + Done: 0.25 . Todo: allow the user interface(s) to work while + still logging to a file. Write a "logfile displaying" mode to + mtr. + + - Request timestamping at the remote site. + Andreas Fasbender has an algorithm that will allow us to + convert these measurements into one-way measurements, not just + round-trip. + + - allow "keyboard navigation" in the GTK version. + + - Keep all packets and make the "best" and "worst" columns show the + xx-th percentile.... + + - Can the reports generated also include any secondary servers? In + the interactive mode, any new servers that are found in the + traceroute are added to the list, but it seems to only include + one set of servers when using the -r option. + + - Being able to expand the "column width" of the hosts listed would + be nice, too. + + +- Bugs to fix? + + - Do something useful if host couldn't be resolved. + -- Done. + + - Revert to curses mode even if DISPLAY is set, but a problem + prevents us from running in X11 mode. + --> The problem is that gtk_init simply calls exit for us if + it finds a problem. Tricky! Suggestions welcome. + --> Call "gtk_check_init" when available. (i.e. new enough + (1.2?) GTK version). + +- Nice to have: + + - stop sending packets when a new host is getting entered. + + - Show state ("looking up host") while doing the DNS lookup for a new + host. + + - to have a choice of icmp, tcp, and udp pings. -- Matt Martini + + - Autoconf 2.13 has a neat function that can be used to find the + res_init function: + + AC_SEARCH_LIBS(res_init, bind resolv, , + AC_MSG_ERROR(No resolver library found)) + + At the moment (march 1999) autoconf 2.13 is still too new to require + everyone to upgrade. About a year from now we can put this in.... + + - Implement rfc2317 mechanism to do reverse lookups for networks that + have DNS delegations on non-octet boundaries. -- Daniel Bergstrom + (noa@melody.se) + + - The longer MTR runs, the less meaningful the packet loss + statistic. Or more meaningful, depending on your point of view. + Perhaps MTR should use a circular buffer of some configurable + number of results, and calculate the loss against that. -- Jacob Elder + + - It would be nice if the window size wasn't fixed. If I'm only 5 + hops from the host I'm monitoring, MTR wastes a lot of screen real + estate. -- Jacob Elder + + - Colors in the curses version. -- Amix + + - If we run a mtr to monitor a connection it would be nice if the time at + which mtr was started is print somewhere. -- Sebastian Ganschow + + + +------------------------------------------------------------------------ + +Things that shouldn't be on the TODO list because they're done. ;-) + + - Allow a toggle between hostname/IP number display. (for example a + click on the hostname could revert to ip number display in gtk version. + curses: "n" key toggles hostnames/ipnumbers?) + + - Allow mtr to also send larger packets. + This will enable us to get a feel for the speed of the links + we're traversing. (Van Jacobson was working on this His tool + was slow, mtr will rock with this feature.... :-) + (Anybody have the statistics experience to tell me how + to do the data analysis?) + -- DONE. Thanks to Olav Kvittem ... + + - The "don't probe all hosts at once" strategy can be improved a bit. + It should not probe more than 10 unknown hosts, but the counter need + not be reset at the start of the "round". This way if you probe + slowly (relative to the RTT time to the end host), it can probe + all hosts in the first "round". + -- DONE. + + - Read environment variable "MTR_DEFAULTS" as a commandline before + parsing the commandline. -- DONE. (ok it's MTR_OPTIONS.) + diff --git a/bash-completion/mtr b/bash-completion/mtr new file mode 100644 index 0000000..4d9d5dd --- /dev/null +++ b/bash-completion/mtr @@ -0,0 +1,68 @@ +_mtr_module() +{ + local cur prev OPTS + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + case $prev in + '-F'|'--filename') + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -- $cur) ) + return 0 + ;; + '-a'|'--address') + COMPREPLY=( $(compgen -W "ADDRESS" -- $cur) ) + return 0 + ;; + '-f'|'--first-ttl'|'-m'|'--max-ttl'|'-m'|'--max-unknown'|'-B'|'--bitpattern'|'-Q'|'--tos'|'-c'|'--report-cycles') + COMPREPLY=( $(compgen -W "NUMBER" -- $cur) ) + return 0 + ;; + '-P'|'--port'|'-L'|'--localport') + COMPREPLY=( $(compgen -W "PORT" -- $cur) ) + return 0 + ;; + '-s'|'--psize') + COMPREPLY=( $(compgen -W "SIZE" -- $cur) ) + return 0 + ;; + '-i'|'--interval'|'-G'|'--gracetime'|'-Z'|'--timeout') + COMPREPLY=( $(compgen -W "SECONDS" -- $cur) ) + return 0 + ;; + '-M'|'--mark') + COMPREPLY=( $(compgen -W "MARK" -- $cur) ) + return 0 + ;; + '--displaymode') + COMPREPLY=( $(compgen -W "{0..2}" -- $cur) ) + return 0 + ;; + '-y'|'--ipinfo') + COMPREPLY=( $(compgen -W "{0..4}" -- $cur) ) + return 0 + ;; + '-o'|'--order') + COMPREPLY=( $(compgen -W "LDRSNBAWVGJMXI" -- $cur) ) + return 0 + ;; + esac + case $cur in + -*) + OPTS=' + --filename --inet --inet6 --udp --tcp --address --first-ttl + --max-ttl --max-unknown --port --localport --psize --bitpattern + --interval --gracetime --tos --mpls --timeout --mark --report + --report-wide --report-cycles --json --xml --csv --raw --split + --curses --displaymode --gtk --no-dns --show-ips --order --ipinfo + --aslookup --help --version + ' + COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) + return 0 + ;; + esac + COMPREPLY=( $(compgen -W "ip_address hostname" -- $cur) ) + return 0 +} +complete -F _mtr_module mtr diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..15ae416 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +aclocal $ACLOCAL_OPTS +autoheader +automake --add-missing --copy --foreign +autoconf --force diff --git a/build-aux/git-version-gen b/build-aux/git-version-gen new file mode 100755 index 0000000..bd2c4b6 --- /dev/null +++ b/build-aux/git-version-gen @@ -0,0 +1,226 @@ +#!/bin/sh +# Print a version string. +scriptversion=2016-05-08.18; # UTC + +# Copyright (C) 2007-2016 Free Software Foundation, Inc. +# +# 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 3 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, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# As with any generated file in a VC'd directory, you should add +# /.version to .gitignore, so that you don't accidentally commit it. +# .tarball-version is never generated in a VC'd directory, so needn't +# be listed there. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .version and +# .tarball-version will exist in distribution tarballs. +# +# EXTRA_DIST = $(top_srcdir)/.version +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + + +me=$0 + +version="git-version-gen $scriptversion + +Copyright 2011 Free Software Foundation, Inc. +There is NO warranty. You may redistribute this software +under the terms of the GNU General Public License. +For more information about these matters, see the files named COPYING." + +usage="\ +Usage: $me [OPTION]... \$srcdir/.tarball-version [TAG-NORMALIZATION-SED-SCRIPT] +Print a version string. + +Options: + + --prefix PREFIX prefix of git tags (default 'v') + --fallback VERSION + fallback version to use if \"git --version\" fails + + --help display this help and exit + --version output version information and exit + +Running without arguments will suffice in most cases." + +prefix=v +fallback= + +while test $# -gt 0; do + case $1 in + --help) echo "$usage"; exit 0;; + --version) echo "$version"; exit 0;; + --prefix) shift; prefix=${1?};; + --fallback) shift; fallback=${1?};; + -*) + echo "$0: Unknown option '$1'." >&2 + echo "$0: Try '--help' for more information." >&2 + exit 1;; + *) + if test "x$tarball_version_file" = x; then + tarball_version_file="$1" + elif test "x$tag_sed_script" = x; then + tag_sed_script="$1" + else + echo "$0: extra non-option argument '$1'." >&2 + exit 1 + fi;; + esac + shift +done + +if test "x$tarball_version_file" = x; then + echo "$usage" + exit 1 +fi + +tag_sed_script="${tag_sed_script:-s/x/x/}" + +nl=' +' + +# Avoid meddling by environment variable of the same name. +v= +v_from_git= + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || v= + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test "x$v" = x \ + && echo "$0: WARNING: $tarball_version_file is missing or damaged" 1>&2 +fi + +if test "x$v" != x +then + : # use $v +# Otherwise, if there is at least one git commit involving the working +# directory, and "git describe" output looks sensible, use that to +# derive a version string. +elif test "`git log -1 --pretty=format:x . 2>&1`" = x \ + && v=`git describe --abbrev=4 --match="$prefix*" HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && v=`printf '%s\n' "$v" | sed "$tag_sed_script"` \ + && case $v in + $prefix[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + commit_list=`git rev-list "$vtag"..HEAD 2>/dev/null` \ + || { commit_list=failed; + echo "$0: WARNING: git rev-list failed" 1>&2; } + numcommits=`echo "$commit_list" | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + test "$commit_list" = failed && v=UNKNOWN + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; + v_from_git=1 +elif test "x$fallback" = x || git --version >/dev/null 2>&1; then + v=UNKNOWN +else + v=$fallback +fi + +v=`echo "$v" |sed "s/^$prefix//"` + +# Test whether to append the "-dirty" suffix only if the version +# string we're using came from git. I.e., skip the test if it's "UNKNOWN" +# or if it came from .tarball-version. +if test "x$v_from_git" != x; then + # Don't declare a version "dirty" merely because a time stamp has changed. + git update-index --refresh > /dev/null 2>&1 + + dirty=`exec 2>/dev/null;git diff-index --name-only HEAD` || dirty= + case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; + esac +fi + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +printf %s "$v" + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/mangen.sh b/build-aux/mangen.sh new file mode 100755 index 0000000..2c12d87 --- /dev/null +++ b/build-aux/mangen.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# +# Generate the man pages. +# +# We are just here to substitute the @VERSION@ string with our real version. +# + +if [ $# -lt 3 ]; then + echo Usage: mangen.sh VERSION IN OUT + exit 1 +fi + +sed -e "s|@VERSION[@]|$1|g" $2 >$3 diff --git a/build-aux/mtr.bat b/build-aux/mtr.bat new file mode 100755 index 0000000..044e4a7 --- /dev/null +++ b/build-aux/mtr.bat @@ -0,0 +1,32 @@ +@echo off +rem +rem mtr -- a network diagnostic tool +rem Copyright (C) 2016 Matt Kimball +rem +rem This program is free software; you can redistribute it and/or modify +rem it under the terms of the GNU General Public License version 2 as +rem published by the Free Software Foundation. +rem +rem This program is distributed in the hope that it will be useful, +rem but WITHOUT ANY WARRANTY; without even the implied warranty of +rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +rem GNU General Public License for more details. +rem +rem You should have received a copy of the GNU General Public License +rem along with this program; if not, write to the Free Software +rem Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +rem + +rem Assume the path of this batch file is the mtr installation location +set "MTR_DIR=%~dp0" + +set "MTR_BIN=%MTR_DIR%\bin" + +rem ncurses needs to locate the cygwin terminfo file +set "TERMINFO=%MTR_DIR%\terminfo" + +rem mtr needs to know the location to the packet generator +set "MTR_PACKET=%MTR_BIN%\mtr-packet.exe" + +rem Pass along commandline arguments +"%MTR_BIN%\mtr" %* diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..a08ce67 --- /dev/null +++ b/configure.ac @@ -0,0 +1,246 @@ +AC_PREREQ([2.59]) +AC_INIT([mtr], + [m4_esyscmd([build-aux/git-version-gen .tarball-version])], + [R.E.Wolff@BitWizard.nl], [], + [http://www.BitWizard.nl/mtr/]) +AC_CONFIG_SRCDIR([ui/mtr.c]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_USE_SYSTEM_EXTENSIONS +AM_INIT_AUTOMAKE([ + 1.7.9 + foreign + subdir-objects +]) + +# --enable-silent-rules +m4_ifdef([AM_SILENT_RULES], + [AM_SILENT_RULES([yes])], + [AC_SUBST([AM_DEFAULT_VERBOSITY], [1])]) + +AC_CANONICAL_HOST +AC_PROG_CC + +# Check pkg-config availability. +m4_ifndef([PKG_PROG_PKG_CONFIG], + [m4_fatal( +[Could not locate the pkg-config autoconf macros. These are usually located +in /usr/share/aclocal/pkg.m4. If your macros are in a different location, +try setting the environment variable ACLOCAL_OPTS="-I/other/macro/dir" +before running ./bootstrap.sh again.]) +]) +PKG_PROG_PKG_CONFIG + +AM_CONDITIONAL([CYGWIN], [test "$host_os" = cygwin]) + +# Check bytes in types. +AC_CHECK_SIZEOF([unsigned char], [1]) +AC_CHECK_SIZEOF([unsigned short], [2]) +AC_CHECK_SIZEOF([unsigned int], [4]) +AC_CHECK_SIZEOF([unsigned long], [4]) + +# Check headers. +AC_CHECK_HEADERS([ \ + arpa/nameser_compat.h \ + curses.h \ + cursesX.h \ + error.h \ + fcntl.h \ + linux/icmp.h \ + ncurses.h \ + ncurses/curses.h \ + netinet/in.h \ + socket.h \ + sys/cdefs.h \ + sys/limits.h \ + sys/socket.h \ + stdio_ext.h \ + sys/types.h \ + sys/xti.h \ + values.h \ +]) + +# Check functions. +AC_CHECK_FUNCS([ \ + __fpending \ + fcntl \ +]) + +AC_CHECK_FUNC([error], [with_error=no], + [AC_CHECK_FUNCS([verr verrx vwarn vwarnx], [with_error=yes], + [AC_MSG_ERROR([cannot find working error printing function]) + ]) +]) +AM_CONDITIONAL([WITH_ERROR], [test "x$with_error" = "xyes"]) + +AC_CHECK_FUNC([getopt_long], [with_getopt=no], [with_getopt=yes]) +AS_IF([test "x$with_getopt" = "xno"], [ + AC_DEFINE([HAVE_GETOPT], [1], [Define if libc has getopt_long]) +]) +AM_CONDITIONAL([WITH_GETOPT], [test "x$with_getopt" = "xyes"]) + +AC_CHECK_LIB([m], [floor], [], [AC_MSG_ERROR([No math library found])]) + +# Find GTK +AC_ARG_WITH([gtk], + [AS_HELP_STRING([--without-gtk], [Build without the GTK+2.0 interface])], + [], [with_gtk=yes]) +AS_IF([test "x$with_gtk" = "xyes"], + [PKG_CHECK_MODULES([GTK], [gtk+-2.0], + [AC_DEFINE([HAVE_GTK], [1], [Define if gtk+-2.0 library available])], + [with_gtk=no]) +]) +AM_CONDITIONAL([WITH_GTK], [test "x$with_gtk" = xyes]) + +# Find ncurses +AC_ARG_WITH([ncurses], + [AS_HELP_STRING([--without-ncurses], [Build without the ncurses interface])], + [], [with_ncurses=yes]) +AS_IF([test "x$with_ncurses" = "xyes"], + + # Prefer ncurses over curses, if both are available. + # (On Solaris 11.3, ncurses builds and links for us, but curses does not.) + [AC_SEARCH_LIBS( + [initscr], [ncurses curses], + [AC_DEFINE([HAVE_CURSES], [1], [Define if a curses library available])], + [with_ncurses=no]) +]) +AM_CONDITIONAL([WITH_CURSES], [test "x$with_ncurses" = xyes]) + +AC_CHECK_LIB([cap], [cap_set_proc], [], + AS_IF([test "$host_os" = linux-gnu], + AC_MSG_WARN([Capabilities support is strongly recommended for increased security. See SECURITY for more information.]))) + +# Enable ipinfo +AC_ARG_WITH([ipinfo], + [AS_HELP_STRING([--without-ipinfo], [Do not try to use ipinfo lookup at all])], + [], [with_ipinfo=yes]) +AM_CONDITIONAL([WITH_IPINFO], [test "x$with_ipinfo" = "xyes"]) +AS_IF([test "x$with_ipinfo" = "xyes"], [ + AC_DEFINE([HAVE_IPINFO], [1], [Define when ipinfo lookups are in use]) +]) + +AC_ARG_ENABLE([ipv6], + [AS_HELP_STRING([--disable-ipv6], [Do not enable IPv6])], + [WANTS_IPV6=$enableval], [WANTS_IPV6=yes]) + +AS_IF([test "x$WANTS_IPV6" = "xyes"], [ + AC_DEFINE([ENABLE_IPV6], [1], [Define to enable IPv6]) + USES_IPV6=yes +]) + +AC_CHECK_FUNC([socket], [], + [AC_CHECK_LIB([socket], [socket], [], [AC_MSG_ERROR([No socket library found])])]) + +AC_CHECK_FUNC([gethostbyname], [], + [AC_CHECK_LIB([nsl], [gethostbyname], [], [AC_MSG_ERROR([No nameservice library found])])]) + +# Find resolver library. +AC_CHECK_FUNC([res_query], [RESOLV_LIBS=""], [ + AC_CHECK_LIB([resolv], [__res_query], [RESOLV_LIBS="-lresolv"], [ + AC_CHECK_LIB([resolv], [res_query], [RESOLV_LIBS="-lresolv"], [ + AC_CHECK_LIB([bind], [res_query], [RESOLV_LIBS="-lbind"], [ + AC_MSG_ERROR([No resolver library found]) + ]) + ]) + ]) +]) +dnl MacOS has res_query in libc, but needs libresolv for dn_expand(). +AS_IF([test "x" = "x$RESOLV_LIBS"], [ + AC_CHECK_LIB([resolv], [dn_expand], [RESOLV_LIBS="-lresolv"]) +]) +AC_SUBST([RESOLV_LIBS]) + +# Check errno and socket data types. +AC_CHECK_DECLS([errno], [], [], [[ +#include +#include + ]]) + +AC_CHECK_TYPE([socklen_t], + [AC_DEFINE([HAVE_SOCKLEN_T], [], [Define if your system has socklen_t])], [], + [[#include +#ifdef HAVE_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif]]) + +AC_CHECK_TYPES([time_t], [], [], [[ +#include +]]) + +# Add C flags to display more warnings +AC_MSG_CHECKING([for C flags to get more warnings]) +ac_save_CFLAGS="$CFLAGS" + +AS_IF([test "x$ac_cv_c_compiler_gnu" = "xyes"], [ + dnl gcc is the easiest C compiler + warning_CFLAGS="-Wall" + # Check if compiler supports -Wno-pointer-sign and add it if supports + CFLAGS_saved="$CFLAGS" + CFLAGS="$CFLAGS -Wno-pointer-sign" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int foo;]])], + [warning_CFLAGS="${warning_CFLAGS} -Wno-pointer-sign"], []) + CFLAGS="$CFLAGS_saved" +], [ + dnl Vendor supplied C compilers are a bit tricky + AS_CASE([$host_os], + dnl SGI IRIX with the MipsPRO C compiler + [irix*], [ + CFLAGS="$CFLAGS -fullwarn" + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM( + [[#include ]], + [[printf("test");]])], + [warning_CFLAGS="-fullwarn"], [] + ) + ], + dnl SunOS 4.x with the SparcWorks(?) acc compiler + [sunos*], [ + AS_IF([test "$CC" = "acc"], [ + CFLAGS="$CFLAGS -vc" + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM( + [[#include ]], + [[printf("test");]])], + [warning_CFLAGS="-vc"], [] + ) + ]) + ] + dnl Unknown, do nothing + [*], [ + warning_CFLAGS="none" + ] + ) +]) +CFLAGS="$ac_save_CFLAGS" + +AS_IF([test "$warning_CFLAGS" = "none"], [ + AC_MSG_RESULT([none]) +], [ + CFLAGS="$CFLAGS $warning_CFLAGS" + AC_MSG_RESULT([$warning_CFLAGS]) +]) + +# bash-completion +AC_ARG_WITH([bashcompletiondir], + AS_HELP_STRING([--with-bashcompletiondir=DIR], [Bash completions directory]), + [], + [AS_IF([`$PKG_CONFIG --exists bash-completion`], [ + with_bashcompletiondir=`$PKG_CONFIG --variable=completionsdir bash-completion` + ], [ + with_bashcompletiondir=${datadir}/bash-completion/completions + ]) +]) +AC_SUBST([bashcompletiondir], [$with_bashcompletiondir]) +AC_ARG_ENABLE([bash-completion], + AS_HELP_STRING([--disable-bash-completion], [do not install bash completion files]), + [], [enable_bash_completion=yes] +) +AM_CONDITIONAL([BUILD_BASH_COMPLETION], [test "x$enable_bash_completion" = xyes]) + +# Prepare config.h, Makefile, and output them. +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/img/mtr_icon.xpm b/img/mtr_icon.xpm new file mode 100644 index 0000000..e7141d2 --- /dev/null +++ b/img/mtr_icon.xpm @@ -0,0 +1,182 @@ +/* XPM */ +static const char * mtr_icon[] = { +"48 48 131 2", +" c #000000", +". c #020204", +"+ c #3E7E3C", +"@ c #62BA64", +"# c #4E9E4C", +"$ c #628664", +"% c #6EDA6C", +"& c #7A9E7C", +"* c #468E44", +"= c #6ACA6C", +"- c #224224", +"; c #56AE54", +"> c #326234", +", c #567E54", +"' c #7ABA7C", +") c #3A723C", +"! c #629E64", +"~ c #428644", +"{ c #6E926C", +"] c #7AEA7C", +"^ c #4A964C", +"/ c #62C264", +"( c #52A654", +"_ c #4A7E4C", +": c #82AE84", +"< c #366A34", +"[ c #5AB65C", +"} c #4A764C", +"| c #6AD26C", +"1 c #8EC68C", +"2 c #7A9A7C", +"3 c #7AE27C", +"4 c #82A684", +"5 c #568E54", +"6 c #568654", +"7 c #3E7A3C", +"8 c #6AC26C", +"9 c #3E6A3C", +"0 c #468244", +"a c #56A254", +"b c #4E8E4C", +"c c #5EAE5C", +"d c #86C684", +"e c #427244", +"f c #62A664", +"g c #769A74", +"h c #82F284", +"i c #5A9A5C", +"j c #5AA65C", +"k c #428244", +"l c #66BE64", +"m c #6A8E6C", +"n c #76DA74", +"o c #7E9E7C", +"p c #2A522C", +"q c #5E825C", +"r c #82BE84", +"s c #669E64", +"t c #6E966C", +"u c #529A54", +"v c #62C664", +"w c #4A824C", +"x c #366E34", +"y c #72D674", +"z c #427E3C", +"A c #5EBE5C", +"B c #52A254", +"C c #628A64", +"D c #4A924C", +"E c #6ACE6C", +"F c #5AB25C", +"G c #326634", +"H c #568254", +"I c #7ABE7C", +"J c #3A763C", +"K c #468A44", +"L c #7EEE7C", +"M c #4E9A4C", +"N c #56AA54", +"O c #86B684", +"P c #62B664", +"Q c #162A14", +"R c #529E54", +"S c #72DA74", +"T c #76A274", +"U c #4A8E4C", +"V c #264A24", +"W c #5AAE5C", +"X c #5EA25C", +"Y c #468644", +"Z c #729274", +"` c #7EEA7C", +" . c #4E964C", +".. c #66C264", +"+. c #56A654", +"@. c #4E7E4C", +"#. c #8AB28C", +"$. c #3A6A3C", +"%. c #5EB65C", +"&. c #527A54", +"*. c #6ED26C", +"=. c #92C694", +"-. c #7E9A7C", +";. c #7AE67C", +">. c #86AA84", +",. c #5E865C", +"'. c #6AC66C", +"). c #3E6E3C", +"!. c #5AA25C", +"~. c #4E924C", +"{. c #5EB25C", +"]. c #467244", +"^. c #86F284", +"/. c #5AAA5C", +"(. c #76DE74", +"_. c #829E84", +":. c #325E34", +"<. c #66A264", +"[. c #729674", +"}. c #66C664", +"|. c #4E824C", +"1. c #3A6E3C", +"2. c #76D674", +"3. c #427E44", +"4. c #62BE64", +"5. c #668A64", +"6. c #6ECE6C", +"7. c #366634", +"8. c #5A825C", +"9. c #7EBE7C", +"0. c #3E763C", +"G 7.G G G G G 7.G < z . .* k 1.G G G G G G 0.K x 7.7.7.7.7.< J Y D M D ) 7.7.7.7.7.7.< 7.7.7.7.", +"7.G 7.> > > 7.G ) D M K 7 < > > > > > > > > + * < G G G G G G 7.G x k * M K x G G G G G G G G G ", +"G G 7.G > > < ~ M D + G > > G > > 7.7.7.7.> 7 D x 7.7.7.7.7.G 7.7.G < ) ~ D M 7 7.7.7.7.7.7.7.7.", +"7.7.G > 7.< * M ~ x > G > G > > 7.> > > > G 7 * 1.G G G G G 7.G G 7.G G G J * M ~ < G G G G G G ", +"> G G > x D M k < G > G > > G > > G G G > 7.7 * x 7.7.7.7.G 7.7.G 7.7.7.7.G x ~ # Y < 7.7.7.7.7.", +"G > > x ^ .0.G G G 7.> > > > > G > > > 7.> 7 U 1.G G G G 7.G G 7.G G G G 7.G < k # K < G G G G ", +"G G < .^ J G G 7.G 7.> G > > > > 7.7.> 7.7.7 * x 7.7.7.G 7.7.G 7.7.7.7.G 7.G G 7.+ B ~ < 7.7.7.", +"> < D M 7 G > > > G > G > > G > 7.> > G > > + U 1.G G G 7.G G 7.G G G G 7.G 7.7.G < k B + < G G ", +"G k # k < 7.7.G > G > > G > > > > G x k .R F A N R U 7 x 7.G 7.7.7.7.G 7.G G 7.G G < K # J $.8.", +") ( K < > > G G 7.G G > > > G G + M +.B M D B W ^ ^ # ( +.^ ) G G G G 7.G 7.7.G 7.7.7.x u ! & o ", +"^ .x > > > > G 7.7.G > > > 0. .+.M K + ) < + D ) < J k * R +.D x 7.G 7.G G 7.G G G 7.e T =.& H ", +"+.+ < G G G 7.G > G G > G ~ ( B ~ ) G > G > 7 U < 7.7.G < J * B B 7 7.G 7.7.G 7.7.9 5.o Z s a ) ", +"D x > > > > > G > G G < K ( D 0.> G 7.> G 7.+ * < G 7.7.G G < + # ( + G G 7.G $.,.2 [.$ ).0.( k ", +"7 7.G G G G 7.G G > 7.K +.K x > G > > G > > 7 D < 7.G G 7.7.G 7.) M +.7 7.< &.[.2 C &.7.7.7.* B ", +"x 7.> > > > > 7.7.G k +.* < > > > G > 7.G G 7 * < 7.7.G G 7.G G G ) M B &.[.o m &.7.G 7.G 7.) %.", +"7.G 7.7.7.G 7.G G x B ^ ) > G > 7.> 7.> > > + D 7.G G 7.7.G 7.7.7.< w r #.{ , 9 7.7.7.G 7.G < ( ", +"> G G > 7.G > 7.7.* ( 7 G > > > > > G 7.1.z B F D 7 < G 7.G G G $.q o : I _ < > G G 7.G 7.G 7.~ ", +"7.7.> G > 7.7.> x +.* 7.> > G > G G < k # # +.[ B N ( k G 7.$.&.2 2 C |.B D < 7.7.7.G 7.G 7.G < ", +"> 7.> G > G G > ~ +.+ > > > > > > < K R K + K M 3.K B W K } Z -.m &.< < Y W ) G G 7.G 7.G 7.G 7.", +"> G G > > > 7.G # ^ ) > G > G > 7.~ # + < > 7 * < G J j d 4 { , $.G 7.G 7 F + 7.7.G 7.G 7.G 7.G ", +"> > > G G > 7.7.N * 7.> > > > > J D Y > G 7.3.D < ).$ 4 1 ' e 7.7.G G G ) # D < G G 7.G 7.G 7.7.", +"> 7.G > > G > x +.Y G > G > G > Y ~.x G > > 7 ~.8.-.2 5.i A 7 G G 7.7.7.< D # x 7.7.G 7.G 7.G G ", +"< < x x < x x + ; * x x x x x ) ^ .1.) 1.) 5 r o t 6 0.K A D J ) ) ) J 7 .F k 7 7 7 7 J 0.0.x ", +"D D * * D U U # A ( * * * K * * N W U U * K X 9.<.D K K R ..N U * * * * U ( A D K K K K U * K ) ", +"7 z z 7 7 z 7 K {.M 7 7 7 0.0.7 # B J 7 7 7 U R 0.7 7 7 D / D 0.0.7 0.J 7 .F + 0.J 0.J 0.J 0.< ", +"G > > > G > G < N K 7.> > G G > ~ D 1.G > > 3.U G G G G * A J G G G G 7.x D # x G 7.G 7.G 7.G G ", +"G G G > G > G < ( * 7.> > > > > J ^ K G G > + D 7.7.7.7 N ( < 7.7.7.G G ) B * < G 7.G 7.G 7.7.G ", +"G G > > > 7.G G # .) G > > G > > k u 0 7.G z U G G 7 ( F + < G G G 7.7.7 W + < 7.G 7.G 7.G G 7.", +"> 7.G 7.> > > 7.~ B + > > > G G > x ~ # .+ ~ .7 D ; ; k G 7.7.7.G G G K W x G 7.G 7.G 7.7.G 7.", +"7.G 7.> G 7.7.G < ( D < G > > > > > < z D # %.A +.B .7 < K = G G 7.7.J B D < 7.G 7.G 7.G G 7.x ", +"G G G > > > > G G K ( 7 > > > > > G > G 1.+ R N K 7 x G ` (./.7.G G < K ; J G 7.G 7.G 7.7.G 7.Y ", +"7.G G > > G G G G x B M ) > > G G > G G > > + D G G 7.7.;.E 4.G 7.7.7 N K < 7.G 7.G 7.G G 7.< ; ", +"1.> G G > > > ` ] %.+ L 3 (.S S +.> /.L ;.(.S y 7.G 6.3 6.= = y ;...B # ` ] @ *.;.S B 7.G G J A ", +"z G > > G 7.7.% = '.n ~.K %.= = = y *.u .4.= = {.7.c /.= = ..u M M B J y = E 6.!.b z G 7.< D W ", +"M ) G > > G G y = ..7.. . +.@ '.= @ V . . 1.}.= {.. G 7.6.= B . . . . 7.y = ..> . . . . G ) [ D ", +"[ + G G G > 7.S = a . . . . 4.E = z . . . G y = @ . . G (.6.a . . . . G y = .. . . . . < ^ W J ", +"N B ) G G G 7.y = .. . . 7.}.6.'.0.. . . G (.= %.. . ~ y = B . . . 7.G *.'.0 . . . G 7.7 [ K < ", +"+ [ D < > > G % = R . . > > }.6.}.0.. . +.+.3 = {.. . B (.E W . . G G 7.y = K . . G 7.x +.( J G ", +"< D {.Y 7.> 7.y = u . . > > ..6.'.0.. . K U (.= %.. . + y = +.. . 7.G 7.y }.Y . . G < ^ F z 7.7.", +"> x B ; + G > y = ( . . G > }.6.}.0.. . > G (.= {.. . G (.= {.. . G 7.G y = k . . < * [ U < G G ", +"> G J +.N + G S '.R . . > G }.6.}.0.. . G G (.= [ . . G *.= }.J . G 7.G *.= ~ . . ~ %.^ x 7.7.7.", +"G > G 7 +.N z y E B . . G > ..6.'.0.. . > > 3 = {.. . 7.'.= = = (.3 p 7.y '.0 . . {.# ) 7.G G G ", +"G > G G 7 ( ; @ N ~.. . > > 4.W +.1.. . G 7.= /.B . . G > {.l 4.%.R G 7.@ /.+ . . M ) G 7.7.7.7.", +"> 7.> > G 0.# %.^ . . . > > > > > . . > > > + D . . . 7.7.7.- - Q . . G < . . . . ) G 7.G G G G ", +"G G 7.> 7.7.) ^ F . . < G > > > G . . 7.G > 3.D 7.. G G G 7.7.. . . . x D F . . < 7.G 7.7.7.7.7.", +"> 7.G G > G G < ~ ( [ B + < > G > > > > > 7.+ U G 7.7.7.7.G G 7.G x K W [ R + < G G 7.G G G G G ", +"> > > G > > > > 7.J D +.[ ; K x G 7.G 7.> 7.) z G G G G 7.7.< 0. .F F B ~ 1.G 7.7.G 7.7.7.7.7.7.", +"G 7.7.> > G 7.G > 7.< M E | +.7 > > > > G > x ) 7.> 7.7.G G < k F % / ~ G G 7.G G 7.G G G G G G "}; diff --git a/man/mtr-packet.8.in b/man/mtr-packet.8.in new file mode 100644 index 0000000..9691f74 --- /dev/null +++ b/man/mtr-packet.8.in @@ -0,0 +1,455 @@ +.TH MTR-PACKET 8 "@VERSION@" "mtr-packet" "System Administration" +.HP 7 +.SH NAME +mtr-packet - send and receive network probes +.SH DESCRIPTION +.B mtr-packet +is a tool for sending network probes to measure network connectivity and +performance. Many network probes can be sent simultaneously by a single +process instance of +.B mtr-packet +and additional probes can be generated by an instance of +.B mtr-packet +which already has network probes in flight. It is intended to be used +by programs which invoke it with Unix pipes attached to its standard input +and output streams. +.LP +.B mtr-packet +reads command requests from +.IR stdin , +each separated by a newline character, and responds with command replies to +.IR stdout , +also each separated by a newline character. The syntactic structure of +requests and replies are the same. The following format is used: +.LP +.RS +.I TOKEN +.I COMMAND +[\c +.I ARGUMENT-NAME +.I ARGUMENT-VALUE +\&...] +.RE +.LP +.I TOKEN +is a unique integer value. The same value will be used as the +.I TOKEN +for the response. This is necessary for associating replies with requests, +as commands may be completed in a different order than they are requested. +The invoker of +.B mtr-packet +should always use the +.I TOKEN +value to determine which command request has completed. +.LP +.I COMMAND +is a string identifying the command request type. A common command is +.BR send-probe , +which will transmit one network probe. +.LP +.I ARGUMENT-NAME +strings and +.I ARGUMENT-VALUE +strings always come in pairs. It is a syntactic error to provide an +.I ARGUMENT-NAME +without a corresponding +.IR ARGUMENT-VALUE . +Valid +.I ARGUMENT-NAME +strings depend on the +.I COMMAND +being used. +.SH REQUESTS +.TP +.B send-probe +Send a network probe to a particular IP address. Either an +.B ip-4 +or +.B ip-6 +argument must be provided. +A valid +.B send-probe +command will reply with +.BR reply , +.BR no-reply , +or +.BR ttl-expired . +.IP +The following arguments may be used: +.IP +.B ip-4 +.I IP-ADDRESS +.HP 14 +.IP +The Internet Protocol version 4 address to probe. +.HP 7 +.IP +.B ip-6 +.I IP-ADDRESS +.HP 14 +.IP +The Internet Protocol version 6 address to probe. +.HP 7 +.IP +.B protocol +.I PROTOCOL +.HP 14 +.IP +The protocol to use for the network probe. +.BR icmp , +.BR sctp , +.BR tcp , +and +.B udp +may be used. The default protocol is +.BR icmp. +.HP 7 +.IP +.B port +.I PORT-NUMBER +.HP 14 +.IP +The destination port to use for +.BR sctp , +.BR tcp , +or +.B udp +probes. +.HP 7 +.IP +.B local-ip-4 +.I IP-ADDRESS +.HP 14 +.IP +The local Internet Procol version 4 address to use when sending probes. +.HP 7 +.IP +.B local-ip-6 +.I IP-ADDRESS +.HP 14 +.IP +The local Internet Protocol version 6 address to use when sending probes. +.HP 7 +.IP +.B local-port +.I PORT-NUMBER +.HP 14 +.IP +For +.B udp +probes, the local port number from which to send probes. +.HP 7 +.IP +.B timeout +.I TIMEOUT-SECONDS +.HP 14 +.IP +The number of seconds to wait for a response to the probe before discarding +the probe as lost, and generating a +.B no-reply +command reply. +.HP 7 +.IP +.B ttl +.I TIME-TO-LIVE +.HP 14 +.IP +The time-to-live value for the Internet Protocol packet header used in +constructing the probe. This value determines the number of network hops +through which the probe will travel before a response is generated by an +intermediate network host. +.HP 7 +.IP +.B size +.I PACKET-SIZE +.HP 14 +.IP +The size of the packet used to send the probe, in bytes, including the +Internet Protocol header and transport protocol header. +.HP 7 +.IP +.B bit-pattern +.I PATTERN-VALUE +.HP 14 +.IP +The packet payload is filled with bytes of the value specified. +Valid pattern values are in the range 0 through 255. +.HP 7 +.IP +.IP +.B tos +.I TYPE-OF-SERVICE +.HP 14 +.IP +In the case of IPv4, the "type of service" field in the IP header +is set to this value. In the case of IPv6, the "traffic class" +field is set. +.HP 7 +.IP +.B mark +.I ROUTING-MARK +.HP 14 +.IP +The packet mark value to be used by mark-based routing. +(Available only on Linux.) +.HP 7 +.TP +.B check-support +Check for support for a particular feature in this version of +.B mtr-packet +and in this particular operating environment. +.B check-support +will reply with +.BR feature-supported . +A +.B feature +argument is required. +.HP 7 +.IP +.B feature +.I FEATURE-NAME +.HP 14 +.IP +The name of a feature requested. +.HP 7 +.IP +Some features which can be checked are +.BR send-probe , +.BR ip-4 , +.BR ip-6 , +.BR icmp , +.BR sctp , +.BR tcp , +.BR udp , +and +.BR mark . +The feature +.B version +can be checked to retrieve the version of +.BR mtr-packet . +.SH REPLIES +.TP +.B reply +The destination host received the +.B send-probe +probe and replied. Arguments of +.B reply +are: +.HP 7 +.IP +.B ip-4 +.I IP-ADDRESS +.HP 14 +.IP +The Internet Protocol version 4 address of the host which replied +to the probe. +.HP 7 +.IP +.B ip-6 +.I IP-ADDRESS +.HP 14 +.IP +The Internet Protocol version 6 address of the host which replied +to the probe. +.HP 7 +.IP +.B round-trip-time +.I TIME +.HP 14 +.IP +The time which passed between the transmission of the probe and its +response. The time is provided as a integral number of microseconds +elapsed. +.HP 7 +.TP +.B no-reply +No response to the probe request was received before the timeout +expired. +.TP +.B ttl-expired +The time-to-live value of the transmitted probe expired before the probe +arrived at its intended destination. Arguments of +.B ttl-expired +are: +.HP 7 +.IP +.B ip-4 +.I IP-ADDRESS +.HP 14 +.IP +The Internet Protocol version 4 address of the host at which the +time-to-live value expired. +.HP 7 +.IP +.B ip-6 +.I IP-ADDRESS +.HP 14 +.IP +The Internet Protocol version 6 address of the host at which the +time-to-live value expired. +.HP 7 +.IP +.B round-trip-time +.I TIME +.HP 14 +.IP +The time which passed between the transmission of the probe and its +response. The time is provided as a integral number of microseconds +elapsed. +.HP 7 +.IP +.B mpls +.I MPLS-LABEL-LIST +.HP 14 +.IP +A list of Multiprotocol Label Switching values returned +with the probe response. +If the +.B mpls +argument is present, one or more MPLS labels will be represented by +a comma separated list of values. The values are provided in groups +of four. The first four values in the list correspond to the +first MPLS label, the next four values correspond to the second MPLS +label, and so on. The values are provided in this order: +.IR label , +.IR experimental-use , +.IR bottom-of-stack , +.IR ttl . +.HP 7 +.TP +.B no-route +There was no route to the host used in a +.B send-probe +request. +.TP +.B network-down +A probe could not be sent because the network is down. +.TP +.B probes-exhausted +A probe could not be sent because there are already too many unresolved +probes in flight. +.TP +.B permission-denied +The operating system denied permission to send the probe with the +specified options. +.TP +.B invalid-argument +The command request contained arguments which are invalid. +.TP +.B feature-support +A reply to provided to +.B check-support +indicating the availability of a particular feature. The argument provided +is: +.HP 7 +.IP +.B support +.I PRESENT +.HP 14 +.IP +In most cases, the +.I PRESENT +value will be either +.BR ok , +indicating the feature is supported, or +.BR no , +indicating no support for the feature. +.IP +In the case that +.B version +is the requested +.IR FEATURE-NAME , +the version of +.B mtr-packet +is provided as the +.I PRESENT +value. +.HP 7 +.IP +.SH EXAMPLES +A controlling program may start +.B mtr-packet +as a child process and issue the following command on +.IR stdin : +.LP +.RS +42 send-probe ip-4 127.0.0.1 +.RE +.LP +This will send a network probe to the loopback interface. When the probe +completes, +.B +mtr-packet +will provide a response on +.I stdout +such as the following: +.LP +.RS +42 reply ip-4 127.0.0.1 round-trip-time 126 +.RE +.LP +This indicates that the loopback address replied to the probe, and the +round-trip time of the probe was 126 microseconds. +.LP +In order to trace the route to a remote host, multiple +.B send-probe +commands, each with a different +.B ttl +value, are used. +.LP +.RS +11 send-probe ip-4 8.8.8.8 ttl 1 +.RS 0 +12 send-probe ip-4 8.8.8.8 ttl 2 +.RS 0 +13 send-probe ip-4 8.8.8.8 ttl 3 +.RS 0 +\&... +.RE 0 +.LP +Each interemediate host would respond with a +.B ttl-expired +message, and the destination host would respond with a +.BR reply : +.LP +.RS +11 ttl-expired ip-4 192.168.254.254 round-trip-time 1634 +.RS 0 +12 ttl-expired ip-4 184.19.243.240 round-trip-time 7609 +.RS 0 +13 ttl-expired ip-4 172.76.20.169 round-trip-time 8643 +.RS 0 +14 ttl-expired ip-4 74.40.1.101 round-trip-time 9755 +.RS 0 +15 ttl-expired ip-4 74.40.5.126 round-trip-time 10695 +.RS 0 +17 ttl-expired ip-4 108.170.245.97 round-trip-time 14077 +.RS 0 +16 ttl-expired ip-4 74.40.26.131 round-trip-time 15253 +.RS 0 +18 ttl-expired ip-4 209.85.245.101 round-trip-time 17080 +.RS 0 +19 reply ip-4 8.8.8.8 round-trip-time 17039 +.RE 0 +.LP +Note that the replies in this example are printed out of order. +(The reply to probe 17 arrives prior to the reply to probe 16.) +This is the reason that it is important to send commands with unique +token values, and to use those token values to match replies with +their originating commands. +.SH CONTACT INFORMATION +.PP +For the latest version, see the mtr web page at +.UR http://\:www.\:bitwizard.\:nl/\:mtr/ +.UE +.PP +For patches, bug reports, or feature requests, please open an issue on +GitHub at: +.UR https://\:github\:.com/\:traviscross/\:mtr +.UE . +.SH "SEE ALSO" +.BR mtr (8), +.BR icmp (7), +.BR tcp (7), +.BR udp (7), +TCP/IP Illustrated (Stevens, ISBN 0201633469). diff --git a/man/mtr.8.in b/man/mtr.8.in new file mode 100644 index 0000000..7b6709b --- /dev/null +++ b/man/mtr.8.in @@ -0,0 +1,491 @@ +.TH MTR 8 "@VERSION@" "mtr" "System Administration" +.SH NAME +mtr \- a network diagnostic tool +.SH SYNOPSIS +.B mtr +[\c +.BR \-4 |\c +.B \-6\c +] +[\c +.BI \-F \ FILENAME\c +] +[\c +.B \-\-report\c +] +[\c +.B \-\-report-wide\c +] +[\c +.B \-\-xml\c +] +[\c +.B \-\-gtk\c +] +[\c +.B \-\-curses\c +] +[\c +.BI \--displaymode \ MODE\c +] +[\c +.B \-\-raw\c +] +[\c +.B \-\-csv\c +] +[\c +.B \-\-json\c +] +[\c +.B \-\-split\c +] +[\c +.B \-\-no-dns\c +] +[\c +.B \-\-show-ips\c +] +[\c +.BI \-o \ FIELDS\c +] +[\c +.BI \-y \ IPINFO\c +] +[\c +.B \-\-aslookup\c +] +[\c +.BI \-i \ INTERVAL\c +] +[\c +.BI \-c \ COUNT\c +] +[\c +.BI \-s \ PACKETSIZE\c +] +[\c +.BI \-B \ BITPATTERN\c +] +[\c +.BI \-G \ GRACEPERIOD\c +] +[\c +.BI \-Q \ TOS\c +] +[\c +.B \-\-mpls\c +] +[\c +.BI \-a \ ADDRESS\c +] +[\c +.BI \-f \ FIRST\-TTL\c +] +[\c +.BI \-m \ MAX\-TTL\c +] +[\c +.BI \-U \ MAX\-UNKNOWN\c +] +[\c +.B \-\-udp\c +] +[\c +.B \-\-tcp\c +] +[\c +.BI \-\-sctp\c +] +[\c +.BI \-P \ PORT\c +] +[\c +.BI \-L \ LOCALPORT\c +] +[\c +.BI \-Z \ TIMEOUT\c +] +[\c +.BI \-M \ MARK\c +] +.I HOSTNAME +.SH DESCRIPTION +.B mtr +combines the functionality of the +.B traceroute +and +.B ping +programs in a single network diagnostic tool. +.PP +As +.B mtr +starts, it investigates the network connection between the host +.B mtr +runs on and +.BR HOSTNAME +by sending packets with purposely low TTLs. It continues to send +packets with low TTL, noting the response time of the intervening +routers. This allows +.B mtr +to print the response percentage and response times of the internet +route to +.BR HOSTNAME . +A sudden increase in packet loss or response time is often an indication +of a bad (or simply overloaded) link. +.PP +The results are usually reported as round-trip-response times in milliseconds +and the percentage of packetloss. +.SH OPTIONS +.TP +.B \-h\fR, \fB\-\-help +Print the summary of command line argument options. +.TP +.B \-v\fR, \fB\-\-version +Print the installed version of mtr. +.TP +.B \-4 +Use IPv4 only. +.TP +.B \-6 +Use IPv6 only. (IPV4 may be used for DNS lookups.) +.TP +.B \-F \fIFILENAME\fR, \fB\-\-filename \fIFILENAME +Reads the list of hostnames from the specified file. +.TP +.B \-r\fR, \fB\-\-report +This option puts +.B mtr +into +.B report +mode. When in this mode, +.B mtr +will run for the number of cycles specified by the +.B \-c +option, and then print statistics and exit. +.TP +\c +This mode is useful for generating statistics about network quality. +Note that each running instance of +.B mtr +generates a significant amount of network traffic. Using +.B mtr +to measure the quality of your network may result in decreased +network performance. +.TP +.B \-w\fR, \fB\-\-report\-wide +This option puts +.B mtr +into +.B wide report +mode. When in this mode, +.B mtr +will not cut hostnames in the report. +.TP +.B \-x\fR, \fB\-\-xml +Use this option to tell +.B mtr +to use the xml output format. This format is better suited for +automated processing of the measurement results. +.TP +.B \-t\fR, \fB\-\-curses +Use this option to force +.B mtr +to use the curses based terminal +interface (if available). +In case the list of hops exceeds the +height of your terminal, you can use the +.B + +and +.B - +keys to scroll up and down half a page. + +.B Ctrl\fR-\fPL +clears spurious error messages that may overwrite other parts of the display. + +.TP +.B -\-displaymode \fIMODE +Use this option to select the initial display mode: 0 (default) +selects statistics, 1 selects the stripchart without latency +information, and 2 selects the stripchart with latency +information. +.TP +.B \-g\fR, \fB\-\-gtk +Use this option to force +.B mtr +to use the GTK+ based X11 window interface (if available). +GTK+ must have been available on the system when +.B mtr +was built for this to work. See the GTK+ web page at +.UR http://\:www.\:gtk.\:org/ +.UE +for more information about GTK+. +.TP +.B \-l\fR, \fB\-\-raw +Use the raw output format. This format is better suited for +archival of the measurement results. It could be parsed to +be presented into any of the other display methods. +.IP +Example of the raw output format: +.nf +h 0 10.1.1.1 +p 0 339 +h 1 46.149.16.4 +p 1 530 +h 2 172.31.1.16 +p 2 531 +h 3 82.221.168.236 +p 3 1523 +h 5 195.130.211.8 +p 5 1603 +h 6 193.4.58.17 +p 6 1127 +h 7 193.4.58.17 +d 7 www.isnic.is +.fi +.TP +.B \-C\fR, \fB\-\-csv +Use the Comma-Separated-Value (CSV) output format. +(Note: The separator is actually a semi-colon ';'.) +.IP +Example of the CSV output format: +.nf +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;1;r-76520-PROD.greenqloud.internal;288 +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;2;46.149.16.4;2086 +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;3;172.31.1.16;600 +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;4;82.221.168.236;1163 +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;5;???;0 +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;6;rix-k2-gw.isnic.is;1654 +MTR.0.86+git:16e39fc0;1435562787;OK;nic.is;7;www.isnic.is;1036 +.fi +.TP +.B \-j\fR, \fB\-\-json +Use this option to tell +.B mtr +to use the JSON output format. This format is better suited for +automated processing of the measurement results. +.TP +.B \-p\fR, \fB\-\-split +Use this option to set +.B mtr +to spit out a format that is suitable for a split-user interface. +.TP +.B \-n\fR, \fB\-\-no\-dns +Use this option to force +.B mtr +to display numeric IP numbers and not try to resolve the +host names. +.TP +.B \-b\fR, \fB\-\-show\-ips +Use this option to tell +.B mtr +to display both the host names and numeric IP numbers. In split mode +this adds an extra field to the output. In report mode, there is usually +too little space to add the IPs, and they will be truncated. Use the +wide report (-w) mode to see the IPs in report mode. +.TP +.B \-o \fIFIELDS\fR, \fB\-\-order \fIFIELDS +Use this option to specify which fields to display and in which order. +You may use one or more space characters to separate fields. +.br +Available fields: +.TS +center allbox tab(%); +ll. +L%Loss ratio +D%Dropped packets +R%Received packets +S%Sent Packets +N%Newest RTT(ms) +B%Min/Best RTT(ms) +A%Average RTT(ms) +W%Max/Worst RTT(ms) +V%Standard Deviation +G%Geometric Mean +J%Current Jitter +M%Jitter Mean/Avg. +X%Worst Jitter +I%Interarrival Jitter +.TE +.br + +Example: +-o "LSD NBAW X" +.TP +.B \-y \fIn\fR, \fB\-\-ipinfo \fIn +Displays information about each IP hop. Valid values for \fIn\fR are: +.TS +tab(%); +ll. +0%Display AS number (equivalent to \fB-z\fR) +1%Display IP prefix +2%Display country code of the origin AS +3%Display RIR (ripencc, arin, ...) +4%Display the allocation date of the IP prefix +.TE +.br + +It is possible to cycle between these fields at runtime (using the \fBy\fR key). +.TP +.B \-z\fR, \fB\-\-aslookup +Displays the Autonomous System (AS) number alongside each hop. Equivalent to \fB\-\-ipinfo 0\fR. +.IP +Example (columns to the right not shown for clarity): +.nf +1. AS??? r-76520-PROD.greenqloud.internal +2. AS51969 46.149.16.4 +3. AS??? 172.31.1.16 +4. AS30818 82.221.168.236 +5. ??? +6. AS??? rix-k2-gw.isnic.is +7. AS1850 www.isnic.is +.fi +.TP +.B \-i \fISECONDS\fR, \fB\-\-interval \fISECONDS +Use this option to specify the positive number of seconds between ICMP +ECHO requests. The default value for this parameter is one second. The +root user may choose values between zero and one. +.TP +.B \-c \fICOUNT\fR, \fB\-\-report\-cycles \fICOUNT +Use this option to set the number of pings sent to determine +both the machines on the network and the reliability of +those machines. Each cycle lasts one second. +.TP +.B \-s \fIPACKETSIZE\fR, \fB\-\-psize \fIPACKETSIZE +This option sets the packet size used for probing. It is in bytes, +inclusive IP and ICMP headers. + +If set to a negative number, every iteration will use a different, random +packet size up to that number. +.TP +.B \-B \fINUM\fR, \fB\-\-bitpattern \fINUM +Specifies bit pattern to use in payload. Should be within range 0 - 255. If +.I NUM +is greater than 255, a random pattern is used. +.TP +.B \-G \fISECONDS\fR, \fB\-\-gracetime \fISECONDS +Use this option to specify the positive number of seconds to wait for responses +after the final request. The default value is five seconds. +.TP +.B \-Q \fINUM\fR, \fB\-\-tos \fINUM +Specifies value for type of service field in IP header. Should be within range 0 +- 255. +.TP +.B \-e\fR, \fB\-\-mpls +Use this option to tell +.B mtr +to display information from ICMP extensions for MPLS (RFC 4950) +that are encoded in the response packets. +.TP +.B \-a \fIADDRESS\fR, \fB\-\-address \fIADDRESS +Use this option to bind the outgoing socket to +.IR ADDRESS , +so that all packets will be sent with +.I ADDRESS +as source address. NOTE that this option doesn't apply to DNS requests +(which could be and could not be what you want). +.TP +.B \-f \fINUM\fR, \fB\-\-first-ttl \fINUM +Specifies with what TTL to start. Defaults to 1. +.TP +.B \-m \fINUM\fR, \fB\-\-max-ttl \fINUM +Specifies the maximum number of hops (max time-to-live value) traceroute will +probe. Default is 30. +.TP +.B \-U \fINUM\fR, \fB\-\-max-unknown \fINUM +Specifies the maximum unknown host. Default is 5. +.TP +.B \-u\fR, \fB\-\-udp +Use UDP datagrams instead of ICMP ECHO. +.TP +.B \-T\fR, \fB\-\-tcp +Use TCP SYN packets instead of ICMP ECHO. +.I PACKETSIZE +is ignored, since SYN packets can not contain data. +.TP +.B \-S\fR, \fB\-\-sctp +Use Stream Control Transmission Protocol packets instead of ICMP ECHO. +.TP +.B \-P \fIPORT\fR, \fB\-\-port \fIPORT +The target port number for TCP/SCTP/UDP traces. +.TP +.B \-L \fILOCALPORT\fR, \fB\-\-localport \fILOCALPORT +The source port number for UDP traces. +.TP +.B \-Z \fISECONDS\fR, \fB\-\-timeout \fISECONDS +The number of seconds to keep probe sockets open before giving up on +the connection. Using large values for this, especially combined with +a short interval, will use up a lot of file descriptors. +.TP +.B \-M \fIMARK\fR, \fB\-\-mark \fIMARK +Set the mark for each packet sent through this socket similar to the +netfilter MARK target but socket-based. +.I MARK +is 32 unsigned integer. See +.BR socket (7) +for full description of this socket option. +.SH ENVIRONMENT +.B mtr +recognizes a few environment variables. +.TP +.B MTR_OPTIONS +This environment variable allows to specify options, as if they were +passed on the command line. It is parsed before reading the actual +command line options, so that options specified in +.B MTR_OPTIONS +are overridden by command-line options. + +Example: + +.BI MTR_OPTIONS ="-4\ -c\ 1" +.B mtr +.I \-6\ localhost + +would send one probe (because of +.I -c\ 1\c +) towards +.B ::1 +(because of +.IR -6 , +which overrides the +.I -4 +passed in +.B MTR_OPTIONS\c +). +.TP +.B MTR_PACKET +A path to the +.I mtr-packet +executable, to be used for sending and receiving network probes. If +.B MTR_PACKET +is unset, the +.B PATH +will be used to search for an +.I mtr-packet +executable. +.TP +.B DISPLAY +Specifies an X11 server for the GTK+ frontend. +.SH BUGS +Some modern routers give a lower priority to ICMP ECHO packets than +to other network traffic. Consequently, the reliability of these +routers reported by +.B mtr +will be significantly lower than the actual reliability of +these routers. +.SH CONTACT INFORMATION +.PP +For the latest version, see the mtr web page at +.UR http://\:www.\:bitwizard.\:nl/\:mtr/ +.UE +.PP +For patches, bug reports, or feature requests, please open an issue on +GitHub at: +.UR https://\:github\:.com/\:traviscross/\:mtr +.UE . +.SH "SEE ALSO" +.BR mtr-packet (8), +.BR traceroute (8), +.BR ping (8), +.BR socket (7), +TCP/IP Illustrated (Stevens, ISBN 0201633469). diff --git a/packet/cmdparse.c b/packet/cmdparse.c new file mode 100644 index 0000000..1084714 --- /dev/null +++ b/packet/cmdparse.c @@ -0,0 +1,129 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "cmdparse.h" + +#include +#include +#include +#include + +/* + NUL terminate the whitespace separated tokens in the command string. + This modifies command_string in-place with NUL characters. + Fill the tokens array with pointers to the tokens, and return the + number of tokens found. +*/ +static +int tokenize_command( + char **tokens, + int max_tokens, + char *command_string) +{ + int token_count = 0; + int on_space = 1; + int i; + + for (i = 0; command_string[i]; i++) { + if (on_space) { + if (!isspace((unsigned char) command_string[i])) { + /* Take care not to exceed the token array length */ + if (token_count >= max_tokens) { + return -1; + } + + tokens[token_count++] = &command_string[i]; + on_space = 0; + } + } else { + if (isspace((unsigned char) command_string[i])) { + command_string[i] = 0; + on_space = 1; + } + } + } + + return token_count; +} + +/* + Parse a command string (or command reply string) into a command_t + structure for later semantic interpretation. Returns EINVAL if the + command string is unparseable or zero for success. + + comamnd_string will be modified in-place with NUL characters terminating + tokens, and the command_t will use pointers to the conents of + command_string without copying, so any interpretation of the + command_t structure requires that the command_string memory has not yet + been freed or otherwise reused. +*/ +int parse_command( + struct command_t *command, + char *command_string) +{ + char *tokens[MAX_COMMAND_TOKENS]; + int token_count; + int i; + + memset(command, 0, sizeof(struct command_t)); + + /* Tokenize the string using whitespace */ + token_count = + tokenize_command(tokens, MAX_COMMAND_TOKENS, command_string); + if (token_count < 2) { + errno = EINVAL; + return -1; + } + + /* Expect the command token to be a numerical value */ + errno = 0; + command->token = strtol(tokens[0], NULL, 10); + if (errno) { + errno = EINVAL; + return -1; + } + command->command_name = tokens[1]; + + /* + The tokens beyond the command name are expected to be in + name, value pairs. + */ + i = 2; + command->argument_count = 0; + while (i < token_count) { + /* It's an error if we get a name without a key */ + if (i + 1 >= token_count) { + errno = EINVAL; + return -1; + } + + /* It's an error if we get more arguments than we have space for */ + if (command->argument_count >= MAX_COMMAND_ARGUMENTS) { + errno = EINVAL; + return -1; + } + + command->argument_name[command->argument_count] = tokens[i]; + command->argument_value[command->argument_count] = tokens[i + 1]; + command->argument_count++; + + i += 2; + } + + return 0; +} diff --git a/packet/cmdparse.h b/packet/cmdparse.h new file mode 100644 index 0000000..f52e48f --- /dev/null +++ b/packet/cmdparse.h @@ -0,0 +1,49 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CMDPARSE_H +#define CMDPARSE_H + +enum { + MAX_COMMAND_ARGUMENTS = 16, + MAX_COMMAND_TOKENS = MAX_COMMAND_ARGUMENTS * 2 + 2 +}; + +/* Parsed commands, or command replies, ready for semantic interpretation */ +struct command_t { + /* A unique value for matching command requests with replies */ + int token; + + /* Text indiciating the command type, or reply type */ + char *command_name; + + /* The number of key, value argument pairs used */ + int argument_count; + + /* Names for each argument */ + char *argument_name[MAX_COMMAND_ARGUMENTS]; + + /* Values for each argument, parallel to the argument_name array */ + char *argument_value[MAX_COMMAND_ARGUMENTS]; +}; + +int parse_command( + struct command_t *command, + char *command_string); + +#endif diff --git a/packet/command.c b/packet/command.c new file mode 100644 index 0000000..42b9a52 --- /dev/null +++ b/packet/command.c @@ -0,0 +1,425 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "command.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "cmdparse.h" +#include "platform.h" +#include "config.h" + +/* + Find a parameter with a particular name in a command_t structure. + If no such parameter exists, return NULL. +*/ +static +const char *find_parameter( + const struct command_t *command, + const char *name_request) +{ + const char *name; + const char *value; + int i; + + for (i = 0; i < command->argument_count; i++) { + name = command->argument_name[i]; + value = command->argument_value[i]; + + if (!strcmp(name, name_request)) { + return value; + } + } + + return NULL; +} + +/* Returns a feature support string for a particular probe protocol */ +static +const char *check_protocol_support( + struct net_state_t *net_state, + int protocol) +{ + if (is_protocol_supported(net_state, protocol)) { + return "ok"; + } else { + return "no"; + } +} + +/* Return a feature support string for an IP protocol version */ +static +const char *check_ip_version_support( + struct net_state_t *net_state, + int ip_version) +{ + if (is_ip_version_supported(net_state, ip_version)) { + return "ok"; + } else { + return "no"; + } +} + +/* Given a feature name, return a string for the check-support reply */ +static +const char *check_support( + const char *feature, + struct net_state_t *net_state) +{ + if (!strcmp(feature, "version")) { + return PACKAGE_VERSION; + } + + if (!strcmp(feature, "ip-4")) { + return check_ip_version_support(net_state, 4); + } + + if (!strcmp(feature, "ip-6")) { + return check_ip_version_support(net_state, 6); + } + + if (!strcmp(feature, "send-probe")) { + return "ok"; + } + + if (!strcmp(feature, "icmp")) { + return check_protocol_support(net_state, IPPROTO_ICMP); + } + + if (!strcmp(feature, "udp")) { + return check_protocol_support(net_state, IPPROTO_UDP); + } + + if (!strcmp(feature, "tcp")) { + return check_protocol_support(net_state, IPPROTO_TCP); + } +#ifdef IPPROTO_SCTP + if (!strcmp(feature, "sctp")) { + return check_protocol_support(net_state, IPPROTO_SCTP); + } +#endif + +#ifdef SO_MARK + if (!strcmp(feature, "mark")) { + return "ok"; + } +#endif + + return "no"; +} + +/* Handle a check-support request by checking for a particular feature */ +static +void check_support_command( + const struct command_t *command, + struct net_state_t *net_state) +{ + const char *feature; + const char *support; + + feature = find_parameter(command, "feature"); + if (feature == NULL) { + printf("%d invalid-argument\n", command->token); + return; + } + + support = check_support(feature, net_state); + printf("%d feature-support support %s\n", command->token, support); +} + +/* + If a named send_probe argument is recognized, fill in the probe paramters + structure with the argument value. +*/ +static +bool decode_probe_argument( + struct probe_param_t *param, + const char *name, + const char *value) +{ + char *endstr = NULL; + + /* Pass IPv4 addresses as string values */ + if (!strcmp(name, "ip-4")) { + param->ip_version = 4; + param->remote_address = value; + } + + /* IPv6 address */ + if (!strcmp(name, "ip-6")) { + param->ip_version = 6; + param->remote_address = value; + } + + /* IPv4 address to send from */ + if (!strcmp(name, "local-ip-4")) { + param->local_address = value; + } + + /* IPv6 address to send from */ + if (!strcmp(name, "local-ip-6")) { + param->local_address = value; + } + + /* Protocol for the probe */ + if (!strcmp(name, "protocol")) { + if (!strcmp(value, "icmp")) { + param->protocol = IPPROTO_ICMP; + } else if (!strcmp(value, "udp")) { + param->protocol = IPPROTO_UDP; + } else if (!strcmp(value, "tcp")) { + param->protocol = IPPROTO_TCP; +#ifdef IPPROTO_SCTP + } else if (!strcmp(value, "sctp")) { + param->protocol = IPPROTO_SCTP; +#endif + } else { + return false; + } + } + + /* Destination port for the probe */ + if (!strcmp(name, "port")) { + param->dest_port = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* The local port to send UDP probes from */ + if (!strcmp(name, "local-port")) { + param->local_port = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + + /* + Don't allow using a local port which requires + privileged binding. + */ + if (param->local_port < 1024) { + param->local_port = 0; + return false; + } + } + + /* The "type of service" field for the IP header */ + if (!strcmp(name, "tos")) { + param->type_of_service = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* The Linux packet mark for mark-based routing */ + if (!strcmp(name, "mark")) { + param->routing_mark = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* The size of the packet (including headers) */ + if (!strcmp(name, "size")) { + param->packet_size = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* The packet's bytes will be filled with this value */ + if (!strcmp(name, "bit-pattern")) { + param->bit_pattern = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* Time-to-live values */ + if (!strcmp(name, "ttl")) { + param->ttl = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + /* Number of seconds to wait for a reply */ + if (!strcmp(name, "timeout")) { + param->timeout = strtol(value, &endstr, 10); + if (*endstr != 0) { + return false; + } + } + + return true; +} + +/* + Check the probe parameters against currently supported features + and report and error if there is a problem. + + Return true if everything is okay, false otherwise. +*/ +static +bool validate_probe_parameters( + struct net_state_t *net_state, + struct probe_param_t *param) +{ + if (!is_ip_version_supported(net_state, param->ip_version)) { + printf("%d invalid-argument reason ip-version-not-supported\n", + param->command_token); + + return false; + } + + if (!is_protocol_supported(net_state, param->protocol)) { + printf("%d invalid-argument reason protocol-not-supported\n", + param->command_token); + + return false; + } + + return true; +} + +/* Handle "send-probe" commands */ +static +void send_probe_command( + const struct command_t *command, + struct net_state_t *net_state) +{ + struct probe_param_t param; + int i; + char *name; + char *value; + + /* We will prepare a probe_param_t for send_probe. */ + memset(¶m, 0, sizeof(struct probe_param_t)); + param.command_token = command->token; + param.protocol = IPPROTO_ICMP; + param.ttl = 255; + param.packet_size = 64; + param.timeout = 10; + + for (i = 0; i < command->argument_count; i++) { + name = command->argument_name[i]; + value = command->argument_value[i]; + + if (!decode_probe_argument(¶m, name, value)) { + printf("%d invalid-argument\n", command->token); + return; + } + } + + if (!validate_probe_parameters(net_state, ¶m)) { + return; + } + + /* Send the probe using a platform specific mechanism */ + send_probe(net_state, ¶m); +} + +/* + Given a parsed command, dispatch to the handler for specific + command requests. +*/ +static +void dispatch_command( + const struct command_t *command, + struct net_state_t *net_state) +{ + if (!strcmp(command->command_name, "check-support")) { + check_support_command(command, net_state); + } else if (!strcmp(command->command_name, "send-probe")) { + send_probe_command(command, net_state); + } else { + /* For unrecognized commands, respond with an error */ + printf("%d unknown-command\n", command->token); + } +} + +/* + With newly read data in our command buffer, dispatch all completed + command requests. +*/ +void dispatch_buffer_commands( + struct command_buffer_t *buffer, + struct net_state_t *net_state) +{ + struct command_t command; + char *end_of_command; + char full_command[COMMAND_BUFFER_SIZE]; + int command_length; + int remaining_count; + + while (true) { + assert(buffer->incoming_read_position < COMMAND_BUFFER_SIZE); + + /* Terminate the buffer string */ + buffer->incoming_buffer[buffer->incoming_read_position] = 0; + + /* Find the next newline, which terminates command requests */ + end_of_command = index(buffer->incoming_buffer, '\n'); + if (end_of_command == NULL) { + /* + No newlines found, so any data we've read so far is + not yet complete. + */ + break; + } + + command_length = end_of_command - buffer->incoming_buffer; + remaining_count = + buffer->incoming_read_position - command_length - 1; + + /* Copy the completed command */ + memmove(full_command, buffer->incoming_buffer, command_length); + full_command[command_length] = 0; + + /* + Free the space used by the completed command by advancing the + remaining requests within the buffer. + */ + memmove(buffer->incoming_buffer, end_of_command + 1, + remaining_count); + buffer->incoming_read_position -= command_length + 1; + + if (parse_command(&command, full_command)) { + /* If the command fails to parse, respond with an error */ + printf("0 command-parse-error\n"); + } else { + dispatch_command(&command, net_state); + } + } + + if (buffer->incoming_read_position >= COMMAND_BUFFER_SIZE - 1) { + /* + If we've filled the buffer without a complete command, the + only thing we can do is discard what we've read and hope that + new data is better formatted. + */ + printf("0 command-buffer-overflow\n"); + buffer->incoming_read_position = 0; + } +} diff --git a/packet/command.h b/packet/command.h new file mode 100644 index 0000000..a1fe025 --- /dev/null +++ b/packet/command.h @@ -0,0 +1,59 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef COMMAND_H +#define COMMAND_H + +#include "platform.h" +#include "probe.h" + +#define COMMAND_BUFFER_SIZE 4096 + +#ifdef PLATFORM_CYGWIN +#include "command_cygwin.h" +#else +#include "command_unix.h" +#endif + +/* Storage for incoming commands, prior to command parsing */ +struct command_buffer_t { + /* The file descriptor of the incoming command stream */ + int command_stream; + + /* Storage to read commands into */ + char incoming_buffer[COMMAND_BUFFER_SIZE]; + + /* The number of bytes read so far in incoming_buffer */ + int incoming_read_position; + + /* Platform specific */ + struct command_buffer_platform_t platform; +}; + +void init_command_buffer( + struct command_buffer_t *command_buffer, + int command_stream); + +int read_commands( + struct command_buffer_t *buffer); + +void dispatch_buffer_commands( + struct command_buffer_t *buffer, + struct net_state_t *net_state); + +#endif diff --git a/packet/command_cygwin.c b/packet/command_cygwin.c new file mode 100644 index 0000000..5e12fd6 --- /dev/null +++ b/packet/command_cygwin.c @@ -0,0 +1,153 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "command.h" + +#include +#include +#include + +/* + A completion routine to be called by Windows when a read from + the command stream has completed. +*/ +static +void CALLBACK finish_read_command( + DWORD status, + DWORD size_read, + OVERLAPPED * overlapped) +{ + struct command_buffer_t *buffer; + char *read_position; + + /* + hEvent is unusuaed by ReadFileEx, so we use it to pass + our command_buffer structure. + */ + buffer = (struct command_buffer_t *) overlapped->hEvent; + + if (status) { + /* When the stream is closed ERROR_BROKEN_PIPE will be the result */ + if (status == ERROR_BROKEN_PIPE) { + buffer->platform.pipe_open = false; + return; + } + + fprintf(stderr, "ReadFileEx completion failure %d\n", status); + exit(EXIT_FAILURE); + } + + /* Copy from the overlapped I/O buffer to the incoming command buffer */ + read_position = + &buffer->incoming_buffer[buffer->incoming_read_position]; + memcpy(read_position, buffer->platform.overlapped_buffer, size_read); + + /* Account for the newly read data */ + buffer->incoming_read_position += size_read; + buffer->platform.read_active = false; +} + +/* + An APC which does nothing, to be used only to wake from the current + alertable wait. +*/ +static +void CALLBACK empty_apc( + ULONG * param) +{ +} + +/* Wake from the next alertable wait without waiting for newly read data */ +static +void queue_empty_apc( + void) +{ + if (QueueUserAPC((PAPCFUNC) empty_apc, GetCurrentThread(), 0) == 0) { + fprintf(stderr, "Unexpected QueueUserAPC failure %d\n", + GetLastError()); + exit(EXIT_FAILURE); + } +} + +/* Start a new overlapped I/O read from the command stream */ +void start_read_command( + struct command_buffer_t *buffer) +{ + HANDLE command_stream = (HANDLE) get_osfhandle(buffer->command_stream); + int space_remaining = + COMMAND_BUFFER_SIZE - buffer->incoming_read_position - 1; + int err; + + /* If a read is already active, or the pipe is closed, do nothing */ + if (!buffer->platform.pipe_open || buffer->platform.read_active) { + return; + } + + memset(&buffer->platform.overlapped, 0, sizeof(OVERLAPPED)); + buffer->platform.overlapped.hEvent = (HANDLE) buffer; + + if (!ReadFileEx + (command_stream, buffer->platform.overlapped_buffer, + space_remaining, &buffer->platform.overlapped, + finish_read_command)) { + + err = GetLastError(); + + if (err == ERROR_BROKEN_PIPE) { + /* If the command stream has been closed, we need to wake from + the next altertable wait to exit the main loop */ + buffer->platform.pipe_open = false; + queue_empty_apc(); + + return; + } else if (err != WAIT_IO_COMPLETION) { + fprintf(stderr, "Unexpected ReadFileEx failure %d\n", + GetLastError()); + exit(EXIT_FAILURE); + } + } + + /* Remember that we have started an overlapped read already */ + buffer->platform.read_active = true; +} + +/* Initialize the command buffer, and start the first overlapped read */ +void init_command_buffer( + struct command_buffer_t *command_buffer, + int command_stream) +{ + memset(command_buffer, 0, sizeof(struct command_buffer_t)); + command_buffer->command_stream = command_stream; + command_buffer->platform.pipe_open = true; +} + +/* + Return with errno EPIPE if the command stream has been closed. + Otherwise, not much to do for Cygwin, since we are using Overlapped I/O + to read commands. +*/ +int read_commands( + struct command_buffer_t *buffer) +{ + if (!buffer->platform.pipe_open) { + errno = EPIPE; + return -1; + } + + return 0; +} diff --git a/packet/command_cygwin.h b/packet/command_cygwin.h new file mode 100644 index 0000000..9224468 --- /dev/null +++ b/packet/command_cygwin.h @@ -0,0 +1,52 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef COMMAND_CYGWIN_H +#define COMMAND_CYGWIN_H + +/* + Though Cygwin supports the usual Unix non-blocking reads on + the command stream, we've got to use Overlapped I/O instead because + ICMP.DLL's support for sending probes requires Overlapped I/O + and alertable waits for notification of replies. Since we need + alertable waits, we can't use Cygwin's select to determine when + command stream data is available, but Overlapped I/O completion + will work. +*/ + +/* Overlapped I/O manament for Windows command buffer reads */ +struct command_buffer_platform_t { + /* true if an overlapped I/O read is active */ + bool read_active; + + /* true if the command pipe is still open */ + bool pipe_open; + + /* Windows OVERLAPPED I/O data */ + OVERLAPPED overlapped; + + /* The buffer which active OVERLAPPED reads read into */ + char overlapped_buffer[COMMAND_BUFFER_SIZE]; +}; + +struct command_buffer_t; + +void start_read_command( + struct command_buffer_t *buffer); + +#endif diff --git a/packet/command_unix.c b/packet/command_unix.c new file mode 100644 index 0000000..f6d20aa --- /dev/null +++ b/packet/command_unix.c @@ -0,0 +1,89 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "command.h" + +#include +#include +#include +#include +#include +#include + +/* + Initialize the command buffer and put the command stream in + non-blocking mode. +*/ +void init_command_buffer( + struct command_buffer_t *command_buffer, + int command_stream) +{ + int flags; + + memset(command_buffer, 0, sizeof(struct command_buffer_t)); + command_buffer->command_stream = command_stream; + + /* Get the current command stream flags */ + flags = fcntl(command_stream, F_GETFL, 0); + if (flags == -1) { + perror("Unexpected command stream error"); + exit(EXIT_FAILURE); + } + + /* Set the O_NONBLOCK bit */ + if (fcntl(command_stream, F_SETFL, flags | O_NONBLOCK)) { + perror("Unexpected command stream error"); + exit(EXIT_FAILURE); + } +} + +/* Read currently available data from the command stream */ +int read_commands( + struct command_buffer_t *buffer) +{ + int space_remaining = + COMMAND_BUFFER_SIZE - buffer->incoming_read_position - 1; + char *read_position = + &buffer->incoming_buffer[buffer->incoming_read_position]; + int read_count; + int command_stream = buffer->command_stream; + + read_count = read(command_stream, read_position, space_remaining); + + /* If the command stream has been closed, read will return zero. */ + if (read_count == 0) { + errno = EPIPE; + return -1; + } + + if (read_count > 0) { + /* Account for the newly read data */ + buffer->incoming_read_position += read_count; + } + + if (read_count < 0) { + /* EAGAIN simply means there is no available data to read */ + /* EINTR indicates we received a signal during read */ + if (errno != EINTR && errno != EAGAIN) { + perror("Unexpected command buffer read error"); + exit(EXIT_FAILURE); + } + } + + return 0; +} diff --git a/packet/command_unix.h b/packet/command_unix.h new file mode 100644 index 0000000..ab89726 --- /dev/null +++ b/packet/command_unix.h @@ -0,0 +1,26 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef COMMAND_UNIX_H +#define COMMAND_UNIX_H + +/* No platform specific data is required for Unix command streams */ +struct command_buffer_platform_t { +}; + +#endif diff --git a/packet/construct_unix.c b/packet/construct_unix.c new file mode 100644 index 0000000..b0f4679 --- /dev/null +++ b/packet/construct_unix.c @@ -0,0 +1,700 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "construct_unix.h" + +#include +#include +#include +#include +#include + +#include "protocols.h" + +/* A source of data for computing a checksum */ +struct checksum_source_t { + const void *data; + size_t size; +}; + +/* Compute the IP checksum (or ICMP checksum) of a packet. */ +static +uint16_t compute_checksum( + const void *packet, + int size) +{ + const uint8_t *packet_bytes = (uint8_t *) packet; + uint32_t sum = 0; + int i; + + for (i = 0; i < size; i++) { + if ((i & 1) == 0) { + sum += packet_bytes[i] << 8; + } else { + sum += packet_bytes[i]; + } + } + + /* + Sums which overflow a 16-bit value have the high bits + added back into the low 16 bits. + */ + while (sum >> 16) { + sum = (sum >> 16) + (sum & 0xffff); + } + + /* + The value stored is the one's complement of the + mathematical sum. + */ + return (~sum & 0xffff); +} + +/* Encode the IP header length field in the order required by the OS. */ +static +uint16_t length_byte_swap( + const struct net_state_t *net_state, + uint16_t length) +{ + if (net_state->platform.ip_length_host_order) { + return length; + } else { + return htons(length); + } +} + +/* Construct a combined sockaddr from a source address and source port */ +static +void construct_addr_port( + struct sockaddr_storage *addr_with_port, + const struct sockaddr_storage *addr, + int port) +{ + struct sockaddr_in *addr4; + struct sockaddr_in6 *addr6; + + memcpy(addr_with_port, addr, sizeof(struct sockaddr_storage)); + + if (addr->ss_family == AF_INET6) { + addr6 = (struct sockaddr_in6 *) addr_with_port; + addr6->sin6_port = htons(port); + } else { + addr4 = (struct sockaddr_in *) addr_with_port; + addr4->sin_port = htons(port); + } +} + +/* Construct a header for IP version 4 */ +static +void construct_ip4_header( + const struct net_state_t *net_state, + char *packet_buffer, + int packet_size, + const struct sockaddr_storage *srcaddr, + const struct sockaddr_storage *destaddr, + const struct probe_param_t *param) +{ + struct IPHeader *ip; + struct sockaddr_in *srcaddr4 = (struct sockaddr_in *) srcaddr; + struct sockaddr_in *destaddr4 = (struct sockaddr_in *) destaddr; + + ip = (struct IPHeader *) &packet_buffer[0]; + + memset(ip, 0, sizeof(struct IPHeader)); + + ip->version = 0x45; + ip->tos = param->type_of_service; + ip->len = length_byte_swap(net_state, packet_size); + ip->ttl = param->ttl; + ip->protocol = param->protocol; + memcpy(&ip->saddr, &srcaddr4->sin_addr, sizeof(uint32_t)); + memcpy(&ip->daddr, &destaddr4->sin_addr, sizeof(uint32_t)); +} + +/* Construct an ICMP header for IPv4 */ +static +void construct_icmp4_header( + const struct net_state_t *net_state, + int sequence, + char *packet_buffer, + int packet_size, + const struct probe_param_t *param) +{ + struct ICMPHeader *icmp; + int icmp_size; + + icmp = (struct ICMPHeader *) &packet_buffer[sizeof(struct IPHeader)]; + icmp_size = packet_size - sizeof(struct IPHeader); + + memset(icmp, 0, sizeof(struct ICMPHeader)); + + icmp->type = ICMP_ECHO; + icmp->id = htons(getpid()); + icmp->sequence = htons(sequence); + icmp->checksum = htons(compute_checksum(icmp, icmp_size)); +} + +/* Construct an ICMP header for IPv6 */ +static +int construct_icmp6_packet( + const struct net_state_t *net_state, + int sequence, + char *packet_buffer, + int packet_size, + const struct probe_param_t *param) +{ + struct ICMPHeader *icmp; + + icmp = (struct ICMPHeader *) packet_buffer; + + memset(icmp, 0, sizeof(struct ICMPHeader)); + + icmp->type = ICMP6_ECHO; + icmp->id = htons(getpid()); + icmp->sequence = htons(sequence); + + return 0; +} + +/* + Set the port numbers for an outgoing UDP probe. + There is limited space in the header for a sequence number + to identify the probe upon return. + + We store the sequence number in the destination port, the local + port, or the checksum. The location chosen depends upon which + probe parameters have been requested. +*/ +static +void set_udp_ports( + struct UDPHeader *udp, + int sequence, + const struct probe_param_t *param) +{ + if (param->dest_port) { + udp->dstport = htons(param->dest_port); + + if (param->local_port) { + udp->srcport = htons(param->local_port); + udp->checksum = htons(sequence); + } else { + udp->srcport = htons(sequence); + udp->checksum = 0; + } + } else { + udp->dstport = htons(sequence); + + if (param->local_port) { + udp->srcport = htons(param->local_port); + } else { + udp->srcport = htons(getpid()); + } + + udp->checksum = 0; + } +} + +/* + Construct a header for UDP probes, using the port number associated + with the probe. +*/ +static +void construct_udp4_header( + const struct net_state_t *net_state, + int sequence, + char *packet_buffer, + int packet_size, + const struct probe_param_t *param) +{ + struct UDPHeader *udp; + int udp_size; + + udp = (struct UDPHeader *) &packet_buffer[sizeof(struct IPHeader)]; + udp_size = packet_size - sizeof(struct IPHeader); + + memset(udp, 0, sizeof(struct UDPHeader)); + + set_udp_ports(udp, sequence, param); + udp->length = htons(udp_size); +} + +/* Construct a header for UDPv6 probes */ +static +int construct_udp6_packet( + const struct net_state_t *net_state, + int sequence, + char *packet_buffer, + int packet_size, + const struct probe_param_t *param) +{ + int udp_socket = net_state->platform.udp6_send_socket; + struct UDPHeader *udp; + int udp_size; + + udp = (struct UDPHeader *) packet_buffer; + udp_size = packet_size; + + memset(udp, 0, sizeof(struct UDPHeader)); + + set_udp_ports(udp, sequence, param); + udp->length = htons(udp_size); + + /* + Instruct the kernel to put the pseudoheader checksum into the + UDP header. + */ + int chksum_offset = (char *) &udp->checksum - (char *) udp; + if (setsockopt(udp_socket, IPPROTO_IPV6, + IPV6_CHECKSUM, &chksum_offset, sizeof(int))) { + return -1; + } + + return 0; +} + +/* + Set the socket options for an outgoing stream protocol socket based on + the packet parameters. +*/ +static +int set_stream_socket_options( + int stream_socket, + const struct probe_param_t *param) +{ + int level; + int opt; + int reuse = 1; + + /* Allow binding to a local port previously in use */ +#ifdef SO_REUSEPORT + /* + FreeBSD wants SO_REUSEPORT in addition to SO_REUSEADDR to + bind to the same port + */ + if (setsockopt(stream_socket, SOL_SOCKET, SO_REUSEPORT, + &reuse, sizeof(int)) == -1) { + + return -1; + } +#endif + + if (setsockopt(stream_socket, SOL_SOCKET, SO_REUSEADDR, + &reuse, sizeof(int)) == -1) { + + return -1; + } + + /* Set the number of hops the probe will transit across */ + if (param->ip_version == 6) { + level = IPPROTO_IPV6; + opt = IPV6_UNICAST_HOPS; + } else { + level = IPPROTO_IP; + opt = IP_TTL; + } + + if (setsockopt(stream_socket, level, opt, ¶m->ttl, sizeof(int)) == + -1) { + + return -1; + } + + /* Set the "type of service" field of the IP header */ + if (param->ip_version == 6) { + level = IPPROTO_IPV6; + opt = IPV6_TCLASS; + } else { + level = IPPROTO_IP; + opt = IP_TOS; + } + + if (setsockopt(stream_socket, level, opt, + ¶m->type_of_service, sizeof(int)) == -1) { + + return -1; + } +#ifdef SO_MARK + if (param->routing_mark) { + if (setsockopt(stream_socket, SOL_SOCKET, + SO_MARK, ¶m->routing_mark, sizeof(int))) { + return -1; + } + } +#endif + + return 0; +} + +/* + Open a TCP or SCTP socket, respecting the probe paramters as much as + we can, and use it as an outgoing probe. +*/ +static +int open_stream_socket( + const struct net_state_t *net_state, + int protocol, + int port, + const struct sockaddr_storage *src_sockaddr, + const struct sockaddr_storage *dest_sockaddr, + const struct probe_param_t *param) +{ + int stream_socket; + int addr_len; + int dest_port; + struct sockaddr_storage dest_port_addr; + struct sockaddr_storage src_port_addr; + + if (param->ip_version == 6) { + stream_socket = socket(AF_INET6, SOCK_STREAM, protocol); + addr_len = sizeof(struct sockaddr_in6); + } else if (param->ip_version == 4) { + stream_socket = socket(AF_INET, SOCK_STREAM, protocol); + addr_len = sizeof(struct sockaddr_in); + } else { + errno = EINVAL; + return -1; + } + + if (stream_socket == -1) { + return -1; + } + + set_socket_nonblocking(stream_socket); + + if (set_stream_socket_options(stream_socket, param)) { + close(stream_socket); + return -1; + } + + /* + Bind to a known local port so we can identify which probe + causes a TTL expiration. + */ + construct_addr_port(&src_port_addr, src_sockaddr, port); + if (bind(stream_socket, (struct sockaddr *) &src_port_addr, addr_len)) { + close(stream_socket); + return -1; + } + + if (param->dest_port) { + dest_port = param->dest_port; + } else { + /* Use http if no port is specified */ + dest_port = HTTP_PORT; + } + + /* Attempt a connection */ + construct_addr_port(&dest_port_addr, dest_sockaddr, dest_port); + if (connect + (stream_socket, (struct sockaddr *) &dest_port_addr, addr_len)) { + + /* EINPROGRESS simply means the connection is in progress */ + if (errno != EINPROGRESS) { + close(stream_socket); + return -1; + } + } + + return stream_socket; +} + +/* + Determine the size of the constructed packet based on the packet + parameters. This is the amount of space the packet *we* construct + uses, and doesn't include any headers the operating system tacks + onto the packet. (Such as the IPv6 header on non-Linux operating + systems.) +*/ +static +int compute_packet_size( + const struct net_state_t *net_state, + const struct probe_param_t *param) +{ + int packet_size; + + if (param->protocol == IPPROTO_TCP) { + return 0; + } +#ifdef IPPROTO_SCTP + if (param->protocol == IPPROTO_SCTP) { + return 0; + } +#endif + + /* Start by determining the full size, including omitted headers */ + if (param->ip_version == 6) { + packet_size = sizeof(struct IP6Header); + } else if (param->ip_version == 4) { + packet_size = sizeof(struct IPHeader); + } else { + errno = EINVAL; + return -1; + } + + if (param->protocol == IPPROTO_ICMP) { + packet_size += sizeof(struct ICMPHeader); + } else if (param->protocol == IPPROTO_UDP) { + packet_size += sizeof(struct UDPHeader); + + /* We may need to put the sequence number in the payload */ + packet_size += sizeof(int); + } else { + errno = EINVAL; + return -1; + } + + /* + If the requested size from send-probe is greater, extend the + packet size. + */ + if (param->packet_size > packet_size) { + packet_size = param->packet_size; + } + + /* + Since we don't explicitly construct the IPv6 header, we + need to account for it in our transmitted size. + */ + if (param->ip_version == 6) { + packet_size -= sizeof(struct IP6Header); + } + + return packet_size; +} + +/* Construct a packet for an IPv4 probe */ +static +int construct_ip4_packet( + const struct net_state_t *net_state, + int *packet_socket, + int sequence, + char *packet_buffer, + int packet_size, + const struct sockaddr_storage *src_sockaddr, + const struct sockaddr_storage *dest_sockaddr, + const struct probe_param_t *param) +{ + int send_socket = net_state->platform.ip4_send_socket; + bool is_stream_protocol = false; + + if (param->protocol == IPPROTO_TCP) { + is_stream_protocol = true; +#ifdef IPPROTO_SCTP + } else if (param->protocol == IPPROTO_SCTP) { + is_stream_protocol = true; +#endif + } else { + construct_ip4_header(net_state, packet_buffer, packet_size, + src_sockaddr, dest_sockaddr, param); + + if (param->protocol == IPPROTO_ICMP) { + construct_icmp4_header(net_state, sequence, packet_buffer, + packet_size, param); + } else if (param->protocol == IPPROTO_UDP) { + construct_udp4_header(net_state, sequence, packet_buffer, + packet_size, param); + } else { + errno = EINVAL; + return -1; + } + } + + if (is_stream_protocol) { + send_socket = + open_stream_socket(net_state, param->protocol, sequence, + src_sockaddr, dest_sockaddr, param); + + if (send_socket == -1) { + return -1; + } + + *packet_socket = send_socket; + return 0; + } + + /* + The routing mark requires CAP_NET_ADMIN, as opposed to the + CAP_NET_RAW which we are sometimes explicitly given. + If we don't have CAP_NET_ADMIN, this will fail, so we'll + only set the mark if the user has explicitly requested it. + + Unfortunately, this means that once the mark is set, it won't + be set on the socket again until a new mark is explicitly + specified. + */ +#ifdef SO_MARK + if (param->routing_mark) { + if (setsockopt(send_socket, SOL_SOCKET, + SO_MARK, ¶m->routing_mark, sizeof(int))) { + return -1; + } + } +#endif + + return 0; +} + +/* Construct a packet for an IPv6 probe */ +static +int construct_ip6_packet( + const struct net_state_t *net_state, + int *packet_socket, + int sequence, + char *packet_buffer, + int packet_size, + const struct sockaddr_storage *src_sockaddr, + const struct sockaddr_storage *dest_sockaddr, + const struct probe_param_t *param) +{ + int send_socket; + bool is_stream_protocol = false; + bool bind_send_socket = true; + struct sockaddr_storage current_sockaddr; + int current_sockaddr_len; + + if (param->protocol == IPPROTO_TCP) { + is_stream_protocol = true; +#ifdef IPPROTO_SCTP + } else if (param->protocol == IPPROTO_SCTP) { + is_stream_protocol = true; +#endif + } else if (param->protocol == IPPROTO_ICMP) { + send_socket = net_state->platform.icmp6_send_socket; + + if (construct_icmp6_packet + (net_state, sequence, packet_buffer, packet_size, param)) { + return -1; + } + } else if (param->protocol == IPPROTO_UDP) { + send_socket = net_state->platform.udp6_send_socket; + + if (construct_udp6_packet + (net_state, sequence, packet_buffer, packet_size, param)) { + return -1; + } + } else { + errno = EINVAL; + return -1; + } + + if (is_stream_protocol) { + send_socket = + open_stream_socket(net_state, param->protocol, sequence, + src_sockaddr, dest_sockaddr, param); + + if (send_socket == -1) { + return -1; + } + + *packet_socket = send_socket; + return 0; + } + + /* + Check the current socket address, and if it is the same + as the source address we intend, we will skip the bind. + This is to accomodate Solaris, which, as of Solaris 11.3, + will return an EINVAL error on bind if the socket is already + bound, even if the same address is used. + */ + current_sockaddr_len = sizeof(struct sockaddr_in6); + if (getsockname(send_socket, (struct sockaddr *) ¤t_sockaddr, + ¤t_sockaddr_len) == 0) { + + if (memcmp(¤t_sockaddr, + src_sockaddr, sizeof(struct sockaddr_in6)) == 0) { + bind_send_socket = false; + } + } + + /* Bind to our local address */ + if (bind_send_socket) { + if (bind(send_socket, (struct sockaddr *) src_sockaddr, + sizeof(struct sockaddr_in6))) { + return -1; + } + } + + /* The traffic class in IPv6 is analagous to ToS in IPv4 */ + if (setsockopt(send_socket, IPPROTO_IPV6, + IPV6_TCLASS, ¶m->type_of_service, sizeof(int))) { + return -1; + } + + /* Set the time-to-live */ + if (setsockopt(send_socket, IPPROTO_IPV6, + IPV6_UNICAST_HOPS, ¶m->ttl, sizeof(int))) { + return -1; + } +#ifdef SO_MARK + if (param->routing_mark) { + if (setsockopt(send_socket, + SOL_SOCKET, SO_MARK, ¶m->routing_mark, + sizeof(int))) { + return -1; + } + } +#endif + + return 0; +} + +/* Construct a probe packet based on the probe parameters */ +int construct_packet( + const struct net_state_t *net_state, + int *packet_socket, + int sequence, + char *packet_buffer, + int packet_buffer_size, + const struct sockaddr_storage *dest_sockaddr, + const struct sockaddr_storage *src_sockaddr, + const struct probe_param_t *param) +{ + int packet_size; + + packet_size = compute_packet_size(net_state, param); + if (packet_size < 0) { + return -1; + } + + if (packet_buffer_size < packet_size) { + errno = EINVAL; + return -1; + } + + memset(packet_buffer, param->bit_pattern, packet_size); + + if (param->ip_version == 6) { + if (construct_ip6_packet(net_state, packet_socket, sequence, + packet_buffer, packet_size, + src_sockaddr, dest_sockaddr, param)) { + return -1; + } + } else if (param->ip_version == 4) { + if (construct_ip4_packet(net_state, packet_socket, sequence, + packet_buffer, packet_size, + src_sockaddr, dest_sockaddr, param)) { + return -1; + } + } else { + errno = EINVAL; + return -1; + } + + return packet_size; +} diff --git a/packet/construct_unix.h b/packet/construct_unix.h new file mode 100644 index 0000000..7ac7110 --- /dev/null +++ b/packet/construct_unix.h @@ -0,0 +1,34 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CONSTRUCT_H +#define CONSTRUCT_H + +#include "probe.h" + +int construct_packet( + const struct net_state_t *net_state, + int *packet_socket, + int sequence, + char *packet_buffer, + int packet_buffer_size, + const struct sockaddr_storage *dest_sockaddr, + const struct sockaddr_storage *src_sockaddr, + const struct probe_param_t *param); + +#endif diff --git a/packet/deconstruct_unix.c b/packet/deconstruct_unix.c new file mode 100644 index 0000000..ce889ca --- /dev/null +++ b/packet/deconstruct_unix.c @@ -0,0 +1,503 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "deconstruct_unix.h" + +#include +#include +#include + +#include "protocols.h" + +#define MAX_MPLS_LABELS 8 + +/* + Given an ICMP id + ICMP sequence, find the match probe we've + transmitted and if found, respond to the command which sent it +*/ +static +void find_and_receive_probe( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + struct timeval *timestamp, + int icmp_type, + int protocol, + int icmp_id, + int icmp_sequence, + int mpls_count, + struct mpls_label_t *mpls) +{ + struct probe_t *probe; + + probe = find_probe(net_state, protocol, icmp_id, icmp_sequence); + if (probe == NULL) { + return; + } + + receive_probe(net_state, probe, icmp_type, + remote_addr, timestamp, mpls_count, mpls); +} + +/* + Handle a UDP packet received embedded in an ICMP reply. + The sequence number identifying the probe might be in + the source port number, the destination port number, or + the checksum. We'll check all three. +*/ +static +void handle_inner_udp_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + int icmp_result, + const struct UDPHeader *udp, + int udp_length, + struct timeval *timestamp, + int mpls_count, + struct mpls_label_t *mpls) +{ + struct probe_t *probe; + + probe = find_probe(net_state, IPPROTO_UDP, 0, udp->dstport); + if (probe == NULL) { + probe = find_probe(net_state, IPPROTO_UDP, 0, udp->srcport); + } + if (probe == NULL) { + probe = find_probe(net_state, IPPROTO_UDP, 0, udp->checksum); + } + + if (probe != NULL) { + receive_probe(net_state, probe, icmp_result, + remote_addr, timestamp, mpls_count, mpls); + } +} + +/* + We've received an ICMP message with an embedded IP packet. + We will try to determine which of our outgoing probes + corresponds to the embedded IP packet and record the response. +*/ +static +void handle_inner_ip4_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + int icmp_result, + const struct IPHeader *ip, + int packet_length, + struct timeval *timestamp, + int mpls_count, + struct mpls_label_t *mpls) +{ + const int ip_icmp_size = + sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + const int ip_udp_size = + sizeof(struct IPHeader) + sizeof(struct UDPHeader); + const int ip_tcp_size = + sizeof(struct IPHeader) + sizeof(struct TCPHeader); + const struct ICMPHeader *icmp; + const struct UDPHeader *udp; + const struct TCPHeader *tcp; + int udp_length; +#ifdef IPPROTO_SCTP + const int ip_sctp_size = + sizeof(struct IPHeader) + sizeof(struct SCTPHeader); + const struct SCTPHeader *sctp; +#endif + + if (ip->protocol == IPPROTO_ICMP) { + if (packet_length < ip_icmp_size) { + return; + } + + icmp = (struct ICMPHeader *) (ip + 1); + + find_and_receive_probe(net_state, remote_addr, timestamp, + icmp_result, IPPROTO_ICMP, icmp->id, + icmp->sequence, mpls_count, mpls); + } else if (ip->protocol == IPPROTO_UDP) { + if (packet_length < ip_udp_size) { + return; + } + + udp = (struct UDPHeader *) (ip + 1); + udp_length = packet_length - sizeof(struct IPHeader); + + handle_inner_udp_packet(net_state, remote_addr, icmp_result, udp, + udp_length, timestamp, mpls_count, mpls); + } else if (ip->protocol == IPPROTO_TCP) { + if (packet_length < ip_tcp_size) { + return; + } + + tcp = (struct TCPHeader *) (ip + 1); + + find_and_receive_probe(net_state, remote_addr, timestamp, + icmp_result, IPPROTO_TCP, 0, tcp->srcport, + mpls_count, mpls); +#ifdef IPPROTO_SCTP + } else if (ip->protocol == IPPROTO_SCTP) { + if (packet_length < ip_sctp_size) { + return; + } + + sctp = (struct SCTPHeader *) (ip + 1); + + find_and_receive_probe(net_state, remote_addr, timestamp, + icmp_result, IPPROTO_SCTP, 0, sctp->srcport, + mpls_count, mpls); +#endif + } +} + +/* + Examine the IPv6 header embedded in a returned ICMPv6 packet + in order to match it with a probe which we previously sent. +*/ +static +void handle_inner_ip6_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + int icmp_result, + const struct IP6Header *ip, + int packet_length, + struct timeval *timestamp, + int mpls_count, + struct mpls_label_t *mpls) +{ + const int ip_icmp_size = + sizeof(struct IP6Header) + sizeof(struct ICMPHeader); + const int ip_udp_size = + sizeof(struct IP6Header) + sizeof(struct UDPHeader); + const int ip_tcp_size = + sizeof(struct IP6Header) + sizeof(struct TCPHeader); + const struct ICMPHeader *icmp; + const struct UDPHeader *udp; + const struct TCPHeader *tcp; + int udp_length; +#ifdef IPPROTO_SCTP + const int ip_sctp_size = + sizeof(struct IPHeader) + sizeof(struct SCTPHeader); + const struct SCTPHeader *sctp; +#endif + + if (ip->protocol == IPPROTO_ICMPV6) { + if (packet_length < ip_icmp_size) { + return; + } + + icmp = (struct ICMPHeader *) (ip + 1); + + find_and_receive_probe(net_state, remote_addr, timestamp, + icmp_result, IPPROTO_ICMP, icmp->id, + icmp->sequence, mpls_count, mpls); + } else if (ip->protocol == IPPROTO_UDP) { + if (packet_length < ip_udp_size) { + return; + } + + udp = (struct UDPHeader *) (ip + 1); + udp_length = packet_length - sizeof(struct IP6Header); + + handle_inner_udp_packet(net_state, remote_addr, icmp_result, udp, + udp_length, timestamp, mpls_count, mpls); + } else if (ip->protocol == IPPROTO_TCP) { + if (packet_length < ip_tcp_size) { + return; + } + + tcp = (struct TCPHeader *) (ip + 1); + find_and_receive_probe(net_state, remote_addr, timestamp, + icmp_result, IPPROTO_TCP, 0, tcp->srcport, + mpls_count, mpls); +#ifdef IPPROTO_SCTP + } else if (ip->protocol == IPPROTO_SCTP) { + if (packet_length < ip_sctp_size) { + return; + } + + sctp = (struct SCTPHeader *) (ip + 1); + + find_and_receive_probe(net_state, remote_addr, timestamp, + icmp_result, IPPROTO_SCTP, 0, sctp->srcport, + mpls_count, mpls); +#endif + } +} + +/* Convert an ICMP MPLS extension object into an mpls_label_t structure */ +static +int decode_mpls_object( + struct ICMPExtensionObject *icmp_obj, + int obj_len, + struct mpls_label_t *mpls, + int mpls_count) +{ + int label_bytes; + int labels_present; + int i; + struct ICMPExtMPLSLabel *ext_mpls; + struct ICMPExtMPLSLabel *ext_label; + struct mpls_label_t *label; + + label_bytes = obj_len - sizeof(struct ICMPExtensionObject); + labels_present = label_bytes / sizeof(struct ICMPExtMPLSLabel); + + ext_mpls = (struct ICMPExtMPLSLabel *) (icmp_obj + 1); + for (i = 0; i < mpls_count && i < labels_present; i++) { + ext_label = &ext_mpls[i]; + label = &mpls[i]; + + memset(label, 0, sizeof(struct mpls_label_t)); + + label->label = + ext_label->label[0] << 12 | + ext_label->label[1] << 4 | ext_label->label[2] >> 4; + label->experimental_use = (ext_label->label[2] & 0x0E) >> 1; + label->bottom_of_stack = ext_label->label[2] & 0x01; + label->ttl = ext_label->ttl; + } + + return i; +} + +/* Extract MPLS labels from the ICMP extension header, if present */ +static +int decode_mpls_labels( + const struct ICMPHeader *icmp, + int packet_length, + struct mpls_label_t *mpls, + int mpls_count) +{ + const int icmp_orig_icmp_ext_size = + sizeof(struct ICMPHeader) + ICMP_ORIGINAL_DATAGRAM_MIN_SIZE + + sizeof(struct ICMPExtensionHeader); + char *inner_packet; + char *icmp_object_bytes; + struct ICMPExtensionHeader *icmp_ext; + struct ICMPExtensionObject *icmp_obj; + int remaining_size; + int obj_len; + + if (packet_length < icmp_orig_icmp_ext_size) { + return 0; + } + + inner_packet = (char *) (icmp + 1); + icmp_ext = (struct ICMPExtensionHeader *) + (inner_packet + ICMP_ORIGINAL_DATAGRAM_MIN_SIZE); + + if ((icmp_ext->version & 0xF0) != 0x20) { + return 0; + } + + remaining_size = packet_length - icmp_orig_icmp_ext_size; + icmp_object_bytes = (char *) (icmp_ext + 1); + + /* + Iterate through the chain of extension objects, looking for + an MPLS label extension. + */ + while (remaining_size >= sizeof(struct ICMPExtensionObject)) { + icmp_obj = (struct ICMPExtensionObject *) icmp_object_bytes; + obj_len = ntohs(icmp_obj->len); + + if (obj_len > remaining_size) { + return 0; + } + if (obj_len < sizeof(struct ICMPExtensionObject)) { + return 0; + } + + if (icmp_obj->classnum == ICMP_EXT_MPLS_CLASSNUM && + icmp_obj->ctype == ICMP_EXT_MPLS_CTYPE) { + + return decode_mpls_object(icmp_obj, obj_len, mpls, mpls_count); + } + + remaining_size -= obj_len; + icmp_object_bytes += obj_len; + } + + return 0; +} + +/* + Decode the ICMP header received and try to find a probe which it + is in response to. +*/ +static +void handle_received_icmp4_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + const struct ICMPHeader *icmp, + int packet_length, + struct timeval *timestamp) +{ + const int icmp_ip_size = + sizeof(struct ICMPHeader) + sizeof(struct IPHeader); + const struct IPHeader *inner_ip; + int inner_size = packet_length - sizeof(struct ICMPHeader); + int mpls_count; + struct mpls_label_t mpls[MAX_MPLS_LABELS]; + + mpls_count = + decode_mpls_labels(icmp, packet_length, mpls, MAX_MPLS_LABELS); + + /* If we get an echo reply, our probe reached the destination host */ + if (icmp->type == ICMP_ECHOREPLY) { + find_and_receive_probe(net_state, remote_addr, timestamp, + ICMP_ECHOREPLY, IPPROTO_ICMP, icmp->id, + icmp->sequence, mpls_count, mpls); + } + + if (packet_length < icmp_ip_size) { + return; + } + inner_ip = (struct IPHeader *) (icmp + 1); + + /* + If we get a time exceeded, we got a response from an intermediate + host along the path to our destination. + */ + if (icmp->type == ICMP_TIME_EXCEEDED) { + /* + The IP packet inside the ICMP response contains our original + IP header. That's where we can get our original ID and + sequence number. + */ + handle_inner_ip4_packet(net_state, remote_addr, + ICMP_TIME_EXCEEDED, inner_ip, inner_size, + timestamp, mpls_count, mpls); + } + + if (icmp->type == ICMP_DEST_UNREACH) { + /* + We'll get a ICMP_PORT_UNREACH when a non-ICMP probe + reaches its final destination. (Assuming that port isn't + open on the destination host.) + */ + if (icmp->code == ICMP_PORT_UNREACH) { + handle_inner_ip4_packet(net_state, remote_addr, + ICMP_ECHOREPLY, inner_ip, inner_size, + timestamp, mpls_count, mpls); + } + } +} + +/* + Decode the ICMPv6 header. The code duplication with ICMPv4 is + unfortunate, but small details in structure size and ICMP + constants differ. +*/ +static +void handle_received_icmp6_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + const struct ICMPHeader *icmp, + int packet_length, + struct timeval *timestamp) +{ + const int icmp_ip_size = + sizeof(struct ICMPHeader) + sizeof(struct IP6Header); + const struct IP6Header *inner_ip; + int inner_size = packet_length - sizeof(struct ICMPHeader); + int mpls_count; + struct mpls_label_t mpls[MAX_MPLS_LABELS]; + + mpls_count = + decode_mpls_labels(icmp, packet_length, mpls, MAX_MPLS_LABELS); + + if (icmp->type == ICMP6_ECHOREPLY) { + find_and_receive_probe(net_state, remote_addr, timestamp, + ICMP_ECHOREPLY, IPPROTO_ICMP, icmp->id, + icmp->sequence, mpls_count, mpls); + } + + if (packet_length < icmp_ip_size) { + return; + } + inner_ip = (struct IP6Header *) (icmp + 1); + + if (icmp->type == ICMP6_TIME_EXCEEDED) { + handle_inner_ip6_packet(net_state, remote_addr, + ICMP_TIME_EXCEEDED, inner_ip, inner_size, + timestamp, mpls_count, mpls); + } + + if (icmp->type == ICMP6_DEST_UNREACH) { + if (icmp->code == ICMP6_PORT_UNREACH) { + handle_inner_ip6_packet(net_state, remote_addr, + ICMP_ECHOREPLY, inner_ip, inner_size, + timestamp, mpls_count, mpls); + } + } +} + +/* + We've received a new IPv4 ICMP packet. + We'll check to see that it is a response to one of our probes, and + if so, report the result of the probe to our command stream. +*/ +void handle_received_ip4_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + const void *packet, + int packet_length, + struct timeval *timestamp) +{ + const int ip_icmp_size = + sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + const struct IPHeader *ip; + const struct ICMPHeader *icmp; + int icmp_length; + + /* Ensure that we don't access memory beyond the bounds of the packet */ + if (packet_length < ip_icmp_size) { + return; + } + + ip = (struct IPHeader *) packet; + if (ip->protocol != IPPROTO_ICMP) { + return; + } + + icmp = (struct ICMPHeader *) (ip + 1); + icmp_length = packet_length - sizeof(struct IPHeader); + + handle_received_icmp4_packet(net_state, remote_addr, icmp, icmp_length, + timestamp); +} + +/* + Unlike ICMPv6 raw sockets, unlike ICMPv4, don't include the IP header + in received packets, so we can assume the packet we got starts + with the ICMP packet. +*/ +void handle_received_ip6_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + const void *packet, + int packet_length, + struct timeval *timestamp) +{ + const struct ICMPHeader *icmp; + + icmp = (struct ICMPHeader *) packet; + + handle_received_icmp6_packet(net_state, remote_addr, icmp, + packet_length, timestamp); +} diff --git a/packet/deconstruct_unix.h b/packet/deconstruct_unix.h new file mode 100644 index 0000000..69186cd --- /dev/null +++ b/packet/deconstruct_unix.h @@ -0,0 +1,46 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef DECONSTRUCT_H +#define DECONSTRUCT_H + +#include "probe.h" + +typedef void ( + *received_packet_func_t) ( + struct net_state_t * net_state, + const struct sockaddr_storage * remote_addr, + const void *packet, + int packet_length, + struct timeval * timestamp); + +void handle_received_ip4_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + const void *packet, + int packet_length, + struct timeval *timestamp); + +void handle_received_ip6_packet( + struct net_state_t *net_state, + const struct sockaddr_storage *remote_addr, + const void *packet, + int packet_length, + struct timeval *timestamp); + +#endif diff --git a/packet/packet.c b/packet/packet.c new file mode 100644 index 0000000..f32f729 --- /dev/null +++ b/packet/packet.c @@ -0,0 +1,138 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBCAP +#include +#endif + +#include "wait.h" + +/* Drop SUID privileges. To be used after accquiring raw sockets. */ +static +int drop_elevated_permissions( + void) +{ +#ifdef HAVE_LIBCAP + cap_t cap; +#endif + + /* Drop any suid permissions granted */ + if (setgid(getgid()) || setuid(getuid())) { + return -1; + } + + if (geteuid() != getuid() || getegid() != getgid()) { + return -1; + } + + /* + Drop all process capabilities. + This will revoke anything granted by a commandline 'setcap' + */ +#ifdef HAVE_LIBCAP + cap = cap_get_proc(); + if (cap == NULL) { + return -1; + } + if (cap_clear(cap)) { + return -1; + } + if (cap_set_proc(cap)) { + return -1; + } +#endif + + return 0; +} + +int main( + int argc, + char **argv) +{ + bool command_pipe_open; + struct command_buffer_t command_buffer; + struct net_state_t net_state; + + /* + To minimize security risk, the only thing done prior to + dropping SUID should be opening the network state for + raw sockets. + */ + init_net_state_privileged(&net_state); + if (drop_elevated_permissions()) { + perror("Unable to drop elevated permissions"); + exit(EXIT_FAILURE); + } + init_net_state(&net_state); + + init_command_buffer(&command_buffer, fileno(stdin)); + + command_pipe_open = true; + + /* + Dispatch commands and respond to probe replies until the + command stream is closed. + */ + while (true) { + /* Ensure any responses are written before waiting */ + fflush(stdout); + wait_for_activity(&command_buffer, &net_state); + + /* + Receive replies first so that the timestamps are as + close to the response arrival time as possible. + */ + receive_replies(&net_state); + + if (command_pipe_open) { + if (read_commands(&command_buffer)) { + if (errno == EPIPE) { + command_pipe_open = false; + } + } + } + + check_probe_timeouts(&net_state); + + /* + Dispatch commands late so that the window between probe + departure and arriving replies is as small as possible. + */ + dispatch_buffer_commands(&command_buffer, &net_state); + + /* + If the command pipe has been closed, exit after all + in-flight probes have reported their status. + */ + if (!command_pipe_open) { + if (net_state.outstanding_probe_count == 0) { + break; + } + } + } + + return 0; +} diff --git a/packet/platform.h b/packet/platform.h new file mode 100644 index 0000000..cca1274 --- /dev/null +++ b/packet/platform.h @@ -0,0 +1,53 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/* + Determine the most appropriate PLATFORM_* define for our + current target. +*/ + +#if defined(__CYGWIN__) + +#define PLATFORM_CYGWIN + +#elif defined(__APPLE__) && defined(__MACH__) + +#define PLATFORM_OS_X + +#elif defined(__gnu_linux__) + +#define PLATFORM_LINUX + +#elif defined (__FreeBSD__) + +#define PLATFORM_FREEBSD + +#elif defined(__unix__) + +#define PLATFORM_UNIX_UNKNOWN + +#else + +#error Unsupported platform + +#endif + +#endif diff --git a/packet/probe.c b/packet/probe.c new file mode 100644 index 0000000..34ff368 --- /dev/null +++ b/packet/probe.c @@ -0,0 +1,367 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "probe.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "command.h" +#include "platform.h" +#include "protocols.h" +#include "timeval.h" + +#define IP_TEXT_LENGTH 64 + +/* Convert the destination address from text to sockaddr */ +int decode_address_string( + int ip_version, + const char *address_string, + struct sockaddr_storage *address) +{ + struct in_addr addr4; + struct in6_addr addr6; + struct sockaddr_in *sockaddr4; + struct sockaddr_in6 *sockaddr6; + + if (address == NULL) { + errno = EINVAL; + return -1; + } + + if (ip_version == 6) { + sockaddr6 = (struct sockaddr_in6 *) address; + + if (inet_pton(AF_INET6, address_string, &addr6) != 1) { + errno = EINVAL; + return -1; + } + + sockaddr6->sin6_family = AF_INET6; + sockaddr6->sin6_port = 0; + sockaddr6->sin6_flowinfo = 0; + sockaddr6->sin6_addr = addr6; + sockaddr6->sin6_scope_id = 0; + } else if (ip_version == 4) { + sockaddr4 = (struct sockaddr_in *) address; + + if (inet_pton(AF_INET, address_string, &addr4) != 1) { + errno = EINVAL; + return -1; + } + + sockaddr4->sin_family = AF_INET; + sockaddr4->sin_port = 0; + sockaddr4->sin_addr = addr4; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* + Resolve the probe parameters into a remote and local address + for the probe. +*/ +int resolve_probe_addresses( + const struct probe_param_t *param, + struct sockaddr_storage *dest_sockaddr, + struct sockaddr_storage *src_sockaddr) +{ + if (decode_address_string + (param->ip_version, param->remote_address, dest_sockaddr)) { + return -1; + } + + if (param->local_address) { + if (decode_address_string + (param->ip_version, param->local_address, src_sockaddr)) { + return -1; + } + } else { + if (find_source_addr(src_sockaddr, dest_sockaddr)) { + return -1; + } + } + + return 0; +} + +/* Allocate a structure for tracking a new probe */ +struct probe_t *alloc_probe( + struct net_state_t *net_state, + int token) +{ + struct probe_t *probe; + + if (net_state->outstanding_probe_count >= MAX_PROBES) { + return NULL; + } + + probe = malloc(sizeof(struct probe_t)); + if (probe == NULL) { + return NULL; + } + + memset(probe, 0, sizeof(struct probe_t)); + probe->token = token; + + platform_alloc_probe(net_state, probe); + + net_state->outstanding_probe_count++; + LIST_INSERT_HEAD(&net_state->outstanding_probes, probe, + probe_list_entry); + + return probe; +} + +/* Mark a probe tracking structure as unused */ +void free_probe( + struct net_state_t *net_state, + struct probe_t *probe) +{ + LIST_REMOVE(probe, probe_list_entry); + net_state->outstanding_probe_count--; + + platform_free_probe(probe); + + free(probe); +} + +/* + Find an existing probe structure by ICMP id and sequence number. + Returns NULL if non is found. +*/ +struct probe_t *find_probe( + struct net_state_t *net_state, + int protocol, + int id, + int sequence) +{ + struct probe_t *probe; + + /* + ICMP has room for an id to check against our process, but + UDP doesn't. + */ + if (protocol == IPPROTO_ICMP) { + /* + If the ICMP id doesn't match our process ID, it wasn't a + probe generated by this process, so ignore it. + */ + if (id != htons(getpid())) { + return NULL; + } + } + + LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) { + if (htons(probe->sequence) == sequence) { + return probe; + } + } + + return NULL; +} + +/* + Format a list of MPLS labels into a string appropriate for including + as an argument to a probe reply. +*/ +static +void format_mpls_string( + char *str, + int buffer_size, + int mpls_count, + const struct mpls_label_t *mpls_list) +{ + int i; + char *append_pos = str; + const struct mpls_label_t *mpls; + + /* Start with an empty string */ + str[0] = 0; + + for (i = 0; i < mpls_count; i++) { + mpls = &mpls_list[i]; + + if (i > 0) { + strncat(append_pos, ",", buffer_size - 1); + + buffer_size -= strlen(append_pos); + append_pos += strlen(append_pos); + } + + snprintf(append_pos, buffer_size, "%d,%d,%d,%d", + mpls->label, mpls->experimental_use, + mpls->bottom_of_stack, mpls->ttl); + + buffer_size -= strlen(append_pos); + append_pos += strlen(append_pos); + } +} + +/* + After a probe reply has arrived, respond to the command request which + sent the probe. +*/ +void respond_to_probe( + struct net_state_t *net_state, + struct probe_t *probe, + int icmp_type, + const struct sockaddr_storage *remote_addr, + unsigned int round_trip_us, + int mpls_count, + const struct mpls_label_t *mpls) +{ + char ip_text[IP_TEXT_LENGTH]; + char response[COMMAND_BUFFER_SIZE]; + char mpls_str[COMMAND_BUFFER_SIZE]; + int remaining_size; + const char *result; + const char *ip_argument; + struct sockaddr_in *sockaddr4; + struct sockaddr_in6 *sockaddr6; + void *addr; + + if (icmp_type == ICMP_TIME_EXCEEDED) { + result = "ttl-expired"; + } else { + assert(icmp_type == ICMP_ECHOREPLY); + result = "reply"; + } + + if (remote_addr->ss_family == AF_INET6) { + ip_argument = "ip-6"; + sockaddr6 = (struct sockaddr_in6 *) remote_addr; + addr = &sockaddr6->sin6_addr; + } else { + ip_argument = "ip-4"; + sockaddr4 = (struct sockaddr_in *) remote_addr; + addr = &sockaddr4->sin_addr; + } + + if (inet_ntop(remote_addr->ss_family, addr, ip_text, IP_TEXT_LENGTH) == + NULL) { + + perror("inet_ntop failure"); + exit(EXIT_FAILURE); + } + + snprintf(response, COMMAND_BUFFER_SIZE, + "%d %s %s %s round-trip-time %d", + probe->token, result, ip_argument, ip_text, round_trip_us); + + if (mpls_count) { + format_mpls_string(mpls_str, COMMAND_BUFFER_SIZE, mpls_count, + mpls); + + remaining_size = COMMAND_BUFFER_SIZE - strlen(response) - 1; + strncat(response, " mpls ", remaining_size); + + remaining_size = COMMAND_BUFFER_SIZE - strlen(response) - 1; + strncat(response, mpls_str, remaining_size); + } + + puts(response); + free_probe(net_state, probe); +} + +/* + Find the source address for transmitting to a particular destination + address. Remember that hosts can have multiple addresses, for example + a unique address for each network interface. So we will bind a UDP + socket to our destination and check the socket address after binding + to get the source for that destination, which will allow the kernel + to do the routing table work for us. + + (connecting UDP sockets, unlike TCP sockets, doesn't transmit any packets. + It's just an association.) +*/ +int find_source_addr( + struct sockaddr_storage *srcaddr, + const struct sockaddr_storage *destaddr) +{ + int sock; + int len; + struct sockaddr_in *destaddr4; + struct sockaddr_in6 *destaddr6; + struct sockaddr_storage dest_with_port; + struct sockaddr_in *srcaddr4; + struct sockaddr_in6 *srcaddr6; + + dest_with_port = *destaddr; + + /* + MacOS requires a non-zero sin_port when used as an + address for a UDP connect. If we provide a zero port, + the connect will fail. We aren't actually sending + anything to the port. + */ + if (destaddr->ss_family == AF_INET6) { + destaddr6 = (struct sockaddr_in6 *) &dest_with_port; + destaddr6->sin6_port = htons(1); + + len = sizeof(struct sockaddr_in6); + } else { + destaddr4 = (struct sockaddr_in *) &dest_with_port; + destaddr4->sin_port = htons(1); + + len = sizeof(struct sockaddr_in); + } + + sock = socket(destaddr->ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (sock == -1) { + return -1; + } + + if (connect(sock, (struct sockaddr *) &dest_with_port, len)) { + close(sock); + return -1; + } + + if (getsockname(sock, (struct sockaddr *) srcaddr, &len)) { + close(sock); + return -1; + } + + close(sock); + + /* + Zero the port, as we may later use this address to finding, and + we don't want to use the port from the socket we just created. + */ + if (destaddr->ss_family == AF_INET6) { + srcaddr6 = (struct sockaddr_in6 *) srcaddr; + + srcaddr6->sin6_port = 0; + } else { + srcaddr4 = (struct sockaddr_in *) srcaddr; + + srcaddr4->sin_port = 0; + } + + return 0; +} diff --git a/packet/probe.h b/packet/probe.h new file mode 100644 index 0000000..9c8dfc6 --- /dev/null +++ b/packet/probe.h @@ -0,0 +1,202 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROBE_H +#define PROBE_H + +#include "platform.h" + +#include +#include +#include +#include + +#include "portability/queue.h" + +#ifdef PLATFORM_CYGWIN +#include "probe_cygwin.h" +#else +#include "probe_unix.h" +#endif + +#define MAX_PROBES 1024 + +/* Use the "jumbo" frame size as the max packet size */ +#define PACKET_BUFFER_SIZE 9000 + +/* Parameters for sending a new probe */ +struct probe_param_t { + /* The version of the Internet Protocol to use. (4 or 6) */ + int ip_version; + + /* The command token used to identify a probe when it is completed */ + int command_token; + + /* The IP address to probe */ + const char *remote_address; + + /* The local address from which to send probes */ + const char *local_address; + + /* Protocol for the probe, using the IPPROTO_* defines */ + int protocol; + + /* The destination port for non-ICMP probes */ + int dest_port; + + /* The local port number to use when sending probes */ + int local_port; + + /* The "type of service" field in the IP header */ + int type_of_service; + + /* The packet "mark" used for mark-based routing on Linux */ + int routing_mark; + + /* Time to live for the transmited probe */ + int ttl; + + /* The packet size (in bytes) including protocol headers */ + int packet_size; + + /* The value with which to fill the bytes of the packet. */ + int bit_pattern; + + /* The number of seconds to wait before assuming the probe was lost */ + int timeout; +}; + +/* Tracking information for an outstanding probe */ +struct probe_t { + /* Our entry in the probe list */ + LIST_ENTRY( + probe_t) probe_list_entry; + + /* + Also the ICMP sequence ID used to identify the probe. + + Also used as the port number to use when binding stream protocol + sockets for this probe. (i.e. TCP or SCTP) + */ + int sequence; + + /* Command token of the probe request */ + int token; + + /* The address being probed */ + struct sockaddr_storage remote_addr; + + /* Platform specific probe tracking */ + struct probe_platform_t platform; +}; + +/* Global state for interacting with the network */ +struct net_state_t { + /* The number of entries in the outstanding_probes list */ + int outstanding_probe_count; + + /* Tracking information for in-flight probes */ + LIST_HEAD( + probe_list_head_t, + probe_t) outstanding_probes; + + /* Platform specific tracking information */ + struct net_state_platform_t platform; +}; + +/* Multiprotocol Label Switching information */ +struct mpls_label_t { + uint32_t label; + uint8_t experimental_use; + uint8_t bottom_of_stack; + uint8_t ttl; +}; + +void init_net_state_privileged( + struct net_state_t *net_state); + +void init_net_state( + struct net_state_t *net_state); + +bool is_ip_version_supported( + struct net_state_t *net_state, + int ip_version); + +bool is_protocol_supported( + struct net_state_t *net_state, + int protocol); + +bool get_next_probe_timeout( + const struct net_state_t *net_state, + struct timeval *timeout); + +void send_probe( + struct net_state_t *net_state, + const struct probe_param_t *param); + +void receive_replies( + struct net_state_t *net_state); + +void check_probe_timeouts( + struct net_state_t *net_state); + +void respond_to_probe( + struct net_state_t *net_state, + struct probe_t *probe, + int icmp_type, + const struct sockaddr_storage *remote_addr, + unsigned int round_trip_us, + int mpls_count, + const struct mpls_label_t *mpls); + +int decode_address_string( + int ip_version, + const char *address_string, + struct sockaddr_storage *address); + +int resolve_probe_addresses( + const struct probe_param_t *param, + struct sockaddr_storage *dest_sockaddr, + struct sockaddr_storage *src_sockaddr); + +struct probe_t *alloc_probe( + struct net_state_t *net_state, + int token); + +void free_probe( + struct net_state_t *net_state, + struct probe_t *probe); + +void platform_alloc_probe( + struct net_state_t *net_state, + struct probe_t *probe); + +void platform_free_probe( + struct probe_t *probe); + +struct probe_t *find_probe( + struct net_state_t *net_state, + int protocol, + int icmp_id, + int icmp_sequence); + +int find_source_addr( + struct sockaddr_storage *srcaddr, + const struct sockaddr_storage *destaddr); + +#endif diff --git a/packet/probe_cygwin.c b/packet/probe_cygwin.c new file mode 100644 index 0000000..56bd2b9 --- /dev/null +++ b/packet/probe_cygwin.c @@ -0,0 +1,381 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "probe.h" + +#include +#include +#include + +#include "protocols.h" + +/* Windows doesn't require any initialization at a privileged level */ +void init_net_state_privileged( + struct net_state_t *net_state) +{ +} + +/* Open the ICMP.DLL interface */ +void init_net_state( + struct net_state_t *net_state) +{ + memset(net_state, 0, sizeof(struct net_state_t)); + + net_state->platform.icmp4 = IcmpCreateFile(); + net_state->platform.icmp6 = Icmp6CreateFile(); + + if (net_state->platform.icmp4 == INVALID_HANDLE_VALUE + && net_state->platform.icmp6 == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Failure opening ICMP %d\n", GetLastError()); + exit(EXIT_FAILURE); + } +} + +/* + If we succeeded at opening the ICMP file handle, we can + assume that IP protocol version is supported. +*/ +bool is_ip_version_supported( + struct net_state_t *net_state, + int ip_version) +{ + if (ip_version == 4) { + return (net_state->platform.icmp4 != INVALID_HANDLE_VALUE); + } else if (ip_version == 6) { + return (net_state->platform.icmp6 != INVALID_HANDLE_VALUE); + } + + return false; +} + +/* On Windows, we only support ICMP probes */ +bool is_protocol_supported( + struct net_state_t * net_state, + int protocol) +{ + if (protocol == IPPROTO_ICMP) { + return true; + } + + return false; +} + +/* Set the back pointer to the net_state when a probe is allocated */ +void platform_alloc_probe( + struct net_state_t *net_state, + struct probe_t *probe) +{ + probe->platform.net_state = net_state; +} + +/* Free the reply buffer when the probe is freed */ +void platform_free_probe( + struct probe_t *probe) +{ + if (probe->platform.reply4) { + free(probe->platform.reply4); + probe->platform.reply4 = NULL; + } +} + +/* Report a windows error code using a platform-independent error string */ +static +void report_win_error( + int command_token, + int err) +{ + /* It could be that we got no reply because of timeout */ + if (err == IP_REQ_TIMED_OUT || err == IP_SOURCE_QUENCH) { + printf("%d no-reply\n", command_token); + } else if (err == IP_DEST_HOST_UNREACHABLE + || err == IP_DEST_PORT_UNREACHABLE + || err == IP_DEST_PROT_UNREACHABLE + || err == IP_DEST_NET_UNREACHABLE + || err == IP_DEST_UNREACHABLE + || err == IP_DEST_NO_ROUTE + || err == IP_BAD_ROUTE || err == IP_BAD_DESTINATION) { + printf("%d no-route\n", command_token); + } else if (err == ERROR_INVALID_NETNAME) { + printf("%d address-not-available\n", command_token); + } else if (err == ERROR_INVALID_PARAMETER) { + printf("%d invalid-argument\n", command_token); + } else { + printf("%d unexpected-error winerror %d\n", command_token, err); + } +} + +/* + The overlapped I/O style completion routine to be called by + Windows during an altertable wait when an ICMP probe has + completed, either by reply, or by ICMP.DLL timeout. +*/ +static +void WINAPI on_icmp_reply( + PVOID context, + PIO_STATUS_BLOCK status, + ULONG reserved) +{ + struct probe_t *probe = (struct probe_t *) context; + struct net_state_t *net_state = probe->platform.net_state; + int icmp_type; + int round_trip_us = 0; + int reply_count; + int reply_status = 0; + struct sockaddr_storage remote_addr; + struct sockaddr_in *remote_addr4; + struct sockaddr_in6 *remote_addr6; + ICMP_ECHO_REPLY32 *reply4; + ICMPV6_ECHO_REPLY *reply6; + + if (probe->platform.ip_version == 6) { + reply6 = probe->platform.reply6; + reply_count = Icmp6ParseReplies(reply6, sizeof(ICMPV6_ECHO_REPLY)); + + if (reply_count > 0) { + reply_status = reply6->Status; + + /* Unfortunately, ICMP.DLL only has millisecond precision */ + round_trip_us = reply6->RoundTripTime * 1000; + + remote_addr6 = (struct sockaddr_in6 *) &remote_addr; + remote_addr6->sin6_family = AF_INET6; + remote_addr6->sin6_port = 0; + remote_addr6->sin6_flowinfo = 0; + memcpy(&remote_addr6->sin6_addr, reply6->AddressBits, + sizeof(struct in6_addr)); + remote_addr6->sin6_scope_id = 0; + } + } else { + reply4 = probe->platform.reply4; + reply_count = IcmpParseReplies(reply4, sizeof(ICMP_ECHO_REPLY)); + + if (reply_count > 0) { + reply_status = reply4->Status; + + /* Unfortunately, ICMP.DLL only has millisecond precision */ + round_trip_us = reply4->RoundTripTime * 1000; + + remote_addr4 = (struct sockaddr_in *) &remote_addr; + remote_addr4->sin_family = AF_INET; + remote_addr4->sin_port = 0; + remote_addr4->sin_addr.s_addr = reply4->Address; + } + } + + if (reply_count == 0) { + reply_status = GetLastError(); + } + + icmp_type = -1; + if (reply_status == IP_SUCCESS) { + icmp_type = ICMP_ECHOREPLY; + } else if (reply_status == IP_TTL_EXPIRED_TRANSIT + || reply_status == IP_TTL_EXPIRED_REASSEM) { + icmp_type = ICMP_TIME_EXCEEDED; + } + + if (icmp_type != -1) { + /* Record probe result */ + respond_to_probe(net_state, probe, icmp_type, + &remote_addr, round_trip_us, 0, NULL); + } else { + report_win_error(probe->token, reply_status); + free_probe(net_state, probe); + } +} + +/* Use ICMP.DLL's send echo support to send a probe */ +static +void icmp_send_probe( + struct net_state_t *net_state, + struct probe_t *probe, + const struct probe_param_t *param, + struct sockaddr_storage *src_sockaddr, + struct sockaddr_storage *dest_sockaddr, + char *payload, + int payload_size) +{ + IP_OPTION_INFORMATION option; + DWORD timeout; + DWORD send_result; + int reply_size; + int err; + struct sockaddr_in *dest_sockaddr4; + struct sockaddr_in6 *src_sockaddr6; + struct sockaddr_in6 *dest_sockaddr6; + + if (param->timeout > 0) { + timeout = 1000 * param->timeout; + } else { + /* + IcmpSendEcho2 will return invalid argument on a timeout of + zero. Our Unix implementation allows it. Bump up the timeout + to 1 millisecond. + */ + timeout = 1; + } + + memset(&option, 0, sizeof(IP_OPTION_INFORMATION32)); + option.Ttl = param->ttl; + + if (param->ip_version == 6) { + reply_size = sizeof(ICMPV6_ECHO_REPLY) + payload_size; + } else { + reply_size = sizeof(ICMP_ECHO_REPLY32) + payload_size; + } + + probe->platform.reply4 = malloc(reply_size); + if (probe->platform.reply4 == NULL) { + perror("failure to allocate reply buffer"); + exit(EXIT_FAILURE); + } + + if (param->ip_version == 6) { + src_sockaddr6 = (struct sockaddr_in6 *) src_sockaddr; + dest_sockaddr6 = (struct sockaddr_in6 *) dest_sockaddr; + + send_result = Icmp6SendEcho2(net_state->platform.icmp6, NULL, + (FARPROC) on_icmp_reply, probe, + src_sockaddr6, dest_sockaddr6, + payload, payload_size, &option, + probe->platform.reply6, reply_size, + timeout); + } else { + dest_sockaddr4 = (struct sockaddr_in *) dest_sockaddr; + + send_result = IcmpSendEcho2(net_state->platform.icmp4, NULL, + (FARPROC) on_icmp_reply, probe, + dest_sockaddr4->sin_addr.s_addr, + payload, payload_size, &option, + probe->platform.reply4, reply_size, + timeout); + } + + if (send_result == 0) { + err = GetLastError(); + + /* + ERROR_IO_PENDING is expected for asynchronous probes, + but any other error is unexpected. + */ + if (err != ERROR_IO_PENDING) { + report_win_error(probe->token, err); + free_probe(net_state, probe); + } + } +} + +/* Fill the payload of the packet as specified by the probe parameters */ +static +int fill_payload( + const struct probe_param_t *param, + char *payload, + int payload_buffer_size) +{ + int ip_icmp_size; + int payload_size; + + if (param->ip_version == 6) { + ip_icmp_size = + sizeof(struct IP6Header) + sizeof(struct ICMPHeader); + } else if (param->ip_version == 4) { + ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + } else { + errno = EINVAL; + return -1; + } + + payload_size = param->packet_size - ip_icmp_size; + if (payload_size < 0) { + payload_size = 0; + } + + if (payload_size > payload_buffer_size) { + errno = EINVAL; + return -1; + } + + memset(payload, param->bit_pattern, payload_size); + + return payload_size; +} + +/* Decode the probe parameters and send a probe */ +void send_probe( + struct net_state_t *net_state, + const struct probe_param_t *param) +{ + struct probe_t *probe; + struct sockaddr_storage dest_sockaddr; + struct sockaddr_storage src_sockaddr; + char payload[PACKET_BUFFER_SIZE]; + int payload_size; + + if (resolve_probe_addresses(param, &dest_sockaddr, &src_sockaddr)) { + printf("%d invalid-argument\n", param->command_token); + return; + } + + probe = alloc_probe(net_state, param->command_token); + if (probe == NULL) { + printf("%d probes-exhausted\n", param->command_token); + return; + } + + probe->platform.ip_version = param->ip_version; + + payload_size = fill_payload(param, payload, PACKET_BUFFER_SIZE); + if (payload_size < 0) { + perror("Error construction packet"); + exit(EXIT_FAILURE); + } + + icmp_send_probe(net_state, probe, param, + &src_sockaddr, &dest_sockaddr, payload, payload_size); +} + +/* + On Windows, an implementation of receive_replies is unnecessary, because, + unlike Unix, replies are completed using Overlapped I/O during an + alertable wait, and don't require explicit reads. +*/ +void receive_replies( + struct net_state_t *net_state) +{ +} + +/* + On Windows, an implementation of check_probe_timeout is unnecesary because + timeouts are managed by ICMP.DLL, including a call to the I/O completion + routine when the time fully expires. +*/ +void check_probe_timeouts( + struct net_state_t *net_state) +{ +} + +/* + As in the case of check_probe_timeout, getting the next probe timeout is + unnecessary under Windows, as ICMP.DLL manages timeouts for us. +*/ +bool get_next_probe_timeout( + const struct net_state_t *net_state, + struct timeval *timeout) +{ + return false; +} diff --git a/packet/probe_cygwin.h b/packet/probe_cygwin.h new file mode 100644 index 0000000..29ef982 --- /dev/null +++ b/packet/probe_cygwin.h @@ -0,0 +1,74 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROBE_CYGWIN_H +#define PROBE_CYGWIN_H + +#include +#include +#include +#include + +/* + This should be in the Windows headers, but is missing from + Cygwin's Windows headers. +*/ +typedef struct icmpv6_echo_reply_lh { + /* + Although Windows uses an IPV6_ADDRESS_EX here, we are using uint8_t + fields to avoid structure padding differences between gcc and + Visual C++. (gcc wants to align the flow info to a 4 byte boundary, + and Windows uses it unaligned.) + */ + uint8_t PortBits[2]; + uint8_t FlowInfoBits[4]; + uint8_t AddressBits[16]; + uint8_t ScopeIdBits[4]; + + ULONG Status; + unsigned int RoundTripTime; +} ICMPV6_ECHO_REPLY, +*PICMPV6_ECHO_REPLY; + +/* + Windows requires an echo reply structure for each in-flight + ICMP probe. +*/ +struct probe_platform_t { + /* + We need a backpointer to the net_state because of the way + IcmpSendEcho2 passes our context. + */ + struct net_state_t *net_state; + + /* IP version (4 or 6) used for the probe */ + int ip_version; + + union { + ICMP_ECHO_REPLY32 *reply4; + ICMPV6_ECHO_REPLY *reply6; + }; +}; + +/* A Windows HANDLE for the ICMP session */ +struct net_state_platform_t { + HANDLE icmp4; + HANDLE icmp6; +}; + +#endif diff --git a/packet/probe_unix.c b/packet/probe_unix.c new file mode 100644 index 0000000..53863eb --- /dev/null +++ b/packet/probe_unix.c @@ -0,0 +1,753 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "probe.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platform.h" +#include "protocols.h" +#include "construct_unix.h" +#include "deconstruct_unix.h" +#include "timeval.h" + +/* A wrapper around sendto for mixed IPv4 and IPv6 sending */ +static +int send_packet( + const struct net_state_t *net_state, + const struct probe_param_t *param, + const char *packet, + int packet_size, + const struct sockaddr_storage *sockaddr) +{ + int send_socket = 0; + int sockaddr_length; + + if (sockaddr->ss_family == AF_INET6) { + sockaddr_length = sizeof(struct sockaddr_in6); + + if (param->protocol == IPPROTO_ICMP) { + send_socket = net_state->platform.icmp6_send_socket; + } else if (param->protocol == IPPROTO_UDP) { + send_socket = net_state->platform.udp6_send_socket; + } + } else if (sockaddr->ss_family == AF_INET) { + sockaddr_length = sizeof(struct sockaddr_in); + + send_socket = net_state->platform.ip4_send_socket; + } + + if (send_socket == 0) { + errno = EINVAL; + return -1; + } + + return sendto(send_socket, packet, packet_size, 0, + (struct sockaddr *) sockaddr, sockaddr_length); +} + +/* + Nearly all fields in the IP header should be encoded in network byte + order prior to passing to send(). However, the required byte order of + the length field of the IP header is inconsistent between operating + systems and operating system versions. FreeBSD 11 requires the length + field in network byte order, but some older versions of FreeBSD + require host byte order. OS X requires the length field in host + byte order. Linux will accept either byte order. + + Test for a byte order which works by sending a ping to localhost. +*/ +static +void check_length_order( + struct net_state_t *net_state) +{ + char packet[PACKET_BUFFER_SIZE]; + struct probe_param_t param; + struct sockaddr_storage dest_sockaddr; + struct sockaddr_storage src_sockaddr; + ssize_t bytes_sent; + int packet_size; + + memset(¶m, 0, sizeof(struct probe_param_t)); + param.ip_version = 4; + param.protocol = IPPROTO_ICMP; + param.ttl = 255; + param.remote_address = "127.0.0.1"; + + if (resolve_probe_addresses(¶m, &dest_sockaddr, &src_sockaddr)) { + fprintf(stderr, "Error decoding localhost address\n"); + exit(EXIT_FAILURE); + } + + /* First attempt to ping the localhost with network byte order */ + net_state->platform.ip_length_host_order = false; + + packet_size = construct_packet(net_state, NULL, MIN_PORT, + packet, PACKET_BUFFER_SIZE, + &dest_sockaddr, &src_sockaddr, ¶m); + if (packet_size < 0) { + perror("Unable to send to localhost"); + exit(EXIT_FAILURE); + } + + bytes_sent = + send_packet(net_state, ¶m, packet, packet_size, + &dest_sockaddr); + if (bytes_sent > 0) { + return; + } + + /* Since network byte order failed, try host byte order */ + net_state->platform.ip_length_host_order = true; + + packet_size = construct_packet(net_state, NULL, MIN_PORT, + packet, PACKET_BUFFER_SIZE, + &dest_sockaddr, &src_sockaddr, ¶m); + if (packet_size < 0) { + perror("Unable to send to localhost"); + exit(EXIT_FAILURE); + } + + bytes_sent = + send_packet(net_state, ¶m, packet, packet_size, + &dest_sockaddr); + if (bytes_sent < 0) { + perror("Unable to send with swapped length"); + exit(EXIT_FAILURE); + } +} + +/* + Check to see if SCTP is support. We can't just rely on checking + if IPPROTO_SCTP is defined, because while that is necessary, + MacOS as of "Sierra" defines IPPROTO_SCTP, but creating an SCTP + socket results in an error. +*/ +static +void check_sctp_support( + struct net_state_t *net_state) +{ +#ifdef IPPROTO_SCTP + int sctp_socket; + + sctp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); + if (sctp_socket != -1) { + close(sctp_socket); + + net_state->platform.sctp_support = true; + } +#endif +} + +/* Set a socket to non-blocking mode */ +void set_socket_nonblocking( + int socket) +{ + int flags; + + flags = fcntl(socket, F_GETFL, 0); + if (flags == -1) { + perror("Unexpected socket F_GETFL error"); + exit(EXIT_FAILURE); + } + + if (fcntl(socket, F_SETFL, flags | O_NONBLOCK)) { + perror("Unexpected socket F_SETFL O_NONBLOCK error"); + exit(EXIT_FAILURE); + } +} + +/* Open the raw sockets for sending/receiving IPv4 packets */ +static +int open_ip4_sockets( + struct net_state_t *net_state) +{ + int send_socket; + int recv_socket; + int trueopt = 1; + + send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (send_socket == -1) { + return -1; + } + + /* + We will be including the IP header in transmitted packets. + Linux doesn't require this, but BSD derived network stacks do. + */ + if (setsockopt + (send_socket, IPPROTO_IP, IP_HDRINCL, &trueopt, sizeof(int))) { + + close(send_socket); + return -1; + } + + /* + Open a second socket with IPPROTO_ICMP because we are only + interested in receiving ICMP packets, not all packets. + */ + recv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (recv_socket == -1) { + close(send_socket); + return -1; + } + + net_state->platform.ip4_present = true; + net_state->platform.ip4_send_socket = send_socket; + net_state->platform.ip4_recv_socket = recv_socket; + + return 0; +} + +/* Open the raw sockets for sending/receiving IPv6 packets */ +static +int open_ip6_sockets( + struct net_state_t *net_state) +{ + int send_socket_icmp; + int send_socket_udp; + int recv_socket; + + send_socket_icmp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (send_socket_icmp == -1) { + return -1; + } + + send_socket_udp = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP); + if (send_socket_udp == -1) { + close(send_socket_icmp); + + return -1; + } + + recv_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (recv_socket == -1) { + close(send_socket_icmp); + close(send_socket_udp); + + return -1; + } + + net_state->platform.ip6_present = true; + net_state->platform.icmp6_send_socket = send_socket_icmp; + net_state->platform.udp6_send_socket = send_socket_udp; + net_state->platform.ip6_recv_socket = recv_socket; + + return 0; +} + +/* + The first half of the net state initialization. Since this + happens with elevated privileges, this is kept as minimal + as possible to minimize security risk. +*/ +void init_net_state_privileged( + struct net_state_t *net_state) +{ + int ip4_err = 0; + int ip6_err = 0; + + memset(net_state, 0, sizeof(struct net_state_t)); + + net_state->platform.next_sequence = MIN_PORT; + + if (open_ip4_sockets(net_state)) { + ip4_err = errno; + } + if (open_ip6_sockets(net_state)) { + ip6_err = errno; + } + + /* + If we couldn't open either IPv4 or IPv6 sockets, we can't do + much, so print errors and exit. + */ + if (!net_state->platform.ip4_present + && !net_state->platform.ip6_present) { + + errno = ip4_err; + perror("Failure to open IPv4 sockets"); + + errno = ip6_err; + perror("Failure to open IPv6 sockets"); + + exit(EXIT_FAILURE); + } +} + +/* + The second half of net state initialization, which is run + at normal privilege levels. +*/ +void init_net_state( + struct net_state_t *net_state) +{ + set_socket_nonblocking(net_state->platform.ip4_recv_socket); + set_socket_nonblocking(net_state->platform.ip6_recv_socket); + + if (net_state->platform.ip4_present) { + check_length_order(net_state); + } + + check_sctp_support(net_state); +} + +/* + Returns true if we were able to open sockets for a particular + IP protocol version. +*/ +bool is_ip_version_supported( + struct net_state_t *net_state, + int ip_version) +{ + if (ip_version == 4) { + return net_state->platform.ip4_present; + } else if (ip_version == 6) { + return net_state->platform.ip6_present; + } else { + return false; + } +} + +/* Returns true if we can transmit probes using the specified protocol */ +bool is_protocol_supported( + struct net_state_t * net_state, + int protocol) +{ + if (protocol == IPPROTO_ICMP) { + return true; + } + + if (protocol == IPPROTO_UDP) { + return true; + } + + if (protocol == IPPROTO_TCP) { + return true; + } +#ifdef IPPROTO_SCTP + if (protocol == IPPROTO_SCTP) { + return net_state->platform.sctp_support; + } +#endif + + return false; +} + +/* Report an error during send_probe based on the errno value */ +static +void report_packet_error( + int command_token) +{ + if (errno == EINVAL) { + printf("%d invalid-argument\n", command_token); + } else if (errno == ENETDOWN) { + printf("%d network-down\n", command_token); + } else if (errno == ENETUNREACH) { + printf("%d no-route\n", command_token); + } else if (errno == EHOSTUNREACH) { + printf("%d no-route\n", command_token); + } else if (errno == EPERM) { + printf("%d permission-denied\n", command_token); + } else if (errno == EADDRINUSE) { + printf("%d address-in-use\n", command_token); + } else if (errno == EADDRNOTAVAIL) { + printf("%d address-not-available\n", command_token); + } else { + printf("%d unexpected-error errno %d\n", command_token, errno); + } +} + +/* Craft a custom ICMP packet for a network probe. */ +void send_probe( + struct net_state_t *net_state, + const struct probe_param_t *param) +{ + char packet[PACKET_BUFFER_SIZE]; + struct probe_t *probe; + int packet_size; + struct sockaddr_storage src_sockaddr; + + probe = alloc_probe(net_state, param->command_token); + if (probe == NULL) { + printf("%d probes-exhausted\n", param->command_token); + return; + } + + if (resolve_probe_addresses(param, &probe->remote_addr, &src_sockaddr)) { + printf("%d invalid-argument\n", param->command_token); + free_probe(net_state, probe); + return; + } + + if (gettimeofday(&probe->platform.departure_time, NULL)) { + perror("gettimeofday failure"); + exit(EXIT_FAILURE); + } + + packet_size = + construct_packet(net_state, &probe->platform.socket, + probe->sequence, packet, PACKET_BUFFER_SIZE, + &probe->remote_addr, &src_sockaddr, param); + + if (packet_size < 0) { + /* + When using a stream protocol, FreeBSD will return ECONNREFUSED + when connecting to localhost if the port doesn't exist, + even if the socket is non-blocking, so we should be + prepared for that. + */ + if (errno == ECONNREFUSED) { + receive_probe(net_state, probe, ICMP_ECHOREPLY, + &probe->remote_addr, NULL, 0, NULL); + } else { + report_packet_error(param->command_token); + free_probe(net_state, probe); + } + + return; + } + + if (packet_size > 0) { + if (send_packet(net_state, param, + packet, packet_size, &probe->remote_addr) == -1) { + + report_packet_error(param->command_token); + free_probe(net_state, probe); + return; + } + } + + probe->platform.timeout_time = probe->platform.departure_time; + probe->platform.timeout_time.tv_sec += param->timeout; +} + +/* When allocating a probe, assign it a unique port number */ +void platform_alloc_probe( + struct net_state_t *net_state, + struct probe_t *probe) +{ + probe->sequence = net_state->platform.next_sequence++; + + if (net_state->platform.next_sequence > MAX_PORT) { + net_state->platform.next_sequence = MIN_PORT; + } +} + +/* + When freeing the probe, close the socket for the probe, + if one has been opened +*/ +void platform_free_probe( + struct probe_t *probe) +{ + if (probe->platform.socket) { + close(probe->platform.socket); + probe->platform.socket = 0; + } +} + +/* + Compute the round trip time of a just-received probe and pass it + to the platform agnostic response handling. +*/ +void receive_probe( + struct net_state_t *net_state, + struct probe_t *probe, + int icmp_type, + const struct sockaddr_storage *remote_addr, + struct timeval *timestamp, + int mpls_count, + struct mpls_label_t *mpls) +{ + unsigned int round_trip_us; + struct timeval *departure_time = &probe->platform.departure_time; + struct timeval now; + + if (timestamp == NULL) { + if (gettimeofday(&now, NULL)) { + perror("gettimeofday failure"); + exit(EXIT_FAILURE); + } + + timestamp = &now; + } + + round_trip_us = + (timestamp->tv_sec - departure_time->tv_sec) * 1000000 + + timestamp->tv_usec - departure_time->tv_usec; + + respond_to_probe(net_state, probe, icmp_type, + remote_addr, round_trip_us, mpls_count, mpls); +} + +/* + Read all available packets through our receiving raw socket, and + handle any responses to probes we have preivously sent. +*/ +static +void receive_replies_from_icmp_socket( + struct net_state_t *net_state, + int socket, + received_packet_func_t handle_received_packet) +{ + char packet[PACKET_BUFFER_SIZE]; + int packet_length; + struct sockaddr_storage remote_addr; + socklen_t sockaddr_length; + struct timeval timestamp; + + /* Read until no more packets are available */ + while (true) { + sockaddr_length = sizeof(struct sockaddr_storage); + packet_length = recvfrom(socket, packet, PACKET_BUFFER_SIZE, 0, + (struct sockaddr *) &remote_addr, + &sockaddr_length); + + /* + Get the time immediately after reading the packet to + keep the timing as precise as we can. + */ + if (gettimeofday(×tamp, NULL)) { + perror("gettimeofday failure"); + exit(EXIT_FAILURE); + } + + if (packet_length == -1) { + /* + EAGAIN will be returned if there is no current packet + available. + */ + if (errno == EAGAIN) { + return; + } + + /* + EINTER will be returned if we received a signal during + receive. + */ + if (errno == EINTR) { + continue; + } + + perror("Failure receiving replies"); + exit(EXIT_FAILURE); + } + + handle_received_packet(net_state, &remote_addr, packet, + packet_length, ×tamp); + } +} + +/* + Attempt to send using the probe's socket, in order to check whether + the connection has completed, for stream oriented protocols such as + TCP. +*/ +static +void receive_replies_from_probe_socket( + struct net_state_t *net_state, + struct probe_t *probe) +{ + int probe_socket; + struct timeval zero_time; + int err; + int err_length = sizeof(int); + fd_set write_set; + + probe_socket = probe->platform.socket; + if (!probe_socket) { + return; + } + + FD_ZERO(&write_set); + FD_SET(probe_socket, &write_set); + + zero_time.tv_sec = 0; + zero_time.tv_usec = 0; + + if (select(probe_socket + 1, NULL, &write_set, NULL, &zero_time) == -1) { + if (errno == EAGAIN) { + return; + } else { + perror("probe socket select error"); + exit(EXIT_FAILURE); + } + } + + /* + If the socket is writable, the connection attempt has completed. + */ + if (!FD_ISSET(probe_socket, &write_set)) { + return; + } + + if (getsockopt(probe_socket, SOL_SOCKET, SO_ERROR, &err, &err_length)) { + perror("probe socket SO_ERROR"); + exit(EXIT_FAILURE); + } + + /* + If the connection complete successfully, or was refused, we can + assume our probe arrived at the destination. + */ + if (!err || err == ECONNREFUSED) { + receive_probe(net_state, probe, ICMP_ECHOREPLY, + &probe->remote_addr, NULL, 0, NULL); + } else { + errno = err; + report_packet_error(probe->token); + free_probe(net_state, probe); + } +} + +/* Check both the IPv4 and IPv6 sockets for incoming packets */ +void receive_replies( + struct net_state_t *net_state) +{ + struct probe_t *probe; + struct probe_t *probe_safe_iter; + + if (net_state->platform.ip4_present) { + receive_replies_from_icmp_socket(net_state, + net_state->platform. + ip4_recv_socket, + handle_received_ip4_packet); + } + + if (net_state->platform.ip6_present) { + receive_replies_from_icmp_socket(net_state, + net_state->platform. + ip6_recv_socket, + handle_received_ip6_packet); + } + + LIST_FOREACH_SAFE(probe, &net_state->outstanding_probes, + probe_list_entry, probe_safe_iter) { + + receive_replies_from_probe_socket(net_state, probe); + } +} + +/* + Put all of our probe sockets in the read set used for an upcoming + select so we can wake when any of them become readable. +*/ +int gather_probe_sockets( + const struct net_state_t *net_state, + fd_set * write_set) +{ + int probe_socket; + int nfds; + const struct probe_t *probe; + + nfds = 0; + + LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) { + probe_socket = probe->platform.socket; + + if (probe_socket) { + FD_SET(probe_socket, write_set); + if (probe_socket >= nfds) { + nfds = probe_socket + 1; + } + } + } + + return nfds; +} + +/* + Check for any probes for which we have not received a response + for some time, and report a time-out, assuming that we won't + receive a future reply. +*/ +void check_probe_timeouts( + struct net_state_t *net_state) +{ + struct timeval now; + struct probe_t *probe; + struct probe_t *probe_safe_iter; + + if (gettimeofday(&now, NULL)) { + perror("gettimeofday failure"); + exit(EXIT_FAILURE); + } + + LIST_FOREACH_SAFE(probe, &net_state->outstanding_probes, + probe_list_entry, probe_safe_iter) { + + if (compare_timeval(probe->platform.timeout_time, now) < 0) { + /* Report timeout to the command stream */ + printf("%d no-reply\n", probe->token); + + free_probe(net_state, probe); + } + } +} + +/* + Find the remaining time until the next probe times out. + This may be a negative value if the next probe timeout has + already elapsed. + + Returns false if no probes are currently outstanding, and true + if a timeout value for the next probe exists. +*/ +bool get_next_probe_timeout( + const struct net_state_t *net_state, + struct timeval *timeout) +{ + bool have_timeout; + const struct probe_t *probe; + struct timeval now; + struct timeval probe_timeout; + + if (gettimeofday(&now, NULL)) { + perror("gettimeofday failure"); + exit(EXIT_FAILURE); + } + + have_timeout = false; + LIST_FOREACH(probe, &net_state->outstanding_probes, probe_list_entry) { + probe_timeout.tv_sec = + probe->platform.timeout_time.tv_sec - now.tv_sec; + probe_timeout.tv_usec = + probe->platform.timeout_time.tv_usec - now.tv_usec; + + normalize_timeval(&probe_timeout); + if (have_timeout) { + if (compare_timeval(probe_timeout, *timeout) < 0) { + /* If this probe has a sooner timeout, store it instead */ + *timeout = probe_timeout; + } + } else { + *timeout = probe_timeout; + have_timeout = true; + } + } + + return have_timeout; +} diff --git a/packet/probe_unix.h b/packet/probe_unix.h new file mode 100644 index 0000000..36b3159 --- /dev/null +++ b/packet/probe_unix.h @@ -0,0 +1,94 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROBE_UNIX_H +#define PROBE_UNIX_H + +/* The range of local port numbers to use for probes */ +#define MIN_PORT 33000 +#define MAX_PORT 65535 + +/* We need to track the transmission and timeouts on Unix systems */ +struct probe_platform_t { + /* The socket for the outgoing connection (used by TCP probes) */ + int socket; + + /* The time at which the probe is considered lost */ + struct timeval timeout_time; + + /* The time at which the probe was sent */ + struct timeval departure_time; +}; + +/* We'll use rack sockets to send and recieve probes on Unix systems */ +struct net_state_platform_t { + /* true if we were successful at opening IPv4 sockets */ + bool ip4_present; + + /* true if we were successful at opening IPv6 sockets */ + bool ip6_present; + + /* Socket used to send raw IPv4 packets */ + int ip4_send_socket; + + /* Socket used to receive IPv4 ICMP replies */ + int ip4_recv_socket; + + /* Send socket for ICMPv6 packets */ + int icmp6_send_socket; + + /* Send socket for UDPv6 packets */ + int udp6_send_socket; + + /* Receive socket for IPv6 packets */ + int ip6_recv_socket; + + /* + true if we should encode the IP header length in host order. + (as opposed to network order) + */ + bool ip_length_host_order; + + /* true if the operating system supports SCTP sockets */ + bool sctp_support; + + /* The next port number to use when creating a new probe */ + int next_sequence; +}; + +struct net_state_t; +struct probe_t; +struct mpls_label_t; + +void set_socket_nonblocking( + int socket); + +void receive_probe( + struct net_state_t *net_state, + struct probe_t *probe, + int icmp_type, + const struct sockaddr_storage *remote_addr, + struct timeval *timestamp, + int mpls_count, + struct mpls_label_t *mpls); + +int gather_probe_sockets( + const struct net_state_t *net_state, + fd_set * write_set); + +#endif diff --git a/packet/protocols.h b/packet/protocols.h new file mode 100644 index 0000000..75a8229 --- /dev/null +++ b/packet/protocols.h @@ -0,0 +1,150 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef PROTOCOLS_H +#define PROTOCOLS_H + +/* ICMPv4 type codes */ +#define ICMP_ECHOREPLY 0 +#define ICMP_DEST_UNREACH 3 +#define ICMP_ECHO 8 +#define ICMP_TIME_EXCEEDED 11 + +/* ICMP_DEST_UNREACH codes */ +#define ICMP_PORT_UNREACH 3 + +/* ICMPv6 type codes */ +#define ICMP6_DEST_UNREACH 1 +#define ICMP6_TIME_EXCEEDED 3 +#define ICMP6_ECHO 128 +#define ICMP6_ECHOREPLY 129 + +/* ICMP6_DEST_UNREACH codes */ +#define ICMP6_PORT_UNREACH 4 + +/* + The minimum size of the ICMP "original datagram" when + using ICMP extensions +*/ +#define ICMP_ORIGINAL_DATAGRAM_MIN_SIZE 128 + +/* The classnum and type of MPLS labels in an ICMP extension object */ +#define ICMP_EXT_MPLS_CLASSNUM 1 +#define ICMP_EXT_MPLS_CTYPE 1 + +#define HTTP_PORT 80 + +/* We can't rely on header files to provide this information, because + the fields have different names between, for instance, Linux and + Solaris */ +struct ICMPHeader { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint16_t id; + uint16_t sequence; +}; + +/* ICMP extension header, which might contain MPLS labels */ +/* See RFC 4884 */ +struct ICMPExtensionHeader { + uint8_t version; + uint8_t reserved; + uint16_t checksum; +}; + +/* An object in an extended ICMP object */ +struct ICMPExtensionObject { + uint16_t len; + uint8_t classnum; + uint8_t ctype; +}; + +/* An MPLS label included in an ICMP extension */ +/* See RFC 4950 */ +struct ICMPExtMPLSLabel { + uint8_t label[3]; // Low 4 bits are Experimental Use, Stack + uint8_t ttl; +}; + +/* Structure of an UDP header. */ +struct UDPHeader { + uint16_t srcport; + uint16_t dstport; + uint16_t length; + uint16_t checksum; +}; + +/* Structure of an TCP header, as far as we need it. */ +struct TCPHeader { + uint16_t srcport; + uint16_t dstport; + uint32_t seq; +}; + +/* Structure of an SCTP header */ +struct SCTPHeader { + uint16_t srcport; + uint16_t dstport; + uint32_t veri_tag; +}; + +/* Structure of an IPv4 UDP pseudoheader. */ +struct UDPPseudoHeader { + uint32_t saddr; + uint32_t daddr; + uint8_t zero; + uint8_t protocol; + uint16_t len; +}; + +/* Structure of an IP header. */ +struct IPHeader { + uint8_t version; + uint8_t tos; + uint16_t len; + uint16_t id; + uint16_t frag; + uint8_t ttl; + uint8_t protocol; + uint16_t check; + uint32_t saddr; + uint32_t daddr; +}; + +/* IP version 6 header */ +struct IP6Header { + uint8_t version; + uint8_t flow[3]; + uint16_t len; + uint8_t protocol; + uint8_t ttl; + uint8_t saddr[16]; + uint8_t daddr[16]; +}; + +/* The pseudo-header used for checksum computation for ICMPv6 and UDPv6 */ +struct IP6PseudoHeader { + uint8_t saddr[16]; + uint8_t daddr[16]; + uint32_t len; + uint8_t zero[3]; + uint8_t protocol; +}; + +#endif diff --git a/packet/timeval.c b/packet/timeval.c new file mode 100644 index 0000000..0f88a3a --- /dev/null +++ b/packet/timeval.c @@ -0,0 +1,77 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "timeval.h" + +/* + Ensure that a timevalue has a microsecond value in the range + [0.0, 1.0e6) microseconds by converting microseconds to full seconds. +*/ +void normalize_timeval( + struct timeval *timeval) +{ + int full_sec; + + /* + If tv_usec has overflowed a full second, convert the overflow + to tv_sec. + */ + full_sec = timeval->tv_usec / 1000000; + timeval->tv_sec += full_sec; + timeval->tv_usec -= 1000000 * full_sec; + + /* If tv_usec is negative, make it positive by rolling tv_sec back */ + if (timeval->tv_usec < 0) { + timeval->tv_sec--; + timeval->tv_usec += 1000000; + } + + /* If the entire time value is negative, clamp to zero */ + if (timeval->tv_sec < 0) { + timeval->tv_sec = 0; + timeval->tv_usec = 0; + } +} + +/* + Compare two time values. Return: + + -1 if a < b + 0 if a == b + 1 if a > b +*/ +int compare_timeval( + struct timeval a, + struct timeval b) +{ + if (a.tv_sec > b.tv_sec) { + return 1; + } + if (a.tv_sec < b.tv_sec) { + return -1; + } + + if (a.tv_usec > b.tv_usec) { + return 1; + } + if (a.tv_usec < b.tv_usec) { + return -1; + } + + return 0; +} diff --git a/packet/timeval.h b/packet/timeval.h new file mode 100644 index 0000000..d00897a --- /dev/null +++ b/packet/timeval.h @@ -0,0 +1,31 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef TIMEVAL_H +#define TIMEVAL_H + +#include + +void normalize_timeval( + struct timeval *timeval); + +int compare_timeval( + struct timeval a, + struct timeval b); + +#endif diff --git a/packet/wait.h b/packet/wait.h new file mode 100644 index 0000000..0d24b4a --- /dev/null +++ b/packet/wait.h @@ -0,0 +1,29 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef WAIT_H +#define WAIT_H + +#include "command.h" +#include "probe.h" + +void wait_for_activity( + struct command_buffer_t *command_buffer, + struct net_state_t *net_state); + +#endif diff --git a/packet/wait_cygwin.c b/packet/wait_cygwin.c new file mode 100644 index 0000000..33cbbb9 --- /dev/null +++ b/packet/wait_cygwin.c @@ -0,0 +1,55 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "wait.h" + +#include +#include +#include + +#include "command.h" + +/* + Sleep until we receive a new probe response, a new command on the + command stream, or a probe timeout. On Windows, this means that + we will sleep with an alertable wait, as all of these conditions + use I/O completion routines as notifications of these events. +*/ +void wait_for_activity( + struct command_buffer_t *command_buffer, + struct net_state_t *net_state) +{ + DWORD wait_result; + + /* + Start the command read overlapped I/O just prior to sleeping. + During development of the Cygwin port, there was a bug where the + overlapped I/O was started earlier in the mtr-packet loop, and + an intermediate alertable wait could leave us in this Sleep + without an active command read. So now we do this here, instead. + */ + start_read_command(command_buffer); + + /* Sleep until an I/O completion routine runs */ + wait_result = SleepEx(INFINITE, TRUE); + + if (wait_result == WAIT_FAILED) { + fprintf(stderr, "SleepEx failure %d\n", GetLastError()); + exit(EXIT_FAILURE); + } +} diff --git a/packet/wait_unix.c b/packet/wait_unix.c new file mode 100644 index 0000000..f347e23 --- /dev/null +++ b/packet/wait_unix.c @@ -0,0 +1,123 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "wait.h" + +#include +#include +#include +#include +#include +#include +#include + +/* + Gather all the file descriptors which should wake our select call when + they become readable. +*/ +static +int gather_read_fds( + const struct command_buffer_t *command_buffer, + const struct net_state_t *net_state, + fd_set * read_set, + fd_set * write_set) +{ + int nfds; + int probe_nfds; + int ip4_socket = net_state->platform.ip4_recv_socket; + int ip6_socket = net_state->platform.ip6_recv_socket; + int command_stream = command_buffer->command_stream; + + FD_ZERO(read_set); + FD_ZERO(write_set); + + FD_SET(command_stream, read_set); + nfds = command_stream + 1; + + FD_SET(ip4_socket, read_set); + if (ip4_socket >= nfds) { + nfds = ip4_socket + 1; + } + + FD_SET(ip6_socket, read_set); + if (ip6_socket >= nfds) { + nfds = ip6_socket + 1; + } + + probe_nfds = gather_probe_sockets(net_state, write_set); + if (probe_nfds > nfds) { + nfds = probe_nfds; + } + + return nfds; +} + +/* + Sleep until we receive a new probe response, a new command on the + command stream, or a probe timeout. On Unix systems, this means + we use select to wait on file descriptors for the command stream + and the raw recieve socket. +*/ +void wait_for_activity( + struct command_buffer_t *command_buffer, + struct net_state_t *net_state) +{ + int nfds; + fd_set read_set; + fd_set write_set; + struct timeval probe_timeout; + struct timeval *select_timeout; + int ready_count; + + nfds = + gather_read_fds(command_buffer, net_state, &read_set, &write_set); + + while (true) { + select_timeout = NULL; + + /* Use the soonest probe timeout time as our maximum wait time */ + if (get_next_probe_timeout(net_state, &probe_timeout)) { + assert(probe_timeout.tv_sec >= 0); + select_timeout = &probe_timeout; + } + + ready_count = + select(nfds, &read_set, &write_set, NULL, select_timeout); + + /* + If we didn't have an error, either one of our descriptors is + readable, or we timed out. So we can now return. + */ + if (ready_count != -1) { + break; + } + + /* + We will get EINTR if we received a signal during the select, so + retry in that case. We may get EAGAIN if "the kernel was + (perhaps temporarily) unable to allocate the requested number of + file descriptors." I haven't seen this in practice, but selecting + again seems like the right thing to do. + */ + if (errno != EINTR && errno != EAGAIN) { + /* We don't expect other errors, so report them */ + perror("unexpected select error"); + exit(EXIT_FAILURE); + } + } +} diff --git a/portability/.gitignore b/portability/.gitignore new file mode 100644 index 0000000..229368a --- /dev/null +++ b/portability/.gitignore @@ -0,0 +1,2 @@ +/.deps +/.dirstamp diff --git a/portability/error.c b/portability/error.c new file mode 100644 index 0000000..99b7c99 --- /dev/null +++ b/portability/error.c @@ -0,0 +1,39 @@ + /* + Linux error(3) function go around for systems that has err(3) and + warn(3), but no error(3). MacOS is good example of such. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation version 2. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. +*/ + +#include +#include + +void error(int status, int errnum, const char *format, ...) { + va_list arg; + + va_start(arg, format); + if (errnum == 0) { + if (status == 0) + vwarnx(format, arg); + else + verrx(status, format, arg); + } else { + if (status == 0) + vwarn(format, arg); + else + verr(status, format, arg); + } + va_end(arg); +} diff --git a/portability/error.h b/portability/error.h new file mode 100644 index 0000000..5efbaed --- /dev/null +++ b/portability/error.h @@ -0,0 +1,20 @@ + /* + Linux error(3) function go around for systems that has err(3) and + warn(3), but no error(3). MacOS is good example of such. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation version 2. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. +*/ + +void error(int status, int errnum, const char *format, ...); diff --git a/portability/getopt.c b/portability/getopt.c new file mode 100644 index 0000000..b7d7d04 --- /dev/null +++ b/portability/getopt.c @@ -0,0 +1,766 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95 + Free Software Foundation, Inc. + + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . + Ditto for AIX 3.2 and . */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if !defined (__STDC__) || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#endif /* GNU C library. */ + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. + When compiling libc, the _ macro is predefined. */ +#ifdef HAVE_LIBINTL_H +# include +# define _(msgid) gettext (msgid) +#else +# define _(msgid) (msgid) +#endif +#endif + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include +#define my_index strchr +#else + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +char *getenv (); + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +#if !defined (__STDC__) || !__STDC__ +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +#endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char * +_getopt_initialize (optstring) + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + optarg = NULL; + + if (optind == 0) + { + optstring = _getopt_initialize (optstring); + optind = 1; /* Don't scan ARGV[0], the program name. */ + } + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc + && (argv[optind][0] != '-' || argv[optind][1] == '\0')) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == '\0')) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if (nameend - nextchar == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + } + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit(EXIT_SUCCESS); +} + +#endif /* TEST */ diff --git a/portability/getopt.h b/portability/getopt.h new file mode 100644 index 0000000..aaa627e --- /dev/null +++ b/portability/getopt.h @@ -0,0 +1,176 @@ +/* Declarations for getopt. + Copyright (C) 1989-1994,1996-1999,2001,2003,2004 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation version 2. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +#define _GETOPT_H 1 +#endif + +/* If __GNU_LIBRARY__ is not already defined, either we are being used + standalone, or this is the first header included in the source file. + If we are being used with glibc, we need to include , but + that does not exist if we are standalone. So: if __GNU_LIBRARY__ is + not defined, include , which will pull in for us + if it's from glibc. (Why ctype.h? It's guaranteed to exist and it + doesn't flood the namespace with stuff the way some other headers do.) */ +#if !defined __GNU_LIBRARY__ +# include +#endif + +#ifndef __THROW +# ifndef __GNUC_PREREQ +# define __GNUC_PREREQ(maj, min) (0) +# endif +# if defined __cplusplus && __GNUC_PREREQ (2,8) +# define __THROW throw () +# else +# define __THROW +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +#ifndef __need_getopt +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ + const char *name; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 +#endif /* need getopt */ + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `getopt'. */ + +#ifdef __GNU_LIBRARY__ +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int ___argc, char *const *___argv, const char *__shortopts) + __THROW; +#else /* not __GNU_LIBRARY__ */ +extern int getopt (); +#endif /* __GNU_LIBRARY__ */ + +#ifndef __need_getopt +extern int getopt_long (int ___argc, char *const *___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind) + __THROW; +extern int getopt_long_only (int ___argc, char *const *___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind) + __THROW; + +#endif + +#ifdef __cplusplus +} +#endif + +/* Make sure we later can get all the definitions and declarations. */ +#undef __need_getopt + +#endif /* getopt.h */ diff --git a/portability/getopt1.c b/portability/getopt1.c new file mode 100644 index 0000000..6dd1183 --- /dev/null +++ b/portability/getopt1.c @@ -0,0 +1,179 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987, 88, 89, 90, 91, 92, 1993, 1994 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "getopt.h" + +#if !defined (__STDC__) || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#else +char *getenv (); +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +#include + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit(EXIT_SUCCESS); +} + +#endif /* TEST */ diff --git a/portability/queue.h b/portability/queue.h new file mode 100644 index 0000000..d19a425 --- /dev/null +++ b/portability/queue.h @@ -0,0 +1,757 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD: releng/11.0/sys/sys/queue.h 284915 2015-06-28 21:06:45Z hselasky $ + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#include "config.h" + +#ifdef HAVE_SYS_CDEFS_H +#include +#endif + +/* + * This file defines four types of data structures: singly-linked lists, + * singly-linked tail queues, lists and tail queues. + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A singly-linked tail queue is headed by a pair of pointers, one to the + * head of the list and the other to the tail of the list. The elements are + * singly linked for minimum space and pointer manipulation overhead at the + * expense of O(n) removal for arbitrary elements. New elements can be added + * to the list after an existing element, at the head of the list, or at the + * end of the list. Elements being removed from the head of the tail queue + * should use the explicit macro for this purpose for optimum efficiency. + * A singly-linked tail queue may only be traversed in the forward direction. + * Singly-linked tail queues are ideal for applications with large datasets + * and few or no removals or for implementing a FIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may be traversed in either direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * For details on the use of these macros, see the queue(3) manual page. + * + * + * SLIST LIST STAILQ TAILQ + * _HEAD + + + + + * _CLASS_HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _CLASS_ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - + - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_FROM + + + + + * _FOREACH_SAFE + + + + + * _FOREACH_FROM_SAFE + + + + + * _FOREACH_REVERSE - - - + + * _FOREACH_REVERSE_FROM - - - + + * _FOREACH_REVERSE_SAFE - - - + + * _FOREACH_REVERSE_FROM_SAFE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _CONCAT - - + + + * _REMOVE_AFTER + - + - + * _REMOVE_HEAD + - + - + * _REMOVE + + + + + * _SWAP + + + + + * + */ +#ifdef QUEUE_MACRO_DEBUG +/* Store the last 2 places the queue element or head was altered */ +struct qm_trace { + unsigned long lastline; + unsigned long prevline; + const char *lastfile; + const char *prevfile; +}; + +#define TRACEBUF struct qm_trace trace; +#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } , +#define TRASHIT(x) do {(x) = (void *)-1;} while (0) +#define QMD_SAVELINK(name, link) void **name = (void *)&(link) + +#define QMD_TRACE_HEAD(head) do { \ + (head)->trace.prevline = (head)->trace.lastline; \ + (head)->trace.prevfile = (head)->trace.lastfile; \ + (head)->trace.lastline = __LINE__; \ + (head)->trace.lastfile = __FILE__; \ +} while (0) + +#define QMD_TRACE_ELEM(elem) do { \ + (elem)->trace.prevline = (elem)->trace.lastline; \ + (elem)->trace.prevfile = (elem)->trace.lastfile; \ + (elem)->trace.lastline = __LINE__; \ + (elem)->trace.lastfile = __FILE__; \ +} while (0) + +#else +#define QMD_TRACE_ELEM(elem) +#define QMD_TRACE_HEAD(head) +#define QMD_SAVELINK(name, link) +#define TRACEBUF +#define TRACEBUF_INITIALIZER +#define TRASHIT(x) +#endif /* QUEUE_MACRO_DEBUG */ + +#ifdef __cplusplus +/* + * In C++ there can be structure lists and class lists: + */ +#define QUEUE_TYPEOF(type) type +#else +#define QUEUE_TYPEOF(type) struct type +#endif + +/* + * Singly-linked List declarations. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_CLASS_HEAD(name, type) \ +struct name { \ + class type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +#define SLIST_CLASS_ENTRY(type) \ +struct { \ + class type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) + +#define SLIST_FIRST(head) ((head)->slh_first) + +#define SLIST_FOREACH(var, head, field) \ + for ((var) = SLIST_FIRST((head)); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != NULL; \ + (varp) = &SLIST_NEXT((var), field)) + +#define SLIST_INIT(head) do { \ + SLIST_FIRST((head)) = NULL; \ +} while (0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ + SLIST_NEXT((slistelm), field) = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ + SLIST_FIRST((head)) = (elm); \ +} while (0) + +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ + if (SLIST_FIRST((head)) == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \ + while (SLIST_NEXT(curelm, field) != (elm)) \ + curelm = SLIST_NEXT(curelm, field); \ + SLIST_REMOVE_AFTER(curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + SLIST_NEXT(elm, field) = \ + SLIST_NEXT(SLIST_NEXT(elm, field), field); \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ +} while (0) + +#define SLIST_SWAP(head1, head2, type) do { \ + QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \ + SLIST_FIRST(head1) = SLIST_FIRST(head2); \ + SLIST_FIRST(head2) = swap_first; \ +} while (0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#define STAILQ_CLASS_HEAD(name, type) \ +struct name { \ + class type *stqh_first; /* first element */ \ + class type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +#define STAILQ_CLASS_ENTRY(type) \ +struct { \ + class type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (0) + +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) + +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#define STAILQ_FOREACH(var, head, field) \ + for((var) = STAILQ_FIRST((head)); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_INIT(head) do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_NEXT((tqelm), field) = (elm); \ +} while (0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_FIRST((head)) = (elm); \ +} while (0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? NULL : \ + __containerof((head)->stqh_last, \ + QUEUE_TYPEOF(type), field.stqe_next)) + +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } \ + else { \ + QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) + +#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ + if ((STAILQ_NEXT(elm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_SWAP(head1, head2, type) do { \ + QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \ + QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \ + STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_FIRST(head2) = swap_first; \ + (head2)->stqh_last = swap_last; \ + if (STAILQ_EMPTY(head1)) \ + (head1)->stqh_last = &STAILQ_FIRST(head1); \ + if (STAILQ_EMPTY(head2)) \ + (head2)->stqh_last = &STAILQ_FIRST(head2); \ +} while (0) + + +/* + * List declarations. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_CLASS_HEAD(name, type) \ +struct name { \ + class type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +#define LIST_CLASS_ENTRY(type) \ +struct { \ + class type *le_next; /* next element */ \ + class type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ + +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_LIST_CHECK_HEAD(head, field) do { \ + if (LIST_FIRST((head)) != NULL && \ + LIST_FIRST((head))->field.le_prev != \ + &LIST_FIRST((head))) \ + panic("Bad list head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_LIST_CHECK_NEXT(elm, field) do { \ + if (LIST_NEXT((elm), field) != NULL && \ + LIST_NEXT((elm), field)->field.le_prev != \ + &((elm)->field.le_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_LIST_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.le_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_LIST_CHECK_HEAD(head, field) +#define QMD_LIST_CHECK_NEXT(elm, field) +#define QMD_LIST_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define LIST_EMPTY(head) ((head)->lh_first == NULL) + +#define LIST_FIRST(head) ((head)->lh_first) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var); \ + (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_INIT(head) do { \ + LIST_FIRST((head)) = NULL; \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + QMD_LIST_CHECK_NEXT(listelm, field); \ + if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ + LIST_NEXT((listelm), field)->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_NEXT((listelm), field) = (elm); \ + (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_LIST_CHECK_PREV(listelm, field); \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + LIST_NEXT((elm), field) = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + QMD_LIST_CHECK_HEAD((head), field); \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ +} while (0) + +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_PREV(elm, head, type, field) \ + ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ + __containerof((elm)->field.le_prev, \ + QUEUE_TYPEOF(type), field.le_next)) + +#define LIST_REMOVE(elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.le_next); \ + QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ + QMD_LIST_CHECK_NEXT(elm, field); \ + QMD_LIST_CHECK_PREV(elm, field); \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ +} while (0) + +#define LIST_SWAP(head1, head2, type, field) do { \ + QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \ + LIST_FIRST((head1)) = LIST_FIRST((head2)); \ + LIST_FIRST((head2)) = swap_tmp; \ + if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ + if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ +} while (0) + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ +} + +#define TAILQ_CLASS_HEAD(name, type) \ +struct name { \ + class type *tqh_first; /* first element */ \ + class type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ +} + +#define TAILQ_CLASS_ENTRY(type) \ +struct { \ + class type *tqe_next; /* next element */ \ + class type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ +} + +/* + * Tail queue functions. + */ +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_TAILQ_CHECK_HEAD(head, field) do { \ + if (!TAILQ_EMPTY(head) && \ + TAILQ_FIRST((head))->field.tqe_prev != \ + &TAILQ_FIRST((head))) \ + panic("Bad tailq head %p first->prev != head", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_TAIL(head, field) do { \ + if (*(head)->tqh_last != NULL) \ + panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ +} while (0) + +#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ + if (TAILQ_NEXT((elm), field) != NULL && \ + TAILQ_NEXT((elm), field)->field.tqe_prev != \ + &((elm)->field.tqe_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ +} while (0) + +#define QMD_TAILQ_CHECK_PREV(elm, field) do { \ + if (*(elm)->field.tqe_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ +} while (0) +#else +#define QMD_TAILQ_CHECK_HEAD(head, field) +#define QMD_TAILQ_CHECK_TAIL(head, headname) +#define QMD_TAILQ_CHECK_NEXT(elm, field) +#define QMD_TAILQ_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + QMD_TRACE_HEAD(head1); \ + QMD_TRACE_HEAD(head2); \ + } \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST((head)); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QMD_TAILQ_CHECK_NEXT(listelm, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + } \ + TAILQ_NEXT((listelm), field) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + QMD_TAILQ_CHECK_PREV(listelm, field); \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + TAILQ_NEXT((elm), field) = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&(listelm)->field); \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + QMD_TAILQ_CHECK_HEAD(head, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + QMD_TAILQ_CHECK_TAIL(head, field); \ + TAILQ_NEXT((elm), field) = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ + QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ + QMD_TAILQ_CHECK_NEXT(elm, field); \ + QMD_TAILQ_CHECK_PREV(elm, field); \ + if ((TAILQ_NEXT((elm), field)) != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else { \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + QMD_TRACE_HEAD(head); \ + } \ + *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ + QMD_TRACE_ELEM(&(elm)->field); \ +} while (0) + +#define TAILQ_SWAP(head1, head2, type, field) do { \ + QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \ + QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/test/cmdparse.py b/test/cmdparse.py new file mode 100755 index 0000000..7a807c6 --- /dev/null +++ b/test/cmdparse.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# +# mtr -- a network diagnostic tool +# Copyright (C) 2016 Matt Kimball +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +'''Test mtr-packet's command parsing.''' + + +import time +import unittest + +import mtrpacket + + +class TestCommandParse(mtrpacket.MtrPacketTest): + '''Test cases with malformed commands and version checks''' + + def test_unknown_command(self): + 'Test sending a command unknown to mtr-packet' + + self.write_command('13 argle-bargle') + self.assertEqual(self.read_reply(), '13 unknown-command') + + def test_malformed_command(self): + 'Test sending a malformed command request to mtr-packet' + + self.write_command('malformed') + self.assertEqual(self.read_reply(), '0 command-parse-error') + + def test_exit_on_stdin_closed(self): + '''Test that the packet process terminates after stdin is closed + + Test that, when outstanding requests are complete, the process + terminates following stdin being closed.''' + + self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1') + self.packet_process.stdin.close() + time.sleep(2) + self.read_reply() + exit_code = self.packet_process.poll() + self.assertIsNotNone(exit_code) + + def test_invalid_argument(self): + 'Test sending invalid arguments with probe requests' + + bad_commands = [ + '22 send-probe', + '23 send-probe ip-4 str-value', + '24 send-probe ip-4 8.8.8.8 timeout str-value', + '25 send-probe ip-4 8.8.8.8 ttl str-value', + ] + + for cmd in bad_commands: + self.write_command(cmd) + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'invalid-argument') + + def test_versioning(self): + 'Test version checks and feature support checks' + + feature_tests = [ + ('31 check-support feature ip-4', 'ok'), + ('32 check-support feature send-probe', 'ok'), + ('33 check-support feature bogus-feature', 'no') + ] + + self.write_command('30 check-support feature version') + reply = self.parse_reply() + self.assertEqual(reply.token, 30) + self.assertEqual(reply.command_name, 'feature-support') + self.assertIn('support', reply.argument) + + for (request, expected) in feature_tests: + self.write_command(request) + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'feature-support') + self.assertIn('support', reply.argument) + self.assertEqual(reply.argument['support'], expected) + + def test_command_overflow(self): + 'Test overflowing the incoming command buffer' + + big_buffer = 'x' * (64 * 1024) + self.write_command(big_buffer) + + reply = self.read_reply() + self.assertEqual(reply, '0 command-buffer-overflow') + + +if __name__ == '__main__': + mtrpacket.check_running_as_root() + unittest.main() diff --git a/test/lint.sh b/test/lint.sh new file mode 100755 index 0000000..ae9aa2a --- /dev/null +++ b/test/lint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Check the Python test source for good style + +PYTHON_SOURCE=*.py + +pep8 $PYTHON_SOURCE +pylint --reports=n $PYTHON_SOURCE 2>/dev/null +mypy --py2 $PYTHON_SOURCE diff --git a/test/mtrpacket.py b/test/mtrpacket.py new file mode 100644 index 0000000..be21dcd --- /dev/null +++ b/test/mtrpacket.py @@ -0,0 +1,363 @@ +# +# mtr -- a network diagnostic tool +# Copyright (C) 2016 Matt Kimball +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +'''Infrastructure for running tests which invoke mtr-packet.''' + +import fcntl +import os +import select +import socket +import subprocess +import sys +import time +import unittest + +# +# typing is used for mypy type checking, but isn't required to run, +# so it's okay if we can't import it. +# +try: + # pylint: disable=locally-disabled, unused-import + from typing import Dict, List +except ImportError: + pass + + +IPV6_TEST_HOST = 'google-public-dns-a.google.com' + + +class MtrPacketExecuteError(Exception): + "Exception raised when MtrPacketTest can't execute mtr-packet" + pass + + +class ReadReplyTimeout(Exception): + 'Exception raised by TestProbe.read_reply upon timeout' + + pass + + +class WriteCommandTimeout(Exception): + 'Exception raised by TestProbe.write_command upon timeout' + + pass + + +class MtrPacketReplyParseError(Exception): + "Exception raised when MtrPacketReply can't parse the reply string" + + pass + + +class PacketListenError(Exception): + 'Exception raised when we have unexpected results from mtr-packet-listen' + + pass + + +def set_nonblocking(file_descriptor): # type: (int) -> None + 'Put a file descriptor into non-blocking mode' + + flags = fcntl.fcntl(file_descriptor, fcntl.F_GETFL) + + # pylint: disable=locally-disabled, no-member + fcntl.fcntl(file_descriptor, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + +def check_for_local_ipv6(): + '''Check for IPv6 support on the test host, to see if we should skip + the IPv6 tests''' + + addrinfo = socket.getaddrinfo(IPV6_TEST_HOST, 1, socket.AF_INET6) + if len(addrinfo): + addr = addrinfo[0][4] + + # Create a UDP socket and check to see it can be connected to + # IPV6_TEST_HOST. (Connecting UDP requires no packets sent, just + # a route present.) + sock = socket.socket( + socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + + connect_success = False + try: + sock.connect(addr) + connect_success = True + except socket.error: + pass + + sock.close() + + if not connect_success: + sys.stderr.write( + 'This host has no IPv6. Skipping IPv6 tests.\n') + + return connect_success + + +HAVE_IPV6 = check_for_local_ipv6() + + +# pylint: disable=locally-disabled, too-few-public-methods +class MtrPacketReply(object): + 'A parsed reply from mtr-packet' + + def __init__(self, reply): # type: (unicode) -> None + self.token = 0 # type: int + self.command_name = None # type: unicode + self.argument = {} # type: Dict[unicode, unicode] + + self.parse_reply(reply) + + def parse_reply(self, reply): # type (unicode) -> None + 'Parses a reply string into members for the instance of this class' + + tokens = reply.split() # type List[unicode] + + try: + self.token = int(tokens[0]) + self.command_name = tokens[1] + except IndexError: + raise MtrPacketReplyParseError(reply) + + i = 2 + while i < len(tokens): + try: + name = tokens[i] + value = tokens[i + 1] + except IndexError: + raise MtrPacketReplyParseError(reply) + + self.argument[name] = value + i += 2 + + +class PacketListen(object): + 'A test process which listens for a single packet' + + def __init__(self, *args): + self.process_args = list(args) # type: List[unicode] + self.listen_process = None # type: subprocess.Popen + self.attrib = None # type: Dict[unicode, unicode] + + def __enter__(self): + try: + self.listen_process = subprocess.Popen( + ['./mtr-packet-listen'] + self.process_args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + except OSError: + raise PacketListenError('unable to launch mtr-packet-listen') + + status = self.listen_process.stdout.readline().decode('utf-8') + if status != 'status listening\n': + raise PacketListenError('unexpected status') + + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.wait_for_exit() + + self.attrib = {} + for line in self.listen_process.stdout.readlines(): + tokens = line.decode('utf-8').split() + + if len(tokens) >= 2: + name = tokens[0] + value = tokens[1] + + self.attrib[name] = value + + self.listen_process.stdin.close() + self.listen_process.stdout.close() + + def wait_for_exit(self): + '''Poll the subprocess for up to ten seconds, until it exits. + + We need to wait for its exit to ensure we are able to read its + output.''' + + wait_time = 10 + wait_step = 0.1 + + steps = int(wait_time / wait_step) + + exit_value = None + + # pylint: disable=locally-disabled, unused-variable + for i in range(steps): + exit_value = self.listen_process.poll() + if exit_value is not None: + break + + time.sleep(wait_step) + + if exit_value is None: + raise PacketListenError('mtr-packet-listen timeout') + + if exit_value != 0: + raise PacketListenError('mtr-packet-listen unexpected error') + + +class MtrPacketTest(unittest.TestCase): + '''Base class for tests invoking mtr-packet. + + Start a new mtr-packet subprocess for each test, and kill it + at the conclusion of the test. + + Provide methods for writing commands and reading replies. + ''' + + def __init__(self, *args): + self.reply_buffer = None # type: unicode + self.packet_process = None # type: subprocess.Popen + self.stdout_fd = None # type: int + + super(MtrPacketTest, self).__init__(*args) + + def setUp(self): + 'Set up a test case by spawning a mtr-packet process' + + packet_path = os.environ.get('MTR_PACKET', './mtr-packet') + + self.reply_buffer = '' + try: + self.packet_process = subprocess.Popen( + [packet_path], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + except OSError: + raise MtrPacketExecuteError(packet_path) + + # Put the mtr-packet process's stdout in non-blocking mode + # so that we can read from it without a timeout when + # no reply is available. + self.stdout_fd = self.packet_process.stdout.fileno() + set_nonblocking(self.stdout_fd) + + self.stdin_fd = self.packet_process.stdin.fileno() + set_nonblocking(self.stdin_fd) + + def tearDown(self): + 'After a test, kill the running mtr-packet instance' + + self.packet_process.stdin.close() + self.packet_process.stdout.close() + + try: + self.packet_process.kill() + except OSError: + return + + def parse_reply(self, timeout=10.0): # type: (float) -> MtrPacketReply + '''Read the next reply from mtr-packet and parse it into + an MtrPacketReply object.''' + + reply_str = self.read_reply(timeout) + + return MtrPacketReply(reply_str) + + def read_reply(self, timeout=10.0): # type: (float) -> unicode + '''Read the next reply from mtr-packet. + + Attempt to read the next command reply from mtr-packet. If no reply + is available withing the timeout time, raise ReadReplyTimeout + instead.''' + + start_time = time.time() + + # Read from mtr-packet until either the timeout time has elapsed + # or we read a newline character, which indicates a finished + # reply. + while True: + now = time.time() + elapsed = now - start_time + + select_time = timeout - elapsed + if select_time < 0: + select_time = 0 + + select.select([self.stdout_fd], [], [], select_time) + + reply_bytes = None + + try: + reply_bytes = os.read(self.stdout_fd, 1024) + except OSError: + pass + + if reply_bytes: + self.reply_buffer += reply_bytes.decode('utf-8') + + # If we have read a newline character, we can stop waiting + # for more input. + newline_ix = self.reply_buffer.find('\n') + if newline_ix != -1: + break + + if elapsed >= timeout: + raise ReadReplyTimeout() + + reply = self.reply_buffer[:newline_ix] + self.reply_buffer = self.reply_buffer[newline_ix + 1:] + return reply + + def write_command(self, cmd, timeout=10.0): + # type: (unicode, float) -> None + + '''Send a command string to the mtr-packet instance, timing out + if we are unable to write for an extended period of time. The + timeout is to avoid deadlocks with the child process where both + the parent and the child are writing to their end of the pipe + and expecting the other end to be reading.''' + + command_str = cmd + '\n' + command_bytes = command_str.encode('utf-8') + + start_time = time.time() + + while True: + now = time.time() + elapsed = now - start_time + + select_time = timeout - elapsed + if select_time < 0: + select_time = 0 + + select.select([], [self.stdin_fd], [], select_time) + + bytes_written = 0 + try: + bytes_written = os.write(self.stdin_fd, command_bytes) + except OSError: + pass + + command_bytes = command_bytes[bytes_written:] + if not len(command_bytes): + break + + if elapsed >= timeout: + raise WriteCommandTimeout() + + +def check_running_as_root(): + 'Print a warning to stderr if we are not running as root.' + + # pylint: disable=locally-disabled, no-member + if sys.platform != 'cygwin' and os.getuid() > 0: + sys.stderr.write( + 'Warning: many tests require running as root\n') diff --git a/test/packet_listen.c b/test/packet_listen.c new file mode 100644 index 0000000..eedda4e --- /dev/null +++ b/test/packet_listen.c @@ -0,0 +1,249 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include + +#include "packet/protocols.h" + +#define MAX_PACKET_SIZE 9000 + +/* + The first probe sent by mtr-packet will have this sequence number, + so wait for an ICMP packet with this sequence ID. +*/ +#define SEQUENCE_NUM 33000 + +/* + Check to see if the packet we've recieved is intended for this test + process. We expected the ICMP sequence number to be equal to our + process ID. +*/ +bool is_packet_for_us4( + char *packet, + int packet_size) +{ + int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + int expected_sequence; + struct IPHeader *ip; + struct ICMPHeader *icmp; + + if (packet_size < ip_icmp_size) { + return false; + } + + ip = (struct IPHeader *)packet; + icmp = (struct ICMPHeader *)(ip + 1); + + expected_sequence = htons(SEQUENCE_NUM); + if (icmp->sequence == expected_sequence) { + return true; + } + + return false; +} + +/* + Check to see if the ICMPv6 packet is for us. + Unlike ICMPv4 packets, ICMPv6 packets don't include the IP header. +*/ +bool is_packet_for_us6( + char *packet, + int packet_size) +{ + int expected_sequence; + struct ICMPHeader *icmp; + + if (packet_size < sizeof(struct ICMPHeader)) { + return false; + } + + icmp = (struct ICMPHeader *)packet; + + expected_sequence = htons(SEQUENCE_NUM); + if (icmp->sequence == expected_sequence) { + return true; + } + + return false; +} + +/* + Check that all the bytes in the body of the packet have the same value. + If so, return that value. If not, return -1. +*/ +int get_packet_pattern( + unsigned char *packet, + int packet_size) +{ + int fill_value; + int i; + + if (packet_size <= 0) { + return -1; + } + + fill_value = packet[0]; + for (i = 1; i < packet_size; i++) { + if (packet[i] != fill_value) { + return -1; + } + } + + return fill_value; +} + +/* Print information about the ICMPv4 packet we received */ +void dump_packet_info4( + char *packet, + int packet_size) +{ + int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader); + int pattern; + struct IPHeader *ip; + struct ICMPHeader *icmp; + unsigned char *body; + int body_size; + + ip = (struct IPHeader *)packet; + icmp = (struct ICMPHeader *)(ip + 1); + body = (unsigned char *)(icmp + 1); + body_size = packet_size - ip_icmp_size; + + printf("size %d\n", packet_size); + printf("tos %d\n", ip->tos); + + pattern = get_packet_pattern(body, body_size); + if (pattern < 0) { + printf("bitpattern none\n"); + } else { + printf("bitpattern %d\n", pattern); + } +} + +/* Print information about an ICMPv6 packet */ +void dump_packet_info6( + char *packet, + int packet_size) +{ + int pattern; + struct ICMPHeader *icmp; + unsigned char *body; + int body_size; + int total_size; + + icmp = (struct ICMPHeader *)packet; + body = (unsigned char *)(icmp + 1); + body_size = packet_size - sizeof(struct ICMPHeader); + + total_size = packet_size + sizeof(struct IP6Header); + printf("size %d\n", total_size); + + pattern = get_packet_pattern(body, body_size); + if (pattern < 0) { + printf("bitpattern none\n"); + } else { + printf("bitpattern %d\n", pattern); + } +} + +/* Receive ICMP packets until we get one intended for this test process */ +void loop_on_receive( + int icmp_socket, + int ip_version) +{ + int packet_size; + char packet[MAX_PACKET_SIZE]; + + while (true) { + packet_size = recv(icmp_socket, packet, MAX_PACKET_SIZE, 0); + if (packet_size < -1) { + perror("Failure during receive"); + exit(EXIT_FAILURE); + } + + if (ip_version == 6) { + if (is_packet_for_us6(packet, packet_size)) { + dump_packet_info6(packet, packet_size); + return; + } + } else { + if (is_packet_for_us4(packet, packet_size)) { + dump_packet_info4(packet, packet_size); + return; + } + } + } +} + +/* Parse the commandline arguments */ +void parse_cmdline( + int argc, + char **argv, + int *ip_version) +{ + int opt; + + *ip_version = 4; + + while ((opt = getopt(argc, argv, "46")) != -1) { + if (opt == '4') { + *ip_version = 4; + } + + if (opt == '6') { + *ip_version = 6; + } + } +} + +/* + A helper for mtr-packet testing which waits for an ICMP packet + intended for this test process, and then prints information about + it. +*/ +int main( + int argc, + char **argv) +{ + int icmp_socket; + int ip_version; + + parse_cmdline(argc, argv, &ip_version); + + if (ip_version == 6) { + icmp_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + } else { + icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + } + if (icmp_socket < 0) { + perror("Failure opening listening socket"); + exit(EXIT_FAILURE); + } + + printf("status listening\n"); + fflush(stdout); + + loop_on_receive(icmp_socket, ip_version); + + return EXIT_SUCCESS; +} diff --git a/test/param.py b/test/param.py new file mode 100755 index 0000000..d511062 --- /dev/null +++ b/test/param.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# +# mtr -- a network diagnostic tool +# Copyright (C) 2016 Matt Kimball +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +'''Test probe customization parameters''' + +import sys +import unittest + +import mtrpacket + + +@unittest.skipIf(sys.platform == 'cygwin', 'No Cygwin test') +class TestParameters(mtrpacket.MtrPacketTest): + 'Use parameter arguments to mtr-packet and examine the resulting packet' + + def test_size(self): + 'Test probes sent with an explicit packet size' + + with mtrpacket.PacketListen('-4') as listen: + cmd = '20 send-probe ip-4 127.0.0.1 size 512' + + self.write_command(cmd) + + self.assertEqual(listen.attrib['size'], '512') + + def test_pattern(self): + 'Test probes are filled with the requested bit pattern' + + with mtrpacket.PacketListen('-4') as listen: + cmd = '20 send-probe ip-4 127.0.0.1 bit-pattern 44' + + self.write_command(cmd) + + self.assertEqual(listen.attrib['bitpattern'], '44') + + def test_tos(self): + 'Test setting the TOS field' + + with mtrpacket.PacketListen('-4') as listen: + cmd = '20 send-probe ip-4 127.0.0.1 tos 62' + + self.write_command(cmd) + + self.assertEqual(listen.attrib['tos'], '62') + + +@unittest.skipIf(sys.platform == 'cygwin', 'No Cygwin test') +class TestIPv6Parameters(mtrpacket.MtrPacketTest): + 'Test packet paramter customization for IPv6' + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_param(self): + 'Test a variety of packet parameters' + + with mtrpacket.PacketListen('-6') as listen: + param = 'size 256 bit-pattern 51 tos 77' + cmd = '20 send-probe ip-6 ::1 ' + param + + self.write_command(cmd) + + self.assertEqual(listen.attrib['size'], '256') + self.assertEqual(listen.attrib['bitpattern'], '51') + + +if __name__ == '__main__': + mtrpacket.check_running_as_root() + unittest.main() diff --git a/test/probe.py b/test/probe.py new file mode 100755 index 0000000..2b500c6 --- /dev/null +++ b/test/probe.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python +# +# mtr -- a network diagnostic tool +# Copyright (C) 2016 Matt Kimball +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +'''Test sending probes and receiving respones.''' + +import socket +import sys +import time +import unittest + +import mtrpacket + + +def resolve_ipv6_address(hostname): # type: (str) -> str + 'Resolve a hostname to an IP version 6 address' + + for addrinfo in socket.getaddrinfo(hostname, 0): + # pylint: disable=locally-disabled, unused-variable + (family, socktype, proto, name, sockaddr) = addrinfo + + if family == socket.AF_INET6: + sockaddr6 = sockaddr # type: tuple + + (address, port, flow, scope) = sockaddr6 + return address + + raise LookupError(hostname) + + +def check_feature(test, feature): + 'Check for support for a particular feature with mtr-packet' + + check_cmd = '70 check-support feature ' + feature + test.write_command(check_cmd) + + reply = test.parse_reply() + test.assertEqual(reply.command_name, 'feature-support') + test.assertIn('support', reply.argument) + + if reply.argument['support'] != 'ok': + return False + + return True + + +def test_basic_remote_probe(test, ip_version, protocol): + 'Test a probe to a remote host with a TTL of 1' + + protocol_str = 'protocol ' + protocol + if ip_version == 6: + address_str = 'ip-6 ' + resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST) + elif ip_version == 4: + address_str = 'ip-4 8.8.8.8' + else: + raise ValueError(ip_version) + + cmd = '60 send-probe ' + \ + protocol_str + ' ' + address_str + ' port 164 ttl 1' + test.write_command(cmd) + + reply = test.parse_reply() + test.assertEqual(reply.command_name, 'ttl-expired') + + +def test_basic_local_probe(test, ip_version, protocol): + 'Test a probe to a closed port on localhost' + + protocol_str = 'protocol ' + protocol + if ip_version == 6: + address_str = 'ip-6 ::1' + elif ip_version == 4: + address_str = 'ip-4 127.0.0.1' + + cmd = '61 send-probe ' + \ + protocol_str + ' ' + address_str + ' port 164' + test.write_command(cmd) + + reply = test.parse_reply() + test.assertEqual(reply.command_name, 'reply') + + if ip_version == 6: + test.assertIn('ip-6', reply.argument) + test.assertEqual(reply.argument['ip-6'], '::1') + elif ip_version == 4: + test.assertIn('ip-4', reply.argument) + test.assertEqual(reply.argument['ip-4'], '127.0.0.1') + + +def test_basic_probe(test, ip_version, protocol): + # type: (mtrpacket.MtrPacketTest, int, unicode) -> None + + '''Test a probe with TTL expiration and a probe which reaches its + destination with a particular protocol.''' + + if not check_feature(test, protocol): + err_str = 'Skipping ' + protocol + ' test due to no support\n' + sys.stderr.write(err_str.encode('utf-8')) + return + + test_basic_remote_probe(test, ip_version, protocol) + test_basic_local_probe(test, ip_version, protocol) + + +class TestProbeICMPv4(mtrpacket.MtrPacketTest): + '''Test sending probes using IP version 4''' + + def test_probe(self): + 'Test sending regular ICMP probes to known addresses' + + # Probe Google's well-known DNS server and expect a reply + self.write_command('14 send-probe ip-4 8.8.8.8') + reply = self.parse_reply() + self.assertEqual(reply.token, 14) + self.assertEqual(reply.command_name, 'reply') + self.assertIn('ip-4', reply.argument) + self.assertEqual(reply.argument['ip-4'], '8.8.8.8') + self.assertIn('round-trip-time', reply.argument) + + def test_timeout(self): + 'Test timeouts when sending to a non-existant address' + + # + # Probe a non-existant address, and expect no reply + # + # I'm not sure what the best way to find an address that doesn't + # exist, but is still route-able. If we use a reserved IP + # address range, Windows will tell us it is non-routeable, + # rather than timing out when transmitting to that address. + # + # We're just using a currently unused address in Google's + # range instead. This is probably not the best solution. + # + + # pylint: disable=locally-disabled, unused-variable + for i in range(16): + self.write_command('15 send-probe ip-4 8.8.254.254 timeout 1') + reply = self.parse_reply() + self.assertEqual(reply.token, 15) + self.assertEqual(reply.command_name, 'no-reply') + + def test_exhaust_probes(self): + 'Test exhausting all available probes' + + probe_count = 4 * 1024 + token = 1024 + + # pylint: disable=locally-disabled, unused-variable + for i in range(probe_count): + command = str(token) + ' send-probe ip-4 8.8.254.254 timeout 60' + token += 1 + self.write_command(command) + + reply = None + try: + reply = self.parse_reply(0) + except mtrpacket.ReadReplyTimeout: + pass + + if reply: + if reply.command_name == 'probes-exhausted': + break + + self.assertIsNotNone(reply) + self.assertEqual(reply.command_name, 'probes-exhausted') + + def test_timeout_values(self): + '''Test that timeout values wait the right amount of time + + Give each probe a half-second grace period to probe a timeout + reply after the expected timeout time.''' + + begin = time.time() + self.write_command('19 send-probe ip-4 8.8.254.254 timeout 0') + self.parse_reply() + elapsed = time.time() - begin + self.assertLess(elapsed, 0.5) + + begin = time.time() + self.write_command('20 send-probe ip-4 8.8.254.254 timeout 1') + self.parse_reply() + elapsed = time.time() - begin + self.assertGreaterEqual(elapsed, 0.9) + self.assertLess(elapsed, 1.5) + + begin = time.time() + self.write_command('21 send-probe ip-4 8.8.254.254 timeout 3') + self.parse_reply() + elapsed = time.time() - begin + self.assertGreaterEqual(elapsed, 2.9) + self.assertLess(elapsed, 3.5) + + def test_ttl_expired(self): + 'Test sending a probe which will have its time-to-live expire' + + # Probe Goolge's DNS server, but give the probe only one hop + # to live. + self.write_command('16 send-probe ip-4 8.8.8.8 ttl 1') + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'ttl-expired') + self.assertIn('ip-4', reply.argument) + self.assertIn('round-trip-time', reply.argument) + + def test_parallel_probes(self): + '''Test sending multiple probes in parallel + + We will expect the probes to complete out-of-order by sending + a probe to a distant host immeidately followed by a probe to + the local host.''' + + success_count = 0 + loop_count = 32 + + # pylint: disable=locally-disabled, unused-variable + for i in range(loop_count): + # Probe the distant host before the local host. + self.write_command('17 send-probe ip-4 8.8.8.8 timeout 1') + self.write_command('18 send-probe ip-4 127.0.0.1 timeout 1') + + reply = self.parse_reply() + if reply.command_name == 'no-reply': + continue + + self.assertEqual(reply.command_name, 'reply') + self.assertIn('ip-4', reply.argument) + self.assertEqual(reply.argument['ip-4'], '127.0.0.1') + self.assertIn('round-trip-time', reply.argument) + first_time = int(reply.argument['round-trip-time']) + + reply = self.parse_reply() + if reply.command_name == 'no-reply': + continue + + self.assertEqual(reply.command_name, 'reply') + self.assertIn('ip-4', reply.argument) + self.assertEqual(reply.argument['ip-4'], '8.8.8.8') + self.assertIn('round-trip-time', reply.argument) + second_time = int(reply.argument['round-trip-time']) + + # Ensure we got a reply from the host with the lowest latency + # first. + self.assertLess(first_time, second_time) + + success_count += 1 + + # We need 90% success to pass. This allows a few probes to be + # occasionally dropped by the network without failing the test. + required_success = int(loop_count * 0.90) + self.assertGreaterEqual(success_count, required_success) + + +class TestProbeICMPv6(mtrpacket.MtrPacketTest): + '''Test sending probes using IP version 6''' + + def __init__(self, *args): + google_addr = resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST) + + self.google_addr = google_addr # type: str + + super(TestProbeICMPv6, self).__init__(*args) + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_probe(self): + "Test a probe to Google's public DNS server" + + # Probe Google's well-known DNS server and expect a reply + self.write_command('51 send-probe ip-6 ' + self.google_addr) + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'reply') + self.assertIn('ip-6', reply.argument) + self.assertIn('round-trip-time', reply.argument) + + # Probe the loopback, and check the address we get a reply from is + # also the loopback. While implementing IPv6, I had a bug where + # the low bits of the received address got zeroed. This checks for + # that bug. + self.write_command('52 send-probe ip-6 ::1') + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'reply') + self.assertIn('ip-6', reply.argument) + self.assertIn('round-trip-time', reply.argument) + self.assertEqual(reply.argument['ip-6'], '::1') + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_ttl_expired(self): + 'Test sending a probe which will have its time-to-live expire' + + # Probe Goolge's DNS server, but give the probe only one hop + # to live. + cmd = '53 send-probe ip-6 ' + self.google_addr + ' ttl 1' + self.write_command(cmd) + reply = self.parse_reply() + self.assertEqual('ttl-expired', reply.command_name) + self.assertIn('ip-6', reply.argument) + self.assertIn('round-trip-time', reply.argument) + + +class TestProbeUDP(mtrpacket.MtrPacketTest): + 'Test transmitting probes using UDP' + + def udp_port_test(self, address): # type: (unicode) -> None + 'Test UDP probes with variations on source port and dest port' + + if not check_feature(self, 'udp'): + return + + cmd = '80 send-probe protocol udp ' + address + self.write_command(cmd) + reply = self.parse_reply() + self.assertEqual('reply', reply.command_name) + + cmd = '81 send-probe protocol udp port 990 ' + address + self.write_command(cmd) + reply = self.parse_reply() + self.assertEqual('reply', reply.command_name) + + cmd = '82 send-probe protocol udp local-port 1991 ' + address + self.write_command(cmd) + reply = self.parse_reply() + self.assertEqual('reply', reply.command_name) + + def test_udp_v4(self): + 'Test IPv4 UDP probes' + + test_basic_probe(self, 4, 'udp') + + self.udp_port_test('ip-4 127.0.0.1') + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_udp_v6(self): + 'Test IPv6 UDP probes' + + test_basic_probe(self, 6, 'udp') + + self.udp_port_test('ip-6 ::1') + + +class TestProbeTCP(mtrpacket.MtrPacketTest): + 'Test TCP probe support' + + def test_tcp_v4(self): + '''Test IPv4 TCP probes, with TTL expiration, to a refused port + and to an open port''' + + test_basic_probe(self, 4, 'tcp') + + if not check_feature(self, 'tcp'): + return + + # Probe a local port assumed to be open (ssh) + cmd = '80 send-probe ip-4 127.0.0.1 protocol tcp port 22' + self.write_command(cmd) + + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'reply') + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_tcp_v6(self): + 'Test IPv6 TCP probes' + + test_basic_probe(self, 6, 'tcp') + + if not check_feature(self, 'tcp'): + return + + # Probe a local port assumed to be open (ssh) + cmd = '80 send-probe ip-6 ::1 protocol tcp port 22' + self.write_command(cmd) + + reply = self.parse_reply() + self.assertEqual(reply.command_name, 'reply') + + +class TestProbeSCTP(mtrpacket.MtrPacketTest): + 'Test SCTP probes' + + def test_sctp_v4(self): + 'Test basic SCTP probes over IPv4' + + test_basic_probe(self, 4, 'sctp') + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_sctp_v6(self): + 'Test basic SCTP probes over IPv6' + + test_basic_probe(self, 6, 'sctp') + + +if __name__ == '__main__': + mtrpacket.check_running_as_root() + unittest.main() diff --git a/ui/asn.c b/ui/asn.c new file mode 100644 index 0000000..04c8e3a --- /dev/null +++ b/ui/asn.c @@ -0,0 +1,333 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif +#include + +#ifdef __APPLE__ +#define BIND_8_COMPAT +#endif +#include +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "mtr.h" +#include "asn.h" +#include "utils.h" + +/* #define IIDEBUG */ +#ifdef IIDEBUG +#include +#define DEB_syslog syslog +#else +#define DEB_syslog(...) do {} while (0) +#endif + +#define IIHASH_HI 128 +#define ITEMSMAX 15 +#define ITEMSEP '|' +#define NAMELEN 127 +#define UNKN "???" + +static int iihash = 0; +static char fmtinfo[32]; + +/* items width: ASN, Route, Country, Registry, Allocated */ +static const int iiwidth[] = { 7, 19, 4, 8, 11 }; /* item len + space */ + +typedef char *items_t[ITEMSMAX + 1]; +static items_t items_a; /* without hash: items */ +static char txtrec[NAMELEN + 1]; /* without hash: txtrec */ +static items_t *items = &items_a; + + +static char *ipinfo_lookup( + const char *domain) +{ + unsigned char answer[PACKETSZ], *pt; + char host[128]; + char *txt; + int len, exp, size, txtlen, type; + + + if (res_init() < 0) { + error(0, 0, "@res_init failed"); + return NULL; + } + + memset(answer, 0, PACKETSZ); + if ((len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ)) < 0) { + if (iihash) + DEB_syslog(LOG_INFO, "Malloc-txt: %s", UNKN); + return xstrdup(UNKN); + } + + pt = answer + sizeof(HEADER); + + if ((exp = + dn_expand(answer, answer + len, pt, host, sizeof(host))) < 0) { + printf("@dn_expand failed\n"); + return NULL; + } + + pt += exp; + + GETSHORT(type, pt); + if (type != T_TXT) { + printf("@Broken DNS reply.\n"); + return NULL; + } + + pt += INT16SZ; /* class */ + + if ((exp = + dn_expand(answer, answer + len, pt, host, sizeof(host))) < 0) { + printf("@second dn_expand failed\n"); + return NULL; + } + + pt += exp; + GETSHORT(type, pt); + if (type != T_TXT) { + printf("@Not a TXT record\n"); + return NULL; + } + + pt += INT16SZ; /* class */ + pt += INT32SZ; /* ttl */ + GETSHORT(size, pt); + txtlen = *pt; + + + if (txtlen >= size || !txtlen) { + printf("@Broken TXT record (txtlen = %d, size = %d)\n", txtlen, + size); + return NULL; + } + + if (txtlen > NAMELEN) + txtlen = NAMELEN; + + if (iihash) { + txt = xmalloc(txtlen + 1); + } else + txt = (char *) txtrec; + + pt++; + xstrncpy(txt, (char *) pt, txtlen + 1); + + if (iihash) + DEB_syslog(LOG_INFO, "Malloc-txt(%p): %s", txt, txt); + + return txt; +} + +/* originX.asn.cymru.com txtrec: ASN | Route | Country | Registry | Allocated */ +static char *split_txtrec( + struct mtr_ctl *ctl, + char *txt_rec) +{ + char *prev; + char *next; + int i = 0, j; + + if (!txt_rec) + return NULL; + if (iihash) { + DEB_syslog(LOG_INFO, "Malloc-tbl: %s", txt_rec); + if (!(items = malloc(sizeof(*items)))) { + DEB_syslog(LOG_INFO, "Free-txt(%p)", txt_rec); + free(txt_rec); + return NULL; + } + } + + prev = txt_rec; + + while ((next = strchr(prev, ITEMSEP)) && (i < ITEMSMAX)) { + *next = '\0'; + next++; + (*items)[i] = trim(prev, ITEMSEP); + prev = next; + i++; + } + (*items)[i] = trim(prev, ITEMSEP); + + if (i < ITEMSMAX) + i++; + for (j = i; j <= ITEMSMAX; j++) + (*items)[j] = NULL; + + if (i > ctl->ipinfo_max) + ctl->ipinfo_max = i; + if (ctl->ipinfo_no >= i) { + if (ctl->ipinfo_no >= ctl->ipinfo_max) + ctl->ipinfo_no = 0; + return (*items)[0]; + } else + return (*items)[ctl->ipinfo_no]; +} + +#ifdef ENABLE_IPV6 +/* from dns.c:addr2ip6arpa() */ +static void reverse_host6( + struct in6_addr *addr, + char *buff, + int buff_length) +{ + int i; + char *b = buff; + for (i = (sizeof(*addr) / 2 - 1); i >= 0; i--, b += 4) /* 64b portion */ + snprintf(b, buff_length, + "%x.%x.", addr->s6_addr[i] & 0xf, addr->s6_addr[i] >> 4); + + buff[strlen(buff) - 1] = '\0'; +} +#endif + +static char *get_ipinfo( + struct mtr_ctl *ctl, + ip_t * addr) +{ + char key[NAMELEN]; + char lookup_key[NAMELEN]; + char *val = NULL; + ENTRY item; + + if (!addr) + return NULL; + + if (ctl->af == AF_INET6) { +#ifdef ENABLE_IPV6 + reverse_host6(addr, key, NAMELEN); + if (snprintf(lookup_key, NAMELEN, "%s.origin6.asn.cymru.com", key) + >= NAMELEN) + return NULL; +#else + return NULL; +#endif + } else { + unsigned char buff[4]; + memcpy(buff, addr, 4); + if (snprintf + (key, NAMELEN, "%d.%d.%d.%d", buff[3], buff[2], buff[1], + buff[0]) >= NAMELEN) + return NULL; + if (snprintf(lookup_key, NAMELEN, "%s.origin.asn.cymru.com", key) + >= NAMELEN) + return NULL; + } + + if (iihash) { + ENTRY *found_item; + + DEB_syslog(LOG_INFO, ">> Search: %s", key); + item.key = key;; + if ((found_item = hsearch(item, FIND))) { + if (!(val = (*((items_t *) found_item->data))[ctl->ipinfo_no])) + val = (*((items_t *) found_item->data))[0]; + DEB_syslog(LOG_INFO, "Found (hashed): %s", val); + } + } + + if (!val) { + DEB_syslog(LOG_INFO, "Lookup: %s", key); + if ((val = split_txtrec(ctl, ipinfo_lookup(lookup_key)))) { + DEB_syslog(LOG_INFO, "Looked up: %s", key); + if (iihash) + if ((item.key = xstrdup(key))) { + item.data = (void *) items; + hsearch(item, ENTER); + DEB_syslog(LOG_INFO, "Insert into hash: %s", key); + } + } + } + + return val; +} + +ATTRIBUTE_CONST size_t get_iiwidth_len( + void) +{ + return (sizeof(iiwidth) / sizeof((iiwidth)[0])); +} + +ATTRIBUTE_CONST int get_iiwidth( + int ipinfo_no) +{ + static const int len = (sizeof(iiwidth) / sizeof((iiwidth)[0])); + + if (ipinfo_no < len) + return iiwidth[ipinfo_no]; + return iiwidth[ipinfo_no % len]; +} + +char *fmt_ipinfo( + struct mtr_ctl *ctl, + ip_t * addr) +{ + char *ipinfo = get_ipinfo(ctl, addr); + char fmt[8]; + snprintf(fmt, sizeof(fmt), "%s%%-%ds", ctl->ipinfo_no ? "" : "AS", + get_iiwidth(ctl->ipinfo_no)); + snprintf(fmtinfo, sizeof(fmtinfo), fmt, ipinfo ? ipinfo : UNKN); + return fmtinfo; +} + +int is_printii( + struct mtr_ctl *ctl) +{ + return ((ctl->ipinfo_no >= 0) && (ctl->ipinfo_no != ctl->ipinfo_max)); +} + +void asn_open( + struct mtr_ctl *ctl) +{ + if (ctl->ipinfo_no >= 0) { + DEB_syslog(LOG_INFO, "hcreate(%d)", IIHASH_HI); + if (!(iihash = hcreate(IIHASH_HI))) + error(0, errno, "ipinfo hash"); + } +} + +void asn_close( + struct mtr_ctl *ctl) +{ + if ((ctl->ipinfo_no >= 0) && iihash) { + DEB_syslog(LOG_INFO, "hdestroy()"); + hdestroy(); + iihash = 0; + } +} diff --git a/ui/asn.h b/ui/asn.h new file mode 100644 index 0000000..5301e9b --- /dev/null +++ b/ui/asn.h @@ -0,0 +1,33 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "mtr.h" + +extern void asn_open( + struct mtr_ctl *ctl); +extern void asn_close( + struct mtr_ctl *ctl); +extern char *fmt_ipinfo( + struct mtr_ctl *ctl, + ip_t * addr); +extern ATTRIBUTE_CONST size_t get_iiwidth_len( + void); +extern ATTRIBUTE_CONST int get_iiwidth( + int ipinfo_no); +extern int is_printii( + struct mtr_ctl *ctl); diff --git a/ui/cmdpipe.c b/ui/cmdpipe.c new file mode 100644 index 0000000..c7120e2 --- /dev/null +++ b/ui/cmdpipe.c @@ -0,0 +1,829 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "cmdpipe.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif + +#include "packet/cmdparse.h" +#include "display.h" + + +/* Set a file descriptor to non-blocking */ +static +void set_fd_nonblock( + int fd) +{ + int flags; + + /* Get the current flags of the file descriptor */ + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + error(EXIT_FAILURE, errno, "F_GETFL failure"); + exit(1); + } + + /* Add the O_NONBLOCK bit to the current flags */ + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + error(EXIT_FAILURE, errno, "Failure to set O_NONBLOCK"); + exit(1); + } +} + + +/* + Send a command synchronously to mtr-packet, blocking until a result + is available. This is intended to be used at start-up to check the + capabilities of mtr-packet, but probes should be sent asynchronously + to avoid blocking other probes and the user interface. +*/ +static +int send_synchronous_command( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + const char *cmd, + struct command_t *result) +{ + char reply[PACKET_REPLY_BUFFER_SIZE]; + int command_length; + int write_length; + int read_length; + + /* Query send-probe support */ + command_length = strlen(cmd); + write_length = write(cmdpipe->write_fd, cmd, command_length); + + if (write_length == -1) { + return -1; + } + + if (write_length != command_length) { + errno = EIO; + return -1; + } + + /* Read the reply to our query */ + read_length = + read(cmdpipe->read_fd, reply, PACKET_REPLY_BUFFER_SIZE - 1); + + if (read_length < 0) { + return -1; + } + + /* Parse the query reply */ + reply[read_length] = 0; + if (parse_command(result, reply)) { + return -1; + } + + return 0; +} + + +/* Check support for a particular feature with the mtr-packet we invoked */ +static +int check_feature( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + const char *feature) +{ + char check_command[COMMAND_BUFFER_SIZE]; + struct command_t reply; + + snprintf(check_command, COMMAND_BUFFER_SIZE, + "1 check-support feature %s\n", feature); + + if (send_synchronous_command(ctl, cmdpipe, check_command, &reply) == + -1) { + return -1; + } + + /* Check that the feature is supported */ + if (!strcmp(reply.command_name, "feature-support") + && reply.argument_count >= 1 + && !strcmp(reply.argument_name[0], "support") + && !strcmp(reply.argument_value[0], "ok")) { + + /* Looks good */ + return 0; + } + + errno = ENOTSUP; + return -1; +} + + +/* + Check the protocol selected against the mtr-packet we are using. + Returns zero if everything is fine, or -1 with errno for either + a failure during the check, or for an unsupported feature. +*/ +static +int check_packet_features( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe) +{ + /* Check the IP protocol version */ + if (ctl->af == AF_INET6) { + if (check_feature(ctl, cmdpipe, "ip-6")) { + return -1; + } + } else if (ctl->af == AF_INET) { + if (check_feature(ctl, cmdpipe, "ip-4")) { + return -1; + } + } else { + errno = EINVAL; + return -1; + } + + /* Check the transport protocol */ + if (ctl->mtrtype == IPPROTO_ICMP) { + if (check_feature(ctl, cmdpipe, "icmp")) { + return -1; + } + } else if (ctl->mtrtype == IPPROTO_UDP) { + if (check_feature(ctl, cmdpipe, "udp")) { + return -1; + } + } else if (ctl->mtrtype == IPPROTO_TCP) { + if (check_feature(ctl, cmdpipe, "tcp")) { + return -1; + } +#ifdef HAS_SCTP + } else if (ctl->mtrtype == IPPROTO_SCTP) { + if (check_feature(ctl, cmdpipe, "sctp")) { + return -1; + } +#endif + } else { + errno = EINVAL; + return -1; + } + +#ifdef SO_MARK + if (ctl->mark) { + if (check_feature(ctl, cmdpipe, "mark")) { + return -1; + } + } +#endif + + return 0; +} + + +/* + Execute mtr-packet, allowing the MTR_PACKET evironment to override + the PATH when locating the executable. +*/ +static +void execute_packet_child( + void) +{ + /* + Allow the MTR_PACKET environment variable to override + the path to the mtr-packet executable. This is necessary + for debugging changes for mtr-packet. + */ + char *mtr_packet_path = getenv("MTR_PACKET"); + if (mtr_packet_path == NULL) { + mtr_packet_path = "mtr-packet"; + } + + /* + First, try to execute mtr-packet from PATH + or MTR_PACKET environment variable. + */ + execlp(mtr_packet_path, "mtr-packet", (char *) NULL); + + /* + If mtr-packet is not found, try to use mtr-packet from current directory + */ + execl("./mtr-packet", "./mtr-packet", (char *) NULL); + + /* Both exec attempts failed, so nothing to do but exit */ + exit(1); +} + + +/* Create the command pipe to a new mtr-packet subprocess */ +int open_command_pipe( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe) +{ + int stdin_pipe[2]; + int stdout_pipe[2]; + pid_t child_pid; + int i; + + /* + We actually need two Unix pipes. One for stdin and one for + stdout on the new process. + */ + if (pipe(stdin_pipe) || pipe(stdout_pipe)) { + return errno; + } + + child_pid = fork(); + if (child_pid == -1) { + return errno; + } + + if (child_pid == 0) { + /* + In the child process, attach our created pipes to stdin + and stdout + */ + dup2(stdin_pipe[0], STDIN_FILENO); + dup2(stdout_pipe[1], STDOUT_FILENO); + + /* Close all unnecessary fds */ + for (i = STDERR_FILENO + 1; i <= stdout_pipe[1]; i++) { + close(i); + } + + execute_packet_child(); + } else { + memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t)); + + /* + In the parent process, save the opposite ends of the pipes + attached as stdin and stdout in the child. + */ + cmdpipe->pid = child_pid; + cmdpipe->read_fd = stdout_pipe[0]; + cmdpipe->write_fd = stdin_pipe[1]; + + /* We don't need the child ends of the pipe open in the parent. */ + close(stdout_pipe[1]); + close(stdin_pipe[0]); + + /* + Check that we can communicate with the client. If we failed to + execute the mtr-packet binary, we will discover that here. + */ + if (check_feature(ctl, cmdpipe, "send-probe")) { + error(EXIT_FAILURE, errno, "Failure to start mtr-packet"); + } + + if (check_packet_features(ctl, cmdpipe)) { + error(EXIT_FAILURE, errno, "Packet type unsupported"); + } + + /* We will need non-blocking reads from the child */ + set_fd_nonblock(cmdpipe->read_fd); + } + + return 0; +} + + +/* Kill the mtr-packet child process and close the command pipe */ +void close_command_pipe( + struct packet_command_pipe_t *cmdpipe) +{ + int child_exit_value; + + if (cmdpipe->pid) { + close(cmdpipe->read_fd); + close(cmdpipe->write_fd); + + kill(cmdpipe->pid, SIGTERM); + waitpid(cmdpipe->pid, &child_exit_value, 0); + } + + memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t)); +} + + +/* Start building the command string for the "send-probe" command */ +static +void construct_base_command( + struct mtr_ctl *ctl, + char *command, + int buffer_size, + int command_token, + ip_t * address, + ip_t * localaddress) +{ + char ip_string[INET6_ADDRSTRLEN]; + char local_ip_string[INET6_ADDRSTRLEN]; + const char *ip_type; + const char *local_ip_type; + const char *protocol = NULL; + + /* Conver the remote IP address to a string */ + if (inet_ntop(ctl->af, address, ip_string, INET6_ADDRSTRLEN) == NULL) { + + display_close(ctl); + error(EXIT_FAILURE, errno, "invalid remote IP address"); + } + + if (inet_ntop(ctl->af, localaddress, + local_ip_string, INET6_ADDRSTRLEN) == NULL) { + + display_close(ctl); + error(EXIT_FAILURE, errno, "invalid local IP address"); + } + + if (ctl->af == AF_INET6) { + ip_type = "ip-6"; + local_ip_type = "local-ip-6"; + } else { + ip_type = "ip-4"; + local_ip_type = "local-ip-4"; + } + + if (ctl->mtrtype == IPPROTO_ICMP) { + protocol = "icmp"; + } else if (ctl->mtrtype == IPPROTO_UDP) { + protocol = "udp"; + } else if (ctl->mtrtype == IPPROTO_TCP) { + protocol = "tcp"; +#ifdef HAS_SCTP + } else if (ctl->mtrtype == IPPROTO_SCTP) { + protocol = "sctp"; +#endif + } else { + display_close(ctl); + error(EXIT_FAILURE, 0, + "protocol unsupported by mtr-packet interface"); + } + + snprintf(command, buffer_size, + "%d send-probe %s %s %s %s protocol %s", + command_token, + ip_type, ip_string, local_ip_type, local_ip_string, protocol); +} + + +/* Append an argument to the "send-probe" command string */ +static +void append_command_argument( + char *command, + int buffer_size, + char *name, + int value) +{ + char argument[COMMAND_BUFFER_SIZE]; + int remaining_size; + + remaining_size = buffer_size - strlen(command) - 1; + + snprintf(argument, buffer_size, " %s %d", name, value); + strncat(command, argument, remaining_size); +} + + +/* Request a new probe from the "mtr-packet" child process */ +void send_probe_command( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + ip_t * address, + ip_t * localaddress, + int packet_size, + int sequence, + int time_to_live) +{ + char command[COMMAND_BUFFER_SIZE]; + int remaining_size; + int timeout; + + construct_base_command(ctl, command, COMMAND_BUFFER_SIZE, sequence, + address, localaddress); + + append_command_argument(command, COMMAND_BUFFER_SIZE, "size", + packet_size); + + append_command_argument(command, COMMAND_BUFFER_SIZE, "bit-pattern", + ctl->bitpattern); + + append_command_argument(command, COMMAND_BUFFER_SIZE, "tos", ctl->tos); + + append_command_argument(command, COMMAND_BUFFER_SIZE, "ttl", + time_to_live); + + timeout = ctl->probe_timeout / 1000000; + append_command_argument(command, COMMAND_BUFFER_SIZE, "timeout", + timeout); + + if (ctl->remoteport) { + append_command_argument(command, COMMAND_BUFFER_SIZE, "port", + ctl->remoteport); + } + + if (ctl->localport) { + append_command_argument(command, COMMAND_BUFFER_SIZE, "local-port", + ctl->localport); + } +#ifdef SO_MARK + if (ctl->mark) { + append_command_argument(command, COMMAND_BUFFER_SIZE, "mark", + ctl->mark); + } +#endif + + remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1; + strncat(command, "\n", remaining_size); + + /* Send a probe using the mtr-packet subprocess */ + if (write(cmdpipe->write_fd, command, strlen(command)) == -1) { + display_close(ctl); + error(EXIT_FAILURE, errno, + "mtr-packet command pipe write failure"); + } +} + + +/* + Parse a comma separated field of mpls values, filling out the mplslen + structure with mpls labels. +*/ +static +void parse_mpls_values( + struct mplslen *mpls, + char *value_str) +{ + char *next_value = value_str; + char *end_of_value; + int value; + int label_count = 0; + int label_field = 0; + + while (*next_value) { + value = strtol(next_value, &end_of_value, 10); + + /* Failure to advance means an invalid numeric value */ + if (end_of_value == next_value) { + return; + } + + /* If the next character is not a comma or a NUL, we have + an invalid string */ + if (*end_of_value == ',') { + next_value = end_of_value + 1; + } else if (*end_of_value == 0) { + next_value = end_of_value; + } else { + return; + } + + /* + Store the converted value in the next field of the MPLS + structure. + */ + if (label_field == 0) { + mpls->label[label_count] = value; + } else if (label_field == 1) { + mpls->exp[label_count] = value; + } else if (label_field == 2) { + mpls->s[label_count] = value; + } else if (label_field == 3) { + mpls->ttl[label_count] = value; + } + + label_field++; + if (label_field > 3) { + label_field = 0; + label_count++; + } + + /* + If we've used up all MPLS labels in the structure, return with + what we've got + */ + if (label_count >= MAXLABELS) { + break; + } + } + + mpls->labels = label_count; +} + + +/* + Extract the IP address and round trip time from a reply to a probe. + Returns true if both arguments are found in the reply, false otherwise. +*/ +static +bool parse_reply_arguments( + struct mtr_ctl *ctl, + struct command_t *reply, + ip_t * fromaddress, + int *round_trip_time, + struct mplslen *mpls) +{ + bool found_round_trip; + bool found_ip; + char *arg_name; + char *arg_value; + int i; + + *round_trip_time = 0; + memset(fromaddress, 0, sizeof(ip_t)); + memset(mpls, 0, sizeof(struct mplslen)); + + found_ip = false; + found_round_trip = false; + + /* Examine the reply arguments for known values */ + for (i = 0; i < reply->argument_count; i++) { + arg_name = reply->argument_name[i]; + arg_value = reply->argument_value[i]; + + if (ctl->af == AF_INET6) { + /* IPv6 address of the responding host */ + if (!strcmp(arg_name, "ip-6")) { + if (inet_pton(AF_INET6, arg_value, fromaddress)) { + found_ip = true; + } + } + } else { + /* IPv4 address of the responding host */ + if (!strcmp(arg_name, "ip-4")) { + if (inet_pton(AF_INET, arg_value, fromaddress)) { + found_ip = true; + } + } + } + + /* The round trip time in microseconds */ + if (!strcmp(arg_name, "round-trip-time")) { + errno = 0; + *round_trip_time = strtol(arg_value, NULL, 10); + if (!errno) { + found_round_trip = true; + } + } + + /* MPLS labels */ + if (!strcmp(arg_name, "mpls")) { + parse_mpls_values(mpls, arg_value); + } + } + + return found_ip && found_round_trip; +} + + +/* + If an mtr-packet command has returned an error result, + report the error and exit. +*/ +static +void handle_reply_errors( + struct mtr_ctl *ctl, + struct command_t *reply) +{ + char *reply_name; + + reply_name = reply->command_name; + + if (!strcmp(reply_name, "no-route")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "No route to host"); + } + + if (!strcmp(reply_name, "network-down")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Network down"); + } + + if (!strcmp(reply_name, "probes-exhausted")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Probes exhausted"); + } + + if (!strcmp(reply_name, "invalid-argument")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument"); + } + + if (!strcmp(reply_name, "permission-denied")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Permission denied"); + } + + if (!strcmp(reply_name, "address-in-use")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Address in use"); + } + + if (!strcmp(reply_name, "address-not-available")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Address not available"); + } + + if (!strcmp(reply_name, "unexpected-error")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Unexpected mtr-packet error"); + } +} + + +/* + A complete mtr-packet reply line has arrived. Parse it and record + the responding IP and round trip time, if it is a reply that we + understand. +*/ +static +void handle_command_reply( + struct mtr_ctl *ctl, + char *reply_str, + probe_reply_func_t reply_func) +{ + struct command_t reply; + ip_t fromaddress; + int seq_num; + int round_trip_time; + char *reply_name; + struct mplslen mpls; + + /* Parse the reply string */ + if (parse_command(&reply, reply_str)) { + /* + If the reply isn't well structured, something is fundamentally + wrong, as we might as well exit. Even if the reply is of an + unknown type, it should still parse. + */ + display_close(ctl); + error(EXIT_FAILURE, errno, "reply parse failure"); + return; + } + + handle_reply_errors(ctl, &reply); + + seq_num = reply.token; + reply_name = reply.command_name; + + /* If the reply type is unknown, ignore it for future compatibility */ + if (strcmp(reply_name, "reply") && strcmp(reply_name, "ttl-expired")) { + return; + } + + /* + If the reply had an IP address and a round trip time, we can + record the result. + */ + if (parse_reply_arguments + (ctl, &reply, &fromaddress, &round_trip_time, &mpls)) { + + reply_func(ctl, seq_num, &mpls, (void *) &fromaddress, + round_trip_time); + } +} + + +/* + Check the command pipe for completed replies to commands + we have previously sent. Record the results of those replies. +*/ +static +void consume_reply_buffer( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + probe_reply_func_t reply_func) +{ + char *reply_buffer; + char *reply_start; + char *end_of_reply; + int used_size; + int move_size; + + reply_buffer = cmdpipe->reply_buffer; + + /* Terminate the string storing the replies */ + assert(cmdpipe->reply_buffer_used < PACKET_REPLY_BUFFER_SIZE); + reply_buffer[cmdpipe->reply_buffer_used] = 0; + + reply_start = reply_buffer; + + /* + We may have multiple completed replies. Loop until we don't + have any more newlines termininating replies. + */ + while (true) { + /* If no newline is found, our reply isn't yet complete */ + end_of_reply = index(reply_start, '\n'); + if (end_of_reply == NULL) { + /* No complete replies remaining */ + break; + } + + /* + Terminate the reply string at the newline, which + is necessary in the case where we are able to read + mulitple replies arriving simultaneously. + */ + *end_of_reply = 0; + + /* Parse and record the reply results */ + handle_command_reply(ctl, reply_start, reply_func); + + reply_start = end_of_reply + 1; + } + + /* + After replies have been processed, free the space used + by the replies, and move any remaining partial reply text + to the start of the reply buffer. + */ + used_size = reply_start - reply_buffer; + move_size = cmdpipe->reply_buffer_used - used_size; + memmove(reply_buffer, reply_start, move_size); + cmdpipe->reply_buffer_used -= used_size; + + if (cmdpipe->reply_buffer_used >= PACKET_REPLY_BUFFER_SIZE - 1) { + /* + We've overflowed the reply buffer without a complete reply. + There's not much we can do about it but discard the data + we've got and hope new data coming in fits. + */ + cmdpipe->reply_buffer_used = 0; + } +} + + +/* + Read as much as we can from the reply pipe from the child process, and + process as many replies as are available. +*/ +void handle_command_replies( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + probe_reply_func_t reply_func) +{ + int read_count; + int buffer_remaining; + char *reply_buffer; + char *read_buffer; + + reply_buffer = cmdpipe->reply_buffer; + + /* + Read the available reply text, up to the the remaining + buffer space. (Minus one for the terminating NUL.) + */ + read_buffer = &reply_buffer[cmdpipe->reply_buffer_used]; + buffer_remaining = + PACKET_REPLY_BUFFER_SIZE - cmdpipe->reply_buffer_used; + read_count = read(cmdpipe->read_fd, read_buffer, buffer_remaining - 1); + + if (read_count < 0) { + /* + EAGAIN simply indicates that there is no data currently + available on our non-blocking pipe. + */ + if (errno == EAGAIN) { + return; + } + + display_close(ctl); + error(EXIT_FAILURE, errno, "command reply read failure"); + return; + } + + if (read_count == 0) { + display_close(ctl); + + errno = EPIPE; + error(EXIT_FAILURE, EPIPE, "unexpected packet generator exit"); + } + + cmdpipe->reply_buffer_used += read_count; + + /* Handle any replies completed by this read */ + consume_reply_buffer(ctl, cmdpipe, reply_func); +} diff --git a/ui/cmdpipe.h b/ui/cmdpipe.h new file mode 100644 index 0000000..7a5b687 --- /dev/null +++ b/ui/cmdpipe.h @@ -0,0 +1,75 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CMDPIPE_H +#define CMDPIPE_H + +#include "mtr.h" + +#define COMMAND_BUFFER_SIZE 4096 +#define PACKET_REPLY_BUFFER_SIZE 4096 + +/* We use a pipe to the mtr-packet subprocess to generate probes */ +struct packet_command_pipe_t { + /* the process id of mtr-packet */ + pid_t pid; + + /* the end of the pipe we read for replies */ + int read_fd; + + /* the end of the pipe we write for commands */ + int write_fd; + + /* storage for incoming replies */ + char reply_buffer[PACKET_REPLY_BUFFER_SIZE]; + + /* the number of bytes currently used in reply_buffer */ + size_t reply_buffer_used; +}; + +typedef +void ( + *probe_reply_func_t) ( + struct mtr_ctl * ctl, + int sequence, + struct mplslen * mpls, + ip_t * addr, + int round_trip_time); + +int open_command_pipe( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe); + +void close_command_pipe( + struct packet_command_pipe_t *cmdpipe); + +void send_probe_command( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + ip_t * address, + ip_t * localaddress, + int packet_size, + int sequence, + int time_to_live); + +void handle_command_replies( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + probe_reply_func_t reply_func); + +#endif diff --git a/ui/curses.c b/ui/curses.c new file mode 100644 index 0000000..a7588ca --- /dev/null +++ b/ui/curses.c @@ -0,0 +1,812 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include "mtr.h" + +#include +#include + +#include +#include +#include +#include + +/* MacOSX may need this before socket.h...*/ +#if defined(HAVE_SYS_TYPES_H) +#include +#endif + +#include +#include +#include + +#if defined(HAVE_NCURSES_H) +#include +#elif defined(HAVE_NCURSES_CURSES_H) +#include +#elif defined(HAVE_CURSES_H) +#include +#elif defined(HAVE_CURSESX_H) +#include +#else +#error No curses header file available +#endif + +/* This go-around is needed only when compiling with antique version of curses. + getmaxyx is part of Technical Standard X/Open Curses Issue 4, Version 2 (1996). + http://pubs.opengroup.org/onlinepubs/9693989999/toc.pdf see page 106 */ +#ifndef getmaxyx +#define getmaxyx(win,y,x) ((y) = (win)->_maxy + 1, (x) = (win)->_maxx + 1) +#endif + +#include "mtr.h" +#include "mtr-curses.h" +#include "net.h" +#include "dns.h" +#include "asn.h" +#include "display.h" +#include "utils.h" + + +enum { NUM_FACTORS = 8 }; +static double factors[NUM_FACTORS]; +static int scale[NUM_FACTORS]; +static char block_map[NUM_FACTORS]; + +enum { black = 1, red, green, yellow, blue, magenta, cyan, white }; +static const int block_col[NUM_FACTORS + 1] = { + COLOR_PAIR(red) | A_BOLD, + A_NORMAL, + COLOR_PAIR(green), + COLOR_PAIR(green) | A_BOLD, + COLOR_PAIR(yellow) | A_BOLD, + COLOR_PAIR(magenta) | A_BOLD, + COLOR_PAIR(magenta), + COLOR_PAIR(red), + COLOR_PAIR(red) | A_BOLD +}; + +static void pwcenter( + char *str) +{ + int maxx; + size_t cx; + int __unused_int ATTRIBUTE_UNUSED; + + getmaxyx(stdscr, __unused_int, maxx); + cx = (size_t) (maxx - strlen(str)) / 2; + printw("%*s%s", (int) cx, "", str); +} + + +static char *format_number( + int n, + int w, + char *buf) +{ + if (w != 5) + /* XXX todo: implement w != 5.. */ + snprintf(buf, w + 1, "%s", "unimpl"); + else if (n < 100000) + /* buf is good as-is */ ; + else if (n < 1000000) + snprintf(buf, w + 1, "%3dk%1d", n / 1000, (n % 1000) / 100); + else if (n < 10000000) + snprintf(buf, w + 1, "%1dM%03d", n / 1000000, + (n % 1000000) / 1000); + else if (n < 100000000) + snprintf(buf, w + 1, "%2dM%02d", n / 1000000, + (n % 1000000) / 10000); + else if (n < 1000000000) + snprintf(buf, w + 1, "%3dM%01d", n / 1000000, + (n % 1000000) / 100000); + else /* if (n < 10000000000) */ + snprintf(buf, w + 1, "%1dG%03d", n / 1000000000, + (n % 1000000000) / 1000000); + + return buf; +} + + +int mtr_curses_keyaction( + struct mtr_ctl *ctl) +{ + int c = getch(); + int i = 0; + float f = 0.0; + char buf[MAXFLD + 1]; + + if (c == 'Q') { /* must be checked before c = tolower(c) */ + mvprintw(2, 0, "Type of Service(tos): %d\n", ctl->tos); + mvprintw(3, 0, + "default 0x00, min cost 0x02, rel 0x04,, thr 0x08, low del 0x10...\n"); + move(2, 22); + refresh(); + while ((c = getch()) != '\n' && i < MAXFLD) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* need more checking on 'c' */ + } + buf[i] = '\0'; + ctl->tos = atoi(buf); + if (ctl->tos > 255 || ctl->tos < 0) + ctl->tos = 0; + return ActionNone; + } + + c = tolower(c); + + switch (c) { + case 'q': + case 3: + return ActionQuit; + case 12: + return ActionClear; + case 19: + case 'p': + return ActionPause; + case 17: + case ' ': + return ActionResume; + case 'r': + return ActionReset; + case 'd': + return ActionDisplay; + case 'e': + return ActionMPLS; + case 'n': + return ActionDNS; +#ifdef HAVE_IPINFO + case 'y': + return ActionII; + case 'z': + return ActionAS; +#endif + case '+': + return ActionScrollDown; + case '-': + return ActionScrollUp; + case 's': + mvprintw(2, 0, "Change Packet Size: %d\n", ctl->cpacketsize); + mvprintw(3, 0, "Size Range: %d-%d, < 0:random.\n", MINPACKET, + MAXPACKET); + move(2, 20); + refresh(); + while ((c = getch()) != '\n' && i < MAXFLD) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* need more checking on 'c' */ + } + buf[i] = '\0'; + ctl->cpacketsize = atoi(buf); + return ActionNone; + case 'b': + mvprintw(2, 0, "Ping Bit Pattern: %d\n", ctl->bitpattern); + mvprintw(3, 0, "Pattern Range: 0(0x00)-255(0xff), <0 random.\n"); + move(2, 18); + refresh(); + while ((c = getch()) != '\n' && i < MAXFLD) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* need more checking on 'c' */ + } + buf[i] = '\0'; + ctl->bitpattern = atoi(buf); + if (ctl->bitpattern > 255) + ctl->bitpattern = -1; + return ActionNone; + case 'i': + mvprintw(2, 0, "Interval : %0.0f\n\n", ctl->WaitTime); + move(2, 11); + refresh(); + while ((c = getch()) != '\n' && i < MAXFLD) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* need more checking on 'c' */ + } + buf[i] = '\0'; + + f = atof(buf); + + if (f <= 0.0) + return ActionNone; + if (getuid() != 0 && f < 1.0) + return ActionNone; + ctl->WaitTime = f; + + return ActionNone; + case 'f': + mvprintw(2, 0, "First TTL: %d\n\n", ctl->fstTTL); + move(2, 11); + refresh(); + while ((c = getch()) != '\n' && i < MAXFLD) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* need more checking on 'c' */ + } + buf[i] = '\0'; + i = atoi(buf); + + if (i < 1 || i > ctl->maxTTL) + return ActionNone; + ctl->fstTTL = i; + + return ActionNone; + case 'm': + mvprintw(2, 0, "Max TTL: %d\n\n", ctl->maxTTL); + move(2, 9); + refresh(); + while ((c = getch()) != '\n' && i < MAXFLD) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* need more checking on 'c' */ + } + buf[i] = '\0'; + i = atoi(buf); + + if (i < ctl->fstTTL || i > (MaxHost - 1)) + return ActionNone; + ctl->maxTTL = i; + + return ActionNone; + /* fields to display & their ordering */ + case 'o': + mvprintw(2, 0, "Fields: %s\n\n", ctl->fld_active); + + for (i = 0; i < MAXFLD; i++) { + if (data_fields[i].descr != NULL) + printw(" %s\n", data_fields[i].descr); + } + printw("\n"); + move(2, 8); /* length of "Fields: " */ + refresh(); + + i = 0; + while ((c = getch()) != '\n' && i < MAXFLD) { + if (strchr(ctl->available_options, c)) { + attron(A_BOLD); + printw("%c", c); + attroff(A_BOLD); + refresh(); + buf[i++] = c; /* Only permit values in "available_options" be entered */ + } else { + printf("\a"); /* Illegal character. Beep, ring the bell. */ + } + } + buf[i] = '\0'; + if (strlen(buf) > 0) + xstrncpy(ctl->fld_active, buf, 2 * MAXFLD); + + return ActionNone; + case 'j': + if (strchr(ctl->fld_active, 'N')) + /* GeoMean and jitter */ + xstrncpy(ctl->fld_active, "DR AGJMXI", 2 * MAXFLD); + else + /* default */ + xstrncpy(ctl->fld_active, "LS NABWV", 2 * MAXFLD); + return ActionNone; + case 'u': + switch (ctl->mtrtype) { + case IPPROTO_ICMP: + case IPPROTO_TCP: + ctl->mtrtype = IPPROTO_UDP; + break; + case IPPROTO_UDP: + ctl->mtrtype = IPPROTO_ICMP; + break; + } + return ActionNone; + case 't': + switch (ctl->mtrtype) { + case IPPROTO_ICMP: + case IPPROTO_UDP: + ctl->mtrtype = IPPROTO_TCP; + break; + case IPPROTO_TCP: + ctl->mtrtype = IPPROTO_ICMP; + break; + } + return ActionNone; + /* reserve to display help message -Min */ + case '?': + case 'h': + mvprintw(2, 0, "Command:\n"); + printw(" ?|h help\n"); + printw(" p pause (SPACE to resume)\n"); + printw(" d switching display mode\n"); + printw(" e toggle MPLS information on/off\n"); + printw(" n toggle DNS on/off\n"); + printw(" r reset all counters\n"); + printw + (" o str set the columns to display, default str='LRS N BAWV'\n"); + printw + (" j toggle latency(LS NABWV)/jitter(DR AGJMXI) stats\n"); + printw(" c report cycle n, default n=infinite\n"); + printw + (" i set the ping interval to n seconds, default n=1\n"); + printw + (" f set the initial time-to-live(ttl), default n=1\n"); + printw + (" m set the max time-to-live, default n= # of hops\n"); + printw(" s set the packet size to n or random(n<0)\n"); + printw + (" b set ping bit pattern to c(0..255) or random(c<0)\n"); + printw(" Q set ping packet's TOS to t\n"); + printw(" u switch between ICMP ECHO and UDP datagrams\n"); +#ifdef HAVE_IPINFO + printw(" y switching IP info\n"); + printw(" z toggle ASN info on/off\n"); +#endif + printw("\n"); + printw(" press any key to go back..."); + getch(); /* read and ignore 'any key' */ + return ActionNone; + default: /* ignore unknown input */ + return ActionNone; + } +} + + +static void format_field( + char *dst, + int dst_length, + const char *format, + int n) +{ + if (index(format, 'N')) { + *dst++ = ' '; + format_number(n, 5, dst); + } else if (strchr(format, 'f')) { + /* this is for fields where we measure integer microseconds but + display floating point miliseconds. Convert to float here. */ + snprintf(dst, dst_length, format, n / 1000.0); + /* this was marked as a temporary hack over 10 years ago. -- REW */ + } else { + snprintf(dst, dst_length, format, n); + } +} + +static void mtr_curses_hosts( + struct mtr_ctl *ctl, + int startstat) +{ + int max; + int at; + struct mplslen *mpls, *mplss; + ip_t *addr, *addrs; + int y; + char *name; + + int i, j, k; + int hd_len; + char buf[1024]; + int __unused_int ATTRIBUTE_UNUSED; + + max = net_max(ctl); + + for (at = net_min(ctl) + ctl->display_offset; at < max; at++) { + printw("%2d. ", at + 1); + addr = net_addr(at); + mpls = net_mpls(at); + + if (addrcmp((void *) addr, (void *) &ctl->unspec_addr, ctl->af) != + 0) { + name = dns_lookup(ctl, addr); + if (!net_up(at)) + attron(A_BOLD); +#ifdef HAVE_IPINFO + if (is_printii(ctl)) + printw(fmt_ipinfo(ctl, addr)); +#endif + if (name != NULL) { + if (ctl->show_ips) + printw("%s (%s)", name, strlongip(ctl, addr)); + else + printw("%s", name); + } else { + printw("%s", strlongip(ctl, addr)); + } + attroff(A_BOLD); + + getyx(stdscr, y, __unused_int); + move(y, startstat); + + /* net_xxx returns times in usecs. Just display millisecs */ + hd_len = 0; + for (i = 0; i < MAXFLD; i++) { + /* Ignore options that don't exist */ + /* On the other hand, we now check the input side. Shouldn't happen, + can't be careful enough. */ + j = ctl->fld_index[ctl->fld_active[i]]; + if (j == -1) + continue; + format_field(buf + hd_len, sizeof(buf) - hd_len, + data_fields[j].format, + data_fields[j].net_xxx(at)); + hd_len += data_fields[j].length; + } + buf[hd_len] = 0; + printw("%s", buf); + + for (k = 0; k < mpls->labels && ctl->enablempls; k++) { + printw("\n [MPLS: Lbl %lu Exp %u S %u TTL %u]", + mpls->label[k], mpls->exp[k], mpls->s[k], + mpls->ttl[k]); + } + + /* Multi path */ + for (i = 0; i < MAXPATH; i++) { + addrs = net_addrs(at, i); + mplss = net_mplss(at, i); + if (addrcmp((void *) addrs, (void *) addr, ctl->af) == 0) + continue; + if (addrcmp + ((void *) addrs, (void *) &ctl->unspec_addr, + ctl->af) == 0) + break; + + name = dns_lookup(ctl, addrs); + if (!net_up(at)) + attron(A_BOLD); + printw("\n "); +#ifdef HAVE_IPINFO + if (is_printii(ctl)) + printw(fmt_ipinfo(ctl, addrs)); +#endif + if (name != NULL) { + if (ctl->show_ips) + printw("%s (%s)", name, strlongip(ctl, addrs)); + else + printw("%s", name); + } else { + printw("%s", strlongip(ctl, addrs)); + } + for (k = 0; k < mplss->labels && ctl->enablempls; k++) { + printw("\n [MPLS: Lbl %lu Exp %u S %u TTL %u]", + mplss->label[k], mplss->exp[k], mplss->s[k], + mplss->ttl[k]); + } + attroff(A_BOLD); + } + + } else { + printw("???"); + } + + printw("\n"); + } + move(2, 0); +} + +static void mtr_gen_scale( + struct mtr_ctl *ctl) +{ + int *saved, i, max, at; + int range; + static int low_ms, high_ms; + + low_ms = 1000000; + high_ms = -1; + + for (i = 0; i < NUM_FACTORS; i++) { + scale[i] = 0; + } + max = net_max(ctl); + for (at = ctl->display_offset; at < max; at++) { + saved = net_saved_pings(at); + for (i = 0; i < SAVED_PINGS; i++) { + if (saved[i] < 0) + continue; + if (saved[i] < low_ms) { + low_ms = saved[i]; + } + if (saved[i] > high_ms) { + high_ms = saved[i]; + } + } + } + range = high_ms - low_ms; + for (i = 0; i < NUM_FACTORS; i++) { + scale[i] = low_ms + ((double) range * factors[i]); + } +} + +static void mtr_curses_init( + void) +{ + int i; + int block_split; + + /* Initialize factors to a log scale. */ + for (i = 0; i < NUM_FACTORS; i++) { + factors[i] = ((double) 1 / NUM_FACTORS) * (i + 1); + factors[i] *= factors[i]; /* Squared. */ + } + + /* Initialize block_map. The block_split is always smaller than 9 */ + block_split = (NUM_FACTORS - 2) / 2; + for (i = 1; i <= block_split; i++) { + block_map[i] = '0' + i; + } + for (i = block_split + 1; i < NUM_FACTORS - 1; i++) { + block_map[i] = 'a' + i - block_split - 1; + } + block_map[0] = '.'; + block_map[NUM_FACTORS - 1] = '>'; +} + +static void mtr_print_scaled( + int ms) +{ + int i; + + for (i = 0; i < NUM_FACTORS; i++) { + if (ms <= scale[i]) { + attrset(block_col[i + 1]); + printw("%c", block_map[i]); + attrset(A_NORMAL); + return; + } + } + printw(">"); +} + + +static void mtr_fill_graph( + struct mtr_ctl *ctl, + int at, + int cols) +{ + int *saved; + int i; + + saved = net_saved_pings(at); + for (i = SAVED_PINGS - cols; i < SAVED_PINGS; i++) { + if (saved[i] == -2) { + printw(" "); + } else if (saved[i] == -1) { + attrset(block_col[0]); + printw("%c", '?'); + attrset(A_NORMAL); + } else { + if (ctl->display_mode == DisplayModeBlockmap) { + if (saved[i] > scale[6]) { + printw("%c", block_map[NUM_FACTORS - 1]); + } else { + printw("."); + } + } else { + mtr_print_scaled(saved[i]); + } + } + } +} + + +static void mtr_curses_graph( + struct mtr_ctl *ctl, + int startstat, + int cols) +{ + int max, at, y; + ip_t *addr; + char *name; + int __unused_int ATTRIBUTE_UNUSED; + + max = net_max(ctl); + + for (at = ctl->display_offset; at < max; at++) { + printw("%2d. ", at + 1); + + addr = net_addr(at); + if (!addr) { + printw("???\n"); + continue; + } + + if (!net_up(at)) + attron(A_BOLD); + if (addrcmp((void *) addr, (void *) &ctl->unspec_addr, ctl->af)) { +#ifdef HAVE_IPINFO + if (is_printii(ctl)) + printw(fmt_ipinfo(ctl, addr)); +#endif + name = dns_lookup(ctl, addr); + printw("%s", name ? name : strlongip(ctl, addr)); + } else + printw("???"); + attroff(A_BOLD); + + getyx(stdscr, y, __unused_int); + move(y, startstat); + + printw(" "); + mtr_fill_graph(ctl, at, cols); + printw("\n"); + } +} + + +void mtr_curses_redraw( + struct mtr_ctl *ctl) +{ + int maxx; + int startstat; + int rowstat; + time_t t; + int __unused_int ATTRIBUTE_UNUSED; + + int i, j; + int hd_len = 0; + char buf[1024]; + char fmt[16]; + + + erase(); + getmaxyx(stdscr, __unused_int, maxx); + + rowstat = 5; + + move(0, 0); + attron(A_BOLD); + snprintf(buf, sizeof(buf), "%s%s%s", "My traceroute [v", + PACKAGE_VERSION, "]"); + pwcenter(buf); + attroff(A_BOLD); + + mvprintw(1, 0, "%s (%s)", ctl->LocalHostname, net_localaddr()); + t = time(NULL); + mvprintw(1, maxx - 25, iso_time(&t)); + printw("\n"); + + printw("Keys: "); + attron(A_BOLD); + printw("H"); + attroff(A_BOLD); + printw("elp "); + attron(A_BOLD); + printw("D"); + attroff(A_BOLD); + printw("isplay mode "); + attron(A_BOLD); + printw("R"); + attroff(A_BOLD); + printw("estart statistics "); + attron(A_BOLD); + printw("O"); + attroff(A_BOLD); + printw("rder of fields "); + attron(A_BOLD); + printw("q"); + attroff(A_BOLD); + printw("uit\n"); + + if (ctl->display_mode == DisplayModeDefault) { + for (i = 0; i < MAXFLD; i++) { + j = ctl->fld_index[ctl->fld_active[i]]; + if (j < 0) + continue; + + snprintf(fmt, sizeof(fmt), "%%%ds", data_fields[j].length); + snprintf(buf + hd_len, sizeof(buf) - hd_len, fmt, + data_fields[j].title); + hd_len += data_fields[j].length; + } + attron(A_BOLD); + mvprintw(rowstat - 1, 0, " Host"); + mvprintw(rowstat - 1, maxx - hd_len - 1, "%s", buf); + mvprintw(rowstat - 2, maxx - hd_len - 1, + " Packets Pings"); + attroff(A_BOLD); + + move(rowstat, 0); + mtr_curses_hosts(ctl, maxx - hd_len - 1); + + } else { + char msg[80]; + int padding = 30; + int max_cols; + +#ifdef HAVE_IPINFO + if (is_printii(ctl)) + padding += get_iiwidth(ctl->ipinfo_no); +#endif + max_cols = + maxx <= SAVED_PINGS + padding ? maxx - padding : SAVED_PINGS; + startstat = padding - 2; + + snprintf(msg, sizeof(msg), " Last %3d pings", max_cols); + mvprintw(rowstat - 1, startstat, msg); + + attroff(A_BOLD); + move(rowstat, 0); + + mtr_gen_scale(ctl); + mtr_curses_graph(ctl, startstat, max_cols); + + printw("\n"); + attron(A_BOLD); + printw("Scale:"); + attroff(A_BOLD); + + for (i = 0; i < NUM_FACTORS - 1; i++) { + printw(" "); + attrset(block_col[i + 1]); + printw("%c", block_map[i]); + attrset(A_NORMAL); + printw(":%d ms", scale[i] / 1000); + } + printw(" "); + attrset(block_col[NUM_FACTORS]); + printw("%c", block_map[NUM_FACTORS - 1]); + attrset(A_NORMAL); + } + + refresh(); +} + + +void mtr_curses_open( + struct mtr_ctl *ctl) +{ + int bg_col = 0; + int i; + + initscr(); + raw(); + noecho(); + start_color(); + if (use_default_colors() == OK) + bg_col = -1; + for (i = 0; i < NUM_FACTORS; i++) + init_pair(i + 1, i, bg_col); + + mtr_curses_init(); + mtr_curses_redraw(ctl); +} + + +void mtr_curses_close( + void) +{ + printw("\n"); + endwin(); +} + + +void mtr_curses_clear( + struct mtr_ctl *ctl) +{ + mtr_curses_close(); + mtr_curses_open(ctl); +} diff --git a/ui/display.c b/ui/display.c new file mode 100644 index 0000000..4c08889 --- /dev/null +++ b/ui/display.c @@ -0,0 +1,252 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include + +#include "mtr.h" +#include "display.h" +#include "report.h" +#include "select.h" +#include "raw.h" +#include "dns.h" +#include "asn.h" + +#ifdef HAVE_CURSES +#include "mtr-curses.h" +#endif + +#ifdef HAVE_GTK +#include "mtr-gtk.h" +#endif + +#include "split.h" + +#ifdef HAVE_CURSES +#define DEFAULT_DISPLAY DisplayCurses +#else +#define DEFAULT_DISPLAY DisplayReport +#endif + +#ifdef HAVE_GTK +#define UNUSED_IF_NO_GTK /* empty */ +#else +#define UNUSED_IF_NO_GTK ATTRIBUTE_UNUSED +#endif + +void display_detect( + struct mtr_ctl *ctl, + int *argc UNUSED_IF_NO_GTK, + char ***argv UNUSED_IF_NO_GTK) +{ + ctl->DisplayMode = DEFAULT_DISPLAY; + +#ifdef HAVE_GTK + if (gtk_detect(argc, argv)) { + ctl->DisplayMode = DisplayGTK; + } +#endif +} + + +void display_open( + struct mtr_ctl *ctl) +{ + switch (ctl->DisplayMode) { + + case DisplayReport: + report_open(); + break; + case DisplayTXT: + txt_open(); + break; + case DisplayJSON: + json_open(); + break; + case DisplayXML: + xml_open(); + break; + case DisplayCSV: + csv_open(); + break; +#ifdef HAVE_CURSES + case DisplayCurses: + mtr_curses_open(ctl); +#ifdef HAVE_IPINFO + asn_open(ctl); +#endif + break; +#endif + case DisplaySplit: + split_open(); + break; +#ifdef HAVE_GTK + case DisplayGTK: + gtk_open(ctl); +#ifdef HAVE_IPINFO + asn_open(ctl); +#endif + break; +#endif + } +} + + +void display_close( + struct mtr_ctl *ctl) +{ + time_t now; + + now = time(NULL); + + switch (ctl->DisplayMode) { + case DisplayReport: + report_close(ctl); + break; + case DisplayTXT: + txt_close(ctl); + break; + case DisplayJSON: + json_close(ctl); + break; + case DisplayXML: + xml_close(ctl); + break; + case DisplayCSV: + csv_close(ctl, now); + break; +#ifdef HAVE_CURSES + case DisplayCurses: +#ifdef HAVE_IPINFO + asn_close(ctl); +#endif + mtr_curses_close(); + break; +#endif + case DisplaySplit: + split_close(); + break; +#ifdef HAVE_GTK + case DisplayGTK: + gtk_close(); + break; +#endif + } +} + + +void display_redraw( + struct mtr_ctl *ctl) +{ + switch (ctl->DisplayMode) { + +#ifdef HAVE_CURSES + case DisplayCurses: + mtr_curses_redraw(ctl); + break; +#endif + + case DisplaySplit: + split_redraw(ctl); + break; + +#ifdef HAVE_GTK + case DisplayGTK: + gtk_redraw(ctl); + break; +#endif + } +} + + +int display_keyaction( + struct mtr_ctl *ctl) +{ + switch (ctl->DisplayMode) { +#ifdef HAVE_CURSES + case DisplayCurses: + return mtr_curses_keyaction(ctl); +#endif + + case DisplaySplit: + return split_keyaction(); + +#ifdef HAVE_GTK + case DisplayGTK: + return gtk_keyaction(); +#endif + } + return 0; +} + + +void display_rawxmit( + struct mtr_ctl *ctl, + int host, + int seq) +{ + if (ctl->DisplayMode == DisplayRaw) + raw_rawxmit(host, seq); +} + + +void display_rawping( + struct mtr_ctl *ctl, + int host, + int msec, + int seq) +{ + if (ctl->DisplayMode == DisplayRaw) + raw_rawping(ctl, host, msec, seq); +} + + +void display_rawhost( + struct mtr_ctl *ctl, + int host, + ip_t * ip_addr) +{ + if (ctl->DisplayMode == DisplayRaw) + raw_rawhost(ctl, host, ip_addr); +} + + +void display_loop( + struct mtr_ctl *ctl) +{ +#ifdef HAVE_GTK + if (ctl->DisplayMode == DisplayGTK) + gtk_loop(ctl); + else +#endif + select_loop(ctl); +} + + +void display_clear( + struct mtr_ctl *ctl) +{ +#ifdef HAVE_CURSES + if (ctl->DisplayMode == DisplayCurses) + mtr_curses_clear(ctl); +#endif +} diff --git a/ui/display.h b/ui/display.h new file mode 100644 index 0000000..d827528 --- /dev/null +++ b/ui/display.h @@ -0,0 +1,83 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include + +/* Don't put a trailing comma in enumeration lists. Some compilers + (notably the one on Irix 5.2) do not like that. */ +enum { ActionNone, ActionQuit, ActionReset, ActionDisplay, + ActionClear, ActionPause, ActionResume, ActionMPLS, ActionDNS, +#ifdef HAVE_IPINFO + ActionII, ActionAS, +#endif + ActionScrollDown, ActionScrollUp +}; + +enum { + DisplayReport, +#ifdef HAVE_CURSES + DisplayCurses, +#endif +#ifdef HAVE_GTK + DisplayGTK, +#endif + DisplaySplit, + DisplayRaw, + DisplayXML, + DisplayCSV, + DisplayTXT, + DisplayJSON +}; + +enum { + DisplayModeDefault, + DisplayModeBlockmap, + DisplayModeBlockmapScale, + DisplayModeMAX /* this must be the last DisplayMode entry */ +}; + +/* Prototypes for display.c */ +extern void display_detect( + struct mtr_ctl *ctl, + int *argc, + char ***argv); +extern void display_open( + struct mtr_ctl *ctl); +extern void display_close( + struct mtr_ctl *ctl); +extern void display_redraw( + struct mtr_ctl *ctl); +extern void display_rawxmit( + struct mtr_ctl *ctl, + int hostnum, + int seq); +extern void display_rawping( + struct mtr_ctl *ctl, + int hostnum, + int msec, + int seq); +extern void display_rawhost( + struct mtr_ctl *ctl, + int hostnum, + ip_t * ip_addr); +extern int display_keyaction( + struct mtr_ctl *ctl); +extern void display_loop( + struct mtr_ctl *ctl); +extern void display_clear( + struct mtr_ctl *ctl); diff --git a/ui/dns.c b/ui/dns.c new file mode 100644 index 0000000..7d5ce1a --- /dev/null +++ b/ui/dns.c @@ -0,0 +1,327 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Non-blocking DNS portion -- + Copyright (C) 1998 by Simon Kirby + Released under GPL, as above. +*/ + +#include "config.h" + +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "mtr.h" +#include "dns.h" +#include "net.h" +#include "utils.h" + +struct dns_results { + ip_t ip; + char *name; + struct dns_results *next; +}; + +static struct dns_results *results; + +char *strlongip( + struct mtr_ctl *ctl, + ip_t * ip) +{ +#ifdef ENABLE_IPV6 + static char addrstr[INET6_ADDRSTRLEN]; + + return (char *) inet_ntop(ctl->af, ip, addrstr, sizeof addrstr); +#else + return inet_ntoa(*ip); +#endif +} + + +#ifdef ENABLE_IPV6 +#define UNUSED_IF_NO_IPV6 /* empty */ +#else +#define UNUSED_IF_NO_IPV6 ATTRIBUTE_UNUSED +#endif + +static int todns[2], fromdns[2]; +static FILE *fromdnsfp; + +static int longipstr( + char *s, + ip_t * dst, + int family UNUSED_IF_NO_IPV6) +{ +#ifdef ENABLE_IPV6 + return inet_pton(family, s, dst); +#else + return inet_aton(s, dst); +#endif +} + + +struct hostent *dns_forward( + const char *name) +{ + struct hostent *host; + + if ((host = gethostbyname(name))) + return host; + else + return NULL; +} + + +static struct dns_results *findip( + struct mtr_ctl *ctl, + ip_t * ip) +{ + struct dns_results *t; + + for (t = results; t; t = t->next) { + if (addrcmp((void *) ip, (void *) &t->ip, ctl->af) == 0) + return t; + } + + return NULL; +} + +static void set_sockaddr_ip( + struct mtr_ctl *ctl, + struct sockaddr_storage *sa, + ip_t * ip) +{ + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + + memset(sa, 0, sizeof(struct sockaddr_storage)); + switch (ctl->af) { + case AF_INET: + sa_in = (struct sockaddr_in *) sa; + sa_in->sin_family = ctl->af; + addrcpy((void *) &sa_in->sin_addr, (void *) ip, ctl->af); + break; + case AF_INET6: + sa_in6 = (struct sockaddr_in6 *) sa; + sa_in6->sin6_family = ctl->af; + addrcpy((void *) &sa_in6->sin6_addr, (void *) ip, ctl->af); + break; + } +} + +void dns_open( + struct mtr_ctl *ctl) +{ + int pid; + + if (pipe(todns) < 0) { + error(EXIT_FAILURE, errno, "can't make a pipe for DNS process"); + } + + if (pipe(fromdns) < 0) { + error(EXIT_FAILURE, errno, "can't make a pipe for DNS process"); + } + fflush(stdout); + pid = fork(); + if (pid < 0) { + error(EXIT_FAILURE, errno, "can't fork for DNS process"); + } + if (pid == 0) { + char buf[2048]; + int i; + FILE *infp; + + /* Automatically reap children. */ + if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) { + error(EXIT_FAILURE, errno, "signal"); + } + + /* Close all unneccessary FDs. + for debugging and error reporting, keep std-in/out/err. */ + for (i = 3; i < fromdns[1]; i++) { + if (i == todns[0]) + continue; + if (i == fromdns[1]) + continue; + close(i); + } + infp = fdopen(todns[0], "r"); + + while (fgets(buf, sizeof(buf), infp)) { + ip_t host; + struct sockaddr_storage sa; + socklen_t salen; + char hostname[NI_MAXHOST]; + char result[INET6_ADDRSTRLEN + NI_MAXHOST + 2]; + + if (!fork()) { + int rv; + + buf[strlen(buf) - 1] = 0; /* chomp newline. */ + + longipstr(buf, &host, ctl->af); + set_sockaddr_ip(ctl, &sa, &host); + salen = (ctl->af == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6); + + rv = getnameinfo((struct sockaddr *) &sa, salen, + hostname, sizeof(hostname), NULL, 0, 0); + if (rv == 0) { + snprintf(result, sizeof(result), + "%s %s\n", strlongip(ctl, &host), hostname); + + rv = write(fromdns[1], result, strlen(result)); + if (rv < 0) + error(0, errno, "write DNS lookup result"); + } + + exit(EXIT_SUCCESS); + } + } + exit(EXIT_SUCCESS); + } else { + int flags; + + /* the parent. */ + close(todns[0]); /* close the pipe ends we don't need. */ + close(fromdns[1]); + fromdnsfp = fdopen(fromdns[0], "r"); + flags = fcntl(fromdns[0], F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(fromdns[0], F_SETFL, flags); + } +} + +int dns_waitfd( + void) +{ + return fromdns[0]; +} + + +void dns_ack( + struct mtr_ctl *ctl) +{ + char buf[2048], host[NI_MAXHOST], name[NI_MAXHOST]; + ip_t hostip; + struct dns_results *r; + + while (fgets(buf, sizeof(buf), fromdnsfp)) { + sscanf(buf, "%s %s", host, name); + + longipstr(host, &hostip, ctl->af); + r = findip(ctl, &hostip); + if (r) + r->name = xstrdup(name); + else + error(0, 0, "dns_ack: Couldn't find host %s", host); + } +} + + + +#ifdef ENABLE_IPV6 + +int dns_waitfd6( + void) +{ + return -1; +} + +void dns_ack6( + void) +{ + return; +} + +#endif + + +char *dns_lookup2( + struct mtr_ctl *ctl, + ip_t * ip) +{ + struct dns_results *r; + char buf[INET6_ADDRSTRLEN + 1]; + int rv; + + r = findip(ctl, ip); + if (r) { + /* we've got a result. */ + if (r->name) + return r->name; + else + return strlongip(ctl, ip); + } else { + r = xmalloc(sizeof(struct dns_results)); + memcpy(&r->ip, ip, sizeof(r->ip)); + r->name = NULL; + r->next = results; + results = r; + snprintf(buf, sizeof(buf), "%s\n", strlongip(ctl, ip)); + rv = write(todns[1], buf, strlen(buf)); + if (rv < 0) + error(0, errno, "couldn't write to resolver process"); + } + return strlongip(ctl, ip); +} + + +char *dns_lookup( + struct mtr_ctl *ctl, + ip_t * ip) +{ + char *t; + + if (!ctl->dns || !ctl->use_dns) + return NULL; + t = dns_lookup2(ctl, ip); + return t; +} + +/* XXX check if necessary/exported. */ + +/* Resolve an IP address to a hostname. */ +struct hostent *addr2host( + const char *addr, + int family) +{ + int len = 0; + switch (family) { + case AF_INET: + len = sizeof(struct in_addr); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + len = sizeof(struct in6_addr); + break; +#endif + } + return gethostbyaddr(addr, len, family); +} diff --git a/ui/dns.h b/ui/dns.h new file mode 100644 index 0000000..f38c846 --- /dev/null +++ b/ui/dns.h @@ -0,0 +1,55 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include + +/* Prototypes for dns.c */ + +extern void dns_open( + struct mtr_ctl *ctl); +extern int dns_waitfd( + void); +extern void dns_ack( + struct mtr_ctl *ctl); +#ifdef ENABLE_IPV6 +extern int dns_waitfd6( + void); +extern void dns_ack6( + void); +#endif + +extern char *dns_lookup( + struct mtr_ctl *ctl, + ip_t * address); +extern char *dns_lookup2( + struct mtr_ctl *ctl, + ip_t * address); +extern struct hostent *dns_forward( + const char *name); +extern char *strlongip( + struct mtr_ctl *ctl, + ip_t * ip); + +extern void addr2ip6arpa( + ip_t * ip, + char *buf); +extern struct hostent *addr2host( + const char *addr, + int type); diff --git a/ui/gtk.c b/ui/gtk.c new file mode 100644 index 0000000..b05e869 --- /dev/null +++ b/ui/gtk.c @@ -0,0 +1,821 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + Changes/additions Copyright (C) 1998 R.E.Wolff@BitWizard.nl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_GTK +#include +#include +#include + +#include "mtr.h" +#include "net.h" +#include "dns.h" +#include "asn.h" +#include "mtr-gtk.h" +#include "utils.h" + +#include "img/mtr_icon.xpm" +#endif + +static gint gtk_ping( + gpointer data); +static gint Copy_activate( + GtkWidget * widget, + gpointer data); +static gint NewDestination_activate( + GtkWidget * widget, + gpointer data); +static gboolean ReportTreeView_clicked( + GtkWidget * Tree, + GdkEventButton * event, + gpointer data); +static gchar *getSelectedHost( + GtkTreePath * path); + +static int ping_timeout_timer; +static GtkWidget *Pause_Button; +static GtkWidget *Entry; +static GtkWidget *main_window; + +static void gtk_add_ping_timeout( + struct mtr_ctl *ctl) +{ + int dt; + + if (gtk_toggle_button_get_active((GtkToggleButton *) Pause_Button)) { + return; + } + dt = calc_deltatime(ctl->WaitTime); + gtk_redraw(ctl); + ping_timeout_timer = g_timeout_add(dt / 1000, gtk_ping, ctl); +} + + +static void gtk_do_init( + int *argc, + char ***argv) +{ + static int done = 0; + + if (!done) { + gtk_init(argc, argv); + + done = 1; + } +} + + +int gtk_detect( + ATTRIBUTE_UNUSED int *argc, + ATTRIBUTE_UNUSED char ***argv) +{ + if (getenv("DISPLAY") != NULL) { + /* If we do this here, gtk_init exits on an error. This happens + BEFORE the user has had a chance to tell us not to use the + display... */ + return TRUE; + } else { + return FALSE; + } +} + + +static gint Window_destroy( + ATTRIBUTE_UNUSED GtkWidget * Window, + ATTRIBUTE_UNUSED gpointer data) +{ + gtk_main_quit(); + + return FALSE; +} + + +static gint Restart_clicked( + ATTRIBUTE_UNUSED GtkWidget * Button, + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + net_reset(ctl); + gtk_redraw(ctl); + + return FALSE; +} + + +static gint Pause_clicked( + ATTRIBUTE_UNUSED GtkWidget * Button, + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + static int paused = 0; + + if (paused) { + gtk_add_ping_timeout(ctl); + } else { + g_source_remove(ping_timeout_timer); + } + paused = !paused; + gtk_redraw(ctl); + + return FALSE; +} + +static gint About_clicked( + ATTRIBUTE_UNUSED GtkWidget * Button, + ATTRIBUTE_UNUSED gpointer data) +{ + static const gchar *authors[] = { + "Matt Kimball ", + "Roger Wolff ", + "Bohdan Vlasyuk ", + "Evgeniy Tretyak ", + "John Thacker ", + "Juha Takala", + "David Sward ", + "David Stone ", + "Andrew Stesin", + "Greg Stark ", + "Robert Sparks ", + "Mike Simons ", + "Aaron Scarisbrick,", + "Craig Milo Rogers ", + "Antonio Querubin ", + "Russell Nelson ", + "Davin Milun ", + "Josh Martin ", + "Alexander V. Lukyanov ", + "Charles Levert ", + "Bertrand Leconte ", + "Anand Kumria", + "Olav Kvittem ", + "Adam Kramer ", + "Philip Kizer ", + "Simon Kirby", + "Sami Kerola ", + "Christophe Kalt", + "Steve Kann ", + "Brett Johnson ", + "Roland Illig ", + "Damian Gryski ", + "Rob Foehl ", + "Mircea Damian", + "Cougar ", + "Travis Cross ", + "Brian Casey", + "Andrew Brown ", + "Bill Bogstad ", + "Marc Bejarano ", + "Moritz Barsnick ", + "Thomas Klausner ", + NULL + }; + + gtk_show_about_dialog(GTK_WINDOW(main_window) + , "version", PACKAGE_VERSION, "copyright", + "Copyright \xc2\xa9 1997,1998 Matt Kimball", + "website", "http://www.bitwizard.nl/mtr/", + "authors", authors, "comments", + "The 'traceroute' and 'ping' programs in a single network diagnostic tool.", + "license", + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License version 2 as\n" + "published by the Free Software Foundation.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.", + NULL); + return TRUE; +} + +/* + * There is a small problem with the following code: + * The timeout is canceled and removed in order to ensure that + * it takes effect (consider what happens if you set the timeout to 999, + * then try to undo the change); is a better approach possible? + * + * What's the problem with this? (-> "I don't think so) + */ + +static gint WaitTime_changed( + ATTRIBUTE_UNUSED GtkAdjustment * Adj, + GtkWidget * data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + GtkWidget *Button = (GtkWidget *) ctl->gtk_data; + + ctl->WaitTime = gtk_spin_button_get_value(GTK_SPIN_BUTTON(Button)); + g_source_remove(ping_timeout_timer); + gtk_add_ping_timeout(ctl); + gtk_redraw(ctl); + + return FALSE; +} + + +static gint Host_activate( + GtkWidget * entry, + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + struct hostent *addr; + + addr = dns_forward(gtk_entry_get_text(GTK_ENTRY(entry))); + if (addr) { + net_reopen(ctl, addr); + /* If we are "Paused" at this point it is usually because someone + entered a non-existing host. Therefore do the go-ahead... */ + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(Pause_Button), 0); + } else { + int pos = strlen(gtk_entry_get_text(GTK_ENTRY(entry))); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(Pause_Button), 1); + gtk_editable_insert_text(GTK_EDITABLE(entry), ": not found", -1, + &pos); + } + + return FALSE; +} + + + +static void Toolbar_fill( + struct mtr_ctl *ctl, + GtkWidget * Toolbar) +{ + GtkWidget *Button; + GtkWidget *Label; + GtkAdjustment *Adjustment; + + Button = gtk_button_new_from_stock(GTK_STOCK_QUIT); + gtk_box_pack_end(GTK_BOX(Toolbar), Button, FALSE, FALSE, 0); + g_signal_connect(GTK_OBJECT(Button), "clicked", + GTK_SIGNAL_FUNC(Window_destroy), NULL); + + Button = gtk_button_new_from_stock(GTK_STOCK_ABOUT); + gtk_box_pack_end(GTK_BOX(Toolbar), Button, FALSE, FALSE, 0); + g_signal_connect(GTK_OBJECT(Button), "clicked", + GTK_SIGNAL_FUNC(About_clicked), NULL); + + Button = gtk_button_new_with_mnemonic("_Restart"); + gtk_box_pack_end(GTK_BOX(Toolbar), Button, FALSE, FALSE, 0); + g_signal_connect(GTK_OBJECT(Button), "clicked", + GTK_SIGNAL_FUNC(Restart_clicked), ctl); + + Pause_Button = gtk_toggle_button_new_with_mnemonic("_Pause"); + gtk_box_pack_end(GTK_BOX(Toolbar), Pause_Button, FALSE, FALSE, 0); + g_signal_connect(GTK_OBJECT(Pause_Button), "clicked", + GTK_SIGNAL_FUNC(Pause_clicked), ctl); + + /* allow root only to set zero delay */ + Adjustment = (GtkAdjustment *) gtk_adjustment_new(ctl->WaitTime, + getuid() == + 0 ? 0.01 : 1.00, + 999.99, 1.0, 10.0, + 0.0); + Button = gtk_spin_button_new(Adjustment, 0.5, 2); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(Button), TRUE); + gtk_box_pack_end(GTK_BOX(Toolbar), Button, FALSE, FALSE, 0); + ctl->gtk_data = Button; + g_signal_connect(GTK_OBJECT(Adjustment), "value_changed", + GTK_SIGNAL_FUNC(WaitTime_changed), ctl); + + Label = gtk_label_new_with_mnemonic("_Hostname:"); + gtk_box_pack_start(GTK_BOX(Toolbar), Label, FALSE, FALSE, 0); + + Entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(Entry), ctl->Hostname); + g_signal_connect(GTK_OBJECT(Entry), "activate", + GTK_SIGNAL_FUNC(Host_activate), ctl); + gtk_box_pack_start(GTK_BOX(Toolbar), Entry, TRUE, TRUE, 0); + + gtk_label_set_mnemonic_widget(GTK_LABEL(Label), Entry); +} + +static GtkWidget *ReportTreeView; +static GtkListStore *ReportStore; + +enum { +#ifdef HAVE_IPINFO + COL_ASN, +#endif + COL_HOSTNAME, + COL_LOSS, + COL_RCV, + COL_SNT, + COL_LAST, + COL_BEST, + COL_AVG, + COL_WORST, + COL_STDEV, + COL_COLOR, + N_COLS +}; + +/* Trick to cast a pointer to integer. We are mis-using a pointer as a + single integer. On 64-bit architectures, the pointer is 64 bits and the + integer only 32. The compiler warns us of loss of precision. However we + know we casted a normal 32-bit integer into this pointer a few + microseconds earlier, so it is ok. Nothing to worry about. */ +#define POINTER_TO_INT(p) ((int)(long)(p)) + +static void float_formatter( + GtkTreeViewColumn * tree_column ATTRIBUTE_UNUSED, + GtkCellRenderer * cell, + GtkTreeModel * tree_model, + GtkTreeIter * iter, + gpointer data) +{ + gfloat f; + gchar text[64]; + gtk_tree_model_get(tree_model, iter, POINTER_TO_INT(data), &f, -1); + sprintf(text, "%.2f", f); + g_object_set(cell, "text", text, NULL); +} + +static void percent_formatter( + GtkTreeViewColumn * tree_column ATTRIBUTE_UNUSED, + GtkCellRenderer * cell, + GtkTreeModel * tree_model, + GtkTreeIter * iter, + gpointer data) +{ + gfloat f; + gchar text[64]; + gtk_tree_model_get(tree_model, iter, POINTER_TO_INT(data), &f, -1); + sprintf(text, "%.1f%%", f); + g_object_set(cell, "text", text, NULL); +} + +static void TreeViewCreate( + struct mtr_ctl *ctl) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + ReportStore = gtk_list_store_new(N_COLS, +#ifdef HAVE_IPINFO + G_TYPE_STRING, +#endif + G_TYPE_STRING, + G_TYPE_FLOAT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_FLOAT, G_TYPE_STRING); + + ReportTreeView = + gtk_tree_view_new_with_model(GTK_TREE_MODEL(ReportStore)); + + g_signal_connect(GTK_OBJECT(ReportTreeView), "button_press_event", + G_CALLBACK(ReportTreeView_clicked), ctl); + +#ifdef HAVE_IPINFO + if (is_printii(ctl)) { + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes("ASN", + renderer, + "text", COL_ASN, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + } +#endif + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes("Hostname", + renderer, + "text", COL_HOSTNAME, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_expand(column, TRUE); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("Loss", + renderer, + "text", COL_LOSS, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_cell_data_func(column, renderer, + percent_formatter, + (void *) COL_LOSS, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("Snt", + renderer, + "text", COL_SNT, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("Last", + renderer, + "text", COL_LAST, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("Avg", + renderer, + "text", COL_AVG, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("Best", + renderer, + "text", COL_BEST, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("Worst", + renderer, + "text", COL_WORST, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL); + column = gtk_tree_view_column_new_with_attributes("StDev", + renderer, + "text", COL_STDEV, + "foreground", + COL_COLOR, NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_cell_data_func(column, renderer, + float_formatter, + (void *) COL_STDEV, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(ReportTreeView), column); + +} + +static void update_tree_row( + struct mtr_ctl *ctl, + int row, + GtkTreeIter * iter) +{ + ip_t *addr; + char str[256] = "???", *name = str; + + addr = net_addr(row); + if (addrcmp((void *) addr, (void *) &ctl->unspec_addr, ctl->af)) { + if ((name = dns_lookup(ctl, addr))) { + if (ctl->show_ips) { + snprintf(str, sizeof(str), "%s (%s)", name, + strlongip(ctl, addr)); + name = str; + } + } else + name = strlongip(ctl, addr); + } + + gtk_list_store_set(ReportStore, iter, + COL_HOSTNAME, name, + COL_LOSS, (float) (net_loss(row) / 1000.0), + COL_RCV, net_returned(row), + COL_SNT, net_xmit(row), + COL_LAST, net_last(row) / 1000, + COL_BEST, net_best(row) / 1000, + COL_AVG, net_avg(row) / 1000, + COL_WORST, net_worst(row) / 1000, + COL_STDEV, (float) (net_stdev(row) / 1000.0), + COL_COLOR, net_up(row) ? NULL : "red", -1); +#ifdef HAVE_IPINFO + if (is_printii(ctl)) + gtk_list_store_set(ReportStore, iter, COL_ASN, + fmt_ipinfo(ctl, addr), -1); +#endif +} + +void gtk_redraw( + struct mtr_ctl *ctl) +{ + int max = net_max(ctl); + + GtkTreeIter iter; + int row = net_min(ctl); + gboolean valid; + + valid = + gtk_tree_model_get_iter_first(GTK_TREE_MODEL(ReportStore), &iter); + + while (valid) { + if (row < max) { + update_tree_row(ctl, row++, &iter); + valid = + gtk_tree_model_iter_next(GTK_TREE_MODEL(ReportStore), + &iter); + } else { + valid = gtk_list_store_remove(ReportStore, &iter); + } + } + while (row < max) { + gtk_list_store_append(ReportStore, &iter); + update_tree_row(ctl, row++, &iter); + } +} + + +static void Window_fill( + struct mtr_ctl *ctl, + GtkWidget * Window) +{ + GtkWidget *VBox; + GtkWidget *Toolbar; + GtkWidget *scroll; + + gtk_window_set_title(GTK_WINDOW(Window), "My traceroute"); + gtk_window_set_default_size(GTK_WINDOW(Window), 650, 400); + gtk_container_set_border_width(GTK_CONTAINER(Window), 10); + VBox = gtk_vbox_new(FALSE, 10); + + Toolbar = gtk_hbox_new(FALSE, 10); + Toolbar_fill(ctl, Toolbar); + gtk_box_pack_start(GTK_BOX(VBox), Toolbar, FALSE, FALSE, 0); + + TreeViewCreate(ctl); + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scroll), ReportTreeView); + gtk_box_pack_start(GTK_BOX(VBox), scroll, TRUE, TRUE, 0); + + gtk_container_add(GTK_CONTAINER(Window), VBox); +} + + +void gtk_open( + struct mtr_ctl *ctl) +{ + GdkPixbuf *icon; + int argc = 1; + char *args[2]; + char **argv; + + argv = args; + argv[0] = xstrdup(""); + argv[1] = NULL; + gtk_do_init(&argc, &argv); + free(argv[0]); + + icon = gdk_pixbuf_new_from_xpm_data((const char **) mtr_icon); + gtk_window_set_default_icon(icon); + + main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + g_set_application_name("My traceroute"); + + Window_fill(ctl, main_window); + + g_signal_connect(GTK_OBJECT(main_window), "delete_event", + GTK_SIGNAL_FUNC(Window_destroy), NULL); + g_signal_connect(GTK_OBJECT(main_window), "destroy", + GTK_SIGNAL_FUNC(Window_destroy), NULL); + + gtk_widget_show_all(main_window); +} + + +void gtk_close( + void) +{ +} + + +int gtk_keyaction( + void) +{ + return 0; +} + + +static gint gtk_ping( + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + gtk_redraw(ctl); + net_send_batch(ctl); + net_harvest_fds(ctl); + g_source_remove(ping_timeout_timer); + gtk_add_ping_timeout(ctl); + return TRUE; +} + + +static gboolean gtk_net_data( + ATTRIBUTE_UNUSED GIOChannel * channel, + ATTRIBUTE_UNUSED GIOCondition cond, + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + net_process_return(ctl); + return TRUE; +} + + +static gboolean gtk_dns_data( + ATTRIBUTE_UNUSED GIOChannel * channel, + ATTRIBUTE_UNUSED GIOCondition cond, + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + dns_ack(ctl); + gtk_redraw(ctl); + return TRUE; +} + +#ifdef ENABLE_IPV6 +static gboolean gtk_dns_data6( + ATTRIBUTE_UNUSED GIOChannel * channel, + ATTRIBUTE_UNUSED GIOCondition cond, + gpointer data) +{ + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + dns_ack6(); + gtk_redraw(ctl); + return TRUE; +} +#endif + + +void gtk_loop( + struct mtr_ctl *ctl) +{ + GIOChannel *net_iochannel, *dns_iochannel; + + gtk_add_ping_timeout(ctl); + + net_iochannel = g_io_channel_unix_new(net_waitfd()); + g_io_add_watch(net_iochannel, G_IO_IN, gtk_net_data, ctl); +#ifdef ENABLE_IPV6 + if (dns_waitfd6() > 0) { + dns_iochannel = g_io_channel_unix_new(dns_waitfd6()); + g_io_add_watch(dns_iochannel, G_IO_IN, gtk_dns_data6, ctl); + } +#endif + dns_iochannel = g_io_channel_unix_new(dns_waitfd()); + g_io_add_watch(dns_iochannel, G_IO_IN, gtk_dns_data, ctl); + + gtk_main(); +} + +static gboolean NewDestination_activate( + GtkWidget * widget ATTRIBUTE_UNUSED, + gpointer data) +{ + gchar *hostname; + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + GtkTreePath *path = (GtkTreePath *) ctl->gtk_data; + + hostname = getSelectedHost(path); + if (hostname) { + ctl->gtk_data = hostname; + gtk_entry_set_text(GTK_ENTRY(Entry), hostname); + Host_activate(Entry, ctl); + g_free(hostname); + } + return TRUE; +} + + +static gboolean Copy_activate( + GtkWidget * widget ATTRIBUTE_UNUSED, + gpointer data) +{ + gchar *hostname; + GtkTreePath *path = (GtkTreePath *) data; + + hostname = getSelectedHost(path); + if (hostname != NULL) { + GtkClipboard *clipboard; + + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clipboard, hostname, -1); + + clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clipboard, hostname, -1); + + g_free(hostname); + } + + return TRUE; +} + +static gchar *getSelectedHost( + GtkTreePath * path) +{ + GtkTreeIter iter; + gchar *name = NULL; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(ReportStore), &iter, path)) { + gtk_tree_model_get(GTK_TREE_MODEL(ReportStore), &iter, + COL_HOSTNAME, &name, -1); + } + gtk_tree_path_free(path); + return name; +} + + +static gboolean ReportTreeView_clicked( + GtkWidget * Tree ATTRIBUTE_UNUSED, + GdkEventButton * event, + gpointer data) +{ + GtkWidget *popup_menu; + GtkWidget *copy_item; + GtkWidget *newdestination_item; + GtkTreePath *path; + struct mtr_ctl *ctl = (struct mtr_ctl *) data; + + if (event->type != GDK_BUTTON_PRESS || event->button != 3) + return FALSE; + + if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(ReportTreeView), + event->x, event->y, &path, NULL, + NULL, NULL)) + return FALSE; + + gtk_tree_view_set_cursor(GTK_TREE_VIEW(ReportTreeView), path, NULL, + FALSE); + + /* Single right click: prepare and show the popup menu */ + popup_menu = gtk_menu_new(); + + copy_item = gtk_menu_item_new_with_label("Copy to clipboard"); + newdestination_item = + gtk_menu_item_new_with_label("Set as new destination"); + + gtk_menu_append(GTK_MENU(popup_menu), copy_item); + gtk_menu_append(GTK_MENU(popup_menu), newdestination_item); + + g_signal_connect(GTK_OBJECT(copy_item), "activate", + GTK_SIGNAL_FUNC(Copy_activate), path); + + ctl->gtk_data = path; + g_signal_connect(GTK_OBJECT(newdestination_item), "activate", + GTK_SIGNAL_FUNC(NewDestination_activate), ctl); + + gtk_widget_show(copy_item); + gtk_widget_show(newdestination_item); + + gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, + 0, event->time); + return TRUE; +} diff --git a/ui/mtr-curses.h b/ui/mtr-curses.h new file mode 100644 index 0000000..71298cf --- /dev/null +++ b/ui/mtr-curses.h @@ -0,0 +1,29 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Prototypes for curses.c */ +extern void mtr_curses_open( + struct mtr_ctl *ctl); +extern void mtr_curses_close( + void); +extern void mtr_curses_redraw( + struct mtr_ctl *ctl); +extern int mtr_curses_keyaction( + struct mtr_ctl *ctl); +extern void mtr_curses_clear( + struct mtr_ctl *ctl); diff --git a/ui/mtr-gtk.h b/ui/mtr-gtk.h new file mode 100644 index 0000000..88fa3a1 --- /dev/null +++ b/ui/mtr-gtk.h @@ -0,0 +1,32 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Prototypes for gtk.c */ +extern int gtk_detect( + int *argc, + char ***argv); +extern void gtk_open( + struct mtr_ctl *ctl); +extern void gtk_close( + void); +extern void gtk_redraw( + struct mtr_ctl *ctl); +extern int gtk_keyaction( + void); +extern void gtk_loop( + struct mtr_ctl *ctl); diff --git a/ui/mtr.c b/ui/mtr.c new file mode 100644 index 0000000..70ae5c4 --- /dev/null +++ b/ui/mtr.c @@ -0,0 +1,853 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif +#ifdef HAVE_VALUES_H +#include +#endif +#ifdef HAVE_SYS_LIMITS_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtr.h" +#include "mtr-curses.h" +#include "display.h" +#include "dns.h" +#include "report.h" +#include "net.h" +#include "asn.h" +#include "utils.h" + +#ifdef HAVE_GETOPT +#include +#else +#include "portability/getopt.h" +#endif + +#ifdef ENABLE_IPV6 +#define DEFAULT_AF AF_UNSPEC +#else +#define DEFAULT_AF AF_INET +#endif + + +const struct fields data_fields[MAXFLD] = { + /* key, Remark, Header, Format, Width, CallBackFunc */ + {' ', ": Space between fields", " ", " ", 1, &net_drop}, + {'L', "L: Loss Ratio", "Loss%", " %4.1f%%", 6, &net_loss}, + {'D', "D: Dropped Packets", "Drop", " %4d", 5, &net_drop}, + {'R', "R: Received Packets", "Rcv", " %5d", 6, &net_returned}, + {'S', "S: Sent Packets", "Snt", " %5d", 6, &net_xmit}, + {'N', "N: Newest RTT(ms)", "Last", " %5.1f", 6, &net_last}, + {'B', "B: Min/Best RTT(ms)", "Best", " %5.1f", 6, &net_best}, + {'A', "A: Average RTT(ms)", "Avg", " %5.1f", 6, &net_avg}, + {'W', "W: Max/Worst RTT(ms)", "Wrst", " %5.1f", 6, &net_worst}, + {'V', "V: Standard Deviation", "StDev", " %5.1f", 6, &net_stdev}, + {'G', "G: Geometric Mean", "Gmean", " %5.1f", 6, &net_gmean}, + {'J', "J: Current Jitter", "Jttr", " %4.1f", 5, &net_jitter}, + {'M', "M: Jitter Mean/Avg.", "Javg", " %4.1f", 5, &net_javg}, + {'X', "X: Worst Jitter", "Jmax", " %4.1f", 5, &net_jworst}, + {'I', "I: Interarrival Jitter", "Jint", " %4.1f", 5, &net_jinta}, + {'\0', NULL, NULL, NULL, 0, NULL} +}; + +typedef struct names { + char *name; + struct names *next; +} names_t; + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + fputs("\nUsage:\n", out); + fputs(" mtr [options] hostname\n", out); + fputs("\n", out); + fputs(" -F, --filename FILE read hostname(s) from a file\n", + out); + fputs(" -4 use IPv4 only\n", out); +#ifdef ENABLE_IPV6 + fputs(" -6 use IPv6 only\n", out); +#endif + fputs(" -u, --udp use UDP instead of ICMP echo\n", + out); + fputs(" -T, --tcp use TCP instead of ICMP echo\n", + out); + fputs + (" -a, --address ADDRESS bind the outgoing socket to ADDRESS\n", + out); + fputs(" -f, --first-ttl NUMBER set what TTL to start\n", out); + fputs(" -m, --max-ttl NUMBER maximum number of hops\n", out); + fputs(" -U, --max-unknown NUMBER maximum unknown host\n", out); + fputs + (" -P, --port PORT target port number for TCP, SCTP, or UDP\n", + out); + fputs(" -L, --localport LOCALPORT source port number for UDP\n", out); + fputs + (" -s, --psize PACKETSIZE set the packet size used for probing\n", + out); + fputs + (" -B, --bitpattern NUMBER set bit pattern to use in payload\n", + out); + fputs(" -i, --interval SECONDS ICMP echo request interval\n", out); + fputs + (" -G, --gracetime SECONDS number of seconds to wait for responses\n", + out); + fputs + (" -Q, --tos NUMBER type of service field in IP header\n", + out); + fputs + (" -e, --mpls display information from ICMP extensions\n", + out); + fputs + (" -Z, --timeout SECONDS seconds to keep probe sockets open\n", + out); +#ifdef SO_MARK + fputs(" -M, --mark MARK mark each sent packet\n", out); +#endif + fputs(" -r, --report output using report mode\n", out); + fputs(" -w, --report-wide output wide report\n", out); + fputs(" -c, --report-cycles COUNT set the number of pings sent\n", + out); + fputs(" -j, --json output json\n", out); + fputs(" -x, --xml output xml\n", out); + fputs(" -C, --csv output comma separated values\n", + out); + fputs(" -l, --raw output raw format\n", out); + fputs(" -p, --split split output\n", out); +#ifdef HAVE_CURSES + fputs(" -t, --curses use curses terminal interface\n", + out); +#endif + fputs(" --displaymode MODE select initial display mode\n", + out); +#ifdef HAVE_GTK + fputs(" -g, --gtk use GTK+ xwindow interface\n", out); +#endif + fputs(" -n, --no-dns do not resove host names\n", out); + fputs(" -b, --show-ips show IP numbers and host names\n", + out); + fputs(" -o, --order FIELDS select output fields\n", out); +#ifdef HAVE_IPINFO + fputs(" -y, --ipinfo NUMBER select IP information in output\n", + out); + fputs(" -z, --aslookup display AS number\n", out); +#endif + fputs(" -h, --help display this help and exit\n", out); + fputs + (" -v, --version output version information and exit\n", + out); + fputs("\n", out); + fputs("See the 'man 8 mtr' for details.\n", out); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + + +static void append_to_names( + names_t ** names_head, + const char *item) +{ + names_t **name_tail = names_head; + + while (*name_tail) { + name_tail = &(*name_tail)->next; + } + + names_t *name = calloc(1, sizeof(names_t)); + if (name == NULL) { + error(EXIT_FAILURE, errno, "memory allocation failure"); + } + name->name = xstrdup(item); + name->next = NULL; + + *name_tail = name; +} + +static void read_from_file( + names_t ** names, + const char *filename) +{ + + FILE *in; + char line[512]; + + if (!filename || strcmp(filename, "-") == 0) { + clearerr(stdin); + in = stdin; + } else { + in = fopen(filename, "r"); + if (!in) { + error(EXIT_FAILURE, errno, "open %s", filename); + } + } + + while (fgets(line, sizeof(line), in)) { + char *name = trim(line, '\0'); + append_to_names(names, name); + } + + if (ferror(in)) { + error(EXIT_FAILURE, errno, "ferror %s", filename); + } + + if (in != stdin) + fclose(in); +} + +/* + * If the file stream is associated with a regular file, lock the file + * in order coordinate writes to a common file from multiple mtr + * instances. This is useful if, for example, multiple mtr instances + * try to append results to a common file. + */ + +static void lock( + FILE * f) +{ + int fd; + struct stat buf; + static struct flock lock; + + assert(f); + + lock.l_type = F_WRLCK; + lock.l_start = 0; + lock.l_whence = SEEK_END; + lock.l_len = 0; + lock.l_pid = getpid(); + + fd = fileno(f); + if ((fstat(fd, &buf) == 0) && S_ISREG(buf.st_mode)) { + if (fcntl(fd, F_SETLKW, &lock) == -1) { + error(0, errno, "fcntl (ignored)"); + } + } +} + +/* + * If the file stream is associated with a regular file, unlock the + * file (which presumably has previously been locked). + */ + +static void unlock( + FILE * f) +{ + int fd; + struct stat buf; + static struct flock lock; + + assert(f); + + lock.l_type = F_UNLCK; + lock.l_start = 0; + lock.l_whence = SEEK_END; + lock.l_len = 0; + lock.l_pid = getpid(); + + fd = fileno(f); + if ((fstat(fd, &buf) == 0) && S_ISREG(buf.st_mode)) { + if (fcntl(fd, F_SETLKW, &lock) == -1) { + error(0, errno, "fcntl (ignored)"); + } + } +} + + +static void init_fld_options( + struct mtr_ctl *ctl) +{ + int i; + + memset(ctl->fld_index, -1, FLD_INDEX_SZ); + + for (i = 0; data_fields[i].key != 0; i++) { + ctl->available_options[i] = data_fields[i].key; + ctl->fld_index[data_fields[i].key] = i; + } + ctl->available_options[i] = 0; +} + + +static void parse_arg( + struct mtr_ctl *ctl, + names_t ** names, + int argc, + char **argv) +{ + int opt; + int i; + /* IMPORTANT: when adding or modifying an option: + 0/ try to find a somewhat logical order; + 1/ add the long option name in "long_options" below; + 2/ update the man page (use the same order); + 3/ update the help message (see usage() function). + */ + enum { + OPT_DISPLAYMODE = CHAR_MAX + 1 + }; + static const struct option long_options[] = { + /* option name, has argument, NULL, short name */ + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'v'}, + + {"inet", 0, NULL, '4'}, /* IPv4 only */ +#ifdef ENABLE_IPV6 + {"inet6", 0, NULL, '6'}, /* IPv6 only */ +#endif + {"filename", 1, NULL, 'F'}, + + {"report", 0, NULL, 'r'}, + {"report-wide", 0, NULL, 'w'}, + {"xml", 0, NULL, 'x'}, +#ifdef HAVE_CURSES + {"curses", 0, NULL, 't'}, +#endif +#ifdef HAVE_GTK + {"gtk", 0, NULL, 'g'}, +#endif + {"raw", 0, NULL, 'l'}, + {"csv", 0, NULL, 'C'}, + {"json", 0, NULL, 'j'}, + {"displaymode", 1, NULL, OPT_DISPLAYMODE}, + {"split", 0, NULL, 'p'}, /* BL */ + /* maybe above should change to -d 'x' */ + + {"no-dns", 0, NULL, 'n'}, + {"show-ips", 0, NULL, 'b'}, + {"order", 1, NULL, 'o'}, /* fields to display & their order */ +#ifdef HAVE_IPINFO + {"ipinfo", 1, NULL, 'y'}, /* IP info lookup */ + {"aslookup", 0, NULL, 'z'}, /* Do AS lookup (--ipinfo 0) */ +#endif + + {"interval", 1, NULL, 'i'}, + {"report-cycles", 1, NULL, 'c'}, + {"psize", 1, NULL, 's'}, /* overload psize<0, ->rand(min,max) */ + {"bitpattern", 1, NULL, 'B'}, /* overload B>255, ->rand(0,255) */ + {"tos", 1, NULL, 'Q'}, /* typeof service (0,255) */ + {"mpls", 0, NULL, 'e'}, + {"address", 1, NULL, 'a'}, + {"first-ttl", 1, NULL, 'f'}, /* -f & -m are borrowed from traceroute */ + {"max-ttl", 1, NULL, 'm'}, + {"max-unknown", 1, NULL, 'U'}, + {"udp", 0, NULL, 'u'}, /* UDP (default is ICMP) */ + {"tcp", 0, NULL, 'T'}, /* TCP (default is ICMP) */ +#ifdef HAS_SCTP + {"sctp", 0, NULL, 'S'}, /* SCTP (default is ICMP) */ +#endif + {"port", 1, NULL, 'P'}, /* target port number for TCP/SCTP/UDP */ + {"localport", 1, NULL, 'L'}, /* source port number for UDP */ + {"timeout", 1, NULL, 'Z'}, /* timeout for probe sockets */ + {"gracetime", 1, NULL, 'G'}, /* gracetime for replies after last probe */ +#ifdef SO_MARK + {"mark", 1, NULL, 'M'}, /* use SO_MARK */ +#endif + {NULL, 0, NULL, 0} + }; + enum { num_options = sizeof(long_options) / sizeof(struct option) }; + char short_options[num_options * 2]; + size_t n, p; + + for (n = p = 0; n < num_options; n++) { + if (CHAR_MAX < long_options[n].val) { + continue; + } + short_options[p] = long_options[n].val; + p++; + if (long_options[n].has_arg == 1) { + short_options[p] = ':'; + p++; + } + /* optional options need two ':', but ignore them now as they are not in use */ + } + + opt = 0; + while (1) { + opt = getopt_long(argc, argv, short_options, long_options, NULL); + if (opt == -1) + break; + + switch (opt) { + case 'v': + printf("mtr " PACKAGE_VERSION "\n"); + exit(EXIT_SUCCESS); + break; + case 'h': + usage(stdout); + break; + + case 'r': + ctl->DisplayMode = DisplayReport; + break; + case 'w': + ctl->reportwide = 1; + ctl->DisplayMode = DisplayReport; + break; +#ifdef HAVE_CURSES + case 't': + ctl->DisplayMode = DisplayCurses; + break; +#endif +#ifdef HAVE_GTK + case 'g': + ctl->DisplayMode = DisplayGTK; + break; +#endif + case 'p': /* BL */ + ctl->DisplayMode = DisplaySplit; + break; + case 'l': + ctl->DisplayMode = DisplayRaw; + break; + case 'C': + ctl->DisplayMode = DisplayCSV; + break; + case 'j': + ctl->DisplayMode = DisplayJSON; + break; + case 'x': + ctl->DisplayMode = DisplayXML; + break; + + case OPT_DISPLAYMODE: + ctl->display_mode = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if ((DisplayModeMAX - 1) < ctl->display_mode) + error(EXIT_FAILURE, 0, "value out of range (%d - %d): %s", + DisplayModeDefault, (DisplayModeMAX - 1), optarg); + break; + case 'c': + ctl->MaxPing = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + ctl->ForceMaxPing = 1; + break; + case 's': + ctl->cpacketsize = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + break; + case 'a': + ctl->InterfaceAddress = optarg; + break; + case 'e': + ctl->enablempls = 1; + break; + case 'n': + ctl->dns = 0; + break; + case 'i': + ctl->WaitTime = strtofloat_or_err(optarg, "invalid argument"); + if (ctl->WaitTime <= 0.0) { + error(EXIT_FAILURE, 0, "wait time must be positive"); + } + if (getuid() != 0 && ctl->WaitTime < 1.0) { + error(EXIT_FAILURE, 0, + "non-root users cannot request an interval < 1.0 seconds"); + } + break; + case 'f': + ctl->fstTTL = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->fstTTL > ctl->maxTTL) { + ctl->fstTTL = ctl->maxTTL; + } + if (ctl->fstTTL < 1) { /* prevent 0 hop */ + ctl->fstTTL = 1; + } + break; + case 'F': + read_from_file(names, optarg); + break; + case 'm': + ctl->maxTTL = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->maxTTL > (MaxHost - 1)) { + ctl->maxTTL = MaxHost - 1; + } + if (ctl->maxTTL < 1) { /* prevent 0 hop */ + ctl->maxTTL = 1; + } + if (ctl->fstTTL > ctl->maxTTL) { /* don't know the pos of -m or -f */ + ctl->fstTTL = ctl->maxTTL; + } + break; + case 'U': + ctl->maxUnknown = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->maxUnknown < 1) { + ctl->maxUnknown = 1; + } + break; + case 'o': + /* Check option before passing it on to fld_active. */ + if (strlen(optarg) > MAXFLD) { + error(EXIT_FAILURE, 0, "Too many fields: %s", optarg); + } + for (i = 0; optarg[i]; i++) { + if (!strchr(ctl->available_options, optarg[i])) { + error(EXIT_FAILURE, 0, "Unknown field identifier: %c", + optarg[i]); + } + } + xstrncpy(ctl->fld_active, optarg, 2 * MAXFLD); + break; + case 'B': + ctl->bitpattern = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->bitpattern > 255) + ctl->bitpattern = -1; + break; + case 'G': + ctl->GraceTime = strtofloat_or_err(optarg, "invalid argument"); + if (ctl->GraceTime <= 0.0) { + error(EXIT_FAILURE, 0, "wait time must be positive"); + } + break; + case 'Q': + ctl->tos = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->tos > 255 || ctl->tos < 0) { + /* error message, should do more checking for valid values, + * details in rfc2474 */ + ctl->tos = 0; + } + break; + case 'u': + if (ctl->mtrtype != IPPROTO_ICMP) { + error(EXIT_FAILURE, 0, + "-u , -T and -S are mutually exclusive"); + } + ctl->mtrtype = IPPROTO_UDP; + break; + case 'T': + if (ctl->mtrtype != IPPROTO_ICMP) { + error(EXIT_FAILURE, 0, + "-u , -T and -S are mutually exclusive"); + } + if (!ctl->remoteport) { + ctl->remoteport = 80; + } + ctl->mtrtype = IPPROTO_TCP; + break; +#ifdef HAS_SCTP + case 'S': + if (ctl->mtrtype != IPPROTO_ICMP) { + error(EXIT_FAILURE, 0, + "-u , -T and -S are mutually exclusive"); + } + if (!ctl->remoteport) { + ctl->remoteport = 80; + } + ctl->mtrtype = IPPROTO_SCTP; + break; +#endif + case 'b': + ctl->show_ips = 1; + break; + case 'P': + ctl->remoteport = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->remoteport < 1 || MaxPort < ctl->remoteport) { + error(EXIT_FAILURE, 0, "Illegal port number: %d", + ctl->remoteport); + } + break; + case 'L': + ctl->localport = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->localport < MinPort || MaxPort < ctl->localport) { + error(EXIT_FAILURE, 0, "Illegal port number: %d", + ctl->localport); + } + break; + case 'Z': + ctl->probe_timeout = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + ctl->probe_timeout *= 1000000; + break; + case '4': + ctl->af = AF_INET; + break; +#ifdef ENABLE_IPV6 + case '6': + ctl->af = AF_INET6; + break; +#endif +#ifdef HAVE_IPINFO + case 'y': + ctl->ipinfo_no = + strtonum_or_err(optarg, "invalid argument", STRTO_INT); + if (ctl->ipinfo_no < 0 || 4 < ctl->ipinfo_no) { + error(EXIT_FAILURE, 0, "value %d out of range (0 - 4)", + ctl->ipinfo_no); + } + break; + case 'z': + ctl->ipinfo_no = 0; + break; +#endif +#ifdef SO_MARK + case 'M': + ctl->mark = + strtonum_or_err(optarg, "invalid argument", STRTO_U32INT); + break; +#endif + default: + usage(stderr); + } + } + + if (ctl->DisplayMode == DisplayReport || + ctl->DisplayMode == DisplayTXT || + ctl->DisplayMode == DisplayJSON || + ctl->DisplayMode == DisplayXML || + ctl->DisplayMode == DisplayRaw || ctl->DisplayMode == DisplayCSV) + ctl->Interactive = 0; + + if (optind > argc - 1) + return; + +} + + +static void parse_mtr_options( + struct mtr_ctl *ctl, + names_t ** names, + char *string) +{ + int argc = 1; + char *argv[128], *p; + + if (!string) + return; + argv[0] = xstrdup(PACKAGE_NAME); + argc = 1; + p = strtok(string, " \t"); + while (p != NULL && ((size_t) argc < (sizeof(argv) / sizeof(argv[0])))) { + argv[argc++] = p; + p = strtok(NULL, " \t"); + } + if (p != NULL) { + error(0, 0, "Warning: extra arguments ignored: %s", p); + } + + parse_arg(ctl, names, argc, argv); + free(argv[0]); + optind = 0; +} + +static void init_rand( + void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); +} + +int main( + int argc, + char **argv) +{ + struct hostent *host = NULL; + struct addrinfo hints, *res; + int gai_error; + struct hostent trhost; + char *alptr[2]; + struct sockaddr_in *sa4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *sa6; +#endif + names_t *names_head = NULL; + names_t *names_walk; + + struct mtr_ctl ctl; + memset(&ctl, 0, sizeof(ctl)); + /* initialize non-null values */ + ctl.Interactive = 1; + ctl.MaxPing = 10; + ctl.WaitTime = 1.0; + ctl.GraceTime = 5.0; + ctl.dns = 1; + ctl.use_dns = 1; + ctl.cpacketsize = 64; + ctl.af = DEFAULT_AF; + ctl.mtrtype = IPPROTO_ICMP; + ctl.fstTTL = 1; + ctl.maxTTL = 30; + ctl.maxUnknown = 12; + ctl.probe_timeout = 10 * 1000000; + ctl.ipinfo_no = -1; + ctl.ipinfo_max = -1; + xstrncpy(ctl.fld_active, "LS NABWV", 2 * MAXFLD); + + /* + mtr used to be suid root. It should not be with this version. + We'll check so that we can notify people using installation + mechanisms with obsolete assumptions. + */ + if ((geteuid() != getuid()) || (getegid() != getgid())) { + error(EXIT_FAILURE, errno, "mtr should not run suid"); + } + + /* This will check if stdout/stderr writing is successful */ + atexit(close_stdout); + + /* reset the random seed */ + init_rand(); + + display_detect(&ctl, &argc, &argv); + ctl.display_mode = DisplayModeDefault; + + /* The field options are now in a static array all together, + but that requires a run-time initialization. */ + init_fld_options(&ctl); + + parse_mtr_options(&ctl, &names_head, getenv("MTR_OPTIONS")); + + parse_arg(&ctl, &names_head, argc, argv); + + while (optind < argc) { + char *name = argv[optind++]; + append_to_names(&names_head, name); + } + + /* default: localhost. */ + if (!names_head) + append_to_names(&names_head, "localhost"); + + names_walk = names_head; + while (names_walk != NULL) { + + ctl.Hostname = names_walk->name; + if (gethostname(ctl.LocalHostname, sizeof(ctl.LocalHostname))) { + xstrncpy(ctl.LocalHostname, "UNKNOWNHOST", + sizeof(ctl.LocalHostname)); + } + + /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */ + memset(&hints, 0, sizeof hints); + hints.ai_family = ctl.af; + hints.ai_socktype = SOCK_DGRAM; + gai_error = getaddrinfo(ctl.Hostname, NULL, &hints, &res); + if (gai_error) { + if (gai_error == EAI_SYSTEM) + error(0, 0, "Failed to resolve host: %s", ctl.Hostname); + else + error(0, 0, "Failed to resolve host: %s: %s", ctl.Hostname, + gai_strerror(gai_error)); + + if (ctl.Interactive) + exit(EXIT_FAILURE); + else { + names_walk = names_walk->next; + continue; + } + } + /* Convert the first addrinfo into a hostent. */ + host = &trhost; + memset(host, 0, sizeof trhost); + host->h_name = res->ai_canonname; + host->h_aliases = NULL; + host->h_addrtype = res->ai_family; + ctl.af = res->ai_family; + host->h_length = res->ai_addrlen; + host->h_addr_list = alptr; + switch (ctl.af) { + case AF_INET: + sa4 = (struct sockaddr_in *) res->ai_addr; + alptr[0] = (void *) &(sa4->sin_addr); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sa6 = (struct sockaddr_in6 *) res->ai_addr; + alptr[0] = (void *) &(sa6->sin6_addr); + break; +#endif + default: + error(0, 0, "unknown address type"); + if (ctl.Interactive) + exit(EXIT_FAILURE); + else { + names_walk = names_walk->next; + continue; + } + } + alptr[1] = NULL; + + if (net_open(&ctl, host) != 0) { + error(0, 0, "Unable to start net module"); + if (ctl.Interactive) + exit(EXIT_FAILURE); + else { + names_walk = names_walk->next; + continue; + } + } + + lock(stdout); + dns_open(&ctl); + display_open(&ctl); + + display_loop(&ctl); + + net_end_transit(); + display_close(&ctl); + unlock(stdout); + + if (ctl.Interactive) + break; + else + names_walk = names_walk->next; + + } + + net_close(); + + while (names_head != NULL) { + names_t *item = names_head; + free(item->name); + item->name = NULL; + names_head = item->next; + free(item); + item = NULL; + } + + return 0; +} diff --git a/ui/mtr.h b/ui/mtr.h new file mode 100644 index 0000000..7ede21d --- /dev/null +++ b/ui/mtr.h @@ -0,0 +1,141 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + Copyright (C) 2005 R.E.Wolff@BitWizard.nl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef MTR_MTR_H +#define MTR_MTR_H + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* Typedefs */ +#ifdef ENABLE_IPV6 +typedef struct in6_addr ip_t; +#else +typedef struct in_addr ip_t; +#endif + +#ifndef HAVE_TIME_T +typedef int time_t; +#endif + +/* The __unused__ attribute was added in gcc 3.2.7. */ +#if __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) +#define ATTRIBUTE_UNUSED __attribute__((__unused__)) +#else +#define ATTRIBUTE_UNUSED /* empty */ +#endif + +/* The __const__ attribute was added in gcc 2.95. */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define ATTRIBUTE_CONST __attribute__ ((__const__)) +#else +#define ATTRIBUTE_CONST /* empty */ +#endif + +/* stuff used by display such as report, curses... */ +#define MAXFLD 20 /* max stats fields to display */ +#define FLD_INDEX_SZ 256 + +/* net related definitions */ +#define SAVED_PINGS 200 +#define MAXPATH 8 +#define MaxHost 256 +#define MinPort 1024 +#define MaxPort 65535 +#define MAXPACKET 4470 /* largest test packet size */ +#define MINPACKET 28 /* 20 bytes IP header and 8 bytes ICMP or UDP */ +#define MAXLABELS 8 /* http://kb.juniper.net/KB2190 (+ 3 just in case) */ + +/* Stream Control Transmission Protocol is defined in netinet/in.h */ +#ifdef IPPROTO_SCTP +#define HAS_SCTP 1 +#endif + +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +struct mtr_ctl { + int MaxPing; + float WaitTime; + float GraceTime; + char *Hostname; + char *InterfaceAddress; + char LocalHostname[128]; + int ipinfo_no; + int ipinfo_max; + int cpacketsize; /* packet size used by ping */ + int bitpattern; /* packet bit pattern used by ping */ + int tos; /* type of service set in ping packet */ +#ifdef SO_MARK + uint32_t mark; +#endif + ip_t unspec_addr; + int af; /* address family of remote target */ + int mtrtype; /* type of query packet used */ + int fstTTL; /* initial hub(ttl) to ping byMin */ + int maxTTL; /* last hub to ping byMin */ + int maxUnknown; /* stop ping threshold */ + int remoteport; /* target port for TCP tracing */ + int localport; /* source port for UDP tracing */ + int probe_timeout; /* timeout for probe sockets */ + unsigned char fld_active[2 * MAXFLD]; /* SO_MARK to set for ping packet */ + int display_mode; /* display mode selector */ + int fld_index[FLD_INDEX_SZ]; /* default display field (defined by key in net.h) and order */ + char available_options[MAXFLD]; + int display_offset; /* only used in text mode */ + void *gtk_data; /* pointer to hold arbitrary gtk data */ + unsigned int /* bit field to hold named booleans */ + ForceMaxPing:1, + use_dns:1, + show_ips:1, + enablempls:1, dns:1, reportwide:1, Interactive:1, DisplayMode:5; +}; + +/* dynamic field drawing */ +struct fields { + const unsigned char key; + const char *descr; + const char *title; + const char *format; + const int length; + int ( + *net_xxx) ( + int); +}; +/* defined in mtr.c */ +extern const struct fields data_fields[MAXFLD]; + +/* MPLS label object */ +struct mplslen { + unsigned long label[MAXLABELS]; /* label value */ + uint8_t exp[MAXLABELS]; /* experimental bits */ + uint8_t ttl[MAXLABELS]; /* MPLS TTL */ + char s[MAXLABELS]; /* bottom of stack */ + char labels; /* how many labels did we get? */ +}; + +#endif /* MTR_MTR_H */ diff --git a/ui/net.c b/ui/net.c new file mode 100644 index 0000000..3a7abc8 --- /dev/null +++ b/ui/net.c @@ -0,0 +1,881 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif + +#include "mtr.h" +#include "cmdpipe.h" +#include "net.h" +#include "display.h" +#include "dns.h" +#include "utils.h" + +#define MinSequence 33000 +#define MaxSequence 65536 + +static int packetsize; /* packet size used by ping */ + +static void sockaddrtop( + struct sockaddr *saddr, + char *strptr, + size_t len); + +struct nethost { + ip_t addr; + ip_t addrs[MAXPATH]; /* for multi paths byMin */ + int xmit; + int returned; + int sent; + int up; + long long ssd; /* sum of squares of differences from the current average */ + int last; + int best; + int worst; + int avg; /* average: addByMin */ + int gmean; /* geometric mean: addByMin */ + int jitter; /* current jitter, defined as t1-t0 addByMin */ + int javg; /* avg jitter */ + int jworst; /* max jitter */ + int jinta; /* estimated variance,? rfc1889's "Interarrival Jitter" */ + int transit; + int saved[SAVED_PINGS]; + int saved_seq_offset; + struct mplslen mpls; + struct mplslen mplss[MAXPATH]; +}; + + +struct sequence { + int index; + int transit; + int saved_seq; + struct timeval time; +}; + + +static struct nethost host[MaxHost]; +static struct sequence sequence[MaxSequence]; +static struct packet_command_pipe_t packet_command_pipe; + +#ifdef ENABLE_IPV6 +static struct sockaddr_storage sourcesockaddr_struct; +static struct sockaddr_storage remotesockaddr_struct; +static struct sockaddr_in6 *ssa6 = + (struct sockaddr_in6 *) &sourcesockaddr_struct; +static struct sockaddr_in6 *rsa6 = + (struct sockaddr_in6 *) &remotesockaddr_struct; +#else +static struct sockaddr_in sourcesockaddr_struct; +static struct sockaddr_in remotesockaddr_struct; +#endif + +static struct sockaddr *sourcesockaddr = + (struct sockaddr *) &sourcesockaddr_struct; +static struct sockaddr *remotesockaddr = + (struct sockaddr *) &remotesockaddr_struct; +static struct sockaddr_in *ssa4 = + (struct sockaddr_in *) &sourcesockaddr_struct; +static struct sockaddr_in *rsa4 = + (struct sockaddr_in *) &remotesockaddr_struct; + +static ip_t *sourceaddress; +static ip_t *remoteaddress; + +#ifdef ENABLE_IPV6 +static char localaddr[INET6_ADDRSTRLEN]; +#else +#ifndef INET_ADDRSTRLEN +#define INET_ADDRSTRLEN 16 +#endif +static char localaddr[INET_ADDRSTRLEN]; +#endif + +static int batch_at = 0; +static int numhosts = 10; + +/* return the number of microseconds to wait before sending the next + ping */ +int calc_deltatime( + float waittime) +{ + waittime /= numhosts; + return 1000000 * waittime; +} + + +static void save_sequence( + struct mtr_ctl *ctl, + int index, + int seq) +{ + display_rawxmit(ctl, index, seq); + + sequence[seq].index = index; + sequence[seq].transit = 1; + sequence[seq].saved_seq = ++host[index].xmit; + memset(&sequence[seq].time, 0, sizeof(sequence[seq].time)); + + host[index].transit = 1; + + if (host[index].sent) { + host[index].up = 0; + } + + host[index].sent = 1; + net_save_xmit(index); +} + +static int new_sequence( + struct mtr_ctl *ctl, + int index) +{ + static int next_sequence = MinSequence; + int seq; + + seq = next_sequence++; + if (next_sequence >= MaxSequence) { + next_sequence = MinSequence; + } + + save_sequence(ctl, index, seq); + + return seq; +} + + +/* Attempt to find the host at a particular number of hops away */ +static void net_send_query( + struct mtr_ctl *ctl, + int index, + int packet_size) +{ + int seq = new_sequence(ctl, index); + int time_to_live = index + 1; + + send_probe_command(ctl, &packet_command_pipe, remoteaddress, + sourceaddress, packetsize, seq, time_to_live); +} + + +/* We got a return on something we sent out. Record the address and + time. */ +static void net_process_ping( + struct mtr_ctl *ctl, + int seq, + struct mplslen *mpls, + ip_t * addr, + int totusec) +{ + int index; + int oldavg; /* usedByMin */ + int oldjavg; /* usedByMin */ + int i; /* usedByMin */ +#ifdef ENABLE_IPV6 + char addrcopy[sizeof(struct in6_addr)]; +#else + char addrcopy[sizeof(struct in_addr)]; +#endif + + addrcpy((void *) &addrcopy, (char *) addr, ctl->af); + + if ((seq < 0) || (seq >= MaxSequence)) { + return; + } + + if (!sequence[seq].transit) { + return; + } + sequence[seq].transit = 0; + + index = sequence[seq].index; + + if (addrcmp((void *) &(host[index].addr), + (void *) &ctl->unspec_addr, ctl->af) == 0) { + /* should be out of if as addr can change */ + addrcpy((void *) &(host[index].addr), addrcopy, ctl->af); + host[index].mpls = *mpls; + display_rawhost(ctl, index, (void *) &(host[index].addr)); + + /* multi paths */ + addrcpy((void *) &(host[index].addrs[0]), addrcopy, ctl->af); + host[index].mplss[0] = *mpls; + } else { + for (i = 0; i < MAXPATH;) { + if (addrcmp + ((void *) &(host[index].addrs[i]), (void *) &addrcopy, + ctl->af) == 0 + || addrcmp((void *) &(host[index].addrs[i]), + (void *) &ctl->unspec_addr, ctl->af) == 0) { + break; + } + i++; + } + + if (addrcmp((void *) &(host[index].addrs[i]), addrcopy, ctl->af) != + 0 && i < MAXPATH) { + addrcpy((void *) &(host[index].addrs[i]), addrcopy, ctl->af); + host[index].mplss[i] = *mpls; + display_rawhost(ctl, index, (void *) &(host[index].addrs[i])); + } + } + + host[index].jitter = totusec - host[index].last; + if (host[index].jitter < 0) { + host[index].jitter = -host[index].jitter; + } + + host[index].last = totusec; + + if (host[index].returned < 1) { + host[index].best = host[index].worst = host[index].gmean = totusec; + host[index].avg = host[index].ssd = 0; + + host[index].jitter = host[index].jworst = host[index].jinta = 0; + } + + if (totusec < host[index].best) { + host[index].best = totusec; + } + if (totusec > host[index].worst) { + host[index].worst = totusec; + } + + if (host[index].jitter > host[index].jworst) { + host[index].jworst = host[index].jitter; + } + + host[index].returned++; + oldavg = host[index].avg; + host[index].avg += (totusec - oldavg + .0) / host[index].returned; + host[index].ssd += + (totusec - oldavg + .0) * (totusec - host[index].avg); + + oldjavg = host[index].javg; + host[index].javg += + (host[index].jitter - oldjavg) / host[index].returned; + /* below algorithm is from rfc1889, A.8 */ + host[index].jinta += + host[index].jitter - ((host[index].jinta + 8) >> 4); + + if (host[index].returned > 1) { + host[index].gmean = + pow((double) host[index].gmean, + (host[index].returned - 1.0) / host[index].returned) + * pow((double) totusec, 1.0 / host[index].returned); + } + + host[index].sent = 0; + host[index].up = 1; + host[index].transit = 0; + + net_save_return(index, sequence[seq].saved_seq, totusec); + display_rawping(ctl, index, totusec, seq); +} + +/* + Invoked when the read pipe from the mtr-packet subprocess is readable. + If we have received a complete reply, process it. +*/ +void net_process_return( + struct mtr_ctl *ctl) +{ + handle_command_replies(ctl, &packet_command_pipe, net_process_ping); +} + + +ip_t *net_addr( + int at) +{ + return (ip_t *) & (host[at].addr); +} + + +ip_t *net_addrs( + int at, + int i) +{ + return (ip_t *) & (host[at].addrs[i]); +} + +void *net_mpls( + int at) +{ + return (struct mplslen *) &(host[at].mplss); +} + +void *net_mplss( + int at, + int i) +{ + return (struct mplslen *) &(host[at].mplss[i]); +} + +int net_loss( + int at) +{ + if ((host[at].xmit - host[at].transit) == 0) { + return 0; + } + + /* times extra 1000 */ + return 1000 * (100 - + (100.0 * host[at].returned / + (host[at].xmit - host[at].transit))); +} + + +int net_drop( + int at) +{ + return (host[at].xmit - host[at].transit) - host[at].returned; +} + + +int net_last( + int at) +{ + return (host[at].last); +} + + +int net_best( + int at) +{ + return (host[at].best); +} + + +int net_worst( + int at) +{ + return (host[at].worst); +} + + +int net_avg( + int at) +{ + return (host[at].avg); +} + + +int net_gmean( + int at) +{ + return (host[at].gmean); +} + + +int net_stdev( + int at) +{ + if (host[at].returned > 1) { + return (sqrt(host[at].ssd / (host[at].returned - 1.0))); + } else { + return (0); + } +} + + +int net_jitter( + int at) +{ + return (host[at].jitter); +} + + +int net_jworst( + int at) +{ + return (host[at].jworst); +} + + +int net_javg( + int at) +{ + return (host[at].javg); +} + + +int net_jinta( + int at) +{ + return (host[at].jinta); +} + + +int net_max( + struct mtr_ctl *ctl) +{ + int at; + int max; + + max = 0; + for (at = 0; at < ctl->maxTTL - 1; at++) { + if (addrcmp((void *) &(host[at].addr), + (void *) remoteaddress, ctl->af) == 0) { + return at + 1; + } else if (addrcmp((void *) &(host[at].addr), + (void *) &ctl->unspec_addr, ctl->af) != 0) { + max = at + 2; + } + } + + return max; +} + + +int net_min( + struct mtr_ctl *ctl) +{ + return (ctl->fstTTL - 1); +} + + +int net_returned( + int at) +{ + return host[at].returned; +} + + +int net_xmit( + int at) +{ + return host[at].xmit; +} + + +int net_up( + int at) +{ + return host[at].up; +} + + +char *net_localaddr( + void) +{ + return localaddr; +} + + +void net_end_transit( + void) +{ + int at; + + for (at = 0; at < MaxHost; at++) { + host[at].transit = 0; + } +} + +int net_send_batch( + struct mtr_ctl *ctl) +{ + int n_unknown = 0, i; + + /* randomized packet size and/or bit pattern if packetsize<0 and/or + bitpattern<0. abs(packetsize) and/or abs(bitpattern) will be used + */ + if (batch_at < ctl->fstTTL) { + if (ctl->cpacketsize < 0) { + /* Someone used a formula here that tried to correct for the + "end-error" in "rand()". By "end-error" I mean that if you + have a range for "rand()" that runs to 32768, and the + destination range is 10000, you end up with 4 out of 32768 + 0-2768's and only 3 out of 32768 for results 2769 .. 9999. + As our detination range (in the example 10000) is much + smaller (reasonable packet sizes), and our rand() range much + larger, this effect is insignificant. Oh! That other formula + didn't work. */ + packetsize = + MINPACKET + rand() % (-ctl->cpacketsize - MINPACKET); + } else { + packetsize = ctl->cpacketsize; + } + if (ctl->bitpattern < 0) { + ctl->bitpattern = + -(int) (256 + 255 * (rand() / (RAND_MAX + 0.1))); + } + } + + net_send_query(ctl, batch_at, abs(packetsize)); + + for (i = ctl->fstTTL - 1; i < batch_at; i++) { + if (addrcmp + ((void *) &(host[i].addr), (void *) &ctl->unspec_addr, + ctl->af) == 0) + n_unknown++; + + /* The second condition in the next "if" statement was added in mtr-0.56, + but I don't remember why. It makes mtr stop skipping sections of unknown + hosts. Removed in 0.65. + If the line proves necessary, it should at least NOT trigger that line + when host[i].addr == 0 */ + if ((addrcmp((void *) &(host[i].addr), + (void *) remoteaddress, ctl->af) == 0)) + n_unknown = MaxHost; /* Make sure we drop into "we should restart" */ + } + + if ( /* success in reaching target */ + (addrcmp((void *) &(host[batch_at].addr), + (void *) remoteaddress, ctl->af) == 0) || + /* fail in consecutive maxUnknown (firewall?) */ + (n_unknown > ctl->maxUnknown) || + /* or reach limit */ + (batch_at >= ctl->maxTTL - 1)) { + numhosts = batch_at + 1; + batch_at = ctl->fstTTL - 1; + return 1; + } + + batch_at++; + return 0; +} + + +/* Ensure the interface address a valid address for our use */ +static void net_validate_interface_address( + int address_family, + char *interface_address) +{ + if (inet_pton(address_family, interface_address, sourceaddress) != 1) { + error(EXIT_FAILURE, errno, "invalid local address"); + } + + if (inet_ntop + (address_family, sourceaddress, localaddr, + sizeof(localaddr)) == NULL) { + error(EXIT_FAILURE, errno, "invalid local address"); + } +} + + +/* + Find the local address we will use to sent to the remote + host by connecting a UDP socket and checking the address + the socket is bound to. +*/ +static void net_find_local_address( + void) +{ + int udp_socket; + int addr_length; + struct sockaddr_storage remote_sockaddr; + struct sockaddr_in *remote4; + struct sockaddr_in6 *remote6; + + udp_socket = + socket(remotesockaddr->sa_family, SOCK_DGRAM, IPPROTO_UDP); + if (udp_socket == -1) { + error(EXIT_FAILURE, errno, "udp socket creation failed"); + } + + /* + We need to set the port to a non-zero value for the connect + to succeed. + */ + if (remotesockaddr->sa_family == AF_INET6) { +#ifdef ENABLE_IPV6 + addr_length = sizeof(struct sockaddr_in6); + + memcpy(&remote_sockaddr, rsa6, addr_length); + remote6 = (struct sockaddr_in6 *) &remote_sockaddr; + remote6->sin6_port = htons(1); +#endif + } else { + addr_length = sizeof(struct sockaddr_in); + + memcpy(&remote_sockaddr, rsa4, addr_length); + remote4 = (struct sockaddr_in *) &remote_sockaddr; + remote4->sin_port = htons(1); + } + + if (connect + (udp_socket, (struct sockaddr *) &remote_sockaddr, addr_length)) { + error(EXIT_FAILURE, errno, "udp socket connect failed"); + } + + if (getsockname(udp_socket, sourcesockaddr, &addr_length)) { + + error(EXIT_FAILURE, errno, "local address determination failed"); + } + + sockaddrtop(sourcesockaddr, localaddr, sizeof(localaddr)); + + close(udp_socket); +} + + +int net_open( + struct mtr_ctl *ctl, + struct hostent *hostent) +{ + int err; + + /* Spawn the mtr-packet child process */ + err = open_command_pipe(ctl, &packet_command_pipe); + if (err) { + return err; + } + + net_reset(ctl); + + remotesockaddr->sa_family = hostent->h_addrtype; + + switch (hostent->h_addrtype) { + case AF_INET: + addrcpy((void *) &(rsa4->sin_addr), hostent->h_addr, AF_INET); + sourceaddress = (ip_t *) & (ssa4->sin_addr); + remoteaddress = (ip_t *) & (rsa4->sin_addr); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + addrcpy((void *) &(rsa6->sin6_addr), hostent->h_addr, AF_INET6); + sourceaddress = (ip_t *) & (ssa6->sin6_addr); + remoteaddress = (ip_t *) & (rsa6->sin6_addr); + break; +#endif + default: + error(EXIT_FAILURE, 0, "net_open bad address type"); + } + + if (ctl->InterfaceAddress) { + net_validate_interface_address(ctl->af, ctl->InterfaceAddress); + } else { + net_find_local_address(); + } + + return 0; +} + + +void net_reopen( + struct mtr_ctl *ctl, + struct hostent *addr) +{ + int at; + + for (at = 0; at < MaxHost; at++) { + memset(&host[at], 0, sizeof(host[at])); + } + + remotesockaddr->sa_family = addr->h_addrtype; + addrcpy((void *) remoteaddress, addr->h_addr, addr->h_addrtype); + + switch (addr->h_addrtype) { + case AF_INET: + addrcpy((void *) &(rsa4->sin_addr), addr->h_addr, AF_INET); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + addrcpy((void *) &(rsa6->sin6_addr), addr->h_addr, AF_INET6); + break; +#endif + default: + error(EXIT_FAILURE, 0, "net_reopen bad address type"); + } + + net_reset(ctl); + net_send_batch(ctl); +} + + +void net_reset( + struct mtr_ctl *ctl) +{ + static struct nethost template = { + .saved_seq_offset = 2 - SAVED_PINGS + }; + + int at, i; + + batch_at = ctl->fstTTL - 1; /* above replacedByMin */ + numhosts = 10; + + for (i = 0; i < SAVED_PINGS; i++) + template.saved[i] = -2; + + for (at = 0; at < MaxHost; at++) { + memcpy(&(host[at]), &template, sizeof(template)); + } + + for (at = 0; at < MaxSequence; at++) { + sequence[at].transit = 0; + } + +} + + +/* Close the pipe to the packet generator process, and kill the process */ +void net_close( + void) +{ + close_command_pipe(&packet_command_pipe); +} + + +int net_waitfd( + void) +{ + return packet_command_pipe.read_fd; +} + + +int *net_saved_pings( + int at) +{ + return host[at].saved; +} + + +static void net_save_increment( + void) +{ + int at; + for (at = 0; at < MaxHost; at++) { + memmove(host[at].saved, host[at].saved + 1, + (SAVED_PINGS - 1) * sizeof(int)); + host[at].saved[SAVED_PINGS - 1] = -2; + host[at].saved_seq_offset += 1; + } +} + + +void net_save_xmit( + int at) +{ + if (host[at].saved[SAVED_PINGS - 1] != -2) + net_save_increment(); + host[at].saved[SAVED_PINGS - 1] = -1; +} + + +void net_save_return( + int at, + int seq, + int ms) +{ + int idx; + idx = seq - host[at].saved_seq_offset; + if ((idx < 0) || (idx >= SAVED_PINGS)) { + return; + } + host[at].saved[idx] = ms; +} + +/* Similar to inet_ntop but uses a sockaddr as it's argument. */ +static void sockaddrtop( + struct sockaddr *saddr, + char *strptr, + size_t len) +{ + struct sockaddr_in *sa4; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *sa6; +#endif + + switch (saddr->sa_family) { + case AF_INET: + sa4 = (struct sockaddr_in *) saddr; + xstrncpy(strptr, inet_ntoa(sa4->sin_addr), len - 1); + strptr[len - 1] = '\0'; + return; +#ifdef ENABLE_IPV6 + case AF_INET6: + sa6 = (struct sockaddr_in6 *) saddr; + inet_ntop(sa6->sin6_family, &(sa6->sin6_addr), strptr, len); + return; +#endif + default: + error(0, 0, "sockaddrtop unknown address type"); + strptr[0] = '\0'; + return; + } +} + + +/* Address comparison. */ +int addrcmp( + char *a, + char *b, + int family) +{ + int rc = -1; + + switch (family) { + case AF_INET: + rc = memcmp(a, b, sizeof(struct in_addr)); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + rc = memcmp(a, b, sizeof(struct in6_addr)); + break; +#endif + } + + return rc; +} + +/* Address copy. */ +void addrcpy( + char *a, + char *b, + int family) +{ + + switch (family) { + case AF_INET: + memcpy(a, b, sizeof(struct in_addr)); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + memcpy(a, b, sizeof(struct in6_addr)); + break; +#endif + } +} + +/* for GTK frontend */ +void net_harvest_fds( + struct mtr_ctl *ctl) +{ + fd_set writefd; + int maxfd = 0; + struct timeval tv; + + FD_ZERO(&writefd); + tv.tv_sec = 0; + tv.tv_usec = 0; + select(maxfd, NULL, &writefd, NULL, &tv); +} diff --git a/ui/net.h b/ui/net.h new file mode 100644 index 0000000..6b95a00 --- /dev/null +++ b/ui/net.h @@ -0,0 +1,128 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Prototypes for functions in net.c */ +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_IPV6 +#include +#endif + +#include + +#include "mtr.h" + +extern int net_open( + struct mtr_ctl *ctl, + struct hostent *host); +extern void net_reopen( + struct mtr_ctl *ctl, + struct hostent *address); +extern void net_reset( + struct mtr_ctl *ctl); +extern void net_close( + void); +extern int net_waitfd( + void); +extern void net_process_return( + struct mtr_ctl *ctl); +extern void net_harvest_fds( + struct mtr_ctl *ctl); + +extern int net_max( + struct mtr_ctl *ctl); +extern int net_min( + struct mtr_ctl *ctl); +extern int net_last( + int at); +extern ip_t *net_addr( + int at); +extern void *net_mpls( + int at); +extern void *net_mplss( + int, + int); +extern int net_loss( + int at); +extern int net_drop( + int at); +extern int net_best( + int at); +extern int net_worst( + int at); +extern int net_avg( + int at); +extern int net_gmean( + int at); +extern int net_stdev( + int at); +extern int net_jitter( + int at); +extern int net_jworst( + int at); +extern int net_javg( + int at); +extern int net_jinta( + int at); +extern ip_t *net_addrs( + int at, + int i); +extern char *net_localaddr( + void); + +extern int net_send_batch( + struct mtr_ctl *ctl); +extern void net_end_transit( + void); + +extern int calc_deltatime( + float WaitTime); + +extern int net_returned( + int at); +extern int net_xmit( + int at); + +extern int net_up( + int at); + +extern int *net_saved_pings( + int at); +extern void net_save_xmit( + int at); +extern void net_save_return( + int at, + int seq, + int ms); + +extern int addrcmp( + char *a, + char *b, + int af); +extern void addrcpy( + char *a, + char *b, + int af); + +extern void net_add_fds( + fd_set * writefd, + int *maxfd); diff --git a/ui/raw.c b/ui/raw.c new file mode 100644 index 0000000..c13f00c --- /dev/null +++ b/ui/raw.c @@ -0,0 +1,75 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1998 R.E.Wolff@BitWizard.nl + + raw.c -- raw output (for logging for later analysis) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "mtr.h" +#include "raw.h" +#include "net.h" +#include "dns.h" + + +/* Log an echo request, or a "ping" */ +void raw_rawxmit( + int host, + int seq) +{ + printf("x %d %d\n", host, seq); + fflush(stdout); +} + +/* Log an echo reply, or a "pong" */ +void raw_rawping( + struct mtr_ctl *ctl, + int host, + int msec, + int seq) +{ + static int havename[MaxHost]; + char *name; + + if (ctl->dns && !havename[host]) { + name = dns_lookup2(ctl, net_addr(host)); + if (name) { + havename[host]++; + printf("d %d %s\n", host, name); + } + } + printf("p %d %d %d\n", host, msec, seq); + fflush(stdout); +} + + +void raw_rawhost( + struct mtr_ctl *ctl, + int host, + ip_t * ip_addr) +{ + printf("h %d %s\n", host, strlongip(ctl, ip_addr)); + fflush(stdout); +} diff --git a/ui/raw.h b/ui/raw.h new file mode 100644 index 0000000..c79d8bb --- /dev/null +++ b/ui/raw.h @@ -0,0 +1,33 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1998 R.E.Wolff@BitWizard.nl + + raw.h -- raw output (for logging for later analysis) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Prototypes for raw.c */ +extern void raw_rawxmit( + int host, + int seq); +extern void raw_rawping( + struct mtr_ctl *ctl, + int host, + int msec, + int seq); +extern void raw_rawhost( + struct mtr_ctl *ctl, + int host, + ip_t * addr); diff --git a/ui/report.c b/ui/report.c new file mode 100644 index 0000000..7700391 --- /dev/null +++ b/ui/report.c @@ -0,0 +1,512 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtr.h" +#include "report.h" +#include "net.h" +#include "dns.h" +#include "asn.h" +#include "utils.h" + +#define MAXLOADBAL 5 +#define MAX_FORMAT_STR 81 + + +void report_open( + void) +{ + const time_t now = time(NULL); + const char *t = iso_time(&now); + + printf("Start: %s\n", t); +} + +static size_t snprint_addr( + struct mtr_ctl *ctl, + char *dst, + size_t dst_len, + ip_t * addr) +{ + if (addrcmp((void *) addr, (void *) &ctl->unspec_addr, ctl->af)) { + struct hostent *host = + ctl->dns ? addr2host((void *) addr, ctl->af) : NULL; + if (!host) + return snprintf(dst, dst_len, "%s", strlongip(ctl, addr)); + else if (ctl->dns && ctl->show_ips) + return snprintf(dst, dst_len, "%s (%s)", host->h_name, + strlongip(ctl, addr)); + else + return snprintf(dst, dst_len, "%s", host->h_name); + } else + return snprintf(dst, dst_len, "%s", "???"); +} + + +#ifdef HAVE_IPINFO +static void print_mpls( + struct mplslen *mpls) +{ + int k; + for (k = 0; k < mpls->labels; k++) + printf(" [MPLS: Lbl %lu Exp %u S %cu TTL %u]\n", + mpls->label[k], mpls->exp[k], mpls->s[k], mpls->ttl[k]); +} +#endif + +void report_close( + struct mtr_ctl *ctl) +{ + int i, j, at, max, z, w; + struct mplslen *mpls, *mplss; + ip_t *addr; + ip_t *addr2 = NULL; + char name[MAX_FORMAT_STR]; + char buf[1024]; + char fmt[16]; + size_t len = 0; + size_t len_hosts = 33; +#ifdef HAVE_IPINFO + int len_tmp; + const size_t iiwidth_len = get_iiwidth_len(); +#endif + + if (ctl->reportwide) { + /* get the longest hostname */ + len_hosts = strlen(ctl->LocalHostname); + max = net_max(ctl); + at = net_min(ctl); + for (; at < max; at++) { + size_t nlen; + addr = net_addr(at); + if ((nlen = snprint_addr(ctl, name, sizeof(name), addr))) + if (len_hosts < nlen) + len_hosts = nlen; + } + } +#ifdef HAVE_IPINFO + len_tmp = len_hosts; + if (ctl->ipinfo_no >= 0 && iiwidth_len) { + ctl->ipinfo_no %= iiwidth_len; + if (ctl->reportwide) { + len_hosts++; /* space */ + len_tmp += get_iiwidth(ctl->ipinfo_no); + if (!ctl->ipinfo_no) + len_tmp += 2; /* align header: AS */ + } + } + snprintf(fmt, sizeof(fmt), "HOST: %%-%ds", len_tmp); +#else + snprintf(fmt, sizeof(fmt), "HOST: %%-%zus", len_hosts); +#endif + snprintf(buf, sizeof(buf), fmt, ctl->LocalHostname); + len = ctl->reportwide ? strlen(buf) : len_hosts; + for (i = 0; i < MAXFLD; i++) { + j = ctl->fld_index[ctl->fld_active[i]]; + if (j < 0) + continue; + + snprintf(fmt, sizeof(fmt), "%%%ds", data_fields[j].length); + snprintf(buf + len, sizeof(buf), fmt, data_fields[j].title); + len += data_fields[j].length; + } + printf("%s\n", buf); + + max = net_max(ctl); + at = net_min(ctl); + for (; at < max; at++) { + addr = net_addr(at); + mpls = net_mpls(at); + snprint_addr(ctl, name, sizeof(name), addr); + +#ifdef HAVE_IPINFO + if (is_printii(ctl)) { + snprintf(fmt, sizeof(fmt), " %%2d. %%s%%-%zus", len_hosts); + snprintf(buf, sizeof(buf), fmt, at + 1, fmt_ipinfo(ctl, addr), + name); + } else { +#endif + snprintf(fmt, sizeof(fmt), " %%2d.|-- %%-%zus", len_hosts); + snprintf(buf, sizeof(buf), fmt, at + 1, name); +#ifdef HAVE_IPINFO + } +#endif + len = ctl->reportwide ? strlen(buf) : len_hosts; + for (i = 0; i < MAXFLD; i++) { + j = ctl->fld_index[ctl->fld_active[i]]; + if (j < 0) + continue; + + /* 1000.0 is a temporay hack for stats usec to ms, impacted net_loss. */ + if (strchr(data_fields[j].format, 'f')) { + snprintf(buf + len, sizeof(buf), data_fields[j].format, + data_fields[j].net_xxx(at) / 1000.0); + } else { + snprintf(buf + len, sizeof(buf), data_fields[j].format, + data_fields[j].net_xxx(at)); + } + len += data_fields[j].length; + } + printf("%s\n", buf); + + /* This feature shows 'loadbalances' on routes */ + + /* z is starting at 1 because addrs[0] is the same that addr */ + for (z = 1; z < MAXPATH; z++) { + int found = 0; + addr2 = net_addrs(at, z); + mplss = net_mplss(at, z); + if ((addrcmp + ((void *) &ctl->unspec_addr, (void *) addr2, + ctl->af)) == 0) + break; + for (w = 0; w < z; w++) + /* Ok... checking if there are ips repeated on same hop */ + if ((addrcmp + ((void *) addr2, (void *) net_addrs(at, w), + ctl->af)) == 0) { + found = 1; + break; + } + + if (!found) { + +#ifdef HAVE_IPINFO + if (is_printii(ctl)) { + if (mpls->labels && z == 1 && ctl->enablempls) + print_mpls(mpls); + snprint_addr(ctl, name, sizeof(name), addr2); + printf(" %s%s\n", fmt_ipinfo(ctl, addr2), name); + if (ctl->enablempls) + print_mpls(mplss); + } +#else + int k; + if (mpls->labels && z == 1 && ctl->enablempls) { + for (k = 0; k < mpls->labels; k++) { + printf + (" | |+-- [MPLS: Lbl %lu Exp %u S %u TTL %u]\n", + mpls->label[k], mpls->exp[k], mpls->s[k], + mpls->ttl[k]); + } + } + + if (z == 1) { + printf(" | `|-- %s\n", strlongip(ctl, addr2)); + for (k = 0; k < mplss->labels && ctl->enablempls; k++) { + printf + (" | +-- [MPLS: Lbl %lu Exp %u S %u TTL %u]\n", + mplss->label[k], mplss->exp[k], mplss->s[k], + mplss->ttl[k]); + } + } else { + printf(" | |-- %s\n", strlongip(ctl, addr2)); + for (k = 0; k < mplss->labels && ctl->enablempls; k++) { + printf + (" | +-- [MPLS: Lbl %lu Exp %u S %u TTL %u]\n", + mplss->label[k], mplss->exp[k], mplss->s[k], + mplss->ttl[k]); + } + } +#endif + } + } + + /* No multipath */ +#ifdef HAVE_IPINFO + if (is_printii(ctl)) { + if (mpls->labels && z == 1 && ctl->enablempls) + print_mpls(mpls); + } +#else + if (mpls->labels && z == 1 && ctl->enablempls) { + int k; + for (k = 0; k < mpls->labels; k++) { + printf(" | +-- [MPLS: Lbl %lu Exp %u S %u TTL %u]\n", + mpls->label[k], mpls->exp[k], mpls->s[k], + mpls->ttl[k]); + } + } +#endif + } +} + + +void txt_open( + void) +{ +} + + +void txt_close( + struct mtr_ctl *ctl) +{ + report_close(ctl); +} + + +void json_open( + void) +{ +} + + +void json_close( + struct mtr_ctl *ctl) +{ + int i, j, at, first, max; + ip_t *addr; + char name[MAX_FORMAT_STR]; + + printf("{\n"); + printf(" \"report\": {\n"); + printf(" \"mtr\": {\n"); + printf(" \"src\": \"%s\",\n", ctl->LocalHostname); + printf(" \"dst\": \"%s\",\n", ctl->Hostname); + printf(" \"tos\": \"0x%X\",\n", ctl->tos); + if (ctl->cpacketsize >= 0) { + printf(" \"psize\": \"%d\",\n", ctl->cpacketsize); + } else { + printf(" \"psize\": \"rand(%d-%d)\",\n", MINPACKET, + -ctl->cpacketsize); + } + if (ctl->bitpattern >= 0) { + printf(" \"bitpattern\": \"0x%02X\",\n", + (unsigned char) (ctl->bitpattern)); + } else { + printf(" \"bitpattern\": \"rand(0x00-FF)\",\n"); + } + printf(" \"tests\": \"%d\"\n", ctl->MaxPing); + printf(" },\n"); + + printf(" \"hubs\": ["); + + max = net_max(ctl); + at = first = net_min(ctl); + for (; at < max; at++) { + addr = net_addr(at); + snprint_addr(ctl, name, sizeof(name), addr); + + if (at == first) { + printf("{\n"); + } else { + printf(" {\n"); + } + printf(" \"count\": \"%d\",\n", at + 1); + printf(" \"host\": \"%s\",\n", name); +#ifdef HAVE_IPINFO + if(!ctl->ipinfo_no) { + char* fmtinfo = fmt_ipinfo(ctl, addr); + if (fmtinfo != NULL) fmtinfo = trim(fmtinfo, '\0'); + printf(" \"ASN\": \"%s\",\n", fmtinfo); + } +#endif + for (i = 0; i < MAXFLD; i++) { + const char *format; + + j = ctl->fld_index[ctl->fld_active[i]]; + + /* Commas */ + if (i + 1 == MAXFLD) { + printf("\n"); + } else if (j > 0 && i != 0) { + printf(",\n"); + } + + if (j <= 0) + continue; /* Field nr 0, " " shouldn't be printed in this method. */ + + /* Format value */ + format = data_fields[j].format; + if (strchr(format, 'f')) { + format = "%.2f"; + } else { + format = "%d"; + } + + /* Format json line */ + snprintf(name, sizeof(name), "%s%s", " \"%s\": ", format); + + /* Output json line */ + if (strchr(data_fields[j].format, 'f')) { + /* 1000.0 is a temporay hack for stats usec to ms, impacted net_loss. */ + printf(name, + data_fields[j].title, + data_fields[j].net_xxx(at) / 1000.0); + } else { + printf(name, + data_fields[j].title, data_fields[j].net_xxx(at)); + } + } + if (at + 1 == max) { + printf(" }]\n"); + } else { + printf(" },\n"); + } + } + printf(" }\n"); + printf("}\n"); +} + + + +void xml_open( + void) +{ +} + + +void xml_close( + struct mtr_ctl *ctl) +{ + int i, j, at, max; + ip_t *addr; + char name[MAX_FORMAT_STR]; + + printf("\n"); + printf("LocalHostname, + ctl->Hostname); + printf(" TOS=\"0x%X\"", ctl->tos); + if (ctl->cpacketsize >= 0) { + printf(" PSIZE=\"%d\"", ctl->cpacketsize); + } else { + printf(" PSIZE=\"rand(%d-%d)\"", MINPACKET, -ctl->cpacketsize); + } + if (ctl->bitpattern >= 0) { + printf(" BITPATTERN=\"0x%02X\"", + (unsigned char) (ctl->bitpattern)); + } else { + printf(" BITPATTERN=\"rand(0x00-FF)\""); + } + printf(" TESTS=\"%d\">\n", ctl->MaxPing); + + max = net_max(ctl); + at = net_min(ctl); + for (; at < max; at++) { + addr = net_addr(at); + snprint_addr(ctl, name, sizeof(name), addr); + + printf(" \n", at + 1, name); + for (i = 0; i < MAXFLD; i++) { + const char *title; + + j = ctl->fld_index[ctl->fld_active[i]]; + if (j <= 0) + continue; /* Field nr 0, " " shouldn't be printed in this method. */ + + snprintf(name, sizeof(name), "%s%s%s", " <%s>", + data_fields[j].format, "\n"); + + /* XML doesn't allow "%" in tag names, rename Loss% to just Loss */ + title = data_fields[j].title; + if (strcmp(data_fields[j].title, "Loss%") == 0) { + title = "Loss"; + } + + /* 1000.0 is a temporay hack for stats usec to ms, impacted net_loss. */ + if (strchr(data_fields[j].format, 'f')) { + printf(name, + title, data_fields[j].net_xxx(at) / 1000.0, title); + } else { + printf(name, title, data_fields[j].net_xxx(at), title); + } + } + printf(" \n"); + } + printf("\n"); +} + + +void csv_open( + void) +{ +} + +void csv_close( + struct mtr_ctl *ctl, + time_t now) +{ + int i, j, at, max; + ip_t *addr; + char name[MAX_FORMAT_STR]; + + for (i = 0; i < MAXFLD; i++) { + j = ctl->fld_index[ctl->fld_active[i]]; + if (j < 0) + continue; + } + + max = net_max(ctl); + at = net_min(ctl); + for (; at < max; at++) { + addr = net_addr(at); + snprint_addr(ctl, name, sizeof(name), addr); + + if (at == net_min(ctl)) { + printf("Mtr_Version,Start_Time,Status,Host,Hop,Ip,"); +#ifdef HAVE_IPINFO + if (!ctl->ipinfo_no) { + printf("Asn,"); + } +#endif + for (i = 0; i < MAXFLD; i++) { + j = ctl->fld_index[ctl->fld_active[i]]; + if (j < 0) + continue; + printf("%s,", data_fields[j].title); + } + printf("\n"); + } +#ifdef HAVE_IPINFO + if (!ctl->ipinfo_no) { + char *fmtinfo = fmt_ipinfo(ctl, addr); + fmtinfo = trim(fmtinfo, '\0'); + printf("MTR.%s,%lld,%s,%s,%d,%s,%s", PACKAGE_VERSION, + (long long) now, "OK", ctl->Hostname, at + 1, name, + fmtinfo); + } else +#endif + printf("MTR.%s,%lld,%s,%s,%d,%s", PACKAGE_VERSION, + (long long) now, "OK", ctl->Hostname, at + 1, name); + + for (i = 0; i < MAXFLD; i++) { + j = ctl->fld_index[ctl->fld_active[i]]; + if (j < 0) + continue; + + /* 1000.0 is a temporay hack for stats usec to ms, impacted net_loss. */ + if (strchr(data_fields[j].format, 'f')) { + printf(",%.2f", + (double) (data_fields[j].net_xxx(at) / 1000.0)); + } else { + printf(",%d", data_fields[j].net_xxx(at)); + } + } + printf("\n"); + } +} diff --git a/ui/report.h b/ui/report.h new file mode 100644 index 0000000..32b31c5 --- /dev/null +++ b/ui/report.h @@ -0,0 +1,41 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Prototypes for report.h */ + +extern void report_open( + void); +extern void report_close( + struct mtr_ctl *ctl); +extern void txt_open( + void); +extern void txt_close( + struct mtr_ctl *ctl); +extern void json_open( + void); +extern void json_close( + struct mtr_ctl *ctl); +extern void xml_open( + void); +extern void xml_close( + struct mtr_ctl *ctl); +extern void csv_open( + void); +extern void csv_close( + struct mtr_ctl *ctl, + time_t now); diff --git a/ui/select.c b/ui/select.c new file mode 100644 index 0000000..15047a1 --- /dev/null +++ b/ui/select.c @@ -0,0 +1,271 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif + +#include "mtr.h" +#include "dns.h" +#include "net.h" +#include "asn.h" +#include "display.h" +#include "select.h" + +void select_loop( + struct mtr_ctl *ctl) +{ + fd_set readfd; + fd_set writefd; + int anyset = 0; + int maxfd = 0; + int dnsfd, netfd; +#ifdef ENABLE_IPV6 + int dnsfd6; +#endif + int NumPing = 0; + int paused = 0; + struct timeval lasttime, thistime, selecttime; + struct timeval startgrace; + int dt; + int rv; + int graceperiod = 0; + struct timeval intervaltime; + static double dnsinterval = 0; + + memset(&startgrace, 0, sizeof(startgrace)); + + gettimeofday(&lasttime, NULL); + + while (1) { + dt = calc_deltatime(ctl->WaitTime); + intervaltime.tv_sec = dt / 1000000; + intervaltime.tv_usec = dt % 1000000; + + FD_ZERO(&readfd); + FD_ZERO(&writefd); + + maxfd = 0; + + if (ctl->Interactive) { + FD_SET(0, &readfd); + maxfd = 1; + } +#ifdef ENABLE_IPV6 + if (ctl->dns) { + dnsfd6 = dns_waitfd6(); + if (dnsfd6 >= 0) { + FD_SET(dnsfd6, &readfd); + if (dnsfd6 >= maxfd) + maxfd = dnsfd6 + 1; + } else { + dnsfd6 = 0; + } + } else + dnsfd6 = 0; +#endif + if (ctl->dns) { + dnsfd = dns_waitfd(); + FD_SET(dnsfd, &readfd); + if (dnsfd >= maxfd) + maxfd = dnsfd + 1; + } else + dnsfd = 0; + + netfd = net_waitfd(); + FD_SET(netfd, &readfd); + if (netfd >= maxfd) + maxfd = netfd + 1; + + do { + if (anyset || paused) { + /* Set timeout to 0.1s. + * While this is almost instantaneous for human operators, + * it's slow enough for computers to go do something else; + * this prevents mtr from hogging 100% CPU time on one core. + */ + selecttime.tv_sec = 0; + selecttime.tv_usec = paused ? 100000 : 0; + + rv = select(maxfd, (void *) &readfd, &writefd, NULL, + &selecttime); + + } else { + if (ctl->Interactive) + display_redraw(ctl); + + gettimeofday(&thistime, NULL); + + if (thistime.tv_sec > lasttime.tv_sec + intervaltime.tv_sec + || (thistime.tv_sec == + lasttime.tv_sec + intervaltime.tv_sec + && thistime.tv_usec >= + lasttime.tv_usec + intervaltime.tv_usec)) { + lasttime = thistime; + + if (!graceperiod) { + if (NumPing >= ctl->MaxPing + && (!ctl->Interactive || ctl->ForceMaxPing)) { + graceperiod = 1; + startgrace = thistime; + } + + /* do not send out batch when we've already initiated grace period */ + if (!graceperiod && net_send_batch(ctl)) + NumPing++; + } + } + + if (graceperiod) { + dt = (thistime.tv_usec - startgrace.tv_usec) + + 1000000 * (thistime.tv_sec - startgrace.tv_sec); + if ((ctl->GraceTime * 1000 * 1000) < dt) + return; + } + + selecttime.tv_usec = (thistime.tv_usec - lasttime.tv_usec); + selecttime.tv_sec = (thistime.tv_sec - lasttime.tv_sec); + if (selecttime.tv_usec < 0) { + --selecttime.tv_sec; + selecttime.tv_usec += 1000000; + } + selecttime.tv_usec = + intervaltime.tv_usec - selecttime.tv_usec; + selecttime.tv_sec = + intervaltime.tv_sec - selecttime.tv_sec; + if (selecttime.tv_usec < 0) { + --selecttime.tv_sec; + selecttime.tv_usec += 1000000; + } + + if (ctl->dns) { + if ((selecttime.tv_sec > (time_t) dnsinterval) || + ((selecttime.tv_sec == (time_t) dnsinterval) && + (selecttime.tv_usec > + ((time_t) (dnsinterval * 1000000) % 1000000)))) { + selecttime.tv_sec = (time_t) dnsinterval; + selecttime.tv_usec = + (time_t) (dnsinterval * 1000000) % 1000000; + } + } + + rv = select(maxfd, (void *) &readfd, NULL, NULL, + &selecttime); + } + } while ((rv < 0) && (errno == EINTR)); + + if (rv < 0) { + error(EXIT_FAILURE, errno, "Select failed"); + } + anyset = 0; + + /* Have we got new packets back? */ + if (FD_ISSET(netfd, &readfd)) { + net_process_return(ctl); + anyset = 1; + } + + if (ctl->dns) { + /* Handle any pending resolver events */ + dnsinterval = ctl->WaitTime; + } + + /* Have we finished a nameservice lookup? */ +#ifdef ENABLE_IPV6 + if (ctl->dns && dnsfd6 && FD_ISSET(dnsfd6, &readfd)) { + dns_ack6(); + anyset = 1; + } +#endif + if (ctl->dns && dnsfd && FD_ISSET(dnsfd, &readfd)) { + dns_ack(ctl); + anyset = 1; + } + + /* Has a key been pressed? */ + if (FD_ISSET(0, &readfd)) { + switch (display_keyaction(ctl)) { + case ActionQuit: + return; + break; + case ActionReset: + net_reset(ctl); + break; + case ActionDisplay: + ctl->display_mode = + (ctl->display_mode + 1) % DisplayModeMAX; + break; + case ActionClear: + display_clear(ctl); + break; + case ActionPause: + paused = 1; + break; + case ActionResume: + paused = 0; + break; + case ActionMPLS: + ctl->enablempls = !ctl->enablempls; + display_clear(ctl); + break; + case ActionDNS: + if (ctl->dns) { + ctl->use_dns = !ctl->use_dns; + display_clear(ctl); + } + break; +#ifdef HAVE_IPINFO + case ActionII: + ctl->ipinfo_no++; + if (ctl->ipinfo_no > ctl->ipinfo_max) + ctl->ipinfo_no = 0; + break; + case ActionAS: + ctl->ipinfo_no = ctl->ipinfo_no ? 0 : ctl->ipinfo_max; + break; +#endif + + case ActionScrollDown: + ctl->display_offset += 5; + break; + case ActionScrollUp: + ctl->display_offset -= 5; + if (ctl->display_offset < 0) { + ctl->display_offset = 0; + } + break; + } + anyset = 1; + } + } + return; +} diff --git a/ui/select.h b/ui/select.h new file mode 100644 index 0000000..08ad44e --- /dev/null +++ b/ui/select.h @@ -0,0 +1,20 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +extern void select_loop( + struct mtr_ctl *ctl); diff --git a/ui/split.c b/ui/split.c new file mode 100644 index 0000000..0895103 --- /dev/null +++ b/ui/split.c @@ -0,0 +1,185 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997 Matt Kimball + + split.c -- raw output (for inclusion in KDE Network Utilities or others + GUI based tools) + Copyright (C) 1998 Bertrand Leconte + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "mtr.h" +#include "display.h" +#include "dns.h" + +#include "net.h" +#include "split.h" +#include "utils.h" + +#ifdef HAVE_CURSES +#if defined(HAVE_NCURSES_H) +#include +#elif defined(HAVE_NCURSES_CURSES_H) +#include +#elif defined(HAVE_CURSES_H) +#include +#else +#error No curses header file available +#endif +#else +#include +#include +#include +#endif + + +/* There is 256 hops max in the IP header (coded with a byte) */ +#define MAX_LINE_COUNT 256 +#define MAX_LINE_SIZE 256 + +static char Lines[MAX_LINE_COUNT][MAX_LINE_SIZE]; +static int LineCount; + + +#define DEBUG 0 + + +void split_redraw( + struct mtr_ctl *ctl) +{ + int max; + int at; + ip_t *addr; + char newLine[MAX_LINE_SIZE]; + int i; + +#if DEBUG + fprintf(stderr, "split_redraw()\n"); +#endif + + /* + * If there is less lines than last time, we delete them + * TEST THIS PLEASE + */ + max = net_max(ctl); + for (i = LineCount; i > max; i--) { + printf("-%d\n", i); + LineCount--; + } + + /* + * For each line, we compute the new one and we compare it to the old one + */ + for (at = 0; at < max; at++) { + addr = net_addr(at); + if (addrcmp((void *) addr, (void *) &ctl->unspec_addr, ctl->af)) { + char str[256], *name; + if (!(name = dns_lookup(ctl, addr))) + name = strlongip(ctl, addr); + if (ctl->show_ips) { + snprintf(str, sizeof(str), "%s %s", name, + strlongip(ctl, addr)); + name = str; + } + /* May be we should test name's length */ + snprintf(newLine, sizeof(newLine), "%s %d %d %d %d %d %d", + name, net_loss(at), net_returned(at), net_xmit(at), + net_best(at) / 1000, net_avg(at) / 1000, + net_worst(at) / 1000); + } else { + snprintf(newLine, sizeof(newLine), "???"); + } + + if (strcmp(newLine, Lines[at]) == 0) { + /* The same, so do nothing */ +#if DEBUG + printf("SAME LINE\n"); +#endif + } else { + printf("%d %s\n", at + 1, newLine); + fflush(stdout); + xstrncpy(Lines[at], newLine, MAX_LINE_SIZE); + if (LineCount < (at + 1)) { + LineCount = at + 1; + } + } + } +} + + +void split_open( + void) +{ + int i; +#if DEBUG + printf("split_open()\n"); +#endif + LineCount = -1; + for (i = 0; i < MAX_LINE_COUNT; i++) { + xstrncpy(Lines[i], "???", MAX_LINE_SIZE); + } +} + + +void split_close( + void) +{ +#if DEBUG + printf("split_close()\n"); +#endif +} + + +int split_keyaction( + void) +{ +#ifdef HAVE_CURSES + unsigned char c = getch(); +#else + fd_set readfds; + struct timeval tv; + char c; + + FD_ZERO(&readfds); + FD_SET(0, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + + if (select(1, &readfds, NULL, NULL, &tv) > 0) { + read(0, &c, 1); + } else + return 0; +#endif + +#if DEBUG + printf("split_keyaction()\n"); +#endif + if (tolower(c) == 'q') + return ActionQuit; + if (c == 3) + return ActionQuit; + if (tolower(c) == 'r') + return ActionReset; + + return 0; +} diff --git a/ui/split.h b/ui/split.h new file mode 100644 index 0000000..84b6794 --- /dev/null +++ b/ui/split.h @@ -0,0 +1,29 @@ +/* + mtr -- a network diagnostic tool + + split.h -- raw output (for inclusion in KDE Network Utilities) + Copyright (C) 1998 Bertrand Leconte + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* Prototypes for split.c */ +extern void split_open( + void); +extern void split_close( + void); +extern void split_redraw( + struct mtr_ctl *ctl); +extern int split_keyaction( + void); diff --git a/ui/utils.c b/ui/utils.c new file mode 100644 index 0000000..94bf21a --- /dev/null +++ b/ui/utils.c @@ -0,0 +1,190 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ERROR_H +#include +#else +#include "portability/error.h" +#endif + +#ifdef HAVE_STDIO_EXT_H +#include +#endif + +#include "utils.h" + +char *trim( + char *str, + const char c) +{ + char *p = str; + size_t len; + + /* left trim */ + while (*p && (isspace((unsigned char) *p) || (c && *p == c))) + p++; + if (str < p) { + len = strlen(str); + memmove(str, p, len + 1); + } + + /* right trim */ + len = strlen(str); + while (len) { + len--; + if (isspace((unsigned char) str[len]) || (c && str[len] == c)) { + continue; + } + len++; + break; + } + str[len] = '\0'; + return str; +} + +/* Parse string, and return positive signed int. */ +int strtonum_or_err( + const char *str, + const char *errmesg, + const int type) +{ + unsigned long int num; + char *end = NULL; + + if (str != NULL && *str != '\0') { + errno = 0; + num = strtoul(str, &end, 10); + if (errno == 0 && str != end && end != NULL && *end == '\0') { + switch (type) { + case STRTO_INT: + if (num < INT_MAX) + return num; + break; + case STRTO_U32INT: + if (num < UINT32_MAX) + return num; + break; + } + } + } + error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str); + return 0; +} + +float strtofloat_or_err( + const char *str, + const char *errmesg) +{ + double num; + char *end = NULL; + + if (str != NULL && *str != '\0') { + errno = 0; + num = strtod(str, &end); + if (errno == 0 && str != end && end != NULL && *end == '\0' +#ifdef FLT_MAX + && num < FLT_MAX +#endif + ) + return num; + } + error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str); + return 0; +} + +void *xmalloc( + const size_t size) +{ + void *ret = malloc(size); + + if (!ret && size) + error(EXIT_FAILURE, errno, "cannot allocate %zu bytes", size); + return ret; +} + +char *xstrdup( + const char *str) +{ + char *ret; + + if (!str) + return NULL; + ret = strdup(str); + if (!ret) + error(EXIT_FAILURE, errno, "cannot duplicate string: %s", str); + return ret; +} + +#ifndef HAVE___FPENDING +static inline int __fpending( + FILE * stream __attribute__ ((__unused__))) +{ + return 0; +} +#endif +static inline int close_stream( + FILE * stream) +{ + const int some_pending = (__fpending(stream) != 0); + const int prev_fail = (ferror(stream) != 0); + const int fclose_fail = (fclose(stream) != 0); + + if (prev_fail || (fclose_fail && (some_pending || errno != EBADF))) { + if (!fclose_fail && !(errno == EPIPE)) + errno = 0; + return EOF; + } + return 0; +} + +/* Meant to be used atexit(close_stdout); */ +void close_stdout( + void) +{ + if (close_stream(stdout) != 0 && !(errno == EPIPE)) { + error(0, errno, "write error"); + _exit(EXIT_FAILURE); + } + if (close_stream(stderr) != 0) + _exit(EXIT_FAILURE); +} + +/* ctime() replacement that will reteturn ISO-8601 timestamp string such as: + * 2016-08-29T19:25:02+01:00 */ +const char *iso_time( + const time_t * t) +{ + static char s[32]; + struct tm *tm; + + tm = localtime(t); + strftime(s, sizeof(s), "%Y-%m-%dT%H:%M:%S%z", tm); + return s; +} diff --git a/ui/utils.h b/ui/utils.h new file mode 100644 index 0000000..7fc2055 --- /dev/null +++ b/ui/utils.h @@ -0,0 +1,55 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 1997,1998 Matt Kimball + Copyright (C) 2005 R.E.Wolff@BitWizard.nl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +enum { + STRTO_INT, + STRTO_U32INT +}; + +extern char *trim( + char *s, + const char c); +extern int strtonum_or_err( + const char *str, + const char *errmesg, + const int type); +extern float strtofloat_or_err( + const char *str, + const char *errmesg); + +/* Like strncpy(3) but ensure null termination. */ +static inline void xstrncpy( + char *dest, + const char *src, + size_t n) +{ + strncpy(dest, src, n - 1); + dest[n - 1] = 0; +} + +extern void *xmalloc( + const size_t size); +extern char *xstrdup( + const char *str); + +extern void close_stdout( + void); + +extern const char *iso_time( + const time_t * t);