diff options
42 files changed, 8968 insertions, 0 deletions
@@ -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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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. + + <signature of Ty Coon>, 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/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This 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; either + version 2.1 of the License, or (at your option) any later version. + + This 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 this library; 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. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! @@ -0,0 +1,28 @@ +Thanks for the testing, proposals, bug fixing: + +Robert Scheck (redhat-bugzilla@linuxnetz.de) +Peter Bieringer (pb@bieringer.de) +Martin Bacovsky (mbacovsk@redhat.com) +Andy Shevchenko (andriy@asplinux.ru) +Mike Frysinger (vapier@gentoo.org) +Chris Ward (cward@redhat.com) +Teran McKinney (sega01@gmail.com) +Kaj Niemi (kajtzu@a51.org) +Milos Malik (mmalik@redhat.com) +Andreas Mohr (andim2@users.sourceforge.net) +Vladz (vladz@devzero.fr) +Daniel Baumann (daniel@debian.org) +Filip Holec (fholec@redhat.com) +Samuel Jero (sj323707@ohio.edu) +Jan Synacek (jsynacek@redhat.com) +Frederic Mangano (fmang@mg0.fr) +Jeff (geogriffin@jsgriff.com) +Andrew Schwartz (schwartz@amacapital.net) +Felix Janda (felix.janda@posteo.de) +Sergey Salnikov (serg@salnikov.ru) +Richard Sheehan (richardsheehan@users.sourceforge.net) +Eric Dumazet (edumazet@google.com) +Francois Rigault <francois.rigault@amadeus.com> +... +maybe you too? ;) + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..ffd2100 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,483 @@ +2023-12-18 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.1.5 + + * Fix rfc5837 parsing (Francois Rigault) + + +2023-12-13 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.1.4 + + * Parse interface information (rfc5837) for ICMP extensions + + * Add `fastopen' tcp module option (cookie negotiation only) + + * Complete tcp module option `mss' to discover possible mss clamping + along the path being traced (idea and testing from Francois Rigault). + The argument is optional now. + + Changed mss is printed once in a form of `M=NUM' at the first probe + it was detected on. (Actually, the mss clamping performed by + some previous hop). + + Note, some routers may return too short original fragment + in the time exceeded message, making the check impossible. + Besides that the responses may come in a different order. + All this can lead to a later place of the report + (using -N 1 can help for the order). + + * Complete tcp module option `info' to print returned tcp header options too + (all those that can be set or altered by `-O' for tcp module). + + +2023-08-30 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.1.3 + + * Fix command line parsing in wrappers. + + +2023-02-13 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.1.2 + + * Fix unprivileged ICMP tracerouting with Linux kernel >= 6.1 + (Eric Dumazet, SF bug #14) + + +2022-12-27 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.1.1 + + * Interpret ipv4-mapped ipv6 addresses (::ffff:A.B.C.D) as true ipv4. + + There are no ipv4-mapped addresses in the real network which we + operate on, so use just ipv4 in such cases, but allow users + to specify it this way for convenience. + + * Return back more robast poll(2) loop handling. + + +2016-03-08 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.1.0 + + * Improve the main loop for better interactivity. + + Instead of waiting silently for maximum expiration time of probes + in progress, use timeout of the first probe (which will be printed + first from now) only. + + * Speedup wait mechanism. + + Traditional traceroute implementation always waited the whole timeout + for any probe. But if we already have some replies from the same hop, + or even from some next hop, we can use the round trip time + of such a reply as a hint to determine the actual reasonable + amount of time to wait. + + Now the `-w' option has a form of three (in general) float values + separated by a comma (or a slash): `-w MAX_SECS,HERE,NEAR' . + (last two are optional). MAX_SECS specifies the maximum time + (in seconds) to wait, in any case. + + The optional HERE specifies a factor to multiply the round trip time + of an already received response from the same hop. + The resulting value is used as a timeout for the probe, instead of + (but no more than) MAX_SECS. The optional NEAR specifies a similar + factor for a response from some next hop. + The time of the first found result is used in both cases. + + First, we look for the same hop (of the probe which will be printed + first from now). If nothing found, then look for some next hop. + If nothing found, use MAX_SECS. If HERE and/or NEAR have zero values, + the corresponding computation is skipped. + + HERE and NEAR are always set to zero if only MAX_SECS is specified + (which provides compatibility with previous versions). Thus, if your + scripts use `-w SECS', then nothing changed for you, since + the lonely SECS implies `-w SECS,0,0' . + + Defaults are 5.0 seconds for MAX_SECS, 3.0 times for HERE and + 10.0 times for NEAR. + + Certainly, the new algorithm can lead to premature expiry + (especially when response times differ at times) and printing "*" + instead of a time. Anyway, you can always switch this algorithm off, + just by specifying `-w' with the desired timeout only (fe. `-w 5'). + + We continue to wait whole MAX_SECS when one probe per time + must be sent (`--sport', `-P proto'), because it seems more harmful + rather than helpful to try to wait less in such cases. + + To provide compatibility with 2.0.x versions, use: + + traceroute -w 5 + + (or any other desired `-w' value). + + * Hint people to use the system traceroute(8) instead of + tcptraceroute wrapper (by providing a stderr header). + + The using of this wrapper is a little bit harmful, since it has + less possibilities and a little different set of options. + + For those who are used to use tcptraceroute in cmdline, + just create a link with that name to the system traceroute. + When invoked as "tcp*", it then behaves as `traceroute -T'. + (The simple manual page added for this case in the wrapper subdir). + + The original tcptraceroute had some options differ ("lpNSAE"), + but they was rare used. Most common "dnFifmqwst" was just the same. + Therefore it should be painless to use the system binary directly, + instead of the limited wrapper (which is still provided indeed). + + +2016-02-15 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.22 + + * Some portability fixing and improvements (Felix Janda) + + * Require clear numbers for options and arguments (Sergey Salnikov) + + * Drop compilation date from the version string (Debian #774365) + + * New tcp module option `reuse', which utilize SO_REUSEADDR + to reuse local port numbers for the huge workloads (Richard Sheehan) + + * Avoid poll(2) call with spurious zero timeout in some rare cases + by rounding the value properly using ceil(3) + + +2014-11-12 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.21 + + * Fix `--mtu' and `-F' working on kernels >= 3.13 + + * Some manual page improving (Christopher Mann) + + +2014-06-14 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.20 + + * Describe all complementary long options in the man page (Jan Synacek) + + * Use correct service name for AS lookups (Frederic Mangano) + + * Avoid some rare case null dereference (geogriffin@jsgriff.com) + + * Improve expiration check for simultaneous probes + + +2012-11-19 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.19 + + * DCCP protocol support (rfc4340), by Samuel Jero + + Use "-D" option for it (the protocol-specific options + are available too). + + * Update COPYING and COPYING.LIB license files to the latest + published ones (due to FSF address changes etc.) (Jan Synacek) + + * Add mention of "-l" option to manual (Filip Holec) + + +2011-08-16 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.18 + + * Handle new dgram icmp sockets ("echo ping sockets"), + appeared in kernel 3.0 . + + Now unprivileged users may perform ICMP tracerouting + without any special rights of the executable + (neither setuid bits nor cap_net_raw settings). + It is allowed if any group of a user matches sysctl range + of "net/ipv4/ping_group_range". + + The support for dgram icmp way (and whether it is allowed) + is auto-detected at runtime. First, the traditional raw socket + is tried (for full compatibility reasons), then new dgram + socket as a fallback. + The icmp module now has two additional options "raw" and "dgram", + which cause to try one particular way only. + + Note, that there is no IPv6 implementation for dgram icmp sockets + in kernels 3.0 yet, but new traceroute is ready for it anyway. + + * New tcp module option `info' ("-T -O info"), + which prints all tcp flags of tcp reply from the reached + target host. + + The flags are shown comma-separated in the same place + where icmp extensions is printed (ie. in `<>' brackets) + + This feature is utilized by tcptraceroute wrapper now, + and allow it to be completely functional replacement + of the original tcptraceroute. + + * Fix determination of system-wide ECN setings for tcp module. + + Since the kernel 2.6.31 the default sysctl net/ipv4/tcp_ecn + was changed from zero to '2', whereas the actual value + for ecn to be set is still '1' + + * Allow different packet sizes for `--mtu'. + Suport `-l' option for tracepath wrapper. + + * Some code and manual cleanups + + +2010-12-14 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.17 + + * Adapt code to make possible the use of Linux capabilities + (for raw sockets etc.) instead of superuser privileges only. + + On modern systems the capabilities can be stored as + file attributes, ie.: + + "setcap cap_net_raw=pe /usr/bin/traceroute" + + +2010-09-13 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.16 + + * A little work-around in the build system + for the new (buggy?) make 3.82 + + * Add `--fwmark=num' option for firewall mark (for kernel >= 2.6.25). + Idea comes from an anonymous SF patch #3042539 + + +2010-07-14 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.15 + + * Use string routines more safely (fix SF bug #3029216) + + * Provide help for lft wrapper + + +2010-04-21 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.14 + + * Fix support for IPv6's flow_labels and tclass. + Thanks to Peter Bieringer for testing + + * Use route header "type 2" instead of deprecated "type 0" + for `-g' option for IPv6. The default value can be changed + by specifying a number in the place of the first `-g' address. + + +2009-11-02 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.13 + + * Check for first_hop is not zero value (vladz@devzero.fr) + + * Always fill unresolved IP address by its numeric interpretation, + even if getnameinfo(3) leaves it untouched (as it does for ipv6 + in some glibc versions, whereas always fills for ipv4) + + * Cosmetic changes for man page (Andreas Mohr) + + +2008-09-15 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.12 + + * Use common recv_reply() routine for all modules which + do recvmsg(2) call. Method-specific things go to callbacks. + + Pass to init methods pointer to datalen instead of the value. + + * Implement ICMP Extension support (rfc4884), `-e' option. + Parse MPLS info (rfc4950) to be more readable (Kaj Niemi) + + * Implement Path MTU Discovery (similar to tracepath(1)), + with `--mtu' option. Changed mtu is printed once in a form + of `F=NUM' at the first probe of a hop which requires + such mtu to be reached. (Actually, the correspond "frag needed" + icmp message is normally sent by the previous hop). + + * Print the number of backward hops when it differs with forward, + by `--back' option. The backward hops is guessed by a technique + similar to tracepath(1), there is no reliable way to obtain + such info though. + + * The optional second argument (packet_len) now is the full length + of the packet, including IP headers. (It is obvious enough due to + the nature of this feature, and this is the behaviour of the + original traceroute). Particular trace methods can ignore this + (fe. tcp), or increase it up to the minimal value (udp, icmp). + The actual packet's size is alvays reported in the output header. + + * Add tracepath(1)/tracepath6(1) shell wrapper. + + * Allow DEF_AF to be redefined at cmdline (Teran McKinney) + + * Do not check the correctness of `sim_probes' value -- it is + unneeded at all. This also fixes a bug when a value of sim_probes + appears to be more than the total number of probes. + Reported by Milos Malik. + + * Allow default UDP method to cross zero port boundary (Milos Malik). + It is a strange corner case, but traditional traceroute + behaves exactly so. + + +2008-04-25 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.11 + + * Use new pmtudisc value "probe" instead of "do" for `-F' option + (available since the kernel 2.6.22). + + For kernels before 2.6.22, the `-F' (dontfragment) option + seems completely useless for IPv6 and partially useful + for IPv4 (when a user can flush routing caches some way). + + * Fix installation in build system (Mike Frysinger) + + * Don't compute checksum for ipv6 icmp packets ourselves, + the kernel overwrites it anyway by the proper values. + + * Don't use explicit path to traceroute in wrapper scripts + + +2008-04-17 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.10 + + * raw_can_connect(): ipv6 connected raw sockets + receive MSG_ERRQUEUE properly only for kernels >= 2.6.25 + + * remove useless "host" parameter for init methods + + * add probe_by_seq() and probe_by_sk() routines, + don't pass whole probes' pointer to recv_probe method + + * collect all sends in do_send() routine + + * Interpret ENOBUFS errors for send(2) as "can retry later". + + Slow devices (like ppp) with small tx_queue_len can reject + the sending of too many packets simultaneously. To handle this, + do_send() now returns a negate value in a case of ENOBUFS + and similar (instead of program exit). The send_probe method + clears the probe and returns immediately in such cases. + Then, if there is an amount of time to wait for something, + the failed probe will be attempted again after that time expired. + If nothing to wait more, the program is exited. + + +2007-09-26 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.9 + + * Complete manual page. + + * Edit manual page to sound more English, thanks to Chris Ward + + +2007-09-04 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.8 + + * Move all wrappers to special "wrappers/" dir. + Add lft(8) shell wrapper. + Add traceproto(8) shell wrapper. + Add traceroute-nanog(8) shell wrapper. + + * Interpret first_hop as number, not index + + * Build system is re-worked to match more the modern requirements + (Thanks to Mike Frysinger for testing). + + * Check for kernel version >= 2.6.22.2 in raw_can_connect() + + * Add generic "raw" method, "-P protonum" option. + New "one_per_time" flag for tr_module. + + +2007-07-31 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.7 + + * Fix revents checking typo + + * Expect normal data reply from udp too. + + * Implement udp to port (-U) and udplite (-UL) methods. + Both available for unprivileged users. + Add "coverage" option for udplite. + + * Allow non-digit service names for `-p' and `--sport' + + * Drop period at the end of "SEE ALSO" section, and + avoid specific distro names in the manual (Mike Frysinger) + + * Explicitly mention that this program is licensed + as "GPL version 2 or any later version". + (Similar for libsupp subdir: LGPL version 2.1 or any later). + + * Always check whether the dest and source port match in + received packets. Can decrease an amount of (hypothetical) + garbage received just after the bind() but before connect() + + +2007-07-19 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.6 + + * Rename tr_ops to tr_module + + * Implement module-specific options (-O opt,...) + + * Add TCP specific options (all the tcp header flags, + ecn, sack, timestamps, window_scaling, mss, sysctl) + Build tcp probe packet depending on them. + + * Add "--sport" option for explicit source port selection. + Always cause "-N 1" when it is set. + + * Add new routine bind_socket(). + Always (auto)bind sockets in tune_socket(). + + * Add tcptraceroute(8) shell wrapper + + +2007-07-16 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.5 + + * Use MSG_ERRQUEUE for raw sockets too. + + * raw_can_connect () work-around for kernel bug #8747 + + * random.c, csum.c: new separate files + + * New implementation of tcp method ("-T"), using + half-open technique. The old implementation module + renamed to "tcpconn" ("-M tcpconn"). + + * Common parse_cmsg() routine + + * put ee_info for parse_icmp_res() too, + handle ICMP6_PACKET_TOO_BIG for IPv6, + report "!F-num" when "frag needed" (legacy compatibility) + + +2007-07-11 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.4 + + * clear includes of unneeded headers + + * move poll stuff to separate poll.c + + * add module stuff (module.c), options etc. + Adapt udp/icmp/tcp for this. + + * Add common routines use_recverr() and set_ttl() + + +2007-02-28 Dmitry Butskoy <Dmitry@Butskoy.name> + + * fix variable type for getsockname (Mike Frysinger) + + +2007-01-09 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.3 + + * version 2.0.3 + + * allow option args without separator (add CLIF_MAY_JOIN_ARG flag), + for compatibility (Benjamin LaHaise) + + * no more "tcptraceroute" symlink for rpm packages, because + it conflicts with the same-name old package anyway (James Ralston) + + * fix compilation on glibc < 2.4 (Andy Shevchenko) + + +2006-10-30 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.2 + + * version 2.0.2 + + * More accurate check_expired() routine. + + * Some minor fixes. + + * Add NOTES section to manual + + +2006-10-20 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.1 + + * version 2.0.1 + + * Now ops methods write send_time (as well as recv_time) + + * Use SO_TIMESTAMP to obtain msecs precisely + + * Complete manual + diff --git a/Make.defines b/Make.defines new file mode 100644 index 0000000..185db54 --- /dev/null +++ b/Make.defines @@ -0,0 +1,55 @@ +# The name of this distributive (used for tarballs etc.). +# *MUST* be defined here... +# +NAME = traceroute + +# All subdir lists below are space-separated, with make's '%' wildcard +# allowed. Default value is shown in commented out example. +# + +# Subdirs to exclude anyway +# +#SKIPDIRS = tmp% + +# Subdirs to be treated as libdirs. +# +#LIBDIRS = lib% + +# An exact non-wildcard list of libdirs to be placed last in LIBDIRS list, +# with the order specified. Useful, when some library should be +# specified last because of referencing from another library... +# +#LIBLAST = + +# Subdirs to be treated as includedirs. +# Also all LIBDIRS will be actually used for includes too. +# +#INCLUDEDIRS = include% + +# Subdirs to be treated as moduledirs. +# +#MODDIRS = mod% + +# Subdirs of executables which will use modules at run-time linking. +# +#MODUSERS = + +# Subdirs of executables to be installed in `*/sbin' instead of `*/bin'. +# +#SBINUSERS = + +# Subdirs to exclude from install +# +SKIPINSTALL = test% libsupp + + +# A better way to alter the variables is to use "+=" or +# "override VAR += value" +# +# CFLAGS += +# CPPFLAGS += +# LDFLAGS += +# LIBS += + +CPPFLAGS += -D_GNU_SOURCE + diff --git a/Make.rules b/Make.rules new file mode 100644 index 0000000..f1ddc25 --- /dev/null +++ b/Make.rules @@ -0,0 +1,213 @@ +# +# Copyright (c) 2000, 2001, 2007 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Common rule and variable definitions. +# This file should be included by main and by per-target Makefiles. +# + + +ifndef srcdir +$(error srcdir variable not defined) +endif + + +MAKE = make --no-print-directory -r + + +# Use env=yes on cmdline to inherit environment values + +ifeq ($(env),yes) +define set +$(eval ifneq ($$(origin $(1)),environment) +$(1) = $(2) +else +override MAKE := $(1)="$($(strip $(1)))" $(MAKE) +endif) +endef +else +set = $(eval $(1) = $(2)) +endif + + +$(call set, CROSS, ) +$(call set, CC, $$(CROSS)gcc) +$(call set, AS, $$(CROSS)as) +$(call set, LD, $$(CROSS)ld) +$(call set, DEPEND, $$(CROSS)gcc -MM -MG) +$(call set, AR, $$(CROSS)ar) +$(call set, RANLIB, $$(CROSS)ranlib) +$(call set, INSTALL, cp) +$(call set, INDENT, true) + +gcc = $(findstring gcc,$(CC)) +$(call set, CFLAGS, $(if $(gcc), -O2 -Wall, -O)) +$(call set, CPPFLAGS, ) +$(call set, LDFLAGS, -s) +$(call set, LIBS, ) + + +# install stuff +prefix = /usr/local + +ifneq ($(wildcard /lib64/libc.* /usr/lib64/libc.*),) +lib := lib64 +else +lib := lib +endif + +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin +sbindir = $(exec_prefix)/sbin +libdir = $(exec_prefix)/$(lib) +libexecdir = $(exec_prefix)/libexec/$(NAME) +sysconfdir = $(prefix)/etc +includedir = $(prefix)/include +datadir = $(prefix)/share +mandir = $(datadir)/man +infodir = $(datadir)/info +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com + +DESTDIR = + + +# layout stuff +SKIPDIRS = tmp% +INCLUDEDIRS = include% +LIBDIRS = lib% +MODDIRS = mod% +SKIPINSTALL = test% + + +include $(srcdir)/Make.defines + + +ifndef NAME +NAME = $(notdir $(srcdir)) +endif + + +ifndef subdirs +ifeq ($(TARGET),.MAIN) +# for better performance... +subdirs := $(patsubst %/,%,$(wildcard */)) +else +subdirs := $(patsubst $(srcdir)/%/,%,$(filter %/,$(wildcard $(srcdir)/*/))) +endif +subdirs := $(filter-out $(SKIPDIRS), $(subdirs)) +endif + +install install-%: subdirs := $(filter-out $(SKIPINSTALL), $(subdirs)) + + +override MAKE += srcdir=$(srcdir) subdirs="$(subdirs)" shared=$(shared) + + +INCLUDEDIRS := $(filter $(INCLUDEDIRS), $(subdirs)) +LIBDIRS := $(filter $(LIBDIRS), $(subdirs)) +MODDIRS := $(filter $(MODDIRS), $(subdirs)) +EXEDIRS := $(filter-out $(INCLUDEDIRS) $(LIBDIRS) $(MODDIRS), $(subdirs)) +MODUSERS := $(filter $(MODUSERS), $(subdirs)) +SBINUSERS := $(filter $(SBINUSERS), $(subdirs)) + +LIBDIRS := $(filter-out $(LIBLAST),$(LIBDIRS)) $(filter $(LIBDIRS),$(LIBLAST)) + + +includes = $(foreach dir, $(INCLUDEDIRS) $(LIBDIRS), $(srcdir)/$(dir)) + +ifneq ($(gcc),) +CPATH = $(subst $(empty) ,:,$(includes)) +export CPATH +else +override CPPFLAGS += $(patsubst %,-I%,$(includes)) +endif + +libraries = $(foreach dir, $(filter lib%,$(LIBDIRS)), $(srcdir)/$(dir)) + +vpath lib%.so $(libraries) +vpath lib%.a $(libraries) + +_libs = $(strip $(foreach _lib,$(LIBDIRS),\ + $(if $(filter lib%,$(_lib)),\ + $(patsubst lib%,-l%,$(_lib)),\ + $(wildcard $(srcdir)/$(_lib)/$(_lib).so \ + $(srcdir)/$(_lib)/$(_lib).a)))) + +override LIBS := $(_libs) -lm $(LIBS) + +ifeq ($(CC),gcc) +LIBRARY_PATH = $(subst $(empty) ,:,$(libraries)) +export LIBRARY_PATH +else +override LIBS += $(patsubst %,-L%,$(libraries)) +endif + +LIBDEPS = $(filter-out -L%,$(LIBS)) + + +# +# SUBDIRS STUFF +# + +ifneq ($(TARGET),.MAIN) + +obj := o +ifeq ($(shared),yes) +ifneq ($(PIC),no) +ifeq ($(filter $(TARGET),$(LIBDIRS) $(MODDIRS) .MODULE),$(TARGET)) +obj := lo +endif +endif +endif + +sources = $(wildcard *.c) +OBJS = $(sources:.c=.$(obj)) + + +.PHONY: dummy all depend install clean force + +dummy: all + + +clean: + rm -f *.o *.a *.lo *.so $(TARGET) core a.out + + +ifneq ($(sources),) +depend: $(sources) + $(DEPEND) $(CFLAGS) $(CPPFLAGS) $^ >.depend +else +depend: + @true +endif + + +%.o: %.c + $(CC) $(CFLAGS) $(CPPFLAGS) -c $*.c + +%.lo: %.c + $(CC) -fPIC $(CFLAGS) $(CPPFLAGS) -o $*.lo -c $*.c + +%.o: %.s + $(AS) -o $*.o $*.s + +%.o: %.S + $(CC) -traditional $(CPPFLAGS) -c $*.S + +# include if it is present only... +ifeq (.depend, $(wildcard .depend)) +include .depend +endif + +endif + +# +# ...end of SUBDIRS STUFF +# + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e302c2b --- /dev/null +++ b/Makefile @@ -0,0 +1,117 @@ +# +# Copyright (c) 2000, 2001 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Global Makefile. +# Global rules, targets etc. +# +# See Make.defines for specific configs. +# + + +srcdir = $(CURDIR) + +override TARGET := .MAIN + +dummy: all + +include ./Make.rules + + +targets = $(EXEDIRS) $(LIBDIRS) $(MODDIRS) + + +# be happy, easy, perfomancy... +.PHONY: $(subdirs) dummy all force +.PHONY: depend indent clean distclean libclean release store libs mods + + +allprereq := $(EXEDIRS) + +ifneq ($(LIBDIRS),) +libs: $(LIBDIRS) +ifneq ($(EXEDIRS),) +$(EXEDIRS): libs +else +allprereq += libs +endif +endif + +ifneq ($(MODDIRS),) +mods: $(MODDIRS) +ifneq ($(MODUSERS),) +$(MODUSERS): mods +else +allprereq += mods +endif +ifneq ($(LIBDIRS),) +$(MODDIRS): libs +endif +endif + +all: $(allprereq) + +depend install: $(allprereq) + +$(foreach goal,$(filter install-%,$(MAKECMDGOALS)),\ + $(eval $(goal): $(patsubst install-%,%,$(goal)))) + + +what = all +depend: what = depend +install install-%: what = install + +ifneq ($(share),) +$(share): shared = yes +endif +ifneq ($(noshare),) +$(noshare): shared = +endif + + +$(targets): mkfile = $(if $(wildcard $@/Makefile),,-f $(srcdir)/default.rules) + +$(targets): force + @$(MAKE) $(mkfile) -C $@ $(what) TARGET=$@ + +force: + + +indent: + find . -type f -name "*.[ch]" -print -exec $(INDENT) {} \; + +clean: + rm -f $(foreach exe, $(EXEDIRS), ./$(exe)/$(exe)) nohup.out + rm -f `find . \( -name "*.[oa]" -o -name "*.[ls]o" \ + -o -name core -o -name "core.[0-9]*" -o -name a.out \) -print` + +distclean: clean + rm -f `find $(foreach dir, $(subdirs), $(dir)/.) \ + \( -name "*.[oa]" -o -name "*.[ls]o" \ + -o -name core -o -name "core.[0-9]*" -o -name a.out \ + -o -name .depend -o -name "_*" -o -name ".cross:*" \) \ + -print` + + +libclean: + rm -f $(foreach lib, $(LIBDIRS), ./$(lib)/$(lib).a ./$(lib)/$(lib).so) + + +# Rules to make whole-distributive operations. +# + +STORE_DIR = $(HOME)/pub + +release release1 release2 release3: + @./chvers.sh $@ + @$(MAKE) store + +store: distclean + @./store.sh $(NAME) $(STORE_DIR) + + @@ -0,0 +1,39 @@ +This is a new modern implementation of the traceroute(8) +utility for Linux systems. + +Traceroute tracks the route packets taken from an IP network on their +way to a given host. It utilizes the IP protocol's time to live (TTL) +field and attempts to elicit an ICMP TIME_EXCEEDED response from each +gateway along the path to the host. + +Main features: +- Full support for both IPv4 and IPv6 protocols +- Several tracerouting methods, including: + * UDP datagrams (including udplite and udp to particlular port) + * ICMP ECHO packets (including dgram icmp sockets) + * TCP SYNs (in general, any TCP request with various flags and options) + * DCCP Request packets + * Generic IP datagrams +- UDP methods do not require root privileges +- Ability to send several probe packets at a time +- Ability to compute a proper time to wait for each probe +- perform AS path lookups for returned addresses +- show ICMP extensions, including MPLS +- perform path MTU discovery automatically +- show guessed number of hops in backward direction +- command line compatible with the original traceroute +- and much more, see traceroute(8) + +This code was written from the scratch, using some ideas of +Olaf Kirch's traceroute, the original implementation of Van Jacobson +(which was long used before) and some current BSD's ones. + +This traceroute requires Linux kernel 2.6 and higher. + +You can try to contact the author at <Dmitry at Butskoy dot name> . + + +Good tracerouting! + +Dmitry Butskoy + @@ -0,0 +1,33 @@ +* It seems that `-l' and `-g' will not work correctly together (IPv6). + + Wait for "flow label" API to be appeared in kernel-headers and + think about it immediately after that... :) + + +* The "final hop" issue. + + All methods, usable when firewalls are present in the network path, + normally use some particular destination port. Most often it is a port + of an already running application. + + It requires that the packet sent should be correct for such an application + (for example, for tracing with udp to port 53, it should be correct DNS + request), and the application normally should answer something on it. + (TCP has no such an issue, as there are just syn to, and ack or reset from). + + In general, we should fill the packet's data depending on the dest port + and protocol. It seems not a task for traceroute itself, it could be + some cmdline option or even external hook... + + +* Think about SCTP method. + + +* Think about "multicast tracerouting" (mrouted(8) and other). + The idea is to increase the room in the mtrace packet step-by-step + (as well as we increase ttl). It seems that if there is no more space + in the probe's room, the mrouted(8) daemon answers immediately, the same + way as if it is a final hop. + + For IPv6 mtrace, there is an RFC draft for this already... + @@ -0,0 +1 @@ +#define VERSION 2.1.5 diff --git a/chvers.sh b/chvers.sh new file mode 100755 index 0000000..8818d7d --- /dev/null +++ b/chvers.sh @@ -0,0 +1,102 @@ +#!/bin/sh +# +# Copyright (c) 2000, 2001 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Change version script. +# Normally invoked by a Makefile. +# +# Changes version info in directory name, in VERSION file +# and rpm spec file (if some of these are used). +# +# `release3' changes: 0.2.7 --> 0.2.8 +# `release2' changes: 0.2.7 --> 0.3.0 +# `release1' changes: 0.2.7 --> 1.0.0 +# etc. +# `release' without a digit increment the last number used. +# +# + + +[ $# -lt 1 ] && { + echo "Usage: $0 release[123...0]" >&2 + exit 2 +} + +level=`expr $1 : '.*\([0-9]\)$'` +[ -z "$level" ] && level=0 + +main_dir=`basename \`pwd\`` + + +# Find current version info. + +dir_v="" +file_v="" + +dir_v=`expr $main_dir : '.*-\([0-9.]*\)$'` +[ -r VERSION ] && file_v=`awk '/^ *#/ { print $3 ; exit; } + /^ *VERSION *=/ {split ($0, a, "="); print a[2]; exit; } + { print $0; exit; }' < VERSION ` +[ -n "$file_v" ] && file_v=`echo $file_v ` # to strip possible spaces + +[ -z "$file_v" -a -z "$dir_v" ] && { + echo "$0: Cannot determine version (use dirname postfix or VERSION file)" >&2 + exit 2 +} + +[ -n "$file_v" -a -n "$dir_v" -a "$dir_v" != "$file_v" ] && { + echo "$0: Different version from dirname postfix and VERSION file" >&2 + exit 2 +} + +version="$dir_v" +[ -z "$version" ] && version="$file_v" + + +# Increment current version, as specified by level. + +new_version=`echo $version | awk '{ + level = '"$level"'; + n = split ($0, a, "."); + if (level == 0) level = n; + for (i = 1; i <= n; i++) { + if (i == level) a[i] = a[i] + 1 ; + else if (i > level) a[i] = 0 ; + } + str = a[1] + for (i = 2; i <= n; i++) str = str "." a[i] + print str +}' 2>/dev/null ` + + +# Adjust VERSION file, if any. + +# it is ugly, because $version contains dots... +[ -n "$file_v" ] && { + sed "s/$version/$new_version/" < VERSION > VERSION.new && mv -f VERSION.new VERSION +} + +# Adjust rpm .spec file, if any. +for spec in *.spec +do + [ -f $spec ] || continue + + grep '^Version:[ ]*'"$version" $spec >/dev/null 2>&1 || continue + sed '/^Version:[ ]*'"$version/ s/$version/$new_version/" < $spec > ${spec}.new && mv -f ${spec}.new $spec +done + + +# Adjust dirname postfix, if any. + +[ -n "$dir_v" ] && { + base=`expr \`pwd\` : '^\(.*\)-'"$version"'$' ` + mv -f ${base}-$version ${base}-$new_version +} + +exit 0 diff --git a/default.rules b/default.rules new file mode 100644 index 0000000..9b6c822 --- /dev/null +++ b/default.rules @@ -0,0 +1,193 @@ +# +# Copyright (c) 2000, 2001, 2007 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Default a target`s Makefile. Useful for any things... +# For some changes, copy it to a dir where needed and edit that copy. +# + + +ifndef srcdir +rul := Make.rules +path := $(word 1, $(wildcard ../$(rul) ../../$(rul))) +ifeq ($(path),../$(rul)) +srcdir = $(dir $(CURDIR)) +endif +ifeq ($(path),../../$(rul)) +srcdir = $(dir $(patsubst %/,%,$(dir $(CURDIR)))) +TARGET = .MODULE +endif +ifeq ($(srcdir),) +$(error Cannot find srcdir (where $(rul) exists)) +endif +endif + +ifndef TARGET +TARGET = $(notdir $(CURDIR)) +endif + +include $(srcdir)/Make.rules + + +# +# LIBDIRS DEFAULTS +# +ifeq ($(filter $(TARGET),$(LIBDIRS)),$(TARGET)) + +ifeq ($(shared),yes) + +all: $(TARGET).so + +$(TARGET).so: $(OBJS) + $(CC) -shared -o $@ -Wl,-soname -Wl,$@ $(OBJS) + +else + +all: $(TARGET).a + +$(TARGET).a: $(OBJS) + rm -f $@ + $(AR) -rc $@ $(OBJS) + $(RANLIB) $@ + +endif + +install_what = $(wildcard $(TARGET).so $(TARGET).a) +install_dir = $(libdir) +install_includes = $(wildcard *.h) + +cross_stamp_file = $(wildcard .cross:*) +ifeq ($(cross_stamp_file),) +cross_stamp = .cross: +else +cross_stamp = $(cross_stamp_file) +endif +new_stamp = .cross:$(subst $(empty) ,:,$(CROSS)) +ifneq ($(cross_stamp),$(new_stamp)) + +$(OBJS): force + +force: + rm -f $(cross_stamp) > $(new_stamp) + @rm -f *.o *.a *.lo *.so + +endif + +endif + + +# +# MODDIRS DEFAULTS +# +ifeq ($(filter $(TARGET),$(MODDIRS)),$(TARGET)) + +modules = $(filter-out $(SKIPDIRS), $(patsubst %/,%,$(wildcard */))) + +.PHONY: $(modules) + +what = all +depend: what = depend +clean: what = clean + +all depend clean: $(modules) + +$(modules): mkfile = $(if $(wildcard $@/Makefile),,-f $(srcdir)/default.rules) + +$(modules): force + @$(MAKE) $(mkfile) -C $@ $(what) TARGET=.MODULE MODULE=$@ + +force: + +install_what = $(wildcard *.so) +install_dir = $(libexecdir) + +endif + + +# +# MODDIRS` subdirectories (i.e., modules) defaults +# +ifeq ($(TARGET),.MODULE) + +ifndef MODULE +MODULE = $(notdir $(CURDIR)) +endif + +ifeq ($(shared),yes) + +all: ../$(MODULE).so + +../$(MODULE).so: $(OBJS) $(LIBDEPS) + $(CC) -shared -o $@ $(OBJS) $(LIBS) + +else + +all: ../$(MODULE).o + +../$(MODULE).o: $(OBJS) + $(LD) -r -o $@ $(OBJS) + +endif + +endif + + +# +# EXEDIRS DEFAULTS (for usual executables) +# +ifeq ($(filter $(TARGET),$(EXEDIRS)),$(TARGET)) + +ifeq ($(filter $(TARGET),$(MODUSERS)),$(TARGET)) +MOD_OBJS = $(wildcard $(foreach dir,$(MODDIRS),$(srcdir)/$(dir)/*.o)) +ifeq ($(shared),yes) +override LDFLAGS := -rdynamic $(LDFLAGS) +endif +install_includes= $(wildcard $(foreach dir,$(INCLUDEDIRS),$(srcdir)/$(dir)/*.h)) +install_includes:= $(filter-out $(srcdir)/include/version.h,$(install_includes)) +else +MOD_OBJS = +endif + +all: $(TARGET) + +$(TARGET): $(OBJS) $(MOD_OBJS) $(LIBDEPS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(MOD_OBJS) $(LIBS) + +install_what = $(wildcard $(TARGET)) +install_dir = $(if $(filter $(TARGET),$(SBINUSERS)),$(sbindir),$(bindir)) + +endif + + +# +# Install stuff +# + +install_manuals = $(wildcard *.[0-9] *.[0-9]?) + + +install: +ifneq ($(install_dir),) + @mkdir -p $(DESTDIR)$(install_dir) +endif +ifneq ($(install_what),) + $(INSTALL) $(install_what) $(DESTDIR)$(install_dir) +endif +ifneq ($(install_includes),) + @mkdir -p $(DESTDIR)$(includedir) + $(INSTALL) $(install_includes) $(DESTDIR)$(includedir) +endif + @true +ifneq ($(install_manuals),) + $(foreach man,$(install_manuals),$(call inst_man,$(man))) +define inst_man +@mkdir -p $(DESTDIR)$(mandir)/man$(patsubst .%,%,$(suffix $(1))) + cp -f $(1) $(DESTDIR)$(mandir)/man$(patsubst .%,%,$(suffix $(1))) + +endef +endif diff --git a/include/version.h b/include/version.h new file mode 100644 index 0000000..af1b427 --- /dev/null +++ b/include/version.h @@ -0,0 +1,9 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include "../VERSION" diff --git a/libsupp/clif.c b/libsupp/clif.c new file mode 100644 index 0000000..2c21326 --- /dev/null +++ b/libsupp/clif.c @@ -0,0 +1,1259 @@ +/* + Copyright (c) 2000, 2003 Dmitry Butskoy + <dmitry@butskoy.name> + License: LGPL v2.1 or any later + + See COPYING.LIB for the status of this software. +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> + +#include "clif.h" + + +#if 1 /* Bad idea, anyway... */ +#define MAX_ARGC_NUMBER 256 +typedef unsigned char _CLIF_index; +#else +#define MAX_ARGC_NUMBER (4096 / 5 + 1) /* POSIX ARG_MAX >= 4096 ... */ +typedef unsigned short _CLIF_index; +#endif + + +/* This is needed for some print info functions. + This is ugly for thread-safe (is it really actual on program invoking?), + and for several CLIF_parse_cmdline invoking... But foo on this. Yeah... +*/ +static struct { + int argc; + char **argv; + CLIF_option *option_list; + CLIF_argument *argument_list; + unsigned int parse_flags; +} curr = { 0, }; + + +static void err_report (const char *format, ...) { + va_list ap; + + if (curr.parse_flags & CLIF_SILENT) + return; + + va_start (ap, format); + + vfprintf (stderr, format, ap); + + va_end (ap); + + fprintf (stderr, "\n"); + + return; +} + + +/* info generation stuff... */ + +#define SHORT_PLUS_MINUS "+/-" +#define LONG_PLUS_MINUS "++/--" +#define EXCL_DLM " | " + +static char *show_short (const CLIF_option *optn) { + static char buf[80]; + char *p = buf; + unsigned int flags = optn->flags | curr.parse_flags; + + if (optn->function_plus) { + if (!optn->function) *p++ = '+'; + else { + strcpy (p, SHORT_PLUS_MINUS); + p += sizeof (SHORT_PLUS_MINUS) - 1; + } + } else + *p++ = '-'; + + *p++ = optn->short_opt[0]; + + if (optn->arg_name) { + char *endp = buf + sizeof (buf) - sizeof (",...]"); + const char *s; + + if (!(flags & _CLIF_STRICT_JOIN_ARG)) *p++ = ' '; + if (flags & CLIF_OPTARG) *p++ = '['; + + s = optn->arg_name; + while (*s && p < endp) *p++ = *s++; + + if (flags & CLIF_SEVERAL) { + strcpy (p, ",..."); + p += sizeof (",...") - 1; /* last '\0' ... */ + } + + if (flags & CLIF_OPTARG) *p++ = ']'; + } + + *p = '\0'; + + return buf; +} + +static char *show_long (const CLIF_option *optn) { + static char buf[80]; + char *p = buf; + char *endp; + const char *s; + unsigned int flags = optn->flags | curr.parse_flags; + + + if (!(flags & _CLIF_STRICT_KEYWORD)) { + + if (!(flags & _CLIF_STRICT_ONEDASH)) { + if (optn->function_plus) { + if (!optn->function) { *p++ = '+'; *p++ = '+'; } + else { + strcpy (p, LONG_PLUS_MINUS); + p += sizeof (LONG_PLUS_MINUS) - 1; + } + } else { *p++ = '-'; *p++ = '-'; } + + } else { + if (optn->function_plus) { + if (!optn->function) *p++ = '+'; + else { + strcpy (p, SHORT_PLUS_MINUS); + p += sizeof (SHORT_PLUS_MINUS) - 1; + } + } else *p++ = '-'; + } + } + + s = optn->long_opt; + endp = buf + sizeof (buf) - sizeof (" ["); + while (*s && p < endp) *p++ = *s++; + + if (optn->arg_name) { + + if (flags & _CLIF_STRICT_NOEQUAL) { + *p++ = ' '; + if (flags & CLIF_OPTARG) *p++ = '['; + } else { + if (flags & CLIF_OPTARG) *p++ = '['; + *p++ = '='; + } + + s = optn->arg_name; + endp = buf + sizeof (buf) - sizeof (",...]"); + while (*s && p < endp) *p++ = *s++; + + if (flags & CLIF_SEVERAL) { + strcpy (p, ",..."); + p += sizeof (",...") - 1; /* last '\0' ... */ + } + + if (flags & CLIF_OPTARG) *p++ = ']'; + } + + *p = '\0'; + + return buf; +} + +static char *show_excl (const CLIF_option *option_list, int *cnt_p) { + static char buf[256]; + const CLIF_option *optn; + char *p = buf; + char *endp = buf + sizeof (buf) - sizeof (EXCL_DLM); + int excl_cnt = 0; + + *p = '\0'; + if (cnt_p) *cnt_p = 0; + if (!option_list) return buf; + + for (optn = option_list; optn->short_opt || optn->long_opt; optn++) { + char *s; + + if (!(optn->flags & CLIF_EXCL)) continue; + + if (optn->short_opt) s = show_short (optn); + else s = show_long (optn); + + if (excl_cnt > 0) { /* i.e., second etc... */ + strcpy (p, EXCL_DLM); + p += sizeof (EXCL_DLM) - 1; + } + + while (*s && p < endp) *p++ = *s++; + + excl_cnt++; + } + + *p = '\0'; + + if (cnt_p) *cnt_p = excl_cnt; + + return buf; +} + + +static int is_keyword (const CLIF_option *optn) { + unsigned int flags = optn->flags | curr.parse_flags; + + return (flags & _CLIF_STRICT_KEYWORD) != 0; +} + + +static void err_bad_opt (const char *arg, char c, int n) { + char sym = (*arg == '+') ? '+' : '-'; + + if (c) err_report ("Bad option `%c%c' (argc %d)", sym, c, n); + else { + char *p = strchr (arg, '='); + const char *type = (*arg == sym) ? "option" : "keyword"; + + if (p) + err_report ("Bad %s `%s' (with arg `%s') (argc %d)", + type, arg, p + 1, n); + else + err_report ("Bad %s `%s' (argc %d)", type, arg, n); + } +} + +static void err_bad_arg (const CLIF_option *optn, char c, int n) { + CLIF_option tmp = *optn; + char ss[80]; + char *s; + + tmp.arg_name = NULL; + + if (c) { + s = show_short (&tmp); /* always without arg... */ + strncpy (ss, s, sizeof (ss)); + s = show_short (optn); + } else { + s = show_long (&tmp); /* always without arg... */ + strncpy (ss, s, sizeof (ss)); + s = show_long (optn); + } + + err_report ("%s `%s' (argc %d) requires an argument: `%s'", + (c || !is_keyword (optn)) ? "Option" : "Keyword", ss, n, s); +} + +static void err_bad_res (const CLIF_option *optn, char c, + const char *opt_arg, int n) { + CLIF_option tmp = *optn; + char *ss; + const char *type; + + tmp.arg_name = NULL; + + if (c) { + ss = show_short (&tmp); + type = "option"; + } else { + ss = show_long (&tmp); + type = is_keyword (optn) ? "keyword" : "option"; + } + + if (optn->arg_name) + err_report ("Cannot handle `%s' %s with arg `%s' (argc %d)", + ss, type, opt_arg, n); + else + err_report ("Cannot handle `%s' %s (argc %d)", ss, type, n); +} + +static void err_bad_excl (const CLIF_option *optn, char c, int n) { + CLIF_option tmp = *optn; + char *ss; + char *excl = show_excl (curr.option_list, 0); + /* Note: show_(short|long)() nested!!! */ + + tmp.arg_name = NULL; + + if (c) ss = show_short (&tmp); + else ss = show_long (&tmp); + + err_report ("%s `%s' (argc %d): Only one of:\n %s\n" + "may be specified.", + (c || !is_keyword (optn)) ? "Option" : "Keyword", + ss, n, excl); +} + + +static CLIF_option *find_long (char *arg, char **arg_p, + unsigned int match, unsigned int nomatch) { + CLIF_option *optn; + CLIF_option *abbrev = NULL; + char *abbrev_arg = NULL; + int abbrev_found = 0; + + + for (optn = curr.option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + char *a; + const char *o; + unsigned int flags; + + if (!optn->long_opt) continue; + + flags = curr.parse_flags | optn->flags; + if (flags & nomatch) continue; + if (match && !(flags & match)) continue; /* XXX: optimize it */ + + + for (a = arg, o = optn->long_opt; *o && *a == *o; a++, o++) ; + + if (*a == '\0' || + (*a == '=' && optn->arg_name && !(flags & _CLIF_STRICT_NOEQUAL)) + ) { /* looks like end of option... */ + + if (!*o) { /* explicit match found */ + if (*a == '=' && arg_p) *arg_p = a + 1; + return optn; + } + + if ((flags & CLIF_ABBREV) && + (a - arg >= CLIF_MIN_ABBREV) + ) { + if (!abbrev_found) { + abbrev_found = 1; + abbrev = optn; + if (*a == '=') abbrev_arg = a + 1; + } else /* several possibility case... */ + abbrev = NULL; + } + } + } + + if (abbrev) { /* implicit match found */ + if (abbrev_arg && arg_p) *arg_p = abbrev_arg; + return abbrev; + } else /* no match found */ + return NULL; +} + +static int check_sym (const CLIF_option *optn, char sym) { + + if (sym == '+') { + if (!optn->function_plus) return -1; + } + else if (sym == '-') { + if (!optn->function && optn->function_plus) + return -1; + } + + return 0; +} + +static int call_function (CLIF_option *optn, char *opt_arg, char sym) { + int (*function) (CLIF_option *, char *); + + function = (sym == '+') ? optn->function_plus : optn->function; + + if (!function) return 0; + + if (opt_arg && ((optn->flags | curr.parse_flags) & CLIF_SEVERAL)) { + char tmp[80]; + char *t; + char *endt = tmp + sizeof (tmp); + + while (*opt_arg) { + + t = tmp; + while (t < endt && *opt_arg && + *opt_arg != ' ' && *opt_arg != '\t' && *opt_arg != ',' + ) *t++ = *opt_arg++; + + if (t >= endt) return -1; + + *t = '\0'; + + if (function (optn, tmp) < 0) return -1; + + while (*opt_arg == ' ' || *opt_arg == '\t' || *opt_arg == ',') + opt_arg++; + } + + return 0; + } + + return function (optn, opt_arg); +} + + +int CLIF_parse_cmdline (int argc, char *argv[], + CLIF_option *option_list, + CLIF_argument *argument_list, + unsigned int parse_flags) { + int i, j; + CLIF_option *optn; + CLIF_argument *argm; + int num_args = 0; + int num_argm = 0, strict_beg = 0, strict_end = 0; + _CLIF_index arg_n[MAX_ARGC_NUMBER]; + unsigned int dirty_flags = 0; + int dirty_plus = 0; + int exclusive_cnt = 0; + int posix = getenv ("POSIXLY_CORRECT") != NULL || + (parse_flags & CLIF_POSIX); + + curr.argc = argc; + curr.argv = argv; + curr.option_list = option_list; + curr.argument_list = argument_list; + curr.parse_flags = parse_flags; + + if (argc <= 1 && (parse_flags & CLIF_HELP_EMPTY)) { + CLIF_current_help (); + exit (0); + } + + /* Scan argument_list for check and some info. */ + + if (argument_list) { + enum stages { STRICT_BEG, OPTIONAL, STRICT_END }; + int stage = STRICT_BEG; + + for (argm = argument_list; argm->name; argm++) { + + if (argm->flags & CLIF_STRICT) { + + if (stage == STRICT_BEG) strict_beg++; + else if (stage == OPTIONAL) { + stage = STRICT_END; + strict_end++; + } + else if (stage == STRICT_END) + strict_end++; + } else { + if (stage == STRICT_BEG) stage = OPTIONAL; + else if (stage == STRICT_END) { + err_report ("Incorrect argument list set in program " + "source: more than one optional area."); + return -1; + } + } + + num_argm++; + } + } + + /* Scan option_list for some info. */ + if (option_list) { + + dirty_flags = parse_flags; + + for (optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + dirty_flags |= optn->flags; + if (optn->function_plus) dirty_plus = 1; + } + } + + if (dirty_flags & CLIF_EXCL) + exclusive_cnt = 1; /* only one is allowed... */ + + + /* Go ! Store arguments, parse options. */ + + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + char *opt_arg = NULL; + char sym = '-'; + + if (!option_list) + goto handle_arg; + + if (*arg == '+' && dirty_plus) + sym = '+'; + + if (*arg != sym) { /* argument or keyword */ + + if (dirty_flags & CLIF_MAY_KEYWORD) { + optn = find_long (arg, &opt_arg, CLIF_MAY_KEYWORD, 0); + if (optn) goto long_found; + } + + if (num_args == 0 && (parse_flags & CLIF_FIRST_GROUP)) { + /* ugly... */ + parse_flags &= ~CLIF_FIRST_GROUP; + dirty_flags &= ~CLIF_FIRST_GROUP; /* to be correct */ + + goto handle_short; + } + + /* else it is an argument */ + goto handle_arg; + + } + else if (*++arg == sym) { /* `--' - long option */ + arg++; + + if (*arg == sym || /* `---' - let it be not option... */ + (parse_flags & (_CLIF_STRICT_KEYWORD|_CLIF_STRICT_ONEDASH)) + ) { + arg -= 2; + goto handle_arg; /* not option anyway */ + } + + optn = find_long (arg, &opt_arg, 0, + _CLIF_STRICT_KEYWORD | _CLIF_STRICT_ONEDASH); + if (optn) goto long_found; + + /* XXX: May be allow only for `--', not `++' too... */ + if (!*arg && sym == '-') { /* `--' and no empty longoption */ + option_list = NULL; /* POSIX way... */ + continue; + } + + /* XXX: or treat as an argument sometimes??? */ + err_bad_opt (argv[i], 0, i); + return -1; + } + else { /* short option, or several short options... */ + + if (dirty_flags & CLIF_MAY_ONEDASH) { + optn = find_long (arg, &opt_arg, CLIF_MAY_ONEDASH, 0); + if (optn) goto long_found; + } + + if (!*arg) { /* POSIX say: only "stdout specification"... */ + arg--; + goto handle_arg; + } + + goto handle_short; + } + + + long_found: + if (check_sym (optn, sym) < 0) { /* Oops... */ + err_bad_opt (argv[i], 0, i); + return -1; + } + + if (optn->flags & CLIF_EXCL) { + if (!exclusive_cnt) { + err_bad_excl (optn, 0, i); + return -1; + } + exclusive_cnt--; + } + + if (optn->arg_name && !opt_arg) { + unsigned int flags = optn->flags | parse_flags; + + if (++i >= argc || + !(flags & CLIF_MAY_NOEQUAL) + ) { /* missing opt arg */ + i--; + + if (!(flags & CLIF_OPTARG)) { + err_bad_arg (optn, 0, i); + return -1; + } + + opt_arg = NULL; + } else + opt_arg = argv[i]; + + } + + + if (call_function (optn, opt_arg, sym) < 0) { + err_bad_res (optn, 0, opt_arg, i); + return -1; + } + + if (optn->flags & CLIF_EXIT) + exit (0); + + continue; + + + handle_arg: + if (argument_list) { + if (i < MAX_ARGC_NUMBER) /* XXX: ugly, better report */ + arg_n[num_args++] = i; + } else { + err_report ("`%s' (argc %d): arguments are not allowed", + argv[i], i); + return -1; + } + + /* POSIX say: No more options after args... */ + if (posix) option_list = NULL; /* geniously... */ + + continue; + + + handle_short: + + opt_arg = NULL; + + do { + + for (optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + if (optn->short_opt && optn->short_opt[0] == *arg) + break; + } + if (!optn->short_opt || + check_sym (optn, sym) < 0 + ) { + err_bad_opt (argv[i], *arg, i); + return -1; + } + + if (optn->flags & CLIF_EXCL) { + if (!exclusive_cnt) { + err_bad_excl (optn, *arg, i); + return -1; + } + exclusive_cnt--; + } + + + if (optn->arg_name) { + unsigned int flags = parse_flags | optn->flags; + + if (arg[1] == '\0') { /* a last one */ + + /* POSIX say: an option with arg cannot be grouped. */ + if (posix && arg != argv[i] && arg[-1] != sym) { + err_bad_arg (optn, *arg, i); /* good way? */ + return -1; + } + + if (++i >= argc || + (flags & _CLIF_STRICT_JOIN_ARG) + ) { + i--; + + if (!(flags & CLIF_OPTARG)) { + err_bad_arg (optn, *arg, i); + return -1; + } + + opt_arg = NULL; + } else + opt_arg = argv[i]; + } + else if ((arg == argv[i] || arg[-1] == sym) && + (flags & CLIF_MAY_JOIN_ARG) + ) { + opt_arg = ++arg; + } + else { /* inside a group... */ + if (!(flags & CLIF_OPTARG) || + (flags & CLIF_MAY_JOIN_ARG) + ) { + err_bad_arg (optn, *arg, i); + return -1; + } + + opt_arg = NULL; + } + } + + if (call_function (optn, opt_arg, sym) < 0) { + err_bad_res (optn, optn->short_opt[0], opt_arg, i); + return -1; + } + + if (optn->flags & CLIF_EXIT) + exit (0); + + } while (!opt_arg && *++arg); + + } /* for ( ... ) */ + + + if ((parse_flags & CLIF_STRICT_EXCL) && exclusive_cnt != 0) { + err_report ("One of these must be specified:\n %s\n", + show_excl (option_list, 0)); + return -1; + } + + + /* Now, after *ALL* options, handle arguments, if any. */ + + if (num_args < strict_beg + strict_end) { + /* Missing some needed arguments. */ + + if (num_args < strict_beg) argm = argument_list + num_args; + else + argm = argument_list + + ((num_args - strict_beg) + (num_argm - strict_end)); + + if (num_args == strict_beg + strict_end - 1) + err_report ("Specify \"%s\" missing argument.", argm->name); + else + err_report ("Specify \"%s\" and other missing arguments.", + argm->name); + return -1; + } + + if (num_args > 0) { + _CLIF_index argm_index[MAX_ARGC_NUMBER]; + + /* assing argm (by index) for each arg... */ + + for (i = 0, j = 0; i < strict_beg; i++, j++) + argm_index[i] = j; + for (i = num_args - strict_end, j = num_argm - strict_end; + i < num_args; i++, j++ + ) argm_index[i] = j; + for (i = strict_beg, j = strict_beg; + i < num_args - strict_end && j < num_argm - strict_end; + i++ + ) { + argm_index[i] = j; + if (!(argument_list[j].flags & CLIF_MORE)) + j++; + } + + if (i < num_args - strict_end) { /* there are extra args... */ + err_report ("Extra arg `%s' (position %d, argc %d)", + argv[arg_n[i]], i + 1, arg_n[i]); + return -1; + } + + if (j < num_argm - strict_end && + !(argument_list[j].flags & CLIF_MORE) && + /* ...i.e, there are some missing optional args... */ + (argument_list[j].flags & CLIF_ACC_PREV) + ) { + if (j == 0) + err_report ("Incorrect argument list set: first arg " + "cannot be `accompanied with previous'."); + else + err_report ("Arg \"%s\" must be specified because " + "\"%s\" `%s' is used.", argument_list[j].name, + argument_list[j - 1].name, argv[arg_n[i - 1]]); + return -1; + } + + if (argm_index[--i] == j && + /* above is true only after OPTIONAL area scan + and when `j' is stopped on CLIF_MORE */ + ++j < num_argm - strict_end + /* i.e: there is a *last* one (after CLIF_MORE) + in the OPTIONAL area */ + ) argm_index[i] = j; /* *last* is better than *more* */ + + + /* ...and work now */ + + for (i = 0; i < num_args; i++) { + argm = argument_list + argm_index[i]; + + if (argm->function && + argm->function (argm, argv[arg_n[i]], i) < 0 + ) { + err_report ("Cannot handle \"%s\" cmdline arg `%s' " + "on position %d (argc %d)", + argm->name, argv[arg_n[i]], i + 1, arg_n[i]); + return -1; + } + } + + /* That`s all. */ + } + + return 0; +} + + +static void box_output (int start, int left, int width, const char *str, + const char *arg_name) { + char *p, *endp, *s; + int l; + char buf[1024]; + char spacer[128]; /* assume it is enough */ + + if (left > sizeof (spacer) - 2) left = sizeof (spacer) - 2; + if (width > sizeof (buf) - 1) width = sizeof (buf) - 1; + + spacer[0] = '\n'; + memset (spacer + 1, ' ', left); + spacer[left + 1] = '\0'; + + + l = left - start; + if (l > 0) { + memset (buf, ' ', l); + buf[l] = '\0'; + fprintf (stderr, "%s", buf); + } else + fprintf (stderr, "%s", spacer); + + + endp = buf + width; + + p = buf; + + while (*str) { + + while (*str && p < endp) { + + if (*str == '%' && arg_name) { + if (str[1] == '%') { + *p++ = '%'; + str += 2; + continue; + } + else if (str[1] == 's') { + const char *a = arg_name; + + while (*a && p < endp) *p++ = *a++; + str += 2; + continue; + } + } + + *p++ = *str++; + } + + *p = '\0'; + + if (p < endp) break; + + + while (p > buf && *p != ' ' && *p != '\t') p--; + if (p <= buf) return; /* foo on you */ + + *p = '\0'; + fprintf (stderr, "%s", buf); + fprintf (stderr, "%s", spacer); + + p++; + for (s = buf; *p; *s++ = *p++) ; + *s = '\0'; + p = s; + } + + + fprintf (stderr, "%s", buf); + + return; +} + + +#define SHORT_LONG_DLM " " +#define OPT_START_DLM " " +#define OPT_FIELD_WIDTH 30 + +#define ARG_MARK_STRICT "+ " +#define ARG_MARK_GROUP0 " . " +#define ARG_MARK_GROUP " ' " +#define ARG_MARK_OPT " " +#define ARG_FIELD_WIDTH 20 + +#define SCREEN_WIDTH 80 + + +void CLIF_print_options (const char *header, + const CLIF_option *option_list) { + const CLIF_option *optn; + char *excl; + int excl_cnt = 0; + + /* Print a header string, if present... */ + if (header) fprintf (stderr, "%s\n", header); + + if (!option_list) return; + + + for (optn = option_list; optn->short_opt || optn->long_opt; optn++) { + int len; + + /* generate and print an option usage */ + + if (optn->short_opt) { + if (optn->long_opt) + len = fprintf (stderr, OPT_START_DLM "%s" + SHORT_LONG_DLM "%s", + show_short (optn), show_long (optn)); + else + len = fprintf (stderr, OPT_START_DLM "%s", + show_short (optn)); + } else + len = fprintf (stderr, OPT_START_DLM "%s", show_long (optn)); + + + /* print a help string, if present */ + + if (optn->help_string) + box_output (len, OPT_FIELD_WIDTH, + SCREEN_WIDTH - OPT_FIELD_WIDTH, + optn->help_string, optn->arg_name); + + fprintf (stderr, "\n"); /* a last one */ + } + + excl = show_excl (option_list, &excl_cnt); + if (excl_cnt > 0) { + + if (excl_cnt == 1) { + if ((curr.parse_flags & CLIF_STRICT_EXCL) && + curr.option_list == option_list + ) fprintf (stderr, "Anyway `%s' must be specified.\n", excl); + else /* simple ordinary option, because excl_cnt == 1 ... */; + } else + fprintf (stderr, "Only one of these may be specified:\n" + " %s\n", excl); + } + + return; +} + + +void CLIF_print_arguments (const char *header, + const CLIF_argument *argument_list) { + const CLIF_argument *argm; + + + if (!argument_list) return; + + /* Print a header string, if present... */ + if (header) fprintf (stderr, "%s\n", header); + + + for (argm = argument_list; argm->name; argm++) { + int len; + + if (argm->flags & CLIF_STRICT) + len = fprintf (stderr, ARG_MARK_STRICT "%s", argm->name); + else if (argm->flags & CLIF_MORE) + len = fprintf (stderr, ARG_MARK_OPT "%s ...", argm->name); + else if (argm->flags & CLIF_ACC_PREV) + len = fprintf (stderr, ARG_MARK_GROUP "%s", argm->name); + else if ((argm + 1)->name && ((argm + 1)->flags & CLIF_ACC_PREV)) + len = fprintf (stderr, ARG_MARK_GROUP0 "%s", argm->name); + else + len = fprintf (stderr, ARG_MARK_OPT "%s", argm->name); + + if (argm->help_string) + box_output (len, ARG_FIELD_WIDTH, + SCREEN_WIDTH - ARG_FIELD_WIDTH, + argm->help_string, argm->name); + + fprintf (stderr, "\n"); + } + + return; +} + + +void CLIF_print_usage (const char *header, const char *progname, + const CLIF_option *option_list, + const CLIF_argument *argument_list) { + + if (!progname && curr.argv) + progname = curr.argv[0]; + + if (!header) { + if (progname) + fprintf (stderr, "Usage: %s", progname); + else + fprintf (stderr, "Command line options:"); + } else { + if (progname) + fprintf (stderr, "%s\n" OPT_START_DLM "%s", header, progname); + else + fprintf (stderr, "%s", header); + } + + + if (option_list) { + const CLIF_option *optn; + char m_buf[256], p_buf[256], mp_buf[256]; + char *m = m_buf, *p = p_buf, *mp = mp_buf; + char *end_m = m_buf + sizeof (m_buf) - 1; + char *end_p = p_buf + sizeof (p_buf) - 1; + char *end_mp = mp_buf + sizeof (mp_buf) - 1; + char *excl; + int excl_cnt = 0; + + + /* first, show exclusive option list, if any... */ + + excl = show_excl (option_list, &excl_cnt); + if (excl_cnt > 0) { + if ((curr.parse_flags & CLIF_STRICT_EXCL) && + curr.option_list == option_list + ) { + if (excl_cnt == 1) + fprintf (stderr, " %s", excl); + else + fprintf (stderr, " { %s }", excl); + } else + fprintf (stderr, " [ %s ]", excl); + } + + + /* second, find short options without arguments... */ + + for (optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + /* We don`t exclude CLIF_EXTRA hear: + simple one char don`t eat a lot of space... + */ + + if (!optn->short_opt || + optn->arg_name || + (optn->flags & CLIF_EXCL) + ) continue; + + if (optn->function_plus) { + if (optn->function) { + if (mp < end_mp) *mp++ = optn->short_opt[0]; + } else { + if (p < end_p) *p++ = optn->short_opt[0]; + } + } else { + if (m < end_m) *m++ = optn->short_opt[0]; + } + } + + if (m > (char *) m_buf) { + *m = '\0'; + fprintf (stderr, " [ -%s ]", m_buf); + } + if (p > (char *) p_buf) { + *p = '\0'; + fprintf (stderr, " [ +%s ]", p_buf); + } + if (mp > (char *) mp_buf) { + *mp = '\0'; + fprintf (stderr, " [ " SHORT_PLUS_MINUS "%s ]", mp_buf); + } + + + /* third, print all another... */ + + for (optn = option_list; + optn->short_opt || optn->long_opt; + optn++ + ) { + if (optn->flags & CLIF_EXTRA) continue; + + if (optn->flags & CLIF_EXCL) + continue; /* already handled */ + + if (optn->short_opt) { + if (optn->arg_name) + fprintf (stderr, " [ %s ]", show_short (optn)); + else + /* already handled */; + } else + fprintf (stderr, " [ %s ]", show_long (optn)); + } + } + + + if (argument_list) { + const CLIF_argument *argm; + int deep = 0; + + for (argm = argument_list; argm->name; argm++) { + + if (argm->flags & CLIF_STRICT) { + if (deep > 0) { + fputc (' ', stderr); + while (deep--) fputc (']', stderr); + deep = 0; + } + + fprintf (stderr, " %s", argm->name); + } else { + if (argm->flags & CLIF_MORE) + fprintf (stderr, " [ %s ...", argm->name); + else if (argm->flags & CLIF_ACC_PREV) { + fprintf (stderr, " %s", argm->name); + --deep; /* ugly, but easy */ + } else + fprintf (stderr, " [ %s", argm->name); + + deep++; + } + } + + if (deep > 0) { + fputc (' ', stderr); + while (deep--) fputc (']', stderr); + } + } + + + fprintf (stderr, "\n"); +} + + +int CLIF_current_help (void) { + + if (!curr.argc) return -1; /* i.e., not inited... */ + + CLIF_print_usage ("Usage:", curr.argv[0], curr.option_list, + curr.argument_list); + + if (curr.option_list) + CLIF_print_options ("Options:", curr.option_list); + + if (curr.argument_list) + CLIF_print_arguments ("\nArguments:", curr.argument_list); + + return 0; +} + + +/* Common useful option handlers. */ + +int CLIF_version_handler (CLIF_option *optn, char *arg) { + + if (!optn->data) return -1; + + fprintf (stderr, "%s\n", ((char *) optn->data)); + + return 0; /* be happy */ +} + + +int CLIF_set_flag (CLIF_option *optn, char *arg) { + + if (!optn->data) return -1; + + *((int *) optn->data) = 1; + + return 0; +} + + +int CLIF_unset_flag (CLIF_option *optn, char *arg) { + + if (!optn->data) return -1; + + *((int *) optn->data) = 0; + + return 0; +} + + +static int set_string (char **data, char *arg) { + + if (!data) return -1; + + *data = arg; + + return 0; +} + +int CLIF_set_string (CLIF_option *optn, char *arg) { + + return set_string (optn->data, arg); +} + +int CLIF_arg_string (CLIF_argument *argm, char *arg, int index) { + + return set_string (argm->data, arg); +} + + +static int set_int (int *data, char *arg) { + char *q; + + if (!data) return -1; + + *data = (int) strtol (arg, &q, 0); + + return (q == arg || *q) ? -1 : 0; +} + +static int set_uint (unsigned int *data, char *arg) { + char *q; + + if (!data) return -1; + + *data = (unsigned int) strtoul (arg, &q, 0); + + return (q == arg || *q) ? -1 : 0; +} + +static int set_double (double *data, char *arg) { + char *q; + + if (!data) return -1; + + *data = strtod (arg, &q); + + return (q == arg || *q) ? -1 : 0; +} + + +int CLIF_set_int (CLIF_option *optn, char *arg) { + + return set_int (optn->data, arg); +} + +int CLIF_set_uint (CLIF_option *optn, char *arg) { + + return set_uint (optn->data, arg); +} + +int CLIF_set_double (CLIF_option *optn, char *arg) { + + return set_double (optn->data, arg); +} + +int CLIF_arg_int (CLIF_argument *argm, char *arg, int index) { + + return set_int (argm->data, arg); +} + +int CLIF_arg_uint (CLIF_argument *argm, char *arg, int index) { + + return set_uint (argm->data, arg); +} + +int CLIF_arg_double (CLIF_argument *argm, char *arg, int index) { + + return set_double (argm->data, arg); +} + + +int CLIF_call_func (CLIF_option *optn, char *arg) { + + if (!optn->data) return -1; + + if (optn->arg_name) { + int (*func) (char *) = optn->data; + + return func (arg); + } else { + int (*func) (void) = optn->data; + + return func (); + } +} + +int CLIF_arg_func (CLIF_argument *argm, char *arg, int index) { + int (*func) (char *, int); + + if (!argm->data) return -1; + + func = (int (*) (char *, int)) argm->data; + + return func (arg, index); +} + diff --git a/libsupp/clif.h b/libsupp/clif.h new file mode 100644 index 0000000..6b7f3f0 --- /dev/null +++ b/libsupp/clif.h @@ -0,0 +1,121 @@ +/* + Copyright (c) 2000, 2003 Dmitry Butskoy + <dmitry@butskoy.name> + License: LGPL v2.1 or any later + + See COPYING.LIB for the status of this software. +*/ + +#ifndef _CLIF_H +#define _CLIF_H + + +typedef struct CLIF_option_struct CLIF_option; +struct CLIF_option_struct { + const char *short_opt; + const char *long_opt; + const char *arg_name; + const char *help_string; + int (*function) (CLIF_option *optn, char *arg); + void *data; + int (*function_plus) (CLIF_option *optn, char *arg); + unsigned int flags; +}; +#define CLIF_END_OPTION { 0, 0, 0, 0, 0, 0, 0, 0 } + +typedef struct CLIF_argument_struct CLIF_argument; +struct CLIF_argument_struct { + const char *name; + const char *help_string; + int (*function) (CLIF_argument *argm, char *arg, int index); + void *data; + unsigned int flags; +}; +#define CLIF_END_ARGUMENT { 0, 0, 0, 0, 0 } + +/* Argument flag bits. */ +#define CLIF_MORE (0x01) /* null or several */ +#define CLIF_STRICT (0x02) /* arg must be present */ +#define CLIF_ACC_PREV (0x04) /* arg must be accompanied with previous */ + + +/* Option flag bits. */ + +/* affected only by per-option flags */ +#define CLIF_EXTRA (0x0001) /* don`t show in usage line */ +#define CLIF_EXIT (0x0002) /* exit after handler return */ +#define CLIF_EXCL (0x0004) /* at exclusive area */ + +/* affected by per-option flags and by common `parse_flags' argument + of CLIF_parse_cmdline(). In last case appropriate bits are translated + for all the options. +*/ +#define CLIF_MAY_JOIN_ARG (0x0010) +#define _CLIF_STRICT_JOIN_ARG (0x0020) +#define CLIF_JOIN_ARG (CLIF_MAY_JOIN_ARG|_CLIF_STRICT_JOIN_ARG) +#define CLIF_MAY_NOEQUAL (0x0040) +#define _CLIF_STRICT_NOEQUAL (0x0080) +#define CLIF_NOEQUAL (CLIF_MAY_NOEQUAL|_CLIF_STRICT_NOEQUAL) +#define CLIF_MAY_KEYWORD (0x0100) +#define _CLIF_STRICT_KEYWORD (0x0200) +#define CLIF_KEYWORD (CLIF_MAY_KEYWORD|_CLIF_STRICT_KEYWORD) +#define CLIF_MAY_ONEDASH (0x0400) +#define _CLIF_STRICT_ONEDASH (0x0800) +#define CLIF_ONEDASH (CLIF_MAY_ONEDASH|_CLIF_STRICT_ONEDASH) +#define CLIF_OPTARG (0x1000) /* allow missing optarg */ +#define CLIF_ABBREV (0x2000) /* allow long opt abbreviation */ +#define CLIF_SEVERAL (0x4000) /* several args in one opt`s arg */ + +/* affected only by common `parse_flags' arg of CLIF_parse_cmdline() . */ +#define CLIF_HELP_EMPTY (0x10000) /* print help on empty cmdline */ +#define CLIF_POSIX (0x20000) /* follow POSIX standard */ +#define CLIF_FIRST_GROUP (0x40000) /* first arg - options` group */ +#define CLIF_STRICT_EXCL (0x80000) /* at least one exclusive */ +#define CLIF_SILENT (0x100000) /* no errors on stderr */ + +#define CLIF_MIN_ABBREV 2 /* a minimal match length in abbrev */ + + +extern int CLIF_parse (int argc, char **argv, CLIF_option *option_list, + CLIF_argument *arg_list, unsigned int parse_flags); +/* history compatibility... */ +#define CLIF_parse_cmdline(ARGC,ARGV,OPTN,ARGS,FLAGS) \ + CLIF_parse (ARGC, ARGV, OPTN, ARGS, FLAGS) + +extern void CLIF_print_options (const char *header, + const CLIF_option *option_list); +extern void CLIF_print_arguments (const char *header, + const CLIF_argument *argument_list); +extern void CLIF_print_usage (const char *header, const char *progname, + const CLIF_option *option_list, + const CLIF_argument *argument_list); + +extern int CLIF_current_help (void); + +/* Common useful option handlers. */ +extern int CLIF_version_handler (CLIF_option *optn, char *arg); +extern int CLIF_set_flag (CLIF_option *optn, char *arg); +extern int CLIF_unset_flag (CLIF_option *optn, char *arg); +extern int CLIF_set_string (CLIF_option *optn, char *arg); +extern int CLIF_set_int (CLIF_option *optn, char *arg); +extern int CLIF_set_uint (CLIF_option *optn, char *arg); +extern int CLIF_set_double (CLIF_option *optn, char *arg); +extern int CLIF_call_func (CLIF_option *optn, char *arg); + +extern int CLIF_arg_string (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_int (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_uint (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_double (CLIF_argument *argm, char *arg, int index); +extern int CLIF_arg_func (CLIF_argument *argm, char *arg, int index); + + +/* Some useful macros. */ + +#define CLIF_HELP_OPTION \ + { 0, "help", 0, "Read this help and exit", \ + CLIF_call_func, CLIF_current_help, 0, CLIF_EXTRA | CLIF_EXIT } +#define CLIF_VERSION_OPTION(STR) \ + { "V", "version", 0, "Print version info and exit", \ + CLIF_version_handler, STR, 0, CLIF_EXTRA | CLIF_EXIT } + +#endif /* _CLIF_H */ diff --git a/store.sh b/store.sh new file mode 100755 index 0000000..e59f99d --- /dev/null +++ b/store.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2000, 2001 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Store package script. +# Normally invoked by a Makefile. +# +# Stores package with an appropriate version info +# as the main directory postfix (i.e., name-0.1.2/*), +# even if the source dir don`t have it. +# + + +[ $# -lt 2 ] && { + echo "Usage: $0 target store_dir" >&2 + exit 2 +} + + +target=$1 +store_dir=$2 +main_dir=`basename \`pwd\`` + + +# Find current version info. + +dir_v="" +file_v="" + +dir_v=`expr $main_dir : '.*-\([0-9.]*\)$'` +[ -r VERSION ] && file_v=`awk '/^ *#/ { print $3 ; exit; } + /^ *VERSION *=/ {split ($0, a, "="); print a[2]; exit; } + { print $0; exit; }' < VERSION ` +[ -n "$file_v" ] && file_v=`echo $file_v ` # to strip possible spaces + +[ -z "$file_v" -a -z "$dir_v" ] && { + echo "$0: Cannot determine version (use dirname postfix or VERSION file)" >&2 + exit 2 +} + +[ -n "$file_v" -a -n "$dir_v" -a "$dir_v" != "$file_v" ] && { + echo "$0: Different version from dirname postfix and VERSION file" >&2 + exit 2 +} + +version="$dir_v" +[ -z "$version" ] && version="$file_v" + + +targ_vers=${target}-$version + +cd .. + +[ "$main_dir" != "$targ_vers" ] && { + ln -s "$main_dir" "${main_dir}~" || exit 1 # paranoia and paranoia + mv -f "$main_dir" "$targ_vers" || exit 1 +} + +tar -cvhf - "$targ_vers" | gzip -c -9 > $store_dir/${targ_vers}.tar.gz + +[ "$main_dir" != "$targ_vers" ] && { + mv -f "$targ_vers" "$main_dir" + rm -f "${main_dir}~" +} + +exit 0 diff --git a/traceroute.spec b/traceroute.spec new file mode 100644 index 0000000..1c95120 --- /dev/null +++ b/traceroute.spec @@ -0,0 +1,59 @@ +Summary: Traces the route taken by packets over an IPv4/IPv6 network +Name: traceroute +Version: 2.1.5 +Release: 1%{?dist} +Group: Applications/Internet +License: GPLv2+ +URL: http://traceroute.sourceforge.net +Source0: http://dl.sourceforge.net/traceroute/traceroute-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + + +%description +The traceroute utility displays the route used by IP packets on their +way to a specified network (or Internet) host. Traceroute displays +the IP number and host name (if possible) of the machines along the +route taken by the packets. Traceroute is used as a network debugging +tool. If you're having network connectivity problems, traceroute will +show you where the trouble is coming from along the route. + +Install traceroute if you need a tool for diagnosing network connectivity +problems. + + +%prep +%setup -q + + +%build +make %{?_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="" + + +%install +rm -rf $RPM_BUILD_ROOT + +install -d $RPM_BUILD_ROOT/bin +install -m755 traceroute/traceroute $RPM_BUILD_ROOT/bin +pushd $RPM_BUILD_ROOT/bin +ln -s traceroute traceroute6 +popd + +install -d $RPM_BUILD_ROOT%{_mandir}/man8 +install -p -m644 traceroute/traceroute.8 $RPM_BUILD_ROOT%{_mandir}/man8 +ln -s traceroute.8 $RPM_BUILD_ROOT%{_mandir}/man8/traceroute6.8 + + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root,-) +%doc COPYING README TODO CREDITS +/bin/* +%{_mandir}/*/* + + +%changelog +* Tue Oct 20 2006 Dmitry Butskoy <Dmitry@Butskoy.name> - 2.0.2-1 +- initial release diff --git a/traceroute/as_lookups.c b/traceroute/as_lookups.c new file mode 100644 index 0000000..a9962be --- /dev/null +++ b/traceroute/as_lookups.c @@ -0,0 +1,128 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include "traceroute.h" + + +#define DEF_RADB_SERVER "whois.radb.net" +#define DEF_RADB_SERVICE "nicname" + + +static sockaddr_any ra_addr = {{ 0, }, }; +static char ra_buf[512] = { 0, }; + + +const char *get_as_path (const char *query) { + int sk, n; + FILE *fp; + char buf[1024]; + int prefix = 0, best_prefix = 0; + char *rb, *re = &ra_buf[sizeof (ra_buf) / sizeof (*ra_buf) - 1]; + + + if (!ra_addr.sa.sa_family) { + const char *server, *service; + struct addrinfo *res; + int ret; + + server = getenv ("RA_SERVER"); + if (!server) server = DEF_RADB_SERVER; + + service = getenv ("RA_SERVICE"); + if (!service) service = DEF_RADB_SERVICE; + + + ret = getaddrinfo (server, service, NULL, &res); + if (ret) { + fprintf (stderr, "%s/%s: %s\n", server, service, + gai_strerror(ret)); + exit (2); + } + + memcpy (&ra_addr, res->ai_addr, res->ai_addrlen); + + freeaddrinfo (res); + } + + + sk = socket (ra_addr.sa.sa_family, SOCK_STREAM, 0); + if (sk < 0) error ("socket"); + + if (connect (sk, &ra_addr.sa, sizeof (ra_addr)) < 0) + goto err_sk; + + n = snprintf (buf, sizeof (buf), "%s\r\n", query); + if (n >= sizeof (buf)) goto err_sk; + + if (write (sk, buf, n) < n) + goto err_sk; + + fp = fdopen (sk, "r"); + if (!fp) goto err_sk; + + + strcpy (ra_buf, "*"); + rb = ra_buf; + + while (fgets (buf, sizeof (buf), fp) != NULL) { + + if (!strncmp (buf, "route:", sizeof ("route:") - 1) || + !strncmp (buf, "route6:", sizeof ("route6:") - 1) + ) { + char *p = strchr (buf, '/'); + + if (p) prefix = strtoul (++p, NULL, 10); + else prefix = 0; /* Hmmm... */ + + } + else if (!strncmp (buf, "origin:", sizeof ("origin:") -1)) { + char *p, *as; + + p = buf + (sizeof ("origin:") - 1); + + while (isspace (*p)) p++; + as = p; + while (*p && !isspace (*p)) p++; + *p = '\0'; + + if (prefix > best_prefix) { + best_prefix = prefix; + + rb = ra_buf; + while (rb < re && (*rb++ = *as++)) ; + } + else if (prefix == best_prefix) { + char *q = strstr (ra_buf, as); + + if (!q || (*(q += strlen (as)) != '\0' && *q != '/')) { + if (rb > ra_buf) rb[-1] = '/'; + while (rb < re && (*rb++ = *as++)) ; + } + } + /* else just ignore it */ + } + } + + fclose (fp); + + return ra_buf; + + +err_sk: + close (sk); + return "!!"; +} diff --git a/traceroute/csum.c b/traceroute/csum.c new file mode 100644 index 0000000..8b7a920 --- /dev/null +++ b/traceroute/csum.c @@ -0,0 +1,34 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> + +#include "traceroute.h" + + +uint16_t in_csum (const void *ptr, size_t len) { + const uint16_t *p = (const uint16_t *) ptr; + size_t nw = len / 2; + unsigned int sum = 0; + uint16_t res; + + while (nw--) sum += *p++; + + if (len & 0x1) + sum += htons (*((unsigned char *) p) << 8); + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + res = ~sum; + if (!res) res = ~0; + + return res; +} + diff --git a/traceroute/extension.c b/traceroute/extension.c new file mode 100644 index 0000000..1ad1d48 --- /dev/null +++ b/traceroute/extension.c @@ -0,0 +1,237 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "traceroute.h" + + +struct icmp_ext_header { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned int version:4; + unsigned int reserved:4; +#else + unsigned int reserved:4; + unsigned int version:4; +#endif + uint8_t reserved1; + uint16_t checksum; +} __attribute__ ((packed)); + + +struct icmp_ext_object { + uint16_t length; + uint8_t class; + uint8_t c_type; + uint8_t data[0]; +}; + +#define MPLS_CLASS 1 +#define MPLS_C_TYPE 1 + +#define IFACE_INFO_CLASS 2 +#define IFACE_INFO_NAME_LEN 64 + + +#define do_snprintf(CURR, END, FMT, ARGS...) \ + do { \ + CURR += snprintf (CURR, END - CURR, (FMT), ## ARGS);\ + if (CURR > END) CURR = END; \ + } while (0) + + +/* rfc 5837 stuff */ + +static int print_iface_info (struct icmp_ext_object *obj, char *buf, size_t length) { + uint32_t *ui; + char tmp[128]; /* enough: 4 + (4 + 16) + 64 + 4 = 92 */ + size_t data_len; + char *curr = buf, *end = buf + length; + char *start; + const char *roles[] = { "INC", "SUB", "OUT", "NXT" }; + + + /* Copy data into temporary array of enough length + to avoid boundary checks on each step. + */ + data_len = ntohs (obj->length) - sizeof (*obj); + if (data_len > sizeof (tmp)) return 0; + + memset (tmp, 0, sizeof (tmp)); + memcpy (tmp, obj->data, data_len); + + ui = (uint32_t *) tmp; + + + do_snprintf (curr, end, "%s:", roles[(obj->c_type >> 6) & 0x03]); + start = curr; + + if (obj->c_type & 0x08) /* index */ + do_snprintf (curr, end, "%u", ntohl (*ui++)); + + if (obj->c_type & 0x04) { /* IP address */ + sockaddr_any addr; + void *ptr; + size_t len; + uint16_t afi = ntohl (*ui++) >> 16; + + memset (&addr, 0, sizeof (addr)); + + if (afi == 1) { /* ipv4 */ + addr.sa.sa_family = AF_INET; + ptr = &addr.sin.sin_addr; + len = sizeof (addr.sin.sin_addr); + } + else if (afi == 2) { /* ipv6 */ + addr.sa.sa_family = AF_INET6; + ptr = &addr.sin6.sin6_addr; + len = sizeof (addr.sin6.sin6_addr); + } else + return 0; + + memcpy (ptr, ui, len); + ui += len / sizeof (*ui); + + do_snprintf (curr, end, "%s%s", (curr > start) ? "," : "", addr2str (&addr)); + } + + if (obj->c_type & 0x02) { /* name */ + uint8_t *name = (uint8_t *) ui; + uint8_t len = *name; + char str[IFACE_INFO_NAME_LEN * 4]; /* enough... */ + char *p = str; + static char hex[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; + int i; + + if (!len || (len % sizeof (uint32_t)) || len > IFACE_INFO_NAME_LEN) + return 0; + + for (i = 1; i < len; i++) { /* name[0] is length */ + int ch = name[i]; + + if (!ch) break; + else if (!isascii (ch) || !isgraph (ch) || ch == '%' || ch == '"') { + *p++ = '%'; + *p++ = hex[(ch >> 4) & 0x0f]; + *p++ = hex[ch & 0x0f]; + } else + *p++ = ch; + } + *p++ = '\0'; + + do_snprintf (curr, end, "%s\"%s\"", (curr > start) ? "," : "", str); + + ui += len / sizeof (*ui); + } + + if (obj->c_type & 0x01) /* mtu */ + do_snprintf (curr, end, "%smtu=%u", (curr > start) ? "," : "", ntohl (*ui++)); + + + if (ui > (uint32_t *) (tmp + data_len)) + return 0; + + return (curr - buf); +} + + +static int try_extension (probe *pb, char *buf, size_t len) { + struct icmp_ext_header *iext = (struct icmp_ext_header *) buf; + char str[1024]; + char *curr = str; + char *end = str + sizeof (str) / sizeof (*str); + + + /* a check for len >= 8 already done for all cases */ + + if (iext->version != 2) return -1; + + if (iext->checksum && + in_csum (iext, len) != (uint16_t) ~0 + ) return -1; + + buf += sizeof (*iext); + len -= sizeof (*iext); + + + while (len >= sizeof (struct icmp_ext_object)) { + struct icmp_ext_object *obj = (struct icmp_ext_object *) buf; + size_t objlen = ntohs (obj->length); + size_t data_len; + uint32_t *ui = (uint32_t *) obj->data; + int i, n; + + if (objlen < sizeof (*obj) || + objlen > len + ) return -1; + + data_len = objlen - sizeof (*obj); + if (data_len % sizeof (uint32_t)) + return -1; /* must be 32bit rounded... */ + + n = data_len / sizeof (*ui); + + + if (curr > (char *) str && curr < end) + *curr++ = ';'; /* a separator */ + + if (obj->class == MPLS_CLASS && + obj->c_type == MPLS_C_TYPE && + n >= 1 + ) { /* people prefer MPLS (rfc4950) to be parsed... */ + + do_snprintf (curr, end, "MPLS:"); + + for (i = 0; i < n; i++, ui++) { + uint32_t mpls = ntohl (*ui); + + do_snprintf (curr, end, "%sL=%u,E=%u,S=%u,T=%u", + i ? "/" : "", + mpls >> 12, + (mpls >> 9) & 0x7, + (mpls >> 8) & 0x1, + mpls & 0xff); + } + + } + else if (obj->class == IFACE_INFO_CLASS && + (i = print_iface_info (obj, curr, end - curr)) > 0 + ) { + curr += i; /* successfully parsed */ + + } + else { /* common case... */ + + do_snprintf (curr, end, "%u/%u:", obj->class, obj->c_type); + + for (i = 0; i < n && curr < end; i++, ui++) + do_snprintf (curr, end, "%s%08x", i ? "," : "", ntohl(*ui)); + } + + buf += objlen; + len -= objlen; + } + + if (len) return -1; + + + pb->ext = strdup (str); + + return 0; +} + + +void handle_extensions (probe *pb, char *buf, int len, int step) { + + if (!step) + try_extension (pb, buf, len); + else { + for ( ; len >= 8; buf += step, len -= step) + if (try_extension (pb, buf, len) == 0) + break; + } + + return; +} + diff --git a/traceroute/flowlabel.h b/traceroute/flowlabel.h new file mode 100644 index 0000000..af5a65a --- /dev/null +++ b/traceroute/flowlabel.h @@ -0,0 +1,40 @@ +/* + It is just a stripped copy of the kernel header "linux/in6.h" + + "Flow label" things are still not defined in "netinet/in*.h" headers, + but we cannot use "linux/in6.h" immediately because it currently + conflicts with "netinet/in.h" . +*/ + +struct in6_flowlabel_req +{ + struct in6_addr flr_dst; + __u32 flr_label; + __u8 flr_action; + __u8 flr_share; + __u16 flr_flags; + __u16 flr_expires; + __u16 flr_linger; + __u32 __flr_pad; + /* Options in format of IPV6_PKTOPTIONS */ +}; + +#define IPV6_FL_A_GET 0 +#define IPV6_FL_A_PUT 1 +#define IPV6_FL_A_RENEW 2 + +#define IPV6_FL_F_CREATE 1 +#define IPV6_FL_F_EXCL 2 + +#define IPV6_FL_S_NONE 0 +#define IPV6_FL_S_EXCL 1 +#define IPV6_FL_S_PROCESS 2 +#define IPV6_FL_S_USER 3 +#define IPV6_FL_S_ANY 255 + +#define IPV6_FLOWINFO_FLOWLABEL 0x000fffff +#define IPV6_FLOWINFO_PRIORITY 0x0ff00000 + +#define IPV6_FLOWLABEL_MGR 32 +#define IPV6_FLOWINFO_SEND 33 + diff --git a/traceroute/mod-dccp.c b/traceroute/mod-dccp.c new file mode 100644 index 0000000..5d8518b --- /dev/null +++ b/traceroute/mod-dccp.c @@ -0,0 +1,289 @@ +/* + Copyright (c) 2012 Samuel Jero <sj323707@ohio.edu> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <linux/dccp.h> + + +#include "traceroute.h" + + +#define DEF_SERVICE_CODE 1885957735 + +#define DCCP_HEADER_LEN (sizeof (struct dccp_hdr) + \ + sizeof (struct dccp_hdr_ext) \ + + sizeof (struct dccp_hdr_request)) + +static sockaddr_any dest_addr = {{ 0, }, }; +static unsigned int dest_port = 0; + +static int raw_sk = -1; +static int last_ttl = 0; + +static uint8_t buf[1024]; /* enough, enough... */ +static size_t csum_len = 0; +static struct dccp_hdr *dh = NULL; +static struct dccp_hdr_ext *dhe = NULL; +static struct dccp_hdr_request *dhr = NULL; +static unsigned int service_code = DEF_SERVICE_CODE; + + +static CLIF_option dccp_options[] = { + { 0, "service", "NUM", "Set DCCP service code to %s (default is " + _TEXT (DEF_SERVICE_CODE) ")", + CLIF_set_uint, &service_code, 0, CLIF_ABBREV }, + CLIF_END_OPTION +}; + + +static int dccp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int af = dest->sa.sa_family; + sockaddr_any src; + socklen_t len; + uint8_t *ptr; + uint16_t *lenp; + + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; /* raw sockets can be confused */ + + if (!port_seq) port_seq = DEF_DCCP_PORT; + dest_port = htons (port_seq); + + + /* Create raw socket for DCCP */ + raw_sk = socket (af, SOCK_RAW, IPPROTO_DCCP); + if (raw_sk < 0) + error_or_perm ("socket"); + + tune_socket (raw_sk); /* including bind, if any */ + + if (connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0) + error ("connect"); + + len = sizeof (src); + if (getsockname (raw_sk, &src.sa, &len) < 0) + error ("getsockname"); + + + if (!raw_can_connect ()) { /* work-around for buggy kernels */ + close (raw_sk); + raw_sk = socket (af, SOCK_RAW, IPPROTO_DCCP); + if (raw_sk < 0) error ("socket"); + tune_socket (raw_sk); + /* but do not connect it... */ + } + + + use_recverr (raw_sk); + + add_poll (raw_sk, POLLIN | POLLERR); + + + /* Now create the sample packet. */ + + /* For easy checksum computing: + saddr + daddr + length + protocol + dccphdr + */ + + ptr = buf; + + if (af == AF_INET) { + len = sizeof (src.sin.sin_addr); + memcpy (ptr, &src.sin.sin_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin.sin_addr, len); + ptr += len; + } else { + len = sizeof (src.sin6.sin6_addr); + memcpy (ptr, &src.sin6.sin6_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin6.sin6_addr, len); + ptr += len; + } + + lenp = (uint16_t *) ptr; + ptr += sizeof (uint16_t); + *((uint16_t *) ptr) = htons ((uint16_t) IPPROTO_DCCP); + ptr += sizeof (uint16_t); + + + /* Construct DCCP header */ + dh = (struct dccp_hdr *) ptr; + + dh->dccph_ccval = 0; + dh->dccph_checksum = 0; + dh->dccph_cscov = 0; + dh->dccph_dport = dest_port; + dh->dccph_reserved = 0; + dh->dccph_sport = 0; /* temporary */ + dh->dccph_x = 1; + dh->dccph_type = DCCP_PKT_REQUEST; + dh->dccph_seq2 = 0; /* reserved if using 48 bit sequence numbers */ + /* high 16 bits of sequence number. Always make 0 for simplicity. */ + dh->dccph_seq = 0; + ptr += sizeof (struct dccp_hdr); + + dhe = (struct dccp_hdr_ext *) ptr; + dhe->dccph_seq_low = 0; /* temporary */ + ptr += sizeof (struct dccp_hdr_ext); + + dhr = (struct dccp_hdr_request *) ptr; + dhr->dccph_req_service = htonl (service_code); + ptr += sizeof (struct dccp_hdr_request); + + + csum_len = ptr - buf; + + if (csum_len > sizeof (buf)) + error ("impossible"); /* paranoia */ + + len = ptr - (uint8_t *) dh; + if (len & 0x03) error ("impossible"); /* as >>2 ... */ + + *lenp = htons (len); + dh->dccph_doff = len >> 2; + + + *packet_len_p = len; + + return 0; +} + + +static void dccp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + sockaddr_any addr; + socklen_t len = sizeof (addr); + + + /* To make sure we have chosen a free unused "source port", + just create, (auto)bind and hold a socket while the port is needed. + */ + + sk = socket (af, SOCK_DCCP, IPPROTO_DCCP); + if (sk < 0) error ("socket"); + + bind_socket (sk); + + if (getsockname (sk, &addr.sa, &len) < 0) + error ("getsockname"); + + /* When we reach the target host, it can send us either Reset or Response. + For Reset all is OK (we and kernel just answer nothing), but + for Response we should reply with our Close. + It is well-known "half-open technique", used by port scanners etc. + This way we do not touch remote applications at all, unlike + the ordinary connect(2) call. + As the port-holding socket neither connect() nor listen(), + it means "no such port yet" for remote ends, and kernel always + send Reset in such a situation automatically (we have to do nothing). + */ + + + dh->dccph_sport = addr.sin.sin_port; + + dhe->dccph_seq_low = random_seq (); + + dh->dccph_checksum = 0; + dh->dccph_checksum = in_csum (buf, csum_len); + + + if (ttl != last_ttl) { + set_ttl (raw_sk, ttl); + last_ttl = ttl; + } + + + pb->send_time = get_time (); + + if (do_send (raw_sk, dh, dh->dccph_doff << 2, &dest_addr) < 0) { + close (sk); + pb->send_time = 0; + return; + } + + + pb->seq = dh->dccph_sport; + + pb->sk = sk; + + return; +} + + +static probe *dccp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + probe *pb; + struct dccp_hdr *ndh = (struct dccp_hdr *) buf; + uint16_t sport, dport; + + + if (len < 8) return NULL; /* too short */ + + + if (err) { + sport = ndh->dccph_sport; + dport = ndh->dccph_dport; + } else { + sport = ndh->dccph_dport; + dport = ndh->dccph_sport; + } + + + if (dport != dest_port) + return NULL; + + if (!equal_addr (&dest_addr, from)) + return NULL; + + pb = probe_by_seq (sport); + if (!pb) return NULL; + + if (!err) pb->final = 1; + + return pb; +} + + +static void dccp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), dccp_check_reply); +} + + +static void dccp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module dccp_ops = { + .name = "dccp", + .init = dccp_init, + .send_probe = dccp_send_probe, + .recv_probe = dccp_recv_probe, + .expire_probe = dccp_expire_probe, + .options = dccp_options, +}; + +TR_MODULE (dccp_ops); diff --git a/traceroute/mod-icmp.c b/traceroute/mod-icmp.c new file mode 100644 index 0000000..d887d3e --- /dev/null +++ b/traceroute/mod-icmp.c @@ -0,0 +1,247 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include "traceroute.h" + + +static sockaddr_any dest_addr = {{ 0, }, }; +static uint16_t seq = 1; +static uint16_t ident = 0; + +static char *data; +static size_t *length_p; + +static int icmp_sk = -1; +static int last_ttl = 0; + +static int raw = 0; +static int dgram = 0; + + +static CLIF_option icmp_options[] = { + { 0, "raw", 0, "Use raw sockets way only. Default is try this way " + "first (probably not allowed for unprivileged users), " + "then try dgram", + CLIF_set_flag, &raw, 0, CLIF_EXCL }, + { 0, "dgram", 0, "Use dgram sockets way only. May be not implemented " + "by old kernels or restricted by sysadmins", + CLIF_set_flag, &dgram, 0, CLIF_EXCL }, + CLIF_END_OPTION +}; + + +static int icmp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int i; + int af = dest->sa.sa_family; + int protocol; + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; + + if (port_seq) seq = port_seq; + + length_p = packet_len_p; + if (*length_p < sizeof (struct icmphdr)) + *length_p = sizeof (struct icmphdr); + + data = malloc (*length_p); + if (!data) error ("malloc"); + + for (i = sizeof (struct icmphdr); i < *length_p; i++) + data[i] = 0x40 + (i & 0x3f); + + + protocol = (af == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6; + + if (!raw) { + icmp_sk = socket (af, SOCK_DGRAM, protocol); + if (icmp_sk < 0 && dgram) + error ("socket"); + } + + if (!dgram) { + int raw_sk = socket (af, SOCK_RAW, protocol); + if (raw_sk < 0) { + if (raw || icmp_sk < 0) + error_or_perm ("socket"); + dgram = 1; + } else { + /* prefer the traditional "raw" way when possible */ + close (icmp_sk); + icmp_sk = raw_sk; + } + } + + + tune_socket (icmp_sk); + + /* Don't want to catch packets from another hosts */ + if (raw_can_connect () && + connect (icmp_sk, &dest_addr.sa, sizeof (dest_addr)) < 0 + ) error ("connect"); + + use_recverr (icmp_sk); + + + if (dgram) { + sockaddr_any addr; + socklen_t len = sizeof (addr); + + if (getsockname (icmp_sk, &addr.sa, &len) < 0) + error ("getsockname"); + ident = ntohs (addr.sin.sin_port); /* both IPv4 and IPv6 */ + + } else + ident = getpid () & 0xffff; + + + add_poll (icmp_sk, POLLIN | POLLERR); + + return 0; +} + + +static void icmp_send_probe (probe *pb, int ttl) { + int af = dest_addr.sa.sa_family; + + + if (ttl != last_ttl) { + + set_ttl (icmp_sk, ttl); + + last_ttl = ttl; + } + + + if (af == AF_INET) { + struct icmp *icmp = (struct icmp *) data; + + icmp->icmp_type = ICMP_ECHO; + icmp->icmp_code = 0; + icmp->icmp_cksum = 0; + icmp->icmp_id = htons (ident); + icmp->icmp_seq = htons (seq); + + icmp->icmp_cksum = in_csum (data, *length_p); + } + else if (af == AF_INET6) { + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) data; + + icmp6->icmp6_type = ICMP6_ECHO_REQUEST; + icmp6->icmp6_code = 0; + icmp6->icmp6_cksum = 0; + icmp6->icmp6_id = htons (ident); + icmp6->icmp6_seq = htons(seq); + + /* icmp6->icmp6_cksum always computed by kernel internally */ + } + + + pb->send_time = get_time (); + + if (do_send (icmp_sk, data, *length_p, &dest_addr) < 0) { + pb->send_time = 0; + return; + } + + + pb->seq = seq; + + seq++; + + return; +} + + +static probe *icmp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + int af = dest_addr.sa.sa_family; + int type; + uint16_t recv_id, recv_seq; + probe *pb; + + + if (len < sizeof (struct icmphdr)) + return NULL; + + + if (af == AF_INET) { + struct icmp *icmp = (struct icmp *) buf; + + type = icmp->icmp_type; + + recv_id = ntohs (icmp->icmp_id); + recv_seq = ntohs (icmp->icmp_seq); + + } + else { /* AF_INET6 */ + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) buf; + + type = icmp6->icmp6_type; + + recv_id = ntohs (icmp6->icmp6_id); + recv_seq = ntohs (icmp6->icmp6_seq); + } + + + if (recv_id != ident) + return NULL; + + pb = probe_by_seq (recv_seq); + if (!pb) return NULL; + + + if (!err) { + + if (!(af == AF_INET && type == ICMP_ECHOREPLY) && + !(af == AF_INET6 && type == ICMP6_ECHO_REPLY) + ) return NULL; + + pb->final = 1; + } + + return pb; +} + + +static void icmp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), icmp_check_reply); +} + + +static void icmp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module icmp_ops = { + .name = "icmp", + .init = icmp_init, + .send_probe = icmp_send_probe, + .recv_probe = icmp_recv_probe, + .expire_probe = icmp_expire_probe, + .options = icmp_options, +}; + +TR_MODULE (icmp_ops); diff --git a/traceroute/mod-raw.c b/traceroute/mod-raw.c new file mode 100644 index 0000000..8eaac35 --- /dev/null +++ b/traceroute/mod-raw.c @@ -0,0 +1,163 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netdb.h> + +#include "traceroute.h" + + +static sockaddr_any dest_addr = {{ 0, }, }; +static int protocol = DEF_RAW_PROT; + +static char *data = NULL; +static size_t *length_p; + +static int raw_sk = -1; +static int last_ttl = 0; +static int seq = 0; + + +static int set_protocol (CLIF_option *optn, char *arg) { + char *q; + + protocol = strtoul (arg, &q, 0); + if (q == arg) { + struct protoent *p = getprotobyname (arg); + + if (!p) return -1; + protocol = p->p_proto; + } + + return 0; +} + + +static CLIF_option raw_options[] = { + { 0, "protocol", "PROT", "Use protocol %s (default is " + _TEXT (DEF_RAW_PROT) ")", + set_protocol, 0, 0, CLIF_ABBREV }, + CLIF_END_OPTION +}; + + +static int raw_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int i; + int af = dest->sa.sa_family; + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; + + if (port_seq) protocol = port_seq; + + + length_p = packet_len_p; + + if (*length_p && + !(data = malloc (*length_p)) + ) error ("malloc"); + + for (i = 0; i < *length_p; i++) + data[i] = 0x40 + (i & 0x3f); + + + raw_sk = socket (af, SOCK_RAW, protocol); + if (raw_sk < 0) + error_or_perm ("socket"); + + tune_socket (raw_sk); + + /* Don't want to catch packets from another hosts */ + if (raw_can_connect () && + connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0 + ) error ("connect"); + + use_recverr (raw_sk); + + + add_poll (raw_sk, POLLIN | POLLERR); + + return 0; +} + + +static void raw_send_probe (probe *pb, int ttl) { + + if (ttl != last_ttl) { + + set_ttl (raw_sk, ttl); + + last_ttl = ttl; + } + + + pb->send_time = get_time (); + + if (do_send (raw_sk, data, *length_p, &dest_addr) < 0) { + pb->send_time = 0; + return; + } + + + pb->seq = ++seq; + + return; +} + + +static probe *raw_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + probe *pb; + + if (!equal_addr (&dest_addr, from)) + return NULL; + + pb = probe_by_seq (seq); + if (!pb) return NULL; + + if (!err) pb->final = 1; + + return pb; +} + + +static void raw_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), raw_check_reply); +} + + +static void raw_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module raw_ops = { + .name = "raw", + .init = raw_init, + .send_probe = raw_send_probe, + .recv_probe = raw_recv_probe, + .expire_probe = raw_expire_probe, + .options = raw_options, + .one_per_time = 1, +}; + +TR_MODULE (raw_ops); diff --git a/traceroute/mod-tcp.c b/traceroute/mod-tcp.c new file mode 100644 index 0000000..c8453bf --- /dev/null +++ b/traceroute/mod-tcp.c @@ -0,0 +1,606 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/tcp.h> + + +#include "traceroute.h" + + +#ifndef IP_MTU +#define IP_MTU 14 +#endif + +#ifndef TCPOPT_FASTOPEN +#define TCPOPT_FASTOPEN 34 +#define TCPOLEN_FASTOPEN_BASE 2 +#endif + + +static sockaddr_any dest_addr = {{ 0, }, }; +static unsigned int dest_port = 0; + +static int raw_sk = -1; +static int last_ttl = 0; + +static uint8_t buf[1024]; /* enough, enough... */ +static size_t csum_len = 0; +static struct tcphdr *th = NULL; + +#define TH_FLAGS(TH) (((uint8_t *) (TH))[13]) +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#define TH_ECE 0x40 +#define TH_CWR 0x80 + + +static int flags = 0; /* & 0xff == tcp_flags ... */ +static int sysctl = 0; +static int reuse = 0; +static int mss = -1; +static int check_mss = 0; +static int info = 0; +static int fastopen = 0; + +#define FL_FLAGS 0x0100 +#define FL_ECN 0x0200 +#define FL_SACK 0x0400 +#define FL_TSTAMP 0x0800 +#define FL_WSCALE 0x1000 + + +static struct { + const char *name; + unsigned int flag; +} tcp_flags[] = { + { "fin", TH_FIN }, + { "syn", TH_SYN }, + { "rst", TH_RST }, + { "psh", TH_PSH }, + { "ack", TH_ACK }, + { "urg", TH_URG }, + { "ece", TH_ECE }, + { "cwr", TH_CWR }, +}; + + +static char *print_tcp_info (struct tcphdr *tcp, size_t len) { + int i; + char str[128]; /* enough... */ + char *curr = str; + char *end = str + (sizeof (str) / sizeof (*str) - 1); + const char *p; + unsigned int flags; + uint8_t *ptr; + + if (len < sizeof (struct tcphdr) || + len != tcp->doff << 2 + ) return NULL; + + + flags = TH_FLAGS(tcp); + + for (i = 0; i < sizeof (tcp_flags) / sizeof (*tcp_flags); i++) { + + if (!(flags & tcp_flags[i].flag)) continue; + + if (curr > str && curr < end) *curr++ = ','; + for (p = tcp_flags[i].name; *p && curr < end; *curr++ = *p++) ; + } + + + ptr = (uint8_t *) (tcp + 1); + len -= sizeof (struct tcphdr); + + while (len > 1) { + int op = *ptr, oplen = ptr[1]; + char buf[16]; + const char *name = NULL; + + switch (op) { + case TCPOPT_EOL: len = 0; continue; /* no more... */ + case TCPOPT_NOP: oplen = 1; break; + case TCPOPT_MAXSEG: + if (oplen == TCPOLEN_MAXSEG && oplen <= len) { + uint16_t rcv_mss = ntohs (*((uint16_t *) (ptr + 2))); + snprintf (buf, sizeof (buf), "mss=%u", rcv_mss); + name = buf; + } + break; + case TCPOPT_SACK_PERMITTED: if (oplen == TCPOLEN_SACK_PERMITTED) name = "sack"; break; + case TCPOPT_TIMESTAMP: if (oplen == TCPOLEN_TIMESTAMP) name = "timestamps"; break; + case TCPOPT_WINDOW: if (oplen == TCPOLEN_WINDOW) name = "window_scaling"; break; + case TCPOPT_FASTOPEN: if (oplen >= TCPOLEN_FASTOPEN_BASE) name = "fastopen"; break; + } + + if (name) { + if (curr > str && curr < end) *curr++ = ','; + for (p = name; *p && curr < end; *curr++ = *p++) ; + } + + if (len < oplen) break; + len -= oplen; + ptr += oplen; + } + + + *curr = '\0'; + + return strdup (str); +} + + +static int set_tcp_flag (CLIF_option *optn, char *arg) { + int i; + + for (i = 0; i < sizeof (tcp_flags) / sizeof (*tcp_flags); i++) { + if (!strcmp (optn->long_opt, tcp_flags[i].name)) { + flags |= tcp_flags[i].flag; + return 0; + } + } + + return -1; +} + +static int set_tcp_flags (CLIF_option *optn, char *arg) { + char *q; + unsigned long value; + + value = strtoul (arg, &q, 0); + if (q == arg) return -1; + + flags = (flags & ~0xff) | (value & 0xff) | FL_FLAGS; + return 0; +} + +static int set_flag (CLIF_option *optn, char *arg) { + + flags |= (unsigned long) optn->data; + + return 0; +} + +static int set_mss (CLIF_option *optn, char *arg) { + + check_mss = 1; + + if (arg) + return CLIF_set_uint (optn, arg); + + return 0; +} + +static CLIF_option tcp_options[] = { + { 0, "syn", 0, "Set tcp flag SYN (default if no other " + "tcp flags specified)", set_tcp_flag, 0, 0, 0 }, + { 0, "ack", 0, "Set tcp flag ACK,", set_tcp_flag, 0, 0, 0 }, + { 0, "fin", 0, "FIN,", set_tcp_flag, 0, 0, 0 }, + { 0, "rst", 0, "RST,", set_tcp_flag, 0, 0, 0 }, + { 0, "psh", 0, "PSH,", set_tcp_flag, 0, 0, 0 }, + { 0, "urg", 0, "URG,", set_tcp_flag, 0, 0, 0 }, + { 0, "ece", 0, "ECE,", set_tcp_flag, 0, 0, 0 }, + { 0, "cwr", 0, "CWR", set_tcp_flag, 0, 0, 0 }, + { 0, "flags", "NUM", "Set tcp flags exactly to value %s", + set_tcp_flags, 0, 0, CLIF_ABBREV }, + { 0, "ecn", 0, "Send syn packet with tcp flags ECE and CWR " + "(for Explicit Congestion Notification, rfc3168)", + set_flag, (void *) FL_ECN, 0, 0 }, + { 0, "sack", 0, "Use sack,", + set_flag, (void *) FL_SACK, 0, 0 }, + { 0, "timestamps", 0, "timestamps,", + set_flag, (void *) FL_TSTAMP, 0, CLIF_ABBREV }, + { 0, "window_scaling", 0, "window_scaling option for tcp", + set_flag, (void *) FL_WSCALE, 0, CLIF_ABBREV }, + { 0, "sysctl", 0, "Use current sysctl (/proc/sys/net/*) setting " + "for the tcp options above and ecn. Always set by default " + "(with \"syn\") if nothing else specified", + CLIF_set_flag, &sysctl, 0, 0 }, + { 0, "fastopen", 0, "Use fastopen tcp option (when syn, cookie negotiation only)", + CLIF_set_flag, &fastopen, 0, 0 }, + { 0, "reuse", 0, "Allow to reuse local port numbers " + "for the huge workloads (SO_REUSEADDR)", + CLIF_set_flag, &reuse, 0, 0 }, + { 0, "mss", "NUM", "Use value of %s (or unchanged) for maxseg tcp option (when syn), " + "and discover its clamping along the path being traced", + set_mss, &mss, 0, CLIF_OPTARG }, + { 0, "info", 0, "Print tcp flags and options of final tcp replies " + "when target host is reached. Useful to determine whether " + "an application listens the port etc.", + CLIF_set_flag, &info, 0, 0 }, + CLIF_END_OPTION +}; + + +#define SYSCTL_PREFIX "/proc/sys/net/ipv4/tcp_" +static int check_sysctl (const char *name) { + int fd, res; + char buf[sizeof (SYSCTL_PREFIX) + strlen (name) + 1]; + uint8_t ch; + + strcpy (buf, SYSCTL_PREFIX); + strcat (buf, name); + + fd = open (buf, O_RDONLY, 0); + if (fd < 0) return 0; + + res = read (fd, &ch, sizeof (ch)); + close (fd); + + if (res != sizeof (ch)) + return 0; + + /* since kernel 2.6.31 "tcp_ecn" can have value of '2'... */ + if (ch == '1') return 1; + + return 0; +} + + +static int tcp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int af = dest->sa.sa_family; + sockaddr_any src; + int mtu; + socklen_t len; + uint8_t *ptr; + uint16_t *lenp; + + + dest_addr = *dest; + dest_addr.sin.sin_port = 0; /* raw sockets can be confused */ + + if (!port_seq) port_seq = DEF_TCP_PORT; + dest_port = htons (port_seq); + + + /* Create raw socket for tcp */ + + raw_sk = socket (af, SOCK_RAW, IPPROTO_TCP); + if (raw_sk < 0) + error_or_perm ("socket"); + + tune_socket (raw_sk); /* including bind, if any */ + + if (connect (raw_sk, &dest_addr.sa, sizeof (dest_addr)) < 0) + error ("connect"); + + len = sizeof (src); + if (getsockname (raw_sk, &src.sa, &len) < 0) + error ("getsockname"); + + + len = sizeof (mtu); + if (getsockopt (raw_sk, af == AF_INET ? SOL_IP : SOL_IPV6, + af == AF_INET ? IP_MTU : IPV6_MTU, + &mtu, &len) < 0 || mtu < 576 + ) mtu = 576; + + /* mss = mtu - headers */ + mtu -= af == AF_INET ? sizeof (struct iphdr) : sizeof (struct ip6_hdr); + mtu -= sizeof (struct tcphdr); + + if (mss < 0) mss = mtu; + + + if (!raw_can_connect ()) { /* work-around for buggy kernels */ + close (raw_sk); + raw_sk = socket (af, SOCK_RAW, IPPROTO_TCP); + if (raw_sk < 0) error ("socket"); + tune_socket (raw_sk); + /* but do not connect it... */ + } + + + use_recverr (raw_sk); + + add_poll (raw_sk, POLLIN | POLLERR); + + + /* Now create the sample packet. */ + + if (!flags) sysctl = 1; + + if (sysctl) { + if (check_sysctl ("ecn")) flags |= FL_ECN; + if (check_sysctl ("sack")) flags |= FL_SACK; + if (check_sysctl ("timestamps")) flags |= FL_TSTAMP; + if (check_sysctl ("window_scaling")) flags |= FL_WSCALE; + } + + if (!(flags & (FL_FLAGS | 0xff))) { /* no any tcp flag set */ + flags |= TH_SYN; + if (flags & FL_ECN) + flags |= TH_ECE | TH_CWR; + } + + + /* For easy checksum computing: + saddr + daddr + length + protocol + tcphdr + tcpoptions + */ + + ptr = buf; + + if (af == AF_INET) { + len = sizeof (src.sin.sin_addr); + memcpy (ptr, &src.sin.sin_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin.sin_addr, len); + ptr += len; + } else { + len = sizeof (src.sin6.sin6_addr); + memcpy (ptr, &src.sin6.sin6_addr, len); + ptr += len; + memcpy (ptr, &dest_addr.sin6.sin6_addr, len); + ptr += len; + } + + lenp = (uint16_t *) ptr; + ptr += sizeof (uint16_t); + *((uint16_t *) ptr) = htons ((uint16_t) IPPROTO_TCP); + ptr += sizeof (uint16_t); + + + /* Construct TCP header */ + + th = (struct tcphdr *) ptr; + + th->source = 0; /* temporary */ + th->dest = dest_port; + th->seq = 0; /* temporary */ + th->ack_seq = 0; + th->doff = 0; /* later... */ + TH_FLAGS(th) = flags & 0xff; + th->window = htons (4 * mtu); + th->check = 0; + th->urg_ptr = 0; + + + /* Build TCP options */ + + ptr = (uint8_t *) (th + 1); + + if (flags & TH_SYN) { + *ptr++ = TCPOPT_MAXSEG; /* 2 */ + *ptr++ = TCPOLEN_MAXSEG; /* 4 */ + *((uint16_t *) ptr) = htons (mss); + ptr += sizeof (uint16_t); + } + + if (flags & FL_TSTAMP) { + + if (flags & FL_SACK) { + *ptr++ = TCPOPT_SACK_PERMITTED; /* 4 */ + *ptr++ = TCPOLEN_SACK_PERMITTED;/* 2 */ + } else { + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_NOP; /* 1 */ + } + *ptr++ = TCPOPT_TIMESTAMP; /* 8 */ + *ptr++ = TCPOLEN_TIMESTAMP; /* 10 */ + + *((uint32_t *) ptr) = random_seq (); /* really! */ + ptr += sizeof (uint32_t); + *((uint32_t *) ptr) = (flags & TH_ACK) ? random_seq () : 0; + ptr += sizeof (uint32_t); + } + else if (flags & FL_SACK) { + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_SACK_PERMITTED; /* 4 */ + *ptr++ = TCPOLEN_SACK_PERMITTED; /* 2 */ + } + + if (flags & FL_WSCALE) { + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_WINDOW; /* 3 */ + *ptr++ = TCPOLEN_WINDOW; /* 3 */ + *ptr++ = 2; /* assume some corect value... */ + } + + if (fastopen && (flags & TH_SYN)) { + *ptr++ = TCPOPT_FASTOPEN; /* 34 */ + if (flags & TH_ACK) { + /* cookie size of 8 is defined in kernel's linux/tcp.h */ + *ptr++ = TCPOLEN_FASTOPEN_BASE + 2 * sizeof (uint32_t); + *((uint32_t *) ptr) = random_seq (); ptr += sizeof (uint32_t); + *((uint32_t *) ptr) = random_seq (); ptr += sizeof (uint32_t); + } else + *ptr++ = TCPOLEN_FASTOPEN_BASE + 0; /* 2 */ + *ptr++ = TCPOPT_NOP; /* 1 */ + *ptr++ = TCPOPT_NOP; /* 1 */ + } + + + csum_len = ptr - buf; + + if (csum_len > sizeof (buf)) + error ("impossible"); /* paranoia */ + + len = ptr - (uint8_t *) th; + if (len & 0x03) error ("impossible"); /* as >>2 ... */ + + *lenp = htons (len); + th->doff = len >> 2; + + + *packet_len_p = len; + + return 0; +} + + +static void tcp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + sockaddr_any addr; + socklen_t len = sizeof (addr); + + + /* To make sure we have chosen a free unused "source port", + just create, (auto)bind and hold a socket while the port is needed. + */ + + sk = socket (af, SOCK_STREAM, 0); + if (sk < 0) error ("socket"); + + if (reuse && setsockopt (sk, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) + error ("setsockopt SO_REUSEADDR"); + + bind_socket (sk); + + if (getsockname (sk, &addr.sa, &len) < 0) + error ("getsockname"); + + /* When we reach the target host, it can send us either RST or SYN+ACK. + For RST all is OK (we and kernel just answer nothing), but + for SYN+ACK we should reply with our RST. + It is well-known "half-open technique", used by port scanners etc. + This way we do not touch remote applications at all, unlike + the ordinary connect(2) call. + As the port-holding socket neither connect() nor listen(), + it means "no such port yet" for remote ends, and kernel always + send RST in such a situation automatically (we have to do nothing). + */ + + + th->source = addr.sin.sin_port; + + th->seq = random_seq (); + + th->check = 0; + th->check = in_csum (buf, csum_len); + + + if (ttl != last_ttl) { + + set_ttl (raw_sk, ttl); + + last_ttl = ttl; + } + + + pb->send_time = get_time (); + + if (do_send (raw_sk, th, th->doff << 2, &dest_addr) < 0) { + close (sk); + pb->send_time = 0; + return; + } + + + pb->seq = th->source; + + pb->sk = sk; + + return; +} + + +static probe *tcp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + probe *pb; + struct tcphdr *tcp = (struct tcphdr *) buf; + uint16_t sport, dport; + + + if (len < 8) return NULL; /* too short */ + + + if (err) { + sport = tcp->source; + dport = tcp->dest; + } else { + sport = tcp->dest; + dport = tcp->source; + } + + + if (dport != dest_port) + return NULL; + + if (!equal_addr (&dest_addr, from)) + return NULL; + + pb = probe_by_seq (sport); + if (!pb) return NULL; + + + if (check_mss && + err && + len >= sizeof (*tcp) + TCPOLEN_MAXSEG + ) { + uint8_t *ptr = (uint8_t *) (tcp + 1); + + if (ptr[0] == TCPOPT_MAXSEG && ptr[1] == TCPOLEN_MAXSEG) { + uint16_t seen_mss = ntohs (*((uint16_t *) (ptr + 2))); + if (mss != seen_mss) { + put_err (pb, "M=%u", seen_mss); + mss = seen_mss; /* print just once */ + } + } + } + + + if (!err) { + + pb->final = 1; + + if (info) + pb->ext = print_tcp_info (tcp, len); + } + + return pb; +} + + +static void tcp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), tcp_check_reply); +} + + +static void tcp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module tcp_ops = { + .name = "tcp", + .init = tcp_init, + .send_probe = tcp_send_probe, + .recv_probe = tcp_recv_probe, + .expire_probe = tcp_expire_probe, + .options = tcp_options, +}; + +TR_MODULE (tcp_ops); diff --git a/traceroute/mod-tcpconn.c b/traceroute/mod-tcpconn.c new file mode 100644 index 0000000..56213b9 --- /dev/null +++ b/traceroute/mod-tcpconn.c @@ -0,0 +1,228 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/tcp.h> +#include <errno.h> + +#include "traceroute.h" + + +static sockaddr_any dest_addr = {{ 0, }, }; + +static int icmp_sk = -1; + + +static int tcp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + int af = dest->sa.sa_family; + + dest_addr = *dest; + dest_addr.sin.sin_port = htons (DEF_TCP_PORT); + + if (port_seq) + dest_addr.sin.sin_port = htons (port_seq); + + + /* Currently an ICMP socket is the only way + to obtain the needed info... + */ + icmp_sk = socket (af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP + : IPPROTO_ICMPV6); + if (icmp_sk < 0) + error_or_perm ("socket"); + + /* icmp_sk not need full tune_socket() here, just a receiving one */ + bind_socket (icmp_sk); + use_timestamp (icmp_sk); + use_recv_ttl (icmp_sk); + + add_poll (icmp_sk, POLLIN); + + return 0; +} + + +static void tcp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + sockaddr_any addr; + socklen_t length = sizeof (addr); + + + sk = socket (af, SOCK_STREAM, 0); + if (sk < 0) error ("socket"); + + tune_socket (sk); /* common stuff */ + + set_ttl (sk, ttl); + + + pb->send_time = get_time (); + + if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) { + if (errno != EINPROGRESS) + error ("connect"); + } + + + if (getsockname (sk, &addr.sa, &length) < 0) + error ("getsockname"); + + pb->seq = addr.sin.sin_port; /* both ipv4/ipv6 */ + + pb->sk = sk; + + add_poll (sk, POLLERR | POLLHUP | POLLOUT); + + return; +} + + +static probe *tcp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + int af = dest_addr.sa.sa_family; + int type, code, info; + probe *pb; + struct tcphdr *tcp; + + + if (len < sizeof (struct icmphdr)) + return NULL; + + + if (af == AF_INET) { + struct icmp *icmp = (struct icmp *) buf; + struct iphdr *ip; + int hlen; + + type = icmp->icmp_type; + code = icmp->icmp_code; + info = icmp->icmp_void; + + if (type != ICMP_TIME_EXCEEDED && type != ICMP_DEST_UNREACH) + return NULL; + + if (len < sizeof (struct icmphdr) + sizeof (struct iphdr) + 8) + /* `8' - rfc1122: 3.2.2 */ + return NULL; + + ip = (struct iphdr *) (((char *)icmp) + sizeof(struct icmphdr)); + hlen = ip->ihl << 2; + + if (len < sizeof (struct icmphdr) + hlen + 8) + return NULL; + if (ip->protocol != IPPROTO_TCP) + return NULL; + + tcp = (struct tcphdr *) (((char *) ip) + hlen); + + } + else { /* AF_INET6 */ + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *) buf; + struct ip6_hdr *ip6; + + type = icmp6->icmp6_type; + code = icmp6->icmp6_code; + info = icmp6->icmp6_mtu; + + if (type != ICMP6_TIME_EXCEEDED && + type != ICMP6_DST_UNREACH && + type != ICMP6_PACKET_TOO_BIG + ) return NULL; + + if (len < sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr) + 8) + return NULL; + + ip6 = (struct ip6_hdr *) (icmp6 + 1); + if (ip6->ip6_nxt != IPPROTO_TCP) + return NULL; + + tcp = (struct tcphdr *) (ip6 + 1); + + } + + + if (tcp->dest != dest_addr.sin.sin_port) + return NULL; + + pb = probe_by_seq (tcp->source); + if (!pb) return NULL; + + + /* here only, high level has no data to do this */ + parse_icmp_res (pb, type, code, info); + + return pb; +} + + +static void tcp_recv_probe (int sk, int revents) { + + if (sk != icmp_sk) { /* a tcp socket */ + probe *pb; + + pb = probe_by_sk (sk); + if (!pb) { + del_poll (sk); + return; + } + + + /* do connect() again and check errno, regardless of revents */ + if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) { + if (errno != EISCONN && errno != ECONNREFUSED) + return; /* ICMP say more */ + } + + /* we have reached the dest host (either connected or refused) */ + + memcpy (&pb->res, &dest_addr, sizeof (pb->res)); + + pb->final = 1; + + pb->recv_time = get_time (); + + probe_done (pb); + + return; + } + + + /* ICMP stuff */ + + if (!(revents & POLLIN)) + return; + + recv_reply (icmp_sk, 0, tcp_check_reply); +} + + +static void tcp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +static tr_module tcp_ops = { + .name = "tcpconn", + .init = tcp_init, + .send_probe = tcp_send_probe, + .recv_probe = tcp_recv_probe, + .expire_probe = tcp_expire_probe, +}; + +TR_MODULE (tcp_ops); diff --git a/traceroute/mod-udp.c b/traceroute/mod-udp.c new file mode 100644 index 0000000..45f3a6e --- /dev/null +++ b/traceroute/mod-udp.c @@ -0,0 +1,234 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/in.h> +#include <netinet/udp.h> + +#include "traceroute.h" + + +#ifndef IPPROTO_UDPLITE +#define IPPROTO_UDPLITE 136 +#endif + +#ifndef UDPLITE_SEND_CSCOV +#define UDPLITE_SEND_CSCOV 10 +#define UDPLITE_RECV_CSCOV 11 +#endif + + +static sockaddr_any dest_addr = {{ 0, }, }; +static unsigned int curr_port = 0; +static unsigned int protocol = IPPROTO_UDP; + + +static char *data = NULL; +static size_t *length_p; + +static void fill_data (size_t *packet_len_p) { + int i; + + length_p = packet_len_p; + + if (*length_p && + !(data = malloc (*length_p)) + ) error ("malloc"); + + for (i = 0; i < *length_p; i++) + data[i] = 0x40 + (i & 0x3f); + + return; +} + + +static int udp_default_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + + curr_port = port_seq ? port_seq : DEF_START_PORT; + + dest_addr = *dest; + dest_addr.sin.sin_port = htons (curr_port); + + fill_data (packet_len_p); + + return 0; +} + + +static int udp_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + + dest_addr = *dest; + + if (!port_seq) port_seq = DEF_UDP_PORT; + dest_addr.sin.sin_port = htons ((uint16_t) port_seq); + + fill_data (packet_len_p); + + return 0; +} + + +static unsigned int coverage = 0; +#define MIN_COVERAGE (sizeof (struct udphdr)) + +static void set_coverage (int sk) { + int val = MIN_COVERAGE; + + if (setsockopt (sk, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, + &coverage, sizeof (coverage)) < 0 + ) error ("UDPLITE_SEND_CSCOV"); + + if (setsockopt (sk, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, + &val, sizeof (val)) < 0 + ) error ("UDPLITE_RECV_CSCOV"); +} + +static CLIF_option udplite_options[] = { + { 0, "coverage", "NUM", "Set udplite send coverage to %s (default is " + _TEXT(MIN_COVERAGE) ")", + CLIF_set_uint, &coverage, 0, CLIF_ABBREV }, + CLIF_END_OPTION +}; + +static int udplite_init (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len_p) { + + dest_addr = *dest; + + if (!port_seq) port_seq = DEF_UDP_PORT; /* XXX: Hmmm... */ + dest_addr.sin.sin_port = htons ((uint16_t) port_seq); + + protocol = IPPROTO_UDPLITE; + + if (!coverage) coverage = MIN_COVERAGE; + + fill_data (packet_len_p); + + return 0; +} + + +static void udp_send_probe (probe *pb, int ttl) { + int sk; + int af = dest_addr.sa.sa_family; + + + sk = socket (af, SOCK_DGRAM, protocol); + if (sk < 0) error ("socket"); + + tune_socket (sk); /* common stuff */ + + if (coverage) set_coverage (sk); /* udplite case */ + + set_ttl (sk, ttl); + + + if (connect (sk, &dest_addr.sa, sizeof (dest_addr)) < 0) + error ("connect"); + + use_recverr (sk); + + + pb->send_time = get_time (); + + if (do_send (sk, data, *length_p, NULL) < 0) { + close (sk); + pb->send_time = 0; + return; + } + + + pb->sk = sk; + + add_poll (sk, POLLIN | POLLERR); + + pb->seq = dest_addr.sin.sin_port; + + if (curr_port) { /* traditional udp method */ + curr_port++; + dest_addr.sin.sin_port = htons (curr_port); /* both ipv4 and ipv6 */ + } + + return; +} + + +static probe *udp_check_reply (int sk, int err, sockaddr_any *from, + char *buf, size_t len) { + probe *pb; + + pb = probe_by_sk (sk); + if (!pb) return NULL; + + if (pb->seq != from->sin.sin_port) + return NULL; + + if (!err) pb->final = 1; + + return pb; +} + + +static void udp_recv_probe (int sk, int revents) { + + if (!(revents & (POLLIN | POLLERR))) + return; + + recv_reply (sk, !!(revents & POLLERR), udp_check_reply); +} + + +static void udp_expire_probe (probe *pb) { + + probe_done (pb); +} + + +/* All three modules share the same methods except the init... */ + +static tr_module default_ops = { + .name = "default", + .init = udp_default_init, + .send_probe = udp_send_probe, + .recv_probe = udp_recv_probe, + .expire_probe = udp_expire_probe, + .header_len = sizeof (struct udphdr), +}; + +TR_MODULE (default_ops); + + +static tr_module udp_ops = { + .name = "udp", + .init = udp_init, + .send_probe = udp_send_probe, + .recv_probe = udp_recv_probe, + .expire_probe = udp_expire_probe, + .header_len = sizeof (struct udphdr), +}; + +TR_MODULE (udp_ops); + + +static tr_module udplite_ops = { + .name = "udplite", + .init = udplite_init, + .send_probe = udp_send_probe, + .recv_probe = udp_recv_probe, + .expire_probe = udp_expire_probe, + .header_len = sizeof (struct udphdr), + .options = udplite_options, +}; + +TR_MODULE (udplite_ops); diff --git a/traceroute/module.c b/traceroute/module.c new file mode 100644 index 0000000..cdaade2 --- /dev/null +++ b/traceroute/module.c @@ -0,0 +1,35 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "traceroute.h" + + +static tr_module *base = NULL; + +void tr_register_module (tr_module *ops) { + + ops->next = base; + base = ops; +} + +const tr_module *tr_get_module (const char *name) { + const tr_module *ops; + + if (!name) return 0; + + for (ops = base; ops; ops = ops->next) { + if (!strcasecmp (name, ops->name)) + return ops; + } + + return NULL; +} diff --git a/traceroute/poll.c b/traceroute/poll.c new file mode 100644 index 0000000..192cdfe --- /dev/null +++ b/traceroute/poll.c @@ -0,0 +1,88 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <poll.h> +#include <errno.h> +#include <math.h> + +#include "traceroute.h" + + +static struct pollfd *pfd = NULL; +static unsigned int num_polls = 0; + + +void add_poll (int fd, int events) { + int i; + + for (i = 0; i < num_polls && pfd[i].fd > 0; i++) ; + + if (i == num_polls) { + pfd = realloc (pfd, ++num_polls * sizeof (*pfd)); + if (!pfd) error ("realloc"); + } + + pfd[i].fd = fd; + pfd[i].events = events; +} + + +void del_poll (int fd) { + int i; + + for (i = 0; i < num_polls && pfd[i].fd != fd; i++) ; + + if (i < num_polls) pfd[i].fd = -1; /* or just zero it... */ +} + + +static int cleanup_polls (void) { + int i; + + for (i = 0; i < num_polls && pfd[i].fd > 0; i++) ; + + if (i < num_polls) { /* a hole have found */ + int j; + + for (j = i + 1; j < num_polls; j++) { + if (pfd[j].fd > 0) { + pfd[i++] = pfd[j]; + pfd[j].fd = -1; + } + } + } + + return i; +} + + +void do_poll (double timeout, void (*callback) (int fd, int revents)) { + int nfds, n, i; + + nfds = cleanup_polls (); + + if (!nfds) return; + + n = poll (pfd, nfds, ceil(timeout * 1000)); + if (n < 0) { + if (errno == EINTR) return; + error ("poll"); + } + + for (i = 0; n && i < num_polls; i++) { + if (pfd[i].revents) { + callback (pfd[i].fd, pfd[i].revents); + n--; + } + } + + return; +} + diff --git a/traceroute/random.c b/traceroute/random.c new file mode 100644 index 0000000..80fba9e --- /dev/null +++ b/traceroute/random.c @@ -0,0 +1,28 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/times.h> + +#include "traceroute.h" + + +static void __init_random_seq (void) __attribute__ ((constructor)); +static void __init_random_seq (void) { + + srand (times (NULL) + getpid ()); +} + + +unsigned int random_seq (void) { + + /* Not to worry about RANDOM_MAX and precision... */ + return (rand () << 16) ^ (rand () << 8) ^ rand () ^ (rand () >> 8); +} + diff --git a/traceroute/time.c b/traceroute/time.c new file mode 100644 index 0000000..e4595a0 --- /dev/null +++ b/traceroute/time.c @@ -0,0 +1,27 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/time.h> + +#include "traceroute.h" + + +/* Just returns current time as double, with most possible precision... */ + +double get_time (void) { + struct timeval tv; + double d; + + gettimeofday (&tv, NULL); + + d = ((double) tv.tv_usec) / 1000000. + (unsigned long) tv.tv_sec; + + return d; +} diff --git a/traceroute/traceroute.8 b/traceroute/traceroute.8 new file mode 100644 index 0000000..46074ad --- /dev/null +++ b/traceroute/traceroute.8 @@ -0,0 +1,629 @@ +.\" Copyright (c) 2006 Dmitry Butskoy (dmitry@butskoy.name) +.\" License: GPL v2 or any later version +.\" See COPYING for the status of this software +.TH TRACEROUTE 8 "11 October 2006" "Traceroute" "Traceroute For Linux" +.\" .UC 6 +.SH NAME +traceroute \- print the route packets trace to network host +.SH SYNOPSIS +.na +.BR traceroute " [" \-46dFITUnreAV "] [" "\-f first_ttl" "] [" "\-g gate,..." ] +.br +.ti +8 +.BR "" [ "-i device" "] [" "-m max_ttl" "] [" "-p port" "] [" "-s src_addr" ] +.br +.ti +8 +.BR "" [ "-q nqueries" "] [" "-N squeries" "] [" "-t tos" ] +.br +.ti +8 +.BR "" [ "-l flow_label" "] [" "-w waittimes" "] [" "-z sendwait" "] [" "-UL" "] [" "-D" ] +.br +.ti +8 +.BR "" [ "-P proto" "] [" "--sport=port" "] [" "-M method" "] [" "-O mod_options" ] +.br +.ti +8 +.BR "" [ "--mtu" "] [" "--back" ] +.br +.ti +8 +.BR host " [" "packet_len" "]" +.br +.BR traceroute6 +.RI " [" options ] +.ad +.SH DESCRIPTION +.I traceroute +tracks the route packets taken from an IP network on their +way to a given host. It utilizes the IP protocol's time to live (TTL) field +and attempts to elicit an ICMP TIME_EXCEEDED response from each gateway +along the path to the host. +.P +.I traceroute6 +is equivalent to +.I traceroute +.B \-6 +.PP +The only required parameter is the name or IP address of the +destination +.BR host \ . +The optional +.B packet_len\fR`gth +is the total size of the probing packet (default 60 bytes +for IPv4 and 80 for IPv6). The specified size can be ignored +in some situations or increased up to a minimal value. +.PP +This program attempts to trace the route an IP packet would follow to some +internet host by launching probe +packets with a small ttl (time to live) then listening for an +ICMP "time exceeded" reply from a gateway. We start our probes +with a ttl of one and increase by one until we get an ICMP "port +unreachable" (or TCP reset), which means we got to the "host", or hit a max (which +defaults to 30 hops). Three probes (by default) are sent at each ttl setting +and a line is printed showing the ttl, address of the gateway and +round trip time of each probe. The address can be followed by additional +information when requested. If the probe answers come from +different gateways, the address of each responding system will +be printed. If there is no response within a certain timeout, +an "*" (asterisk) is printed for that probe. +.PP +After the trip time, some additional annotation can be printed: +.BR !H , +.BR !N , +or +.B !P +(host, network or protocol unreachable), +.B !S +(source route failed), +.B !F +(fragmentation needed), +.B !X +(communication administratively prohibited), +.B !V +(host precedence violation), +.B !C +(precedence cutoff in effect), or +.B !<num> +(ICMP unreachable code <num>). +If almost all the probes result in some kind of unreachable, traceroute +will give up and exit. +.PP +We don't want the destination host to process the UDP probe packets, +so the destination port is set to an unlikely value (you can change it with the +.B \-p +flag). There is no such a problem for ICMP or TCP tracerouting (for TCP we +use half-open technique, which prevents our probes to be seen by applications +on the destination host). +.PP +In the modern network environment the traditional traceroute methods +can not be always applicable, because of widespread use of firewalls. +Such firewalls filter the "unlikely" UDP ports, or even ICMP echoes. +To solve this, some additional tracerouting methods are implemented +(including tcp), see +.B LIST OF AVAILABLE METHODS +below. Such methods try to use particular protocol +and source/destination port, in order to bypass firewalls (to be seen +by firewalls just as a start of allowed type of a network session). +.SH OPTIONS +.TP +.BI \--help +Print help info and exit. +.TP +.BR \-4 ", " \-6 +Explicitly force IPv4 or IPv6 tracerouting. By default, the program +will try to resolve the name given, and choose the appropriate +protocol automatically. If resolving a host name returns both +IPv4 and IPv6 addresses, +.I traceroute +will use IPv4. +.TP +.B \-I, \-\-icmp +Use ICMP ECHO for probes +.TP +.B \-T, \-\-tcp +Use TCP SYN for probes +.TP +.B \-d, --debug +Enable socket level debugging (when the Linux kernel supports it) +.TP +.B \-F, --dont-fragment +Do not fragment probe packets. (For IPv4 it also sets DF bit, which tells +intermediate routers not to fragment remotely as well). +.br + +.br +Varying the size of the probing packet by the +.B packet_len +command line parameter, you can manually obtain information +about the MTU of individual network hops. The +.B \--mtu +option (see below) tries to do this automatically. +.br + +.br +Note, that non-fragmented features (like +.B \-F +or +.B \--mtu\fR) +work properly since the Linux kernel 2.6.22 only. +Before that version, IPv6 was always fragmented, IPv4 could use +the once the discovered final mtu only (from the route cache), which can be +less than the actual mtu of a device. +.TP +.BI \-f " first_ttl" ", --first=" first_ttl +Specifies with what TTL to start. Defaults to 1. +.TP +.BI \-g " gateway" ", --gateway=" gateway +Tells traceroute to add an IP source routing option to the outgoing +packet that tells the network to route the packet through the +specified +.IR gateway +(most routers have disabled source routing for security reasons). +In general, several +.IR gateway\fR's +is allowed (comma separated). For IPv6, the form of +.IR num\fB,\fIaddr\fB,\fIaddr... +is allowed, where +.IR num +is a route header type (default is type 2). Note the type 0 route header +is now deprecated (rfc5095). +.TP +.BI \-i " interface" ", --interface=" interface +Specifies the interface through which +.I traceroute +should send packets. By default, the interface is selected +according to the routing table. +.TP +.BI \-m " max_ttl" ", --max-hops=" max_ttl +Specifies the maximum number of hops (max time-to-live value) +.I traceroute +will probe. The default is 30. +.TP +.BI \-N " squeries" ", --sim-queries=" squeries +Specifies the number of probe packets sent out simultaneously. +Sending several probes concurrently can speed up +.I traceroute +considerably. The default value is 16. +.br +Note that some routers and hosts can use ICMP rate throttling. In such +a situation specifying too large number can lead to loss of some responses. +.TP +.BI \-n +Do not try to map IP addresses to host names when displaying them. +.TP +.BI \-p " port" ", --port=" port +For UDP tracing, specifies the destination port base +.I traceroute +will use (the destination port number will be incremented by each probe). +.br +For ICMP tracing, specifies the initial ICMP sequence value (incremented +by each probe too). +.br +For TCP and others specifies just the (constant) destination +port to connect. +.TP +.BI \-t " tos" ", --tos=" tos +For IPv4, set the Type of Service (TOS) and Precedence value. Useful values +are 16 (low delay) and 8 (high throughput). Note that in order to use +some TOS precedence values, you have to be super user. +.br +For IPv6, set the Traffic Control value. +.TP +.BI \-l " flow_label" ", --flowlabel=" flow_label +Use specified flow_label for IPv6 packets. +.TP +.BI \-w " max\fR[\fB,\fIhere\fB,\fInear\fR]" ", --wait=" max\fR[\fB,\fIhere\fB,\fInear\fR] +Determines how long to wait for a response to a probe. +.br + +.br +There are three (in general) float values separated by a comma +(or a slash). +.IR Max +specifies the maximum time (in seconds, default 5.0) to wait, in any case. +.br + +.br +Traditional traceroute implementation always waited whole +.IR max +seconds for any probe. But if we already have some replies from the +.B same +hop, or even from some +.B next +hop, we can use the round trip time of such a reply as a hint +to determine the actual reasonable amount of time to wait. +.br + +.br +The optional +.IR here +(default 3.0) specifies a factor to multiply the round trip time of an already +received response from the +.B same +hop. The resulting value is used as a timeout for the probe, instead of +(but no more than) +.IR max\fR. +The optional +.IR near +(default 10.0) specifies a similar factor for a response from some +.B next +hop. +(The time of the first found result is used in both cases). +.br + +.br +First, we look for the +.B same +hop (of the probe which will be printed first from now). +If nothing found, then look for some +.B next +hop. If nothing found, use +.IR max\fR. +If +.IR here +and/or +.IR near +have zero values, the corresponding computation is skipped. +.br +.IR Here +and +.IR near +are always set to zero if only +.IR max +is specified (for compatibility with previous versions). +.TP +.BI \-q " nqueries" ", --queries=" nqueries +Sets the number of probe packets per hop. The default is 3. +.TP +.BI \-r +Bypass the normal routing tables and send directly to a host on +an attached network. If the host is not on a directly-attached +network, an error is returned. This option can be used to ping a +local host through an interface that has no route through it. +.TP +.BI \-s " source_addr" ", --source=" source_addr +Chooses an alternative source address. Note that you must select the +address of one of the interfaces. +By default, the address of the outgoing interface is used. +.TP +.BI \-z " sendwait" ", --sendwait=" sendwait +Minimal time interval between probes (default 0). +If the value is more than 10, then it specifies a number in milliseconds, +else it is a number of seconds (float point values allowed too). +Useful when some routers use rate-limit for ICMP messages. +.TP +.B \-e, \-\-extensions +Show ICMP extensions (rfc4884). The general form is +.I CLASS\fB/\fITYPE\fB: +followed by a hexadecimal dump. +The MPLS (rfc4950) is shown parsed, in a form: +.B MPLS:L=\fIlabel\fB,E=\fIexp_use\fB,S=\fIstack_bottom\fB,T=\fITTL +(more objects separated by +.B / +). The Interface Information (rfc5837) is shown parsed as well, in a following form: +.B \fR{\fBINC\fR|\fBSUB\fR|\fBOUT\fR|\fBNXT\fR}\fB:\fIindex\fB,\fIIP_addr\fB,"\fIname\fB",mtu=\fIMTU\fB +(all four fields may be missing). +.TP +.B \-A, \-\-as\-path\-lookups +Perform AS path lookups in routing registries and print results +directly after the corresponding addresses. +.TP +.B \-V, \-\-version +Print the version and exit. +.br +.P +There are additional options intended for advanced usage +(such as alternate trace methods etc.): +.TP +.B \--sport\fR=\fIport +Chooses the source port to use. Implies +.B \-N\ 1\fR\ -w\ 5 . +Normally source ports (if applicable) are chosen by the system. +.TP +.B \--fwmark\fR=\fImark +Set the firewall mark for outgoing packets (since the Linux kernel 2.6.25). +.TP +.BI \-M " method" ", --module=" name +Use specified method for traceroute operations. Default traditional udp method +has name +.IR default , +icmp +.BR "" ( "-I" ) " +and tcp +.BR "" ( "-T" ) " +have names +.I icmp +and +.I tcp +respectively. +.br +Method-specific options can be passed by +.BR \-O\ . +Most methods have their simple shortcuts, +.BR "" ( "-I " means " -M icmp" , +etc). +.TP +.BI \-O " option" ", --options=" options +Specifies some method-specific option. Several options are separated by comma (or use several +.B \-O +on cmdline). +Each method may have its own specific options, or many not have them at all. +To print information about available options, use +.BR \-O\ help . +.TP +.B \-U, \-\-udp +Use UDP to particular destination port for tracerouting (instead of increasing +the port per each probe). Default port is 53 (dns). +.TP +.BI \-UL +Use UDPLITE for tracerouting (default port is 53). +.TP +.B \-D, \-\-dccp +Use DCCP Requests for probes. +.TP +.BI \-P " protocol" ", --protocol=" protocol +Use raw packet of specified protocol for tracerouting. Default protocol is +253 (rfc3692). +.TP +.BI \--mtu +Discover MTU along the path being traced. Implies +.BR \-F\ \-N\ 1 . +New +.I mtu +is printed once in a form of +.B F=\fINUM +at the first probe of a hop which requires such +.I mtu +to be reached. (Actually, the correspond "frag needed" icmp message +normally is sent by the previous hop). +.br + +.br +Note, that some routers might cache once the seen information +on a fragmentation. Thus you can receive the final mtu from a closer hop. +Try to specify an unusual +.I tos +by +.B \-t +, this can help for one attempt (then it can be cached there as well). +.br +See +.B \-F +option for more info. +.TP +.BI \--back +Print the number of backward hops when it seems different with the forward +direction. This number is guessed in assumption that remote hops send reply +packets with initial ttl set to either 64, or 128 or 255 (which seems +a common practice). It is printed as a negate value in a form of '-NUM' . +.SH LIST OF AVAILABLE METHODS +In general, a particular traceroute method may have to be chosen by +.BR \-M\ name , +but most of the methods have their simple cmdline switches +(you can see them after the method name, if present). +.SS default +The traditional, ancient method of tracerouting. Used by default. +.P +Probe packets are udp datagrams with so-called "unlikely" destination ports. +The "unlikely" port of the first probe is 33434, then for each next probe +it is incremented by one. Since the ports are expected to be unused, +the destination host normally returns "icmp unreach port" as a final response. +(Nobody knows what happens when some application listens for such ports, +though). +.P +This method is allowed for unprivileged users. +.SS icmp \ \ \ \-I +Most usual method for now, which uses icmp echo packets for probes. +.br +If you can ping(8) the destination host, icmp tracerouting is applicable +as well. +.P +This method may be allowed for unprivileged users +since the kernel 3.0 (IPv4, for IPv6 since 3.11), which supports new +.I dgram icmp +(or +.IR \fR"\fIping\fR") +sockets. To allow such sockets, sysadmin should provide +.I net/ipv4/ping_group_range +sysctl range to match any group of the user. +.br +Options: +.TP +.B raw +Use only raw sockets (the traditional way). +.br +This way is tried first by default (for compatibility reasons), +then new dgram icmp sockets as fallback. +.TP +.B dgram +Use only dgram icmp sockets. +.SS tcp \ \ \ \ \-T +Well-known modern method, intended to bypass firewalls. +.br +Uses the constant destination port (default is 80, http). +.P +If some filters are present in the network path, then most probably +any "unlikely" udp ports (as for +.I default +method) or even icmp echoes (as for +.IR icmp ) +are filtered, and whole tracerouting will just stop at such a firewall. +To bypass a network filter, we have to use only allowed protocol/port +combinations. If we trace for some, say, mailserver, then more likely +.B \-T \-p 25 +can reach it, even when +.B \-I +can not. +.P +This method uses well-known "half-open technique", which prevents +applications on the destination host from seeing our probes at all. +Normally, a tcp syn is sent. For non-listened ports we receive tcp reset, +and all is done. For active listening ports we receive tcp syn+ack, but +answer by tcp reset (instead of expected tcp ack), this way the remote tcp +session is dropped even without the application ever taking notice. +.P +There is a couple of options for +.I tcp +method: +.TP +.B syn,ack,fin,rst,psh,urg,ece,cwr +Sets specified tcp flags for probe packet, in any combination. +.TP +.B flags\fR=\fInum +Sets the flags field in the tcp header exactly to +.IR num . +.TP +.B ecn +Send syn packet with tcp flags ECE and CWR (for Explicit Congestion +Notification, rfc3168). +.TP +.B sack,timestamps,window_scaling +Use the corresponding tcp header option in the outgoing probe packet. +.TP +.B sysctl +Use current sysctl +.IR "" ( "/proc/sys/net/*" ) +setting for the tcp header options above and +.BR ecn . +Always set by default, if nothing else specified. +.TP +.B fastopen +Use fastopen tcp option (when +.BR syn ), +for initial cookie negotiation only. +.TP +.B mss\fR=[\fInum\fR] +Use value of +.I num +(or unchanged) for maxseg tcp header option (when +.BR syn ), +and discover its clamping along the path being traced. +New changed +.I mss +is printed once in a form of +.B M=\fINUM +at the first probe on which it was detected. +Note, some routers may return too short original fragment +in the time exceeded message, making the check impossible. +Besides that the responses may come in a different order. +All this can lead to a later place of the report +(using +.BR \-N\ 1 +can help for the order). +.TP +.B info +Print tcp flags and supported options +of final tcp replies when the target host is reached. +Allows to determine whether an application listens the port and +other useful things. +Supported tcp options are all that can be set by +.BR \-T\ \-O , +ie. +.IR mss , +.IR sack , +.IR timestamps , +.IR window_scaling +and +.IR fastopen , +with the similar output format (a value for +.IR mss +and just presence for others). +.P +Default options is +.BR syn,sysctl . +.SS tcpconn +An initial implementation of tcp method, simple using connect(2) call, +which does full tcp session opening. Not recommended for normal use, because +a destination application is always affected (and can be confused). +.SS udp \ \ \ \ \-U +Use udp datagram with constant destination port (default 53, dns). +.br +Intended to bypass firewall as well. +.P +Note, that unlike in +.I tcp +method, the correspond application on the destination host +.B always +receive our probes (with random data), and most can easily be confused +by them. Most cases it will not respond to our packets though, so we will never +see the final hop in the trace. (Fortunately, it seems that at least +dns servers replies with something angry). +.P +This method is allowed for unprivileged users. +.SS udplite \ \ \-UL +Use udplite datagram for probes (with constant destination port, +default 53). +.P +This method is allowed for unprivileged users. +.br +Options: +.TP +.B coverage\fR=\fInum +Set udplite send coverage to +.IR num . +.SS dccp \ \ \-D +Use DCCP Request packets for probes (rfc4340). +.P +This method uses the same "half-open technique" as used for TCP. +The default destination port is 33434. +.P +Options: +.TP +.B service\fR=\fInum +Set DCCP service code to +.IR num +(default is 1885957735). +.SS raw \ \ \ \ \-P proto +Send raw packet of protocol +.IR proto . +.br +No protocol-specific headers are used, just IP header only. +.br +Implies +.B \-N\ 1\fR\ -w\ 5 . +.br +Options: +.TP +.B protocol\fR=\fIproto +Use IP protocol +.I proto +(default 253). +.SH NOTES +.PP +To speed up work, normally several probes are sent simultaneously. +On the other hand, it creates a "storm of packages", especially +in the reply direction. Routers can throttle the rate of icmp responses, +and some of replies can be lost. To avoid this, decrease the number +of simultaneous probes, or even set it to 1 (like in initial traceroute +implementation), i.e. +.B \-N 1 +.PP +The final (target) host can drop some of the simultaneous probes, +and might even answer only the latest ones. It can lead to extra +"looks like expired" hops near the final hop. We use a smart algorithm +to auto-detect such a situation, but if it cannot help in your case, just use +.B \-N 1 +too. +.PP +For even greater stability you can slow down the program's work by +.B \-z +option, for example use +.B \-z 0.5 +for half-second pause between probes. +.PP +To avoid an extra waiting, we use adaptive algorithm for timeouts (see +.B \-w +option for more info). It can lead to premature expiry +(especially when response times differ at times) and printing "*" +instead of a time. In such a case, switch this algorithm off, by specifying +.B \-w +with the desired timeout only (for example, +.B \-w 5\fR). +.PP +If some hops report nothing for every method, the last chance to obtain +something is to use +.B ping -R +command (IPv4, and for nearest 8 hops only). +.SH SEE ALSO +.BR ping (8), +.BR ping6 (8), +.BR tcpdump (8), +.BR netstat (8) diff --git a/traceroute/traceroute.c b/traceroute/traceroute.c new file mode 100644 index 0000000..08b850a --- /dev/null +++ b/traceroute/traceroute.c @@ -0,0 +1,1693 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <poll.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netdb.h> +#include <errno.h> +#include <locale.h> +#include <sys/utsname.h> +#include <linux/types.h> +#include <linux/errqueue.h> + +/* XXX: Remove this when things will be defined properly in netinet/ ... */ +#include "flowlabel.h" + +#include <clif.h> +#include "version.h" +#include "traceroute.h" + + +#ifndef ICMP6_DST_UNREACH_BEYONDSCOPE +#ifdef ICMP6_DST_UNREACH_NOTNEIGHBOR +#define ICMP6_DST_UNREACH_BEYONDSCOPE ICMP6_DST_UNREACH_NOTNEIGHBOR +#else +#define ICMP6_DST_UNREACH_BEYONDSCOPE 2 +#endif +#endif + +#ifndef IPV6_RECVHOPLIMIT +#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT +#endif + +#ifndef IP_PMTUDISC_PROBE +#define IP_PMTUDISC_PROBE 3 +#endif + +#ifndef IPV6_PMTUDISC_PROBE +#define IPV6_PMTUDISC_PROBE 3 +#endif + +#ifndef AI_IDN +#define AI_IDN 0 +#endif + +#ifndef NI_IDN +#define NI_IDN 0 +#endif + + +#define MAX_HOPS 255 +#define MAX_PROBES 10 +#define MAX_GATEWAYS_4 8 +#define MAX_GATEWAYS_6 127 +#define DEF_HOPS 30 +#define DEF_SIM_PROBES 16 /* including several hops */ +#define DEF_NUM_PROBES 3 +#define DEF_WAIT_SECS 5.0 +#define DEF_HERE_FACTOR 3 +#define DEF_NEAR_FACTOR 10 +#ifndef DEF_WAIT_PREC +#define DEF_WAIT_PREC 0.001 /* +1 ms to avoid precision issues */ +#endif +#define DEF_SEND_SECS 0 +#define DEF_DATA_LEN 40 /* all but IP header... */ +#define MAX_PACKET_LEN 65000 +#ifndef DEF_AF +#define DEF_AF AF_INET +#endif + +#define ttl2hops(X) (((X) <= 64 ? 65 : ((X) <= 128 ? 129 : 256)) - (X)) + + +static char version_string[] = "Modern traceroute for Linux, " + "version " _TEXT(VERSION) + "\nCopyright (c) 2016 Dmitry Butskoy, " + " License: GPL v2 or any later"; +static int debug = 0; +static unsigned int first_hop = 1; +static unsigned int max_hops = DEF_HOPS; +static unsigned int sim_probes = DEF_SIM_PROBES; +static unsigned int probes_per_hop = DEF_NUM_PROBES; + +static char **gateways = NULL; +static int num_gateways = 0; +static unsigned char *rtbuf = NULL; +static size_t rtbuf_len = 0; +static unsigned int ipv6_rthdr_type = 2; /* IPV6_RTHDR_TYPE_2 */ + +static size_t header_len = 0; +static size_t data_len = 0; + +static int dontfrag = 0; +static int noresolve = 0; +static int extension = 0; +static int as_lookups = 0; +static unsigned int dst_port_seq = 0; +static unsigned int tos = 0; +static unsigned int flow_label = 0; +static int noroute = 0; +static unsigned int fwmark = 0; +static int packet_len = -1; +static double wait_secs = DEF_WAIT_SECS; +static double here_factor = DEF_HERE_FACTOR; +static double near_factor = DEF_NEAR_FACTOR; +static double send_secs = DEF_SEND_SECS; +static int mtudisc = 0; +static int backward = 0; + +static sockaddr_any dst_addr = {{ 0, }, }; +static char *dst_name = NULL; +static char *device = NULL; +static sockaddr_any src_addr = {{ 0, }, }; +static unsigned int src_port = 0; + +static const char *module = "default"; +static const tr_module *ops = NULL; + +static char *opts[16] = { NULL, }; /* assume enough */ +static unsigned int opts_idx = 1; /* first one reserved... */ + + +static int af = 0; + +static probe *probes = NULL; +static unsigned int num_probes = 0; + + +static void ex_error (const char *format, ...) { + va_list ap; + + va_start (ap, format); + vfprintf (stderr, format, ap); + va_end (ap); + + fprintf (stderr, "\n"); + + exit (2); +} + +void error (const char *str) { + + fprintf (stderr, "\n"); + + perror (str); + + exit (1); +} + +void error_or_perm (const char *str) { + + if (errno == EPERM) + fprintf (stderr, "You do not have enough privileges to use " + "this traceroute method."); + error (str); +} + + +void put_err (probe *pb, const char *format, ...) { + va_list ap; + char *curr = pb->err_str; + char *end = pb->err_str + sizeof (pb->err_str) - 1; + + /* It can already contain something when `--mtu' or `-T -O mss' */ + while (curr < end && *curr) curr++; + + va_start (ap, format); + vsnprintf (curr, end - curr, format, ap); + va_end (ap); +} + + +/* Set initial parameters according to how we was called */ + +static void check_progname (const char *name) { + const char *p; + int l; + + p = strrchr (name, '/'); + if (p) p++; + else p = name; + + l = strlen (p); + if (l <= 0) return; + l--; + + if (p[l] == '6') af = AF_INET6; + else if (p[l] == '4') af = AF_INET; + + if (!strncmp (p, "tcp", 3)) + module = "tcp"; + if (!strncmp (p, "tracert", 7)) + module = "icmp"; + + return; +} + + +static int getaddr (const char *name, sockaddr_any *addr) { + int ret; + struct addrinfo hints, *ai, *res = NULL; + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = af; + hints.ai_flags = AI_IDN; + + ret = getaddrinfo (name, NULL, &hints, &res); + if (ret) { + fprintf (stderr, "%s: %s\n", name, gai_strerror (ret)); + return -1; + } + + for (ai = res; ai; ai = ai->ai_next) { + if (ai->ai_family == af) break; + /* when af not specified, choose DEF_AF if present */ + if (!af && ai->ai_family == DEF_AF) + break; + } + if (!ai) ai = res; /* anything... */ + + if (ai->ai_addrlen > sizeof (*addr)) + return -1; /* paranoia */ + memcpy (addr, ai->ai_addr, ai->ai_addrlen); + + freeaddrinfo (res); + + /* No v4mapped addresses in real network, interpret it as ipv4 anyway */ + if (addr->sa.sa_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED (&addr->sin6.sin6_addr) + ) { + if (af == AF_INET6) return -1; + addr->sa.sa_family = AF_INET; + addr->sin.sin_addr.s_addr = addr->sin6.sin6_addr.s6_addr32[3]; + } + + return 0; +} + + +static void make_fd_used (int fd) { + int nfd; + + if (fcntl (fd, F_GETFL) != -1) + return; + + if (errno != EBADF) + error ("fcntl F_GETFL"); + + nfd = open ("/dev/null", O_RDONLY); + if (nfd < 0) error ("open /dev/null"); + + if (nfd != fd) { + dup2 (nfd, fd); + close (nfd); + } + + return; +} + + +static char addr2str_buf[INET6_ADDRSTRLEN]; + +const char *addr2str (const sockaddr_any *addr) { + + getnameinfo (&addr->sa, sizeof (*addr), + addr2str_buf, sizeof (addr2str_buf), 0, 0, NI_NUMERICHOST); + + return addr2str_buf; +} + + +/* IP options stuff */ + +static void init_ip_options (void) { + sockaddr_any *gates; + int i, max; + + if (!num_gateways) + return; + + /* check for TYPE,ADDR,ADDR... form */ + if (af == AF_INET6 && num_gateways > 1 && gateways[0]) { + char *q; + unsigned int value = strtoul (gateways[0], &q, 0); + + if (!*q) { + ipv6_rthdr_type = value; + num_gateways--; + for (i = 0; i < num_gateways; i++) + gateways[i] = gateways[i + 1]; + } + } + + + max = af == AF_INET ? MAX_GATEWAYS_4 : MAX_GATEWAYS_6; + if (num_gateways > max) + ex_error ("Too many gateways specified. No more than %d", max); + + + gates = alloca (num_gateways * sizeof (*gates)); + + for (i = 0; i < num_gateways; i++) { + + if (!gateways[i]) error ("strdup"); + + if (getaddr (gateways[i], &gates[i]) < 0) + ex_error (""); /* already reported */ + if (gates[i].sa.sa_family != af) + ex_error ("IP versions mismatch in gateway addresses"); + + free (gateways[i]); + } + + free (gateways); + gateways = NULL; + + + if (af == AF_INET) { + struct in_addr *in; + + rtbuf_len = 4 + (num_gateways + 1) * sizeof (*in); + rtbuf = malloc (rtbuf_len); + if (!rtbuf) error ("malloc"); + + in = (struct in_addr *) &rtbuf[4]; + for (i = 0; i < num_gateways; i++) + memcpy (&in[i], &gates[i].sin.sin_addr, sizeof (*in)); + /* final hop */ + memcpy (&in[i], &dst_addr.sin.sin_addr, sizeof (*in)); + i++; + + rtbuf[0] = IPOPT_NOP; + rtbuf[1] = IPOPT_LSRR; + rtbuf[2] = (i * sizeof (*in)) + 3; + rtbuf[3] = IPOPT_MINOFF; + + } + else if (af == AF_INET6) { + struct in6_addr *in6; + struct ip6_rthdr *rth; + + /* IPV6_RTHDR_TYPE_0 length is 8 */ + rtbuf_len = 8 + num_gateways * sizeof (*in6); + rtbuf = malloc (rtbuf_len); + if (!rtbuf) error ("malloc"); + + rth = (struct ip6_rthdr *) rtbuf; + rth->ip6r_nxt = 0; + rth->ip6r_len = 2 * num_gateways; + rth->ip6r_type = ipv6_rthdr_type; + rth->ip6r_segleft = num_gateways; + + *((uint32_t *) (rth + 1)) = 0; + + in6 = (struct in6_addr *) (rtbuf + 8); + for (i = 0; i < num_gateways; i++) + memcpy (&in6[i], &gates[i].sin6.sin6_addr, sizeof (*in6)); + } + + return; +} + + +/* Command line stuff */ + +static int set_af (CLIF_option *optn, char *arg) { + int vers = (long) optn->data; + + if (vers == 4) af = AF_INET; + else if (vers == 6) af = AF_INET6; + else + return -1; + + return 0; +} + +static int add_gateway (CLIF_option *optn, char *arg) { + + if (num_gateways >= MAX_GATEWAYS_6) { /* 127 > 8 ... :) */ + fprintf (stderr, "Too many gateways specified."); + return -1; + } + + gateways = realloc (gateways, (num_gateways + 1) * sizeof (*gateways)); + if (!gateways) error ("malloc"); + gateways[num_gateways++] = strdup (arg); + + return 0; +} + +static int set_source (CLIF_option *optn, char *arg) { + + return getaddr (arg, &src_addr); +} + +static int set_port (CLIF_option *optn, char *arg) { + unsigned int *up = (unsigned int *) optn->data; + char *q; + + *up = strtoul (arg, &q, 0); + if (q == arg) { + struct servent *s = getservbyname (arg, NULL); + + if (!s) return -1; + *up = ntohs (s->s_port); + } + + return 0; +} + +static int set_module (CLIF_option *optn, char *arg) { + + module = (char *) optn->data; + + return 0; +} + + +static int set_mod_option (CLIF_option *optn, char *arg) { + + if (!strcmp (arg, "help")) { + const tr_module *mod = tr_get_module (module); + + if (mod && mod->options) { + /* just to set common keyword flag... */ + CLIF_parse (1, &arg, 0, 0, CLIF_KEYWORD); + CLIF_print_options (NULL, mod->options); + } else + fprintf (stderr, "No options for module `%s'\n", module); + + exit (0); + } + + if (opts_idx >= sizeof (opts) / sizeof (*opts)) { + fprintf (stderr, "Too many module options\n"); + return -1; + } + + opts[opts_idx] = strdup (arg); + if (!opts[opts_idx]) error ("strdup"); + opts_idx++; + + return 0; +} + + +static int set_raw (CLIF_option *optn, char *arg) { + char buf[1024]; + + module = "raw"; + + snprintf (buf, sizeof (buf), "protocol=%s", arg); + return set_mod_option (optn, buf); +} + + +static int set_wait_specs (CLIF_option *optn, char *arg) { + char *p, *q; + + here_factor = near_factor = 0; + + wait_secs = strtod (p = arg, &q); + if (q == p) return -1; + if (!*q++) return 0; + + here_factor = strtod (p = q, &q); + if (q == p) return -1; + if (!*q++) return 0; + + near_factor = strtod (p = q, &q); + if (q == p || *q) return -1; + + return 0; +} + + +static int set_host (CLIF_argument *argm, char *arg, int index) { + + if (getaddr (arg, &dst_addr) < 0) + return -1; + + dst_name = arg; + + /* i.e., guess it by the addr in cmdline... */ + if (!af) af = dst_addr.sa.sa_family; + + return 0; +} + + +static CLIF_option option_list[] = { + { "4", 0, 0, "Use IPv4", set_af, (void *) 4, 0, CLIF_EXTRA }, + { "6", 0, 0, "Use IPv6", set_af, (void *) 6, 0, 0 }, + { "d", "debug", 0, "Enable socket level debugging", + CLIF_set_flag, &debug, 0, 0 }, + { "F", "dont-fragment", 0, "Do not fragment packets", + CLIF_set_flag, &dontfrag, 0, CLIF_ABBREV }, + { "f", "first", "first_ttl", "Start from the %s hop (instead from 1)", + CLIF_set_uint, &first_hop, 0, 0 }, + { "g", "gateway", "gate", "Route packets through the specified gateway " + "(maximum " _TEXT(MAX_GATEWAYS_4) " for IPv4 and " + _TEXT(MAX_GATEWAYS_6) " for IPv6)", + add_gateway, 0, 0, CLIF_SEVERAL }, + { "I", "icmp", 0, "Use ICMP ECHO for tracerouting", + set_module, "icmp", 0, 0 }, + { "T", "tcp", 0, "Use TCP SYN for tracerouting (default " + "port is " _TEXT(DEF_TCP_PORT) ")", + set_module, "tcp", 0, 0 }, + { "i", "interface", "device", "Specify a network interface " + "to operate with", + CLIF_set_string, &device, 0, 0 }, + { "m", "max-hops", "max_ttl", "Set the max number of hops (max TTL " + "to be reached). Default is " _TEXT(DEF_HOPS) , + CLIF_set_uint, &max_hops, 0, 0 }, + { "N", "sim-queries", "squeries", "Set the number of probes " + "to be tried simultaneously (default is " + _TEXT(DEF_SIM_PROBES) ")", + CLIF_set_uint, &sim_probes, 0, 0 }, + { "n", 0, 0, "Do not resolve IP addresses to their domain names", + CLIF_set_flag, &noresolve, 0, 0 }, + { "p", "port", "port", "Set the destination port to use. " + "It is either initial udp port value for " + "\"default\" method (incremented by each probe, " + "default is " _TEXT(DEF_START_PORT) "), " + "or initial seq for \"icmp\" (incremented as well, " + "default from 1), or some constant destination port" + " for other methods (with default of " + _TEXT(DEF_TCP_PORT) " for \"tcp\", " + _TEXT(DEF_UDP_PORT) " for \"udp\", etc.)", + set_port, &dst_port_seq, 0, 0 }, + { "t", "tos", "tos", "Set the TOS (IPv4 type of service) or TC " + "(IPv6 traffic class) value for outgoing packets", + CLIF_set_uint, &tos, 0, 0 }, + { "l", "flowlabel", "flow_label", "Use specified %s for IPv6 packets", + CLIF_set_uint, &flow_label, 0, 0 }, + { "w", "wait", "MAX,HERE,NEAR", "Wait for a probe no more than HERE " + "(default " _TEXT(DEF_HERE_FACTOR) ") times longer " + "than a response from the same hop, or no more " + "than NEAR (default " _TEXT(DEF_NEAR_FACTOR) ") " + "times than some next hop, or MAX (default " + _TEXT(DEF_WAIT_SECS) ") seconds " + "(float point values allowed too)", + set_wait_specs, 0, 0, 0 }, + { "q", "queries", "nqueries", "Set the number of probes per each hop. " + "Default is " _TEXT(DEF_NUM_PROBES), + CLIF_set_uint, &probes_per_hop, 0, 0 }, + { "r", 0, 0, "Bypass the normal routing and send directly to a host " + "on an attached network", + CLIF_set_flag, &noroute, 0, 0 }, + { "s", "source", "src_addr", "Use source %s for outgoing packets", + set_source, 0, 0, 0 }, + { "z", "sendwait", "sendwait", "Minimal time interval between probes " + "(default " _TEXT(DEF_SEND_SECS) "). If the value " + "is more than 10, then it specifies a number " + "in milliseconds, else it is a number of seconds " + "(float point values allowed too)", + CLIF_set_double, &send_secs, 0, 0 }, + { "e", "extensions", 0, "Show ICMP extensions (if present), " + "including MPLS", + CLIF_set_flag, &extension, 0, CLIF_ABBREV }, + { "A", "as-path-lookups", 0, "Perform AS path lookups in routing " + "registries and print results directly after " + "the corresponding addresses", + CLIF_set_flag, &as_lookups, 0, 0 }, + { "M", "module", "name", "Use specified module (either builtin or " + "external) for traceroute operations. Most methods " + "have their shortcuts (`-I' means `-M icmp' etc.)", + CLIF_set_string, &module, 0, CLIF_EXTRA }, + { "O", "options", "OPTS", "Use module-specific option %s for the " + "traceroute module. Several %s allowed, separated " + "by comma. If %s is \"help\", print info about " + "available options", + set_mod_option, 0, 0, CLIF_SEVERAL | CLIF_EXTRA }, + { 0, "sport", "num", "Use source port %s for outgoing packets. " + "Implies `-N 1'", + set_port, &src_port, 0, CLIF_EXTRA }, +#ifdef SO_MARK + { 0, "fwmark", "num", "Set firewall mark for outgoing packets", + CLIF_set_uint, &fwmark, 0, 0 }, +#endif + { "U", "udp", 0, "Use UDP to particular port for tracerouting " + "(instead of increasing the port per each probe), " + "default port is " _TEXT(DEF_UDP_PORT), + set_module, "udp", 0, CLIF_EXTRA }, + { 0, "UL", 0, "Use UDPLITE for tracerouting (default dest port is " + _TEXT(DEF_UDP_PORT) ")", + set_module, "udplite", 0, CLIF_ONEDASH|CLIF_EXTRA }, + { "D", "dccp", 0, "Use DCCP Request for tracerouting (default " + "port is " _TEXT(DEF_DCCP_PORT) ")", + set_module, "dccp", 0, CLIF_EXTRA }, + { "P", "protocol", "prot", "Use raw packet of protocol %s " + "for tracerouting", + set_raw, 0, 0, CLIF_EXTRA }, + { 0, "mtu", 0, "Discover MTU along the path being traced. " + "Implies `-F -N 1'", + CLIF_set_flag, &mtudisc, 0, CLIF_EXTRA }, + { 0, "back", 0, "Guess the number of hops in the backward path " + "and print if it differs", + CLIF_set_flag, &backward, 0, CLIF_EXTRA }, + CLIF_VERSION_OPTION (version_string), + CLIF_HELP_OPTION, + CLIF_END_OPTION +}; + +static CLIF_argument arg_list[] = { + { "host", "The host to traceroute to", + set_host, 0, CLIF_STRICT }, + { "packetlen", "The full packet length (default is the length of " + "an IP header plus " _TEXT(DEF_DATA_LEN) "). Can be " + "ignored or increased to a minimal allowed value", + CLIF_arg_int, &packet_len, 0 }, + CLIF_END_ARGUMENT +}; + + +static void do_it (void); + +int main (int argc, char *argv[]) { + + setlocale (LC_ALL, ""); + setlocale (LC_NUMERIC, "C"); /* avoid commas in msec printed */ + + check_progname (argv[0]); + + + if (CLIF_parse (argc, argv, option_list, arg_list, + CLIF_MAY_JOIN_ARG | CLIF_HELP_EMPTY) < 0 + ) exit (2); + + + ops = tr_get_module (module); + if (!ops) ex_error ("Unknown traceroute module %s", module); + + + if (!first_hop || first_hop > max_hops) + ex_error ("first hop out of range"); + if (max_hops > MAX_HOPS) + ex_error ("max hops cannot be more than " _TEXT(MAX_HOPS)); + if (!probes_per_hop || probes_per_hop > MAX_PROBES) + ex_error ("no more than " _TEXT(MAX_PROBES) " probes per hop"); + if (wait_secs < 0 || here_factor < 0 || near_factor < 0) + ex_error ("bad wait specifications `%g,%g,%g' used", + wait_secs, here_factor, near_factor); + if (packet_len > MAX_PACKET_LEN) + ex_error ("too big packetlen %d specified", packet_len); + if (src_addr.sa.sa_family && src_addr.sa.sa_family != af) + ex_error ("IP version mismatch in addresses specified"); + if (send_secs < 0) + ex_error ("bad sendtime `%g' specified", send_secs); + if (send_secs >= 10) /* it is milliseconds */ + send_secs /= 1000; + + if (af == AF_INET6 && (tos || flow_label)) + dst_addr.sin6.sin6_flowinfo = + htonl (((tos & 0xff) << 20) | (flow_label & 0x000fffff)); + + if (src_port) { + src_addr.sin.sin_port = htons ((uint16_t) src_port); + src_addr.sa.sa_family = af; + } + + if (src_port || ops->one_per_time) { + sim_probes = 1; + here_factor = near_factor = 0; + } + + + /* make sure we don't std{in,out,err} to open sockets */ + make_fd_used (0); + make_fd_used (1); + make_fd_used (2); + + + init_ip_options (); + + header_len = (af == AF_INET ? sizeof (struct iphdr) + : sizeof (struct ip6_hdr)) + + rtbuf_len + ops->header_len; + + if (mtudisc) { + dontfrag = 1; + sim_probes = 1; + if (packet_len < 0) + packet_len = MAX_PACKET_LEN; + } + + if (packet_len < 0) { + if (DEF_DATA_LEN >= ops->header_len) + data_len = DEF_DATA_LEN - ops->header_len; + } else { + if (packet_len >= header_len) + data_len = packet_len - header_len; + } + + + num_probes = max_hops * probes_per_hop; + probes = calloc (num_probes, sizeof (*probes)); + if (!probes) error ("calloc"); + + + if (ops->options && opts_idx > 1) { + opts[0] = strdup (module); /* aka argv[0] ... */ + if (CLIF_parse (opts_idx, opts, ops->options, 0, CLIF_KEYWORD) < 0) + exit (2); + } + + if (ops->init (&dst_addr, dst_port_seq, &data_len) < 0) + ex_error ("trace method's init failed"); + + + do_it (); + + return 0; +} + + +/* PRINT STUFF */ + +static void print_header (void) { + + /* Note, without ending new-line! */ + printf ("traceroute to %s (%s), %u hops max, %zu byte packets", + dst_name, addr2str (&dst_addr), max_hops, + header_len + data_len); + fflush (stdout); +} + + +static void print_addr (sockaddr_any *res) { + const char *str; + + if (!res->sa.sa_family) + return; + + str = addr2str (res); + + + if (noresolve) + printf (" %s", str); + else { + char buf[1024]; + + buf[0] = '\0'; + getnameinfo (&res->sa, sizeof (*res), buf, sizeof (buf), + 0, 0, NI_IDN); + printf (" %s (%s)", buf[0] ? buf : str, str); + } + + if (as_lookups) + printf (" [%s]", get_as_path (str)); +} + + +static void print_probe (probe *pb) { + unsigned int idx = (pb - probes); + unsigned int ttl = idx / probes_per_hop + 1; + unsigned int np = idx % probes_per_hop; + + if (np == 0) + printf ("\n%2u ", ttl); + + + if (!pb->res.sa.sa_family) + printf (" *"); + else { + int prn = !np; /* print if the first... */ + + if (np) { /* ...and if differs with previous */ + probe *p; + + /* skip expired */ + for (p = pb - 1; np && !p->res.sa.sa_family; p--, np--) ; + + if (!np || + !equal_addr (&p->res, &pb->res) || + (p->ext != pb->ext && + !(p->ext && pb->ext && !strcmp (p->ext, pb->ext))) || + (backward && p->recv_ttl != pb->recv_ttl) + ) prn = 1; + } + + if (prn) { + print_addr (&pb->res); + + if (pb->ext) printf (" <%s>", pb->ext); + + if (backward && pb->recv_ttl) { + int hops = ttl2hops (pb->recv_ttl); + if (hops != ttl) printf (" '-%d'", hops); + } + } + } + + + if (pb->recv_time) { + double diff = pb->recv_time - pb->send_time; + + printf (" %.3f ms", diff * 1000); + } + + if (pb->err_str[0]) + printf (" %s", pb->err_str); + + + fflush (stdout); + + return; +} + + +static void print_end (void) { + + printf ("\n"); +} + + +/* Compute timeout stuff */ + +static double get_timeout (probe *pb) { + double value; + + if (here_factor) { + /* check for already replied from the same hop */ + int i, idx = (pb - probes); + probe *p = &probes[idx - (idx % probes_per_hop)]; + + for (i = 0; i < probes_per_hop; i++, p++) { + /* `p == pb' skipped since !pb->done */ + + if (p->done && (value = p->recv_time - p->send_time) > 0) { + value += DEF_WAIT_PREC; + value *= here_factor; + return value < wait_secs ? value : wait_secs; + } + } + } + + if (near_factor) { + /* check forward for already replied */ + probe *p, *endp = probes + num_probes; + + for (p = pb + 1; p < endp && p->send_time; p++) { + + if (p->done && (value = p->recv_time - p->send_time) > 0) { + value += DEF_WAIT_PREC; + value *= near_factor; + return value < wait_secs ? value : wait_secs; + } + } + } + + return wait_secs; +} + + +/* Check expiration stuff */ + +static void check_expired (probe *pb) { + int idx = (pb - probes); + probe *p, *endp = probes + num_probes; + probe *fp = NULL, *pfp = NULL; + + if (!pb->done) /* an ops method still not release it */ + return; + + + /* check all the previous in the same hop */ + for (p = &probes[idx - (idx % probes_per_hop)]; p < pb; p++) { + + if (!p->done || /* too early to decide something */ + !p->final /* already ttl-exceeded in the same hop */ + ) return; + + pfp = p; /* some of the previous probes is final */ + } + + /* check forward all the sent probes */ + for (p = pb + 1; p < endp && p->send_time; p++) { + + if (p->done) { /* some next probe already done... */ + if (!p->final) /* ...was ttl-exceeded. OK, we are expired. */ + return; + else { + fp = p; + break; + } + } + } + + if (!fp) /* no any final probe found. Assume expired. */ + return; + + + /* Well. There is a situation "*(this) * * * * ... * * final" + We cannot guarantee that "final" is in its right place. + We've sent "sim_probes" simultaneously, and the final hop + can drop some of them and answer only for latest ones. + If we can detect/assume that it so, then just put "final" + to the (pseudo-expired) "this" place. + */ + + /* It seems that the case of "answers for latest ones only" + occurs mostly with icmp_unreach error answers ("!H" etc.). + Icmp_echoreply, tcp_reset and even icmp_port_unreach looks + like going in the right order. + */ + if (!fp->err_str[0]) /* not an icmp_unreach error report... */ + return; + + + if (pfp || + (idx % probes_per_hop) + (fp - pb) < probes_per_hop + ) { + /* Either some previous (pfp) or some next probe + in this hop is final. It means that the whole hop is final. + Do the replace (it also causes further "final"s to be shifted + here too). + */ + goto replace_by_final; + } + + + /* If the final probe is an icmp_unreachable report + (either in a case of some error, like "!H", or just port_unreach), + it could follow the "time-exceed" report from the *same* hop. + */ + for (p = pb - 1; p >= probes; p--) { + if (equal_addr (&p->res, &fp->res)) { + /* ...Yes. Put "final" to the "this" place. */ + goto replace_by_final; + } + } + + + if (fp->recv_ttl) { + /* Consider the ttl value of the report packet and guess where + the "final" should be. If it seems that it should be + in the same hop as "this", then do replace. + */ + int back_hops, ttl; + + /* We assume that the reporting one has an initial ttl value + of either 64, or 128, or 255. It is most widely used + in the modern routers and computers. + The idea comes from tracepath(1) routine. + */ + back_hops = ttl2hops (fp->recv_ttl); + + /* It is possible that the back path differs from the forward + and therefore has different number of hops. To minimize + such an influence, get the nearest previous time-exceeded + probe and compare with it. + */ + for (p = pb - 1; p >= probes; p--) { + if (p->done && !p->final && p->recv_ttl) { + int hops = ttl2hops (p->recv_ttl); + + if (hops < back_hops) { + ttl = (p - probes) / probes_per_hop + 1; + back_hops = (back_hops - hops) + ttl; + break; + } + } + } + + ttl = idx / probes_per_hop + 1; + if (back_hops == ttl) + /* Yes! It seems that "final" should be at "this" place */ + goto replace_by_final; + else if (back_hops < ttl) + /* Hmmm... Assume better to replace here too... */ + goto replace_by_final; + + } + + + /* No idea what to do. Assume expired. */ + + return; + + +replace_by_final: + + *pb = *fp; + + memset (fp, 0, sizeof (*fp)); + /* block extra re-send */ + fp->send_time = 1.; + + return; +} + + +probe *probe_by_seq (int seq) { + int n; + + if (seq <= 0) return NULL; + + for (n = 0; n < num_probes; n++) { + if (probes[n].seq == seq) + return &probes[n]; + } + + return NULL; +} + +probe *probe_by_sk (int sk) { + int n; + + if (sk <= 0) return NULL; + + for (n = 0; n < num_probes; n++) { + if (probes[n].sk == sk) + return &probes[n]; + } + + return NULL; +} + + +static void poll_callback (int fd, int revents) { + + ops->recv_probe (fd, revents); +} + + +static void do_it (void) { + int start = (first_hop - 1) * probes_per_hop; + int end = num_probes; + double last_send = 0; + + print_header (); + + + while (start < end) { + int n, num = 0; + double next_time = 0; + double now_time = get_time (); + + + for (n = start; n < end; n++) { + probe *pb = &probes[n]; + + + if (n == start && /* probably time to print... */ + !pb->done && pb->send_time /* ...but yet not replied */ + ) { + double expire_time = pb->send_time + get_timeout (pb); + + if (expire_time > now_time) + next_time = expire_time; + else { + ops->expire_probe (pb); + check_expired (pb); + } + } + + + if (pb->done) { + + if (n == start) { /* can print it now */ + print_probe (pb); + start++; + } + + if (pb->final) + end = (n / probes_per_hop + 1) * probes_per_hop; + + continue; + } + + + if (!pb->send_time) { + int ttl; + double next; + + if (send_secs && (next = last_send + send_secs) > now_time) { + next_time = next; + break; + } + + ttl = n / probes_per_hop + 1; + + ops->send_probe (pb, ttl); + + if (!pb->send_time) { + if (next_time) break; /* have chances later */ + else error ("send probe"); + } + + last_send = pb->send_time; + } + + + if (!next_time) + next_time = pb->send_time + get_timeout (pb); + + num++; + if (num >= sim_probes) break; + } + + + if (next_time) { + double timeout = next_time - get_time (); + + if (timeout < 0) timeout = 0; + + do_poll (timeout, poll_callback); + } + + } + + + print_end (); + + return; +} + + +void tune_socket (int sk) { + int i = 0; + + if (debug) { + i = 1; + if (setsockopt (sk, SOL_SOCKET, SO_DEBUG, &i, sizeof (i)) < 0) + error ("setsockopt SO_DEBUG"); + } + + +#ifdef SO_MARK + if (fwmark) { + if (setsockopt (sk, SOL_SOCKET, SO_MARK, + &fwmark, sizeof (fwmark)) < 0 + ) error ("setsockopt SO_MARK"); + } +#endif + + + if (rtbuf && rtbuf_len) { + if (af == AF_INET) { + if (setsockopt (sk, IPPROTO_IP, IP_OPTIONS, + rtbuf, rtbuf_len) < 0 + ) error ("setsockopt IP_OPTIONS"); + } + else if (af == AF_INET6) { + if (setsockopt (sk, IPPROTO_IPV6, IPV6_RTHDR, + rtbuf, rtbuf_len) < 0 + ) error ("setsockopt IPV6_RTHDR"); + } + } + + + bind_socket (sk); + + + if (af == AF_INET) { + + i = dontfrag ? IP_PMTUDISC_PROBE : IP_PMTUDISC_DONT; + if (setsockopt (sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0 && + (!dontfrag || (i = IP_PMTUDISC_DO, + setsockopt (sk, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0)) + ) error ("setsockopt IP_MTU_DISCOVER"); + + if (tos) { + i = tos; + if (setsockopt (sk, SOL_IP, IP_TOS, &i, sizeof (i)) < 0) + error ("setsockopt IP_TOS"); + } + + } + else if (af == AF_INET6) { + + i = dontfrag ? IPV6_PMTUDISC_PROBE : IPV6_PMTUDISC_DONT; + if (setsockopt (sk, SOL_IPV6, IPV6_MTU_DISCOVER,&i,sizeof(i)) < 0 && + (!dontfrag || (i = IPV6_PMTUDISC_DO, + setsockopt (sk, SOL_IPV6, IPV6_MTU_DISCOVER,&i,sizeof(i)) < 0)) + ) error ("setsockopt IPV6_MTU_DISCOVER"); + + + if (flow_label) { + struct in6_flowlabel_req flr; + + memset (&flr, 0, sizeof (flr)); + flr.flr_label = htonl (flow_label & 0x000fffff); + flr.flr_action = IPV6_FL_A_GET; + flr.flr_flags = IPV6_FL_F_CREATE; + flr.flr_share = IPV6_FL_S_ANY; + memcpy (&flr.flr_dst, &dst_addr.sin6.sin6_addr, + sizeof (flr.flr_dst)); + + if (setsockopt (sk, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, + &flr, sizeof (flr)) < 0 + ) error ("setsockopt IPV6_FLOWLABEL_MGR"); + } + + if (tos) { + i = tos; + if (setsockopt (sk, IPPROTO_IPV6, IPV6_TCLASS, + &i, sizeof (i)) < 0 + ) error ("setsockopt IPV6_TCLASS"); + } + + if (tos || flow_label) { + i = 1; + if (setsockopt (sk, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, + &i, sizeof (i)) < 0 + ) error ("setsockopt IPV6_FLOWINFO_SEND"); + } + } + + + if (noroute) { + i = noroute; + if (setsockopt (sk, SOL_SOCKET, SO_DONTROUTE, &i, sizeof (i)) < 0) + error ("setsockopt SO_DONTROUTE"); + } + + + use_timestamp (sk); + + use_recv_ttl (sk); + + fcntl (sk, F_SETFL, O_NONBLOCK); + + return; +} + + +void parse_icmp_res (probe *pb, int type, int code, int info) { + + if (af == AF_INET) { + + if (type == ICMP_TIME_EXCEEDED) { + if (code == ICMP_EXC_TTL) + return; + } + + if (type == ICMP_DEST_UNREACH) { + + switch (code) { + case ICMP_UNREACH_NET: + case ICMP_UNREACH_NET_UNKNOWN: + case ICMP_UNREACH_ISOLATED: + case ICMP_UNREACH_TOSNET: + put_err (pb, "!N"); + break; + + case ICMP_UNREACH_HOST: + case ICMP_UNREACH_HOST_UNKNOWN: + case ICMP_UNREACH_TOSHOST: + put_err (pb, "!H"); + break; + + case ICMP_UNREACH_NET_PROHIB: + case ICMP_UNREACH_HOST_PROHIB: + case ICMP_UNREACH_FILTER_PROHIB: + put_err (pb, "!X"); + break; + + case ICMP_UNREACH_PORT: + /* dest host is reached */ + break; + + case ICMP_UNREACH_PROTOCOL: + put_err (pb, "!P"); + break; + + case ICMP_UNREACH_NEEDFRAG: + put_err (pb, "!F-%d", info); + break; + + case ICMP_UNREACH_SRCFAIL: + put_err (pb, "!S"); + break; + + case ICMP_UNREACH_HOST_PRECEDENCE: + put_err (pb, "!V"); + break; + + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + put_err (pb, "!C"); + break; + + default: + put_err (pb, "!<%u>", code); + break; + } + + } else + put_err (pb, "!<%u-%u>", type, code); + + } + else if (af == AF_INET6) { + + if (type == ICMP6_TIME_EXCEEDED) { + if (code == ICMP6_TIME_EXCEED_TRANSIT) + return; + } + + if (type == ICMP6_DST_UNREACH) { + + switch (code) { + + case ICMP6_DST_UNREACH_NOROUTE: + put_err (pb, "!N"); + break; + + case ICMP6_DST_UNREACH_BEYONDSCOPE: + case ICMP6_DST_UNREACH_ADDR: + put_err (pb, "!H"); + break; + + case ICMP6_DST_UNREACH_ADMIN: + put_err (pb, "!X"); + break; + + case ICMP6_DST_UNREACH_NOPORT: + /* dest host is reached */ + break; + + default: + put_err (pb, "!<%u>", code); + break; + } + } + else if (type == ICMP6_PACKET_TOO_BIG) + put_err (pb, "!F-%d", info); + else + put_err (pb, "!<%u-%u>", type, code); + } + + pb->final = 1; + + return; +} + + +static void parse_local_res (probe *pb, int ee_errno, int info) { + + if (ee_errno == EMSGSIZE && info != 0) { + put_err (pb, "!F-%d", info); + pb->final = 1; + return; + } + + errno = ee_errno; + error ("local recverr"); +} + + +void probe_done (probe *pb) { + + if (pb->sk) { + del_poll (pb->sk); + close (pb->sk); + pb->sk = 0; + } + + pb->seq = 0; + + pb->done = 1; +} + + +void recv_reply (int sk, int err, check_reply_t check_reply) { + struct msghdr msg; + sockaddr_any from; + struct iovec iov; + int n; + probe *pb; + char buf[1280]; /* min mtu for ipv6 ( >= 576 for ipv4) */ + char *bufp = buf; + char control[1024]; + struct cmsghdr *cm; + double recv_time = 0; + int recv_ttl = 0; + struct sock_extended_err *ee = NULL; + + + memset (&msg, 0, sizeof (msg)); + msg.msg_name = &from; + msg.msg_namelen = sizeof (from); + msg.msg_control = control; + msg.msg_controllen = sizeof (control); + iov.iov_base = buf; + iov.iov_len = sizeof (buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + + n = recvmsg (sk, &msg, err ? MSG_ERRQUEUE : 0); + if (n < 0) return; + + + /* when not MSG_ERRQUEUE, AF_INET returns full ipv4 header + on raw sockets... + */ + + if (!err && + af == AF_INET && + /* XXX: Assume that the presence of an extra header means + that it is not a raw socket... + */ + ops->header_len == 0 + ) { + struct iphdr *ip = (struct iphdr *) bufp; + int hlen; + + if (n < sizeof (struct iphdr)) return; + + hlen = ip->ihl << 2; + if (n < hlen) return; + + bufp += hlen; + n -= hlen; + } + + + pb = check_reply (sk, err, &from, bufp, n); + if (!pb) { + + /* for `frag needed' case at the local host, + kernel >= 3.13 sends local error (no more icmp) + */ + if (!n && err && dontfrag) { + pb = &probes[(first_hop - 1) * probes_per_hop]; + if (pb->done) return; + } else + return; + } + + + /* Parse CMSG stuff */ + + for (cm = CMSG_FIRSTHDR (&msg); cm; cm = CMSG_NXTHDR (&msg, cm)) { + void *ptr = CMSG_DATA (cm); + + if (cm->cmsg_level == SOL_SOCKET) { + + if (cm->cmsg_type == SO_TIMESTAMP) { + struct timeval *tv = (struct timeval *) ptr; + + recv_time = tv->tv_sec + tv->tv_usec / 1000000.; + } + } + else if (cm->cmsg_level == SOL_IP) { + + if (cm->cmsg_type == IP_TTL) + recv_ttl = *((int *) ptr); + else if (cm->cmsg_type == IP_RECVERR) { + + ee = (struct sock_extended_err *) ptr; + + if (ee->ee_origin != SO_EE_ORIGIN_ICMP && + ee->ee_origin != SO_EE_ORIGIN_LOCAL + ) return; + + /* dgram icmp sockets might return extra things... */ + if (ee->ee_origin == SO_EE_ORIGIN_ICMP && + (ee->ee_type == ICMP_SOURCE_QUENCH || + ee->ee_type == ICMP_REDIRECT) + ) return; + } + } + else if (cm->cmsg_level == SOL_IPV6) { + + if (cm->cmsg_type == IPV6_HOPLIMIT) + recv_ttl = *((int *) ptr); + else if (cm->cmsg_type == IPV6_RECVERR) { + + ee = (struct sock_extended_err *) ptr; + + if (ee->ee_origin != SO_EE_ORIGIN_ICMP6 && + ee->ee_origin != SO_EE_ORIGIN_LOCAL + ) return; + } + } + } + + if (!recv_time) + recv_time = get_time (); + + + if (!err) + memcpy (&pb->res, &from, sizeof (pb->res)); + + pb->recv_time = recv_time; + + pb->recv_ttl = recv_ttl; + + if (ee && ee->ee_origin != SO_EE_ORIGIN_LOCAL) { /* icmp or icmp6 */ + memcpy (&pb->res, SO_EE_OFFENDER (ee), sizeof(pb->res)); + parse_icmp_res (pb, ee->ee_type, ee->ee_code, ee->ee_info); + } + + if (ee && ee->ee_origin == SO_EE_ORIGIN_LOCAL) + parse_local_res (pb, ee->ee_errno, ee->ee_info); + + + if (ee && + mtudisc && + ee->ee_info >= header_len && + ee->ee_info < header_len + data_len + ) { + data_len = ee->ee_info - header_len; + + probe_done (pb); + + /* clear this probe (as actually the previous hop answers here) + but fill its `err_str' by the info obtained. Ugly, but easy... + */ + memset (pb, 0, sizeof (*pb)); + put_err (pb, "F=%d", ee->ee_info); + + return; + } + + + if (ee && + extension && + header_len + n >= (128 + 8) && /* at least... (rfc4884) */ + header_len <= 128 && /* paranoia */ + ((af == AF_INET && (ee->ee_type == ICMP_TIME_EXCEEDED || + ee->ee_type == ICMP_DEST_UNREACH || + ee->ee_type == ICMP_PARAMETERPROB)) || + (af == AF_INET6 && (ee->ee_type == ICMP6_TIME_EXCEEDED || + ee->ee_type == ICMP6_DST_UNREACH)) + ) + ) { + int step; + int offs = 128 - header_len; + + if (n > data_len) step = 0; /* guaranteed at 128 ... */ + else + step = af == AF_INET ? 4 : 8; + + handle_extensions (pb, bufp + offs, n - offs, step); + } + + + probe_done (pb); +} + + +int equal_addr (const sockaddr_any *a, const sockaddr_any *b) { + + if (!a->sa.sa_family) + return 0; + + if (a->sa.sa_family != b->sa.sa_family) + return 0; + + if (a->sa.sa_family == AF_INET6) + return !memcmp (&a->sin6.sin6_addr, &b->sin6.sin6_addr, + sizeof (a->sin6.sin6_addr)); + else + return !memcmp (&a->sin.sin_addr, &b->sin.sin_addr, + sizeof (a->sin.sin_addr)); + return 0; /* not reached */ +} + + +void bind_socket (int sk) { + sockaddr_any *addr, tmp; + + if (device) { + if (setsockopt (sk, SOL_SOCKET, SO_BINDTODEVICE, + device, strlen (device) + 1) < 0 + ) error ("setsockopt SO_BINDTODEVICE"); + } + + if (!src_addr.sa.sa_family) { + memset (&tmp, 0, sizeof (tmp)); + tmp.sa.sa_family = af; + addr = &tmp; + } else + addr = &src_addr; + + if (bind (sk, &addr->sa, sizeof (*addr)) < 0) + error ("bind"); + + return; +} + + +void use_timestamp (int sk) { + int n = 1; + + setsockopt (sk, SOL_SOCKET, SO_TIMESTAMP, &n, sizeof (n)); + /* foo on errors... */ +} + + +void use_recv_ttl (int sk) { + int n = 1; + + if (af == AF_INET) + setsockopt (sk, SOL_IP, IP_RECVTTL, &n, sizeof (n)); + else if (af == AF_INET6) + setsockopt (sk, SOL_IPV6, IPV6_RECVHOPLIMIT, &n, sizeof (n)); + /* foo on errors */ +} + + +void use_recverr (int sk) { + int val = 1; + + if (af == AF_INET) { + if (setsockopt (sk, SOL_IP, IP_RECVERR, &val, sizeof (val)) < 0) + error ("setsockopt IP_RECVERR"); + } + else if (af == AF_INET6) { + if (setsockopt (sk, SOL_IPV6, IPV6_RECVERR, &val, sizeof (val)) < 0) + error ("setsockopt IPV6_RECVERR"); + } +} + + +void set_ttl (int sk, int ttl) { + + if (af == AF_INET) { + if (setsockopt (sk, SOL_IP, IP_TTL, &ttl, sizeof (ttl)) < 0) + error ("setsockopt IP_TTL"); + } + else if (af == AF_INET6) { + if (setsockopt (sk, SOL_IPV6, IPV6_UNICAST_HOPS, + &ttl, sizeof (ttl)) < 0 + ) error ("setsockopt IPV6_UNICAST_HOPS"); + } +} + + +int do_send (int sk, const void *data, size_t len, const sockaddr_any *addr) { + int res; + + if (!addr || raw_can_connect ()) + res = send (sk, data, len, 0); + else + res = sendto (sk, data, len, 0, &addr->sa, sizeof (*addr)); + + if (res < 0) { + if (errno == ENOBUFS || errno == EAGAIN) + return res; + if (errno == EMSGSIZE || errno == EHOSTUNREACH) + return 0; /* recverr will say more... */ + error ("send"); /* not recoverable */ + } + + return res; +} + + +/* There is a bug in the kernel before 2.6.25, which prevents icmp errors + to be obtained by MSG_ERRQUEUE for ipv6 connected raw sockets. +*/ +static int can_connect = -1; + +#define VER(A,B,C,D) (((((((A) << 8) | (B)) << 8) | (C)) << 8) | (D)) + +int raw_can_connect (void) { + + if (can_connect < 0) { + + if (af == AF_INET) + can_connect = 1; + else { /* AF_INET6 */ + struct utsname uts; + int n; + unsigned int a, b, c, d = 0; + + if (uname (&uts) < 0) + return 0; + + n = sscanf (uts.release, "%u.%u.%u.%u", &a, &b, &c, &d); + can_connect = (n >= 3 && VER (a, b, c, d) >= VER (2, 6, 25, 0)); + } + } + + return can_connect; +} diff --git a/traceroute/traceroute.h b/traceroute/traceroute.h new file mode 100644 index 0000000..0c3b974 --- /dev/null +++ b/traceroute/traceroute.h @@ -0,0 +1,109 @@ +/* + Copyright (c) 2006, 2007 Dmitry Butskoy + <dmitry@butskoy.name> + License: GPL v2 or any later + + See COPYING for the status of this software. +*/ + +#include <netinet/in.h> + +#include <clif.h> + + +union common_sockaddr { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; +typedef union common_sockaddr sockaddr_any; + +struct probe_struct { + int done; + int final; + sockaddr_any res; + double send_time; + double recv_time; + int recv_ttl; + int sk; + int seq; + char *ext; + char err_str[32]; /* assume enough */ +}; +typedef struct probe_struct probe; + + +struct tr_module_struct { + struct tr_module_struct *next; + const char *name; + int (*init) (const sockaddr_any *dest, + unsigned int port_seq, size_t *packet_len); + void (*send_probe) (probe *pb, int ttl); + void (*recv_probe) (int fd, int revents); + void (*expire_probe) (probe *pb); + CLIF_option *options; /* per module options, if any */ + int one_per_time; /* no simultaneous probes */ + size_t header_len; /* additional header length (aka for udp) */ +}; +typedef struct tr_module_struct tr_module; + + +#define __TEXT(X) #X +#define _TEXT(X) __TEXT(X) + +#define DEF_START_PORT 33434 /* start for traditional udp method */ +#define DEF_UDP_PORT 53 /* dns */ +#define DEF_TCP_PORT 80 /* web */ +#define DEF_DCCP_PORT DEF_START_PORT /* is it a good choice?... */ +#define DEF_RAW_PROT 253 /* for experimentation and testing, rfc3692 */ + + +void error (const char *str) __attribute__ ((noreturn)); +void error_or_perm (const char *str) __attribute__ ((noreturn)); +void put_err (probe *pb, const char *format, ...) __attribute__ ((format (printf, 2, 3))); +const char *addr2str (const sockaddr_any *addr); + +double get_time (void); +void tune_socket (int sk); +void parse_icmp_res (probe *pb, int type, int code, int info); +void probe_done (probe *pb); + +typedef probe *(*check_reply_t) (int sk, int err, sockaddr_any *from, + char *buf, size_t len); +void recv_reply (int sk, int err, check_reply_t check_reply); + +int equal_addr (const sockaddr_any *a, const sockaddr_any *b); + +probe *probe_by_seq (int seq); +probe *probe_by_sk (int sk); + +void bind_socket (int sk); +void use_timestamp (int sk); +void use_recv_ttl (int sk); +void use_recverr (int sk); +void set_ttl (int sk, int ttl); +int do_send (int sk, const void *data, size_t len, const sockaddr_any *addr); + +void add_poll (int fd, int events); +void del_poll (int fd); +void do_poll (double timeout, void (*callback) (int fd, int revents)); + +void handle_extensions (probe *pb, char *buf, int len, int step); +const char *get_as_path (const char *query); + +int raw_can_connect (void); + +unsigned int random_seq (void); +uint16_t in_csum (const void *ptr, size_t len); + + +void tr_register_module (tr_module *module); +const tr_module *tr_get_module (const char *name); + +#define TR_MODULE(MOD) \ +static void __init_ ## MOD (void) __attribute__ ((constructor)); \ +static void __init_ ## MOD (void) { \ + \ + tr_register_module (&MOD); \ +} + diff --git a/wrappers/Makefile b/wrappers/Makefile new file mode 100644 index 0000000..53935f6 --- /dev/null +++ b/wrappers/Makefile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2007 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Nothing to do. +# + +all depend install: + @true + diff --git a/wrappers/README.wrappers b/wrappers/README.wrappers new file mode 100644 index 0000000..bd0e2f4 --- /dev/null +++ b/wrappers/README.wrappers @@ -0,0 +1,18 @@ + +This directory contains various wrappers for traceroute, which provide +some compatibility for some another traceroute-like software, +including tcptraceroute(8). + +These wrappers try to emulate the command-line interface only. +The actual output is the output of the underlain traceroute(8). + +Some of options and features certainly are not supported +(especially if it is considered too extra :) ). + +The idea of such wrappers is mostly not wrappers itself. It is to inspect +the features implemented by competitors, whether it is already supported by us, +or it is useful to be supported, or it is not useful or excessive. + +We have no plans to implement all features of all existing software. +Let the people a chance to play with something... ;) + diff --git a/wrappers/lft b/wrappers/lft new file mode 100755 index 0000000..a6cb089 --- /dev/null +++ b/wrappers/lft @@ -0,0 +1,117 @@ +#!/bin/sh +# +# Copyright (c) 2007 Dmitry K. Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Shell wrapper providing lft(8) command line interface. +# +# The original implementation of lft(8) can be obtained +# from http://pwhois.org/lft/ +# + + +prgname=$0 +opts="" +length="" +method="-T" + +dport="" +sport="" +queries=2 +ahead=5 +scatter=20 +timeout=1000 + + +usage () { + echo "Usage: $prgname [-ACEFINRSTUVbehinpruvz] [-d dport] [-s sport] + [-m retry min] [-M retry max] [-a ahead] [-c scatter ms] [-t timeout ms] + [-l min ttl] [-H max ttl] [-L length] [-q ISN] [-D device] [--help] + [gateway ...] target:dport" >&2 +} + +warning () { + echo "$prgname: Option '$1' is not implemented in this wrapper" >&2 +} + + +PARSED=`getopt -o 'ACEFINRSTUVbehinpruvzd:s:m:M:a:c:t:l:H:L:q:D:' -l help -- "$@"` +[ $? != 0 ] && exit 2 + +eval set -- "$PARSED" + +while [ $# -gt 0 ] +do + case "$1" in + -d) dport=$2; shift 2 ;; + -s) sport=$2; shift 2 ;; + -z) sport=""; shift ;; + -m) queries=$2; shift 2 ;; + -a) ahead=$2; shift 2 ;; + -c) scatter=$2; shift 2 ;; + -t) timeout=$2; shift 2 ;; + -l) opts="$opts -f $2"; shift 2 ;; + -L) length=$2; shift 2 ;; + -D) case "$2" in + [0-9]*) opts="$opts -s $2" ;; + *) opts="$opts -i $2" ;; + esac + shift 2 + ;; + -H) opts="$opts -m $2"; shift 2 ;; + -I) opts="$opts -t 0x10"; shift ;; + -n) opts="$opts -n"; shift ;; + -h) shift ;; + -F) opts="$opts -O fin"; shift ;; + -u) method="-U"; shift ;; + -N) opts="$opts -A"; shift ;; + -p) method="-I"; shift ;; + -b) shift ;; + -A) opts="$opts -A"; shift ;; + -[ieErCTUV]) warning $1; shift ;; + -[Mq]) warning $1; shift 2 ;; + -[RS]) shift ;; + --help) usage; exit 0 ;; + -v) echo "\"lft\"-compatible wrapper for new Linux Traceroute" >&2; + exit 0 ;; + --) shift; break ;; + *) echo "$prgname: Internal parsing error" >&2; exit 2 ;; + esac +done + +while [ $# -gt 1 ] +do + opts="$opts -g $1" + shift +done + +[ $# -eq 0 ] && { + usage + exit 2 +} + +case "$1" in + *:*:*) host=$1 ;; + *:*) dport=${1##*:}; host=${1%:*} ;; + *) host=$1 ;; +esac + +[ -n "$dport" ] && opts="$opts -p $dport" +[ -n "$sport" ] && opts="$opts --sport=$sport" +opts="$opts -q $queries" +opts="$opts -N $(($ahead * $queries))" +opts="$opts -z $scatter" + +timeout=`printf "%04d" $timeout` +sec=${timeout%???} +opts="$opts -w $sec.${timeout#$sec}" + +opts="$method $opts" + +exec traceroute $opts $host $length + diff --git a/wrappers/tcptraceroute b/wrappers/tcptraceroute new file mode 100755 index 0000000..6abe4bf --- /dev/null +++ b/wrappers/tcptraceroute @@ -0,0 +1,78 @@ +#!/bin/sh +# +# Copyright (c) 2007 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Shell wrapper providing tcptraceroute(8) command line interface. +# +# The original implementation of tcptraceroute(8) can be obtained +# from http://michael.toren.net/code/tcptraceroute/ +# + +opts="-T -O info" +length="" +prgname=$0 + + +usage () { + echo "Usage: $prgname [-hvnFSAE] [-i dev] [-f furst_ttl] [-l length] + [-q nqueries] [-t tos] [-m max_ttl] [-p src_port] [-s src_addr] + [-w wait_time] host [dest_port] [length]" >&2 +} + + +PARSED=`getopt -- 'hvdnNi:l:f:Fm:p:q:w:s:t:SAE' "$@"` +[ $? != 0 ] && exit 2 + +eval set -- "$PARSED" + +while [ $# -gt 0 ] +do + case "$1" in + -[dnF]) opts="$opts $1"; shift ;; + -N) shift ;; + -[ifmqwst]) opts="$opts $1 $2"; shift 2 ;; + -l) length=$2; shift 2 ;; + -p) opts="$opts --sport=$2"; shift 2 ;; + -S) opts="$opts -O syn"; shift ;; + -A) opts="$opts -O ack"; shift ;; + -E) opts="$opts -O ecn"; shift ;; + -h) usage ; exit 0 ;; + -v) echo "\"tcptraceroute\"-compatible wrapper for new Linux Traceroute" >&2; + exit 0 ;; + --) shift; break ;; + *) echo "$prgname: Internal parsing error" >&2; exit 2 ;; + esac +done + +[ $# -eq 0 ] && { + usage + exit 2 +} + +host=$1 +shift + +[ $# -gt 0 ] && { + opts="$opts -p $1" + shift +} + +[ $# -gt 0 ] && { + length=$1 + shift +} + + +# Say to the people it is actually another program... +echo "Running: + traceroute $opts $host $length" >&2 + + +exec traceroute $opts $host $length + diff --git a/wrappers/tcptraceroute.8 b/wrappers/tcptraceroute.8 new file mode 100644 index 0000000..7c19ecf --- /dev/null +++ b/wrappers/tcptraceroute.8 @@ -0,0 +1,34 @@ +.\" Copyright (c) 2016 Dmitry Butskoy (dmitry@butskoy.name) +.\" License: GPL v2 or any later version +.\" See COPYING for the status of this software +.TH TCPTRACEROUTE 8 "8 March 2016" "Traceroute" "Traceroute For Linux" +.\" .UC 6 +.SH NAME +tcptraceroute \- print the route packets trace to network host +.SH SYNOPSIS +.na +.BR tcptraceroute +.RI " [" options ] +.ad +.SH DESCRIPTION +.I tcptraceroute +is just a link to the system +.B traceroute\fR, +to allow run it without specifying +.B \-T +option each time (for switch to the TCP method). It is fully equivalent to +.B traceroute \-T\fR, +the rest of the command line is the same. +.br +See +.BR traceroute (8) +for more info. +.SH NOTES +There was an original implementation of tcptraceroute, which had some options +differ (but rare used). Most common +.BR "" [ "dnFi:f:m:q:w:s:t:" ] +was exactly the same, but +.BR "" [ "l:p:NSAE" ] +was not. For full compatibility, a wrapper provided (available in the source code). +.SH SEE ALSO +.BR traceroute (8) diff --git a/wrappers/tracepath b/wrappers/tracepath new file mode 100755 index 0000000..c7d3770 --- /dev/null +++ b/wrappers/tracepath @@ -0,0 +1,63 @@ +#!/bin/sh +# +# Copyright (c) 2008 Dmitry Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Shell wrapper providing tracepath(1) or tracepath6(1) command line interface. +# +# The original implementation of tracepath/tracepath6 can be obtained +# from the iputils package, available at http://www.skbuff.net/iputils/ +# + +opts="-q1 --mtu --back" +port=44444 +prgname=$0 +length="" + + +usage () { + echo "Usage: $prgname [-nbh] [ -l pktlen ] host[/port]" >&2 +} + + +PARSED=`getopt -- 'hnbl:' "$@"` +[ $? != 0 ] && exit 2 + +eval set -- "$PARSED" + +while [ $# -gt 0 ] +do + case "$1" in + -n) opts="$opts $1"; shift ;; + -b) shift ;; + -l) length=$2; shift 2 ;; + -h) usage ; exit 0 ;; + --) shift; break ;; + *) echo "$prgname: Internal parsing error" >&2; exit 2 ;; + esac +done + +[ $# -eq 0 ] && { + usage + exit 2 +} + +host=$1 +case "$host" in + */*) port=${host##*/} + host=${host%/*} + ;; +esac + + +case "$prgname" in + *6) opts="$opts -6" ;; +esac + +exec traceroute $opts -p $port $host $length + diff --git a/wrappers/traceproto b/wrappers/traceproto new file mode 100755 index 0000000..0a03b2d --- /dev/null +++ b/wrappers/traceproto @@ -0,0 +1,127 @@ +#!/bin/sh +# +# Copyright (c) 2007 Dmitry K. Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Shell wrapper providing traceproto(8) command line interface. +# +# The original implementation of traceproto(8) can be obtained +# from http://traceproto.sourceforge.net/ +# + + +prgname=$0 +opts="" +length="" +method="tcp" +sendwait=100 +cont="" +iface=$TP_DEFAULT_IF + + +usage () { + echo "Usage: $prgname [-cCTfAhvR] [-p protocol] [-d dst_port] [-D max_dst_port] + [-s src_port] [-S max_src_port] [-m min_ttl] [-M max_ttl] [-w response_timeout] + [-W send_delay] [-a account_level] [-P payload_size] + [-F interface] [-k skips] [-I consecutive_trace_count] + [-H packets_per_hop] [-i incr_pattern] [-o output_style] [-t tcp_flags] + target" >&2 +} + +warning () { + echo "$prgname: Option '$1' is not implemented in this wrapper" >&2 +} + + +PARSED=`getopt -- 'cCTfAhvRp:d:D:s:S:m:M:w:W:a:P:F:k:I:H:i:o:t:' "$@"` +[ $? != 0 ] && exit 2 + +eval set -- "$PARSED" + +while [ $# -gt 0 ] +do + case "$1" in + -p) method=$2; shift 2 ;; + -d) opts="$opts -p $2"; shift 2 ;; + -s) opts="$opts --sport=$2"; shift 2 ;; + -m) opts="$opts -f $2"; shift 2 ;; + -M) opts="$opts -m $2"; shift 2 ;; + -w) opts="$opts -w $2"; shift 2 ;; + -W) sendwait=$2; shift 2 ;; + -P) length=$2; shift 2 ;; + -c) cont=100000; shift ;; + -I) cont=$2; shift 2 ;; + -H) opts="$opts -q $2"; shift 2 ;; + -f) opts="$opts -F"; shift ;; + -F) iface=$2; shift 2 ;; + -A) opts="$opts -A"; shift ;; + -o) [ $2 != "c" ] && warning $1; shift 2 ;; + -t) case $2 in + *S*) opts="$opts -O syn" ;; + esac + case $2 in + *A*) opts="$opts -O ack" ;; + esac + case $2 in + *R*) opts="$opts -O rst" ;; + esac + case $2 in + *U*) opts="$opts -O urg" ;; + esac + case $2 in + *P*) opts="$opts -O psh" ;; + esac + case $2 in + *F*) opts="$opts -O fin" ;; + esac + case $2 in + *E*) opts="$opts -O ece" ;; + esac + case $2 in + *C*) opts="$opts -O cwr" ;; + esac + shift 2 ;; + -[DSaki]) warning $1; shift 2 ;; + -[TCR]) warning $1; shift ;; + -h) usage; exit 0 ;; + -v) echo "\"traceproto\"-compatible wrapper for new Linux Traceroute" >&2; + exit 0 ;; + --) shift; break ;; + *) echo "$prgname: Internal parsing error" >&2; exit 2 ;; + esac +done + + + +[ $# -eq 0 ] && { + usage + exit 2 +} + +host=$1 + + +opts="-M $method $opts" +opts="$opts -z $sendwait" +[ -n "$iface" ] && opts="$opts -i $iface" + +[ -n "$TP_OUTPUT_STYLE" -a "$TP_OUTPUT_STYLE" != "classic" ] && { + echo "$prgname: warning: only classic output style supported" >&2 +} + +[ -n "$TP_RA_SERVER" -a -z "$RA_SERVER" ] && RA_SERVER=$TP_RA_SERVER + + +[ -z "$cont" ] && exec traceroute $opts $host $length + +while [ "$cont" -gt 0 ] +do + cont=$(($cont - 1)) + traceroute $opts $host $length +done + diff --git a/wrappers/traceroute-nanog b/wrappers/traceroute-nanog new file mode 100755 index 0000000..0145464 --- /dev/null +++ b/wrappers/traceroute-nanog @@ -0,0 +1,76 @@ +#!/bin/sh +# +# Copyright (c) 2007 Dmitry K. Butskoy +# <dmitry@butskoy.name> +# License: GPL v2 or any later +# +# See COPYING for the status of this software. +# + +# +# Shell wrapper providing traceroute-nanog(8) command line interface. +# +# The original implementation of traceroute-nanog(8) can be obtained +# from ftp://ftp.login.com/pub/software/traceroute/ +# + + +prgname=$0 +opts="" +spray="" + + +usage () { + echo "Usage: $prgname [-adnruvAMOPQU$] [-w wait] [-S start_ttl] + [-m max_ttl] [-p port] [-q nqueries] [-g gateway] [-t tos] + [-s src_addr] [-I proto] host [data_size]" >&2 +} + +warning () { + echo "$prgname: Option '$1' is not implemented in this wrapper" >&2 +} + + +PARSED=`getopt -- 'adnruvAMOPQU$w:S:m:p:q:g:t:s:I:f:T:' "$@"` +[ $? != 0 ] && { + usage + exit 2 +} + +eval set -- "$PARSED" + +while [ $# -gt 0 ] +do + case "$1" in + -[Adrn]) opts="$opts $1"; shift ;; + -[gmpqstw]) opts="$opts $1 $2"; shift 2 ;; + -I) case "$2" in + icmp) opts="$opts -I" ;; + tcp) opts="$opts -T" ;; + udp) ;; + udplite) opts="$opts -UL" ;; + *) opts="$opts -P $2" ;; + esac + shift 2 ;; + -S) opts="$opts -f $2"; shift 2 ;; + -P) spray=1; shift ;; + -f) opts="$opts --sport=$2"; shift 2 ;; + -u) ;; + -$) opts="-f 64 -m 64 -q 1"; shift ;; + -M) opts="$opts --mtu"; shift ;; + -[aUOvQ]) warning $1; shift ;; + -T) warning $1; shift 2 ;; + --) shift; break ;; + *) echo "$prgname: Internal parsing error" >&2; exit 2 ;; + esac +done + +[ $# -eq 0 ] && { + usage + exit 2 +} + +[ -z "$spray" ] && opts="$opts -N 1" + +exec traceroute $opts $1 $2 + |