├── .dockerignore ├── .github └── workflows │ └── ci.yml ├── .travis.yml ├── COPYING ├── Dockerfile ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── doc ├── Makefile.am ├── dropwatch.1 └── dwdump.1 ├── spec └── dropwatch.spec ├── src ├── Makefile.am ├── dwdump.c ├── lookup.c ├── lookup.h ├── lookup_bfd.c ├── lookup_kas.c ├── main.c └── net_dropmon.h └── tests ├── Makefile.am └── rundropwatch.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | .git* 4 | *.md -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | # Allow manual trigger from Actions tab 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build_and_test: 13 | name: Configure, build, and test 14 | 15 | strategy: 16 | matrix: 17 | platform: 18 | - ubuntu-20.04 19 | - ubuntu-22.04 20 | compiler: 21 | - gcc 22 | - clang 23 | 24 | runs-on: ${{ matrix.platform }} 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Install dependencies 29 | run: sudo apt-get install -y binutils-dev libreadline-dev libnl-3-dev libnl-genl-3-dev libpcap-dev 30 | - name: Configure and build 31 | run: ./autogen.sh && ./configure && make 32 | env: 33 | CC: ${{ matrix.compiler }} 34 | - name: Run tests 35 | run: make check 36 | env: 37 | CC: ${{ matrix.compiler }} 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | dist: focal 3 | arch: 4 | - arm64 5 | - amd64 6 | - ppc64le 7 | compiler: 8 | - clang 9 | - gcc 10 | addons: 11 | apt: 12 | packages: 13 | binutils-dev 14 | libreadline-dev 15 | libnl-3-dev 16 | libnl-genl-3-dev 17 | libpcap-dev 18 | 19 | script: ./autogen.sh && ./configure && make && make check 20 | 21 | after_script: cat ./tests/rundropwatch.sh.log 22 | 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | FROM ubuntu:22.04 AS build_stage 3 | WORKDIR /dropwatch 4 | 5 | RUN apt update \ 6 | && apt install -y \ 7 | build-essential \ 8 | autoconf \ 9 | libnl-3-dev \ 10 | libnl-genl-3-dev \ 11 | libpcap-dev \ 12 | libreadline-dev \ 13 | binutils-dev \ 14 | && apt-get clean \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | COPY . . 18 | 19 | RUN ./autogen.sh 20 | RUN ./configure 21 | RUN make 22 | 23 | ################################################################## 24 | FROM ubuntu:22.04 AS run_stage 25 | WORKDIR /dropwatch 26 | 27 | RUN apt update \ 28 | && apt install -y \ 29 | libnl-3-200 \ 30 | libnl-genl-3-200 \ 31 | libpcap0.8 \ 32 | libreadline8 \ 33 | binutils \ 34 | && apt-get clean \ 35 | && rm -rf /var/lib/apt/lists/* 36 | 37 | COPY --from=build_stage /dropwatch/src/dropwatch /dropwatch/dropwatch 38 | 39 | CMD /dropwatch/dropwatch -l kas -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # Makefile.am -- 2 | # Copyright 2018 Neil Horman 3 | # All Rights Reserved. 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | AUTOMAKE_OPTIONS = no-dependencies 7 | ACLOCAL_AMFLAGS = -I m4 8 | EXTRA_DIST = COPYING autogen.sh 9 | 10 | SUBDIRS = src doc tests 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DropWatch 2 | ========= 3 | 4 | [![Build Status](https://travis-ci.org/nhorman/dropwatch.svg?branch=master)](https://travis-ci.org/nhorman/dropwatch) 5 | 6 | Thanks for Downloading Dropwatch! 7 | 8 | What is Dropwatch? 9 | ------------------ 10 | Dropwatch is a project I started in an effort to improve the ability for 11 | developers and system administrators to diagnose problems in the Linux Networking 12 | stack, specifically in our ability to diagnose where packets are getting 13 | dropped. From my probing, I've come to the conclusion that there are four main 14 | shortcomings in our current environment: 15 | 16 | 1) _Consolidation, or lack thereof._ Currently, if you would like to check on the 17 | status of dropped packets in the kernel, you need to check at least 4 places, 18 | and possibly more: The /proc/net/snmp file, the netstat utility, the tc utility, 19 | and ethtool. This project aims to consolidate several of those checks into one 20 | tool, making it easier for a sysadmin or developer to detect lost packets 21 | 22 | 2) _Clarity of information._ Dropped packets are not obvious. A sysadmin needs 23 | to be intimately familiar with each of the above tools to understand which 24 | events or statistics correlate to a dropped packet and which do not. While that 25 | is often self evident, it is also often not. Dropwatch aims to improve that 26 | clarity 27 | 28 | 3) _Ambiguity._ Even when a dropped packet is detected, the causes for those 29 | dropped packets are not always clear. Does a UDPInError mean the application 30 | receive buffer was full, or does it mean its checksum was bad? Dropwatch 31 | attempts to disambiguate the causes for dropped packets. 32 | 33 | 4) _Performance._ Utilities can be written to aggregate the data in the various 34 | other utilities to solve some of these problems, but such solutions require 35 | periodic polling of several interfaces, which is far from optimal, especially 36 | when lost packets are rare. This solution improves on the performance aspect by 37 | implementing a kernel feature which allows asynchronous notification of dropped 38 | packets when they happen. 39 | 40 | Building Dropwatch 41 | ------------------ 42 | Dropwatch uses the autotools suite (autoconf/automake) to build. To build and install the utility run the following commands: 43 | ``` 44 | ./autogen.sh 45 | ./configure 46 | make 47 | make install 48 | ``` 49 | 50 | Building and using Dropwatch in Docker 51 | ------------------ 52 | ``` 53 | docker build -t dropwatch . 54 | docker run -it --rm -v /usr/src:/usr/src:ro -v /lib/modules/:/lib/modules:ro -v /sys/:/sys/:rw --net=host --pid=host --privileged dropwatch 55 | ``` 56 | 57 | Questions 58 | --------- 59 | Feel free to email me directly at nhorman@tuxdriver.com with question, or if you 60 | find a bug, open an issue here on the github page 61 | 62 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | set -x -e 5 | mkdir -p m4 6 | # --no-recursive is available only in recent autoconf versions 7 | autoreconf -fv --install 8 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | AC_INIT(dropwatch,1.5.5) 4 | AC_PREREQ(2.12)dnl 5 | AC_CONFIG_HEADERS(config.h) 6 | 7 | AC_CONFIG_MACRO_DIR([m4]) 8 | AM_INIT_AUTOMAKE([foreign] [subdir-objects]) 9 | LT_INIT 10 | AC_SUBST(LIBTOOL_DEPS) 11 | 12 | AC_PROG_CC 13 | AC_PROG_INSTALL 14 | AC_PROG_AWK 15 | 16 | AC_CHECK_FUNCS(getopt_long) 17 | 18 | PKG_CHECK_MODULES([LIBNL3], [libnl-3.0], [], [AC_MSG_ERROR([libnl-3.0 is required])]) 19 | PKG_CHECK_MODULES([LIBNLG3], [libnl-genl-3.0], [], [AC_MSG_ERROR([libnl-genl-3.0 is required])]) 20 | PKG_CHECK_MODULES([READLINE], [readline], [], [AC_MSG_ERROR([libreadline is required])]) 21 | PKG_CHECK_MODULES([LIBPCAP], [libpcap], [], [ 22 | AC_CHECK_LIB(pcap, pcap_open_live,[], 23 | [AC_MSG_ERROR([libpcap is required])])]) 24 | 25 | AC_ARG_WITH([bfd], 26 | [AS_HELP_STRING([--without-bfd], [Build without bfd library (default: yes)])], 27 | [with_bfd=$withval], 28 | [with_bfd=yes]) 29 | AS_IF([test "x$with_bfd" != "xno"], [ 30 | AC_CHECK_HEADERS([bfd.h], [], [AC_MSG_ERROR([Couldn't find or include bfd.h])]) 31 | ]) 32 | AM_CONDITIONAL(USE_BFD, test "x$with_bfd" != "xno") 33 | 34 | AC_OUTPUT(Makefile src/Makefile doc/Makefile tests/Makefile) 35 | 36 | AC_MSG_NOTICE() 37 | AC_MSG_NOTICE([dropwatch Version: $VERSION]) 38 | AC_MSG_NOTICE([Target: $target]) 39 | AC_MSG_NOTICE([Installation prefix: $prefix]) 40 | AC_MSG_NOTICE([Compiler: $CC]) 41 | AC_MSG_NOTICE([Compiler flags: $CFLAGS]) 42 | AC_MSG_NOTICE([BFD library support: $with_bfd]) 43 | -------------------------------------------------------------------------------- /doc/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | dist_man_MANS = dropwatch.1 dwdump.1 3 | 4 | -------------------------------------------------------------------------------- /doc/dropwatch.1: -------------------------------------------------------------------------------- 1 | .TH dropwatch "1" "Mar 2009" "Neil Horman" 2 | .SH NAME 3 | dropwatch \- kernel dropped packet monitoring utility 4 | .SH SYNOPSIS 5 | \fBdropwatch\fP [\fB\-l\fP \fImethod\fP | \fBlist\fP] 6 | .SH DESCRIPTION 7 | .B dropwatch 8 | is an interactive utility for monitoring and recording packets that 9 | are dropped by the kernel. 10 | .SH OPTIONS 11 | .TP 12 | \fB\-l\fP \fImethod\fP | \fBlist\fP, \fB\-\-lmethod\fP \fImethod\fP | \fBlist\fP 13 | Select the translation method to use when a drop alert arrives. By default the 14 | raw instruction pointer of a drop location is output, but by the use of the \fB\-l\fP 15 | option, we can assign a translation method so that the instruction pointer can 16 | be translated into function names. Currently supported lookup \fImethods\fP are: 17 | .RS 18 | .TP 19 | .B list 20 | Show all supported methods. 21 | .TP 22 | .B kas 23 | Use \fI/proc/kallsyms\fP to lookup instruction pointers to function mappings. 24 | .RE 25 | .SH INTERACTIVE COMMANDS 26 | .TP 27 | .B start 28 | Tells the kernel to start reporting dropped packets. 29 | .TP 30 | .B stop 31 | Tells the kernel to discontinue reporting dropped packets. 32 | .TP 33 | .B exit 34 | Exits the \fBdropmonitor\fP program. 35 | .TP 36 | .B help 37 | Displays summary of all commands. 38 | .TP 39 | \fBset alertlimit\fP \fIvalue\fP 40 | Sets a triggerpoint to stop monitoring for dropped packets after \fIvalue\fP alerts 41 | have been received. 42 | .TP 43 | .BR "set alertmode " "{ " summary " | " packet " }" 44 | .I summary 45 | - Default mode. A summary of recent packet drops is sent to user space. The 46 | alert includes drop locations and number of drops in each location. 47 | 48 | .I packet 49 | - Each dropped packet is sent to user space. The alert includes the packet 50 | itself and various metadata such as drop location and timestamp. 51 | .TP 52 | .BI "set trunc " "len" 53 | Sets the truncation length. Reported packets will be truncated to length 54 | \fIlen\fP. This setting is only applicable when \fIalertmode\fP is set to 55 | \fIpacket\fP. By default packets are not truncated. A value of \fI0\fP disables 56 | truncation. 57 | .TP 58 | .BI "set queue " "len" 59 | Sets the queue length in the kernel. When \fIalertmode\fP is set to 60 | \fIpacket\fP, the kernel queues dropped packets in a per-CPU drop list before 61 | preparing a netlink message for each. This setting controls the queue's length. 62 | By default this is limited by the kernel to 1,000 packets. Increasing this 63 | value will increase the memory utilization of the system. 64 | .TP 65 | .BR "set sw " "{ " true " | " false " }" 66 | Enables or disables the monitoring of software originated drops. By default 67 | software originated drops are monitored. 68 | .TP 69 | .BR "set hw " "{ " true " | " false " }" 70 | Enables or disables the monitoring of hardware originated drops. By default 71 | hardware originated drops are not monitored. 72 | .TP 73 | .B show 74 | Query existing configuration from the kernel and show it. 75 | .TP 76 | .B stats 77 | Query statistics from the kernel and show it. 78 | 79 | .I "Tail dropped" 80 | - Number of packets that could not be enqueued to the per-CPU drop list(s). 81 | -------------------------------------------------------------------------------- /doc/dwdump.1: -------------------------------------------------------------------------------- 1 | .TH dwdump 1 "Jan 2020" "Ido Schimmel" 2 | .SH NAME 3 | dwdump \- dump kernel dropped packets to a file 4 | .SH SYNOPSIS 5 | .sp 6 | .ad l 7 | .in +8 8 | .ti -8 9 | .B dwdump 10 | .RI "[ " OPTIONS " ]" 11 | .sp 12 | 13 | .SH OPTIONS 14 | 15 | .TP 16 | .BR "\-w" , " --write " \fIFILE 17 | Dump packets to provided file in pcap format. Defaults to standard output. 18 | 19 | .TP 20 | .BR "\-t", " --trunc " \fILENGTH 21 | Ask the kernel to truncate packets to provided length. Defaults to no 22 | truncation. 23 | 24 | .TP 25 | .BR "-q", " --query" 26 | Query the kernel for current configuration and exit. 27 | 28 | .TP 29 | .BR "-l", " --limit " \fILIMIT 30 | Ask the kernel to set the per-CPU packet queue limit to provided limit. 31 | Defaults to 1,000 packets. 32 | 33 | .TP 34 | .BR "-p", " --passive" 35 | Only listen on notified packets with no configuration. This is useful if the 36 | kernel is already monitoring dropped packets and you only want to open another 37 | listening socket. 38 | 39 | .TP 40 | .BR "-s", " --stats" 41 | Query the kernel for statistics and exit. 42 | 43 | .TP 44 | .BR "-b", " --bufsize " \fISIZE 45 | Set the socket's receive buffer to provided size. Defaults to 1MB. 46 | 47 | .TP 48 | .BR "-o", " --origin " "{ " sw " | " hw " }" 49 | Ask the kernel to only monitor software or hardware originated drops. Defaults 50 | to both. See \fBdevlink-trap\fR(8) for details on how to get hardware 51 | originated drops to the kernel. 52 | 53 | .TP 54 | .BR "-e", " --exit" 55 | Ask the kernel to stop monitoring and exit. 56 | 57 | .SH "EXAMPLES" 58 | .PP 59 | dwdump -w drops.pcap 60 | .RS 4 61 | Dump dropped packets to a file. 62 | .RE 63 | .PP 64 | dwdump | tshark -V -r - 65 | .RS 4 66 | Pipe dropped packets to Wireshark. 67 | .RE 68 | .PP 69 | dwdump -o sw -w drops.pcap 70 | .RS 4 71 | Only monitor software originated drops. 72 | .RE 73 | .PP 74 | dwdump -q 75 | .RS 4 76 | Query current configuration from the kernel and exit. 77 | .RE 78 | 79 | .SH SEE ALSO 80 | .BR dropwatch (1), 81 | .BR devlink-trap (8), 82 | .BR tshark (1), 83 | .br 84 | -------------------------------------------------------------------------------- /spec/dropwatch.spec: -------------------------------------------------------------------------------- 1 | %define uversion MAKEFILE_VERSION 2 | Summary: Kernel dropped packet monitor 3 | Name: dropwatch 4 | Version: %{uversion} 5 | Release: 0%{?dist} 6 | Source0: https://fedorahosted.org/releases/d/r/dropwatch/dropwatch-%{uversion}.tbz2 7 | URL: http://fedorahosted.org/dropwatch 8 | License: GPLv2+ 9 | Group: Applications/System 10 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root 11 | BuildRequires: autoconf, automake, libtool 12 | BuildRequires: kernel-devel, libnl-devel, readline-devel 13 | BuildRequires: binutils-devel, binutils-static, pkgconfig 14 | Requires: libnl, readline 15 | 16 | %description 17 | dropwatch is an utility to interface to the kernel to monitor for dropped 18 | network packets. 19 | 20 | %prep 21 | %setup -q 22 | 23 | %build 24 | cd src 25 | export CFLAGS=$RPM_OPT_FLAGS 26 | make 27 | 28 | %install 29 | rm -rf $RPM_BUILD_ROOT 30 | mkdir -p $RPM_BUILD_ROOT%{_bindir} 31 | mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 32 | install -m0755 src/dropwatch $RPM_BUILD_ROOT%{_bindir} 33 | install -m0644 doc/dropwatch.1 $RPM_BUILD_ROOT%{_mandir}/man1 34 | 35 | %clean 36 | rm -rf $RPM_BUILD_ROOT 37 | 38 | 39 | %files 40 | %defattr(-,root,root,-) 41 | %{_bindir}/* 42 | %{_mandir}/man1/* 43 | %doc README 44 | %doc COPYING 45 | 46 | %changelog 47 | * Thu Apr 08 2010 Neil Horman 1.1-0 48 | - Fixing BuildRequires in spec, and removing release variable 49 | 50 | * Thu Mar 26 2009 Neil Horman 1.0-3 51 | - Updating Makefile to include release num in tarball 52 | 53 | * Fri Mar 20 2009 Neil Horman 1.0-2 54 | - Fixed up Errors found in package review (bz 491240) 55 | 56 | * Tue Mar 17 2009 Neil Horman 1.0-1 57 | - Initial build 58 | 59 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | # 3 | bin_PROGRAMS = dropwatch dwdump 4 | 5 | AM_CFLAGS = -g -Wall -Werror $(LIBNL3_CFLAGS) $(LIBNLG3_CFLAGS) $(READLINE_CFLAGS) 6 | AM_LDFLAGS = 7 | ALL_LIBS = $(LIBNL3_LIBS) $(LIBNLG3_LIBS) $(READLINE_LIBS) $(LIBPCAP_LIBS) 8 | dropwatch_LDADD = $(ALL_LIBS) 9 | dwdump_LDADD = $(ALL_LIBS) 10 | AM_CPPFLAGS = -D_GNU_SOURCE 11 | 12 | dropwatch_SOURCES = main.c lookup.c lookup_kas.c 13 | dwdump_SOURCES = dwdump.c 14 | 15 | if USE_BFD 16 | dropwatch_SOURCES += lookup_bfd.c 17 | dropwatch_LDADD += -lbfd 18 | endif 19 | -------------------------------------------------------------------------------- /src/dwdump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-2.0-or-later 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "net_dropmon.h" 28 | 29 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 30 | 31 | static struct nla_policy net_dm_policy[NET_DM_ATTR_MAX + 1] = { 32 | [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 }, 33 | [NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 }, 34 | [NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 }, 35 | [NET_DM_ATTR_STATS] = { .type = NLA_NESTED }, 36 | [NET_DM_ATTR_HW_STATS] = { .type = NLA_NESTED }, 37 | }; 38 | 39 | static struct nla_policy 40 | net_dm_stats_policy[NET_DM_ATTR_STATS_MAX + 1] = { 41 | [NET_DM_ATTR_STATS_DROPPED] = { .type = NLA_U64 }, 42 | }; 43 | 44 | static bool stop; 45 | 46 | enum dwdump_pkt_origin { 47 | DWDUMP_PKT_ORIGIN_ALL, 48 | DWDUMP_PKT_ORIGIN_SW, 49 | DWDUMP_PKT_ORIGIN_HW, 50 | }; 51 | 52 | struct dwdump_options { 53 | const char *dumpfile; 54 | __u32 trunc_len; 55 | bool query; 56 | __u32 queue_len; 57 | bool passive; 58 | bool stats; 59 | int rxbuf; 60 | bool exit; 61 | enum dwdump_pkt_origin origin; 62 | bool need_pcap; 63 | bool need_mon; 64 | }; 65 | 66 | struct dwdump { 67 | struct nl_sock *csk, *dsk; 68 | pcap_t *pcap_handle; 69 | pcap_dumper_t *pcap_dumper; 70 | struct dwdump_options options; 71 | int family; 72 | long snaplen; 73 | char *pcap_buf, *pkt; 74 | }; 75 | 76 | /* Based on https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html */ 77 | struct linux_sll { 78 | __be16 pkttype; 79 | __be16 hatype; 80 | __be16 halen; 81 | unsigned char addr[8]; 82 | __be16 family; 83 | }; 84 | 85 | static int dwdump_data_init(struct dwdump *dwdump) 86 | { 87 | int fd, optval, family, err; 88 | struct nl_sock *sk; 89 | 90 | sk = nl_socket_alloc(); 91 | if (!sk) { 92 | fprintf(stderr, "Failed to allocate data socket\n"); 93 | return -1; 94 | } 95 | 96 | /* Add wiggle room for other netlink attributes in addition to the 97 | * payload. 98 | */ 99 | dwdump->snaplen = dwdump->options.trunc_len + 2048; 100 | dwdump->pkt = calloc(1, dwdump->snaplen); 101 | if (!dwdump->pkt) { 102 | perror("calloc"); 103 | goto err_pkt_alloc; 104 | } 105 | 106 | err = genl_connect(sk); 107 | if (err) { 108 | fprintf(stderr, "Failed to connect data socket\n"); 109 | goto err_genl_connect; 110 | } 111 | 112 | family = genl_ctrl_resolve(sk, "NET_DM"); 113 | if (family < 0) { 114 | fprintf(stderr, "Failed to resolve ID of \"NET_DM\" family\n"); 115 | goto err_genl_ctrl_resolve; 116 | } 117 | 118 | err = nl_socket_set_buffer_size(sk, dwdump->options.rxbuf, 0); 119 | if (err < 0) { 120 | fprintf(stderr, "Failed to set receive buffer size of data socket\n"); 121 | goto err_set_buffer_size; 122 | } 123 | 124 | optval = 1; 125 | fd = nl_socket_get_fd(sk); 126 | err = setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &optval, 127 | sizeof(optval)); 128 | if (err < 0) { 129 | fprintf(stderr, "Failed to set NETLINK_NO_ENOBUFS socket option\n"); 130 | goto err_setsockopt; 131 | } 132 | 133 | err = nl_socket_add_memberships(sk, NET_DM_GRP_ALERT, NFNLGRP_NONE); 134 | if (err) { 135 | fprintf(stderr, "Failed to join multicast group\n"); 136 | goto err_add_memberships; 137 | } 138 | 139 | dwdump->dsk = sk; 140 | dwdump->family = family; 141 | 142 | return 0; 143 | 144 | err_add_memberships: 145 | err_setsockopt: 146 | err_set_buffer_size: 147 | err_genl_ctrl_resolve: 148 | err_genl_connect: 149 | free(dwdump->pkt); 150 | err_pkt_alloc: 151 | nl_socket_free(sk); 152 | return -1; 153 | } 154 | 155 | static void dwdump_data_fini(struct dwdump *dwdump) 156 | { 157 | nl_socket_drop_memberships(dwdump->dsk, NET_DM_GRP_ALERT, NFNLGRP_NONE); 158 | free(dwdump->pkt); 159 | nl_socket_free(dwdump->dsk); 160 | } 161 | 162 | static const char *dwdump_alert_mode(uint8_t alert_mode) 163 | { 164 | switch (alert_mode) { 165 | case NET_DM_ALERT_MODE_SUMMARY: 166 | return "summary"; 167 | case NET_DM_ALERT_MODE_PACKET: 168 | return "packet"; 169 | } 170 | 171 | return "invalid alert mode"; 172 | } 173 | 174 | static int dwdump_config_set(struct dwdump *dwdump) 175 | { 176 | struct nl_sock *sk = dwdump->csk; 177 | struct nl_msg *msg; 178 | int err; 179 | 180 | msg = nlmsg_alloc(); 181 | if (!msg) { 182 | fprintf(stderr, "Failed to allocate netlink message\n"); 183 | return -1; 184 | } 185 | 186 | if (!genlmsg_put(msg, 0, NL_AUTO_SEQ, dwdump->family, 0, 187 | NLM_F_REQUEST|NLM_F_ACK, NET_DM_CMD_CONFIG, 0)) 188 | goto genlmsg_put_failure; 189 | 190 | if (nla_put_u8(msg, NET_DM_ATTR_ALERT_MODE, NET_DM_ALERT_MODE_PACKET)) 191 | goto nla_put_failure; 192 | 193 | if (nla_put_u32(msg, NET_DM_ATTR_TRUNC_LEN, dwdump->options.trunc_len)) 194 | goto nla_put_failure; 195 | 196 | if (nla_put_u32(msg, NET_DM_ATTR_QUEUE_LEN, dwdump->options.queue_len)) 197 | goto nla_put_failure; 198 | 199 | err = nl_send_sync(sk, msg); 200 | if (err < 0) { 201 | fprintf(stderr, "Failed to configure drop monitor kernel module\n"); 202 | return err; 203 | } 204 | 205 | return 0; 206 | 207 | nla_put_failure: 208 | genlmsg_put_failure: 209 | nlmsg_free(msg); 210 | return -EMSGSIZE; 211 | } 212 | 213 | static int dwdump_config_get(struct dwdump *dwdump) 214 | { 215 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 216 | struct sockaddr_nl nla; 217 | unsigned char *buf; 218 | uint8_t alert_mode; 219 | int len, err; 220 | 221 | err = genl_send_simple(dwdump->csk, dwdump->family, 222 | NET_DM_CMD_CONFIG_GET, 0, NLM_F_REQUEST); 223 | if (err < 0) { 224 | fprintf(stderr, "Failed to query configuration\n"); 225 | return -1; 226 | } 227 | 228 | len = nl_recv(dwdump->csk, &nla, &buf, NULL); 229 | if (len < 0) 230 | return -1; 231 | 232 | err = genlmsg_parse((void *) buf, 0, attrs, NET_DM_ATTR_MAX, 233 | net_dm_policy); 234 | if (err < 0) 235 | return -1; 236 | 237 | if (!attrs[NET_DM_ATTR_ALERT_MODE] || !attrs[NET_DM_ATTR_TRUNC_LEN] || 238 | !attrs[NET_DM_ATTR_QUEUE_LEN]) 239 | return -1; 240 | 241 | alert_mode = nla_get_u8(attrs[NET_DM_ATTR_ALERT_MODE]); 242 | printf("Alert mode: %s\n", dwdump_alert_mode(alert_mode)); 243 | 244 | printf("Truncation length: %u\n", 245 | nla_get_u32(attrs[NET_DM_ATTR_TRUNC_LEN])); 246 | 247 | printf("Queue length: %u\n", nla_get_u32(attrs[NET_DM_ATTR_QUEUE_LEN])); 248 | 249 | return 0; 250 | } 251 | 252 | static void dwdump_nested_stats_print(struct nlattr *attr) 253 | { 254 | struct nlattr *attrs[NET_DM_ATTR_STATS_MAX + 1]; 255 | int err; 256 | 257 | err = nla_parse_nested(attrs, NET_DM_ATTR_STATS_MAX, attr, 258 | net_dm_stats_policy); 259 | if (err) 260 | return; 261 | 262 | if (attrs[NET_DM_ATTR_STATS_DROPPED]) 263 | printf("Tail dropped: %" PRIu64 "\n", 264 | nla_get_u64(attrs[NET_DM_ATTR_STATS_DROPPED])); 265 | } 266 | 267 | static int dwdump_stats_get(struct dwdump *dwdump) 268 | { 269 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 270 | struct sockaddr_nl nla; 271 | unsigned char *buf; 272 | int len, err; 273 | 274 | err = genl_send_simple(dwdump->csk, dwdump->family, 275 | NET_DM_CMD_STATS_GET, 0, NLM_F_REQUEST); 276 | if (err < 0) { 277 | fprintf(stderr, "Failed to query statistics\n"); 278 | return -1; 279 | } 280 | 281 | len = nl_recv(dwdump->csk, &nla, &buf, NULL); 282 | if (len < 0) 283 | return -1; 284 | 285 | err = genlmsg_parse((void *) buf, 0, attrs, NET_DM_ATTR_MAX, 286 | net_dm_policy); 287 | if (err < 0) 288 | return -1; 289 | 290 | if (attrs[NET_DM_ATTR_STATS]) { 291 | printf("Software statistics:\n"); 292 | dwdump_nested_stats_print(attrs[NET_DM_ATTR_STATS]); 293 | } 294 | 295 | if (attrs[NET_DM_ATTR_HW_STATS]) { 296 | printf("Hardware statistics:\n"); 297 | dwdump_nested_stats_print(attrs[NET_DM_ATTR_HW_STATS]); 298 | } 299 | 300 | return 0; 301 | } 302 | 303 | static int dwdump_monitor_origin_put(const struct dwdump *dwdump, 304 | struct nl_msg *msg) 305 | { 306 | switch (dwdump->options.origin) { 307 | case DWDUMP_PKT_ORIGIN_ALL: 308 | if (nla_put_flag(msg, NET_DM_ATTR_SW_DROPS) || 309 | nla_put_flag(msg, NET_DM_ATTR_HW_DROPS)) 310 | return -EMSGSIZE; 311 | break; 312 | case DWDUMP_PKT_ORIGIN_SW: 313 | if (nla_put_flag(msg, NET_DM_ATTR_SW_DROPS)) 314 | return -EMSGSIZE; 315 | break; 316 | case DWDUMP_PKT_ORIGIN_HW: 317 | if (nla_put_flag(msg, NET_DM_ATTR_HW_DROPS)) 318 | return -EMSGSIZE; 319 | break; 320 | } 321 | 322 | return 0; 323 | } 324 | 325 | static int dwdump_monitor(struct dwdump *dwdump, bool start) 326 | { 327 | uint8_t cmd = start ? NET_DM_CMD_START : NET_DM_CMD_STOP; 328 | struct nl_sock *sk = dwdump->csk; 329 | struct nl_msg *msg; 330 | int err; 331 | 332 | msg = nlmsg_alloc(); 333 | if (!msg) { 334 | fprintf(stderr, "Failed to allocate netlink message\n"); 335 | return -1; 336 | } 337 | 338 | if (!genlmsg_put(msg, 0, NL_AUTO_SEQ, dwdump->family, 0, 339 | NLM_F_REQUEST|NLM_F_ACK, cmd, 0)) 340 | goto genlmsg_put_failure; 341 | 342 | err = dwdump_monitor_origin_put(dwdump, msg); 343 | if (err < 0) 344 | goto genlmsg_put_failure; 345 | 346 | err = nl_send_sync(sk, msg); 347 | if (err < 0) { 348 | fprintf(stderr, "Failed to %s monitoring\n", 349 | start ? "start" : "stop"); 350 | return err; 351 | } 352 | 353 | return 0; 354 | 355 | genlmsg_put_failure: 356 | nlmsg_free(msg); 357 | return -EMSGSIZE; 358 | } 359 | 360 | static void dwdump_handler(int sig) 361 | { 362 | stop = true; 363 | } 364 | 365 | static int dwdump_sighandler_install(void) 366 | { 367 | static const int signals_catch[] = { 368 | SIGINT, SIGQUIT, SIGTERM, SIGPIPE, SIGHUP, 369 | }; 370 | struct sigaction saction; 371 | int i, err; 372 | 373 | memset(&saction, 0, sizeof(saction)); 374 | saction.sa_handler = dwdump_handler; 375 | 376 | for (i = 0; i < ARRAY_SIZE(signals_catch); i++) { 377 | err = sigaction(signals_catch[i], &saction, NULL); 378 | if (err) { 379 | perror("sigaction"); 380 | return err; 381 | } 382 | } 383 | 384 | return 0; 385 | } 386 | 387 | static int dwdump_ctrl_init(struct dwdump *dwdump) 388 | { 389 | struct nl_sock *sk; 390 | int err; 391 | 392 | sk = nl_socket_alloc(); 393 | if (!sk) { 394 | fprintf(stderr, "Failed to allocate control socket\n"); 395 | return -1; 396 | } 397 | 398 | err = genl_connect(sk); 399 | if (err) { 400 | fprintf(stderr, "Failed to connect control socket"); 401 | goto err_genl_connect; 402 | } 403 | 404 | dwdump->csk = sk; 405 | 406 | if (!dwdump->options.need_mon) 407 | return 0; 408 | 409 | err = dwdump_config_set(dwdump); 410 | if (err) 411 | goto err_config_set; 412 | 413 | err = dwdump_monitor(dwdump, true); 414 | if (err) 415 | goto err_monitor; 416 | 417 | return 0; 418 | 419 | err_monitor: 420 | err_config_set: 421 | err_genl_connect: 422 | nl_socket_free(sk); 423 | return err; 424 | } 425 | 426 | static void dwdump_ctrl_fini(struct dwdump *dwdump) 427 | { 428 | if (!dwdump->options.need_mon) 429 | goto out; 430 | 431 | dwdump_monitor(dwdump, false); 432 | out: 433 | nl_socket_free(dwdump->csk); 434 | } 435 | 436 | static void dwdump_pcap_write(struct dwdump *dwdump, unsigned char *buf, 437 | int len) 438 | { 439 | struct pcap_pkthdr hdr; 440 | int pkt_len; 441 | 442 | if (len + sizeof(struct linux_sll) < dwdump->snaplen) 443 | pkt_len = len + sizeof(struct linux_sll); 444 | else 445 | pkt_len = dwdump->snaplen - sizeof(struct linux_sll); 446 | 447 | memcpy(dwdump->pcap_buf + sizeof(struct linux_sll), buf, pkt_len); 448 | 449 | hdr.caplen = pkt_len; 450 | hdr.len = pkt_len; 451 | gettimeofday(&hdr.ts, NULL); 452 | 453 | pcap_dump((unsigned char *) dwdump->pcap_dumper, &hdr, 454 | (const unsigned char *) dwdump->pcap_buf); 455 | /* In case packets are written to stdout, make sure each packet is 456 | * immediately written and not buffered. 457 | */ 458 | fflush(NULL); 459 | } 460 | 461 | static int dwdump_pcap_buf_init(struct dwdump *dwdump) 462 | { 463 | struct linux_sll sll; 464 | 465 | /* The wireshark netlink dissector expects netlink messages to start 466 | * with a Linux cooked header (SLL), so include it before each packet. 467 | */ 468 | memset(&sll, 0, sizeof(sll)); 469 | sll.pkttype = htons(PACKET_OUTGOING); 470 | sll.hatype = htons(ARPHRD_NETLINK); 471 | sll.family = htons(AF_NETLINK); 472 | 473 | dwdump->pcap_buf = calloc(1, dwdump->snaplen); 474 | if (!dwdump->pcap_buf) { 475 | perror("calloc"); 476 | return -1; 477 | } 478 | 479 | memcpy(dwdump->pcap_buf, &sll, sizeof(sll)); 480 | 481 | return 0; 482 | } 483 | 484 | static void dwdump_pcap_buf_fini(struct dwdump *dwdump) 485 | { 486 | free(dwdump->pcap_buf); 487 | } 488 | 489 | static int dwdump_pcap_genl_init(struct dwdump *dwdump) 490 | { 491 | struct sockaddr_nl nla; 492 | unsigned char *buf; 493 | int len, err; 494 | 495 | /* In order for wireshark to be able to invoke the net_dm dissector, 496 | * it must learn about the mapping between the generic netlink 497 | * family ID and its name from this dump. 498 | * 499 | * Reference: 500 | * https://www.wireshark.org/lists/wireshark-users/201907/msg00027.html 501 | */ 502 | err = genl_send_simple(dwdump->csk, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 503 | 1, NLM_F_DUMP); 504 | if (err < 0) { 505 | fprintf(stderr, "Failed to dump generic netlink families\n"); 506 | return -1; 507 | } 508 | 509 | len = nl_recv(dwdump->csk, &nla, &buf, NULL); 510 | if (len < 0) 511 | return -1; 512 | 513 | dwdump_pcap_write(dwdump, buf, len); 514 | 515 | return 0; 516 | } 517 | 518 | static int dwdump_pcap_init(struct dwdump *dwdump) 519 | { 520 | int err; 521 | 522 | if (!dwdump->options.need_pcap) 523 | return 0; 524 | 525 | dwdump->pcap_handle = pcap_open_dead(DLT_NETLINK, dwdump->snaplen); 526 | if (!dwdump->pcap_handle) { 527 | perror("pcap_open_dead"); 528 | return -1; 529 | } 530 | 531 | dwdump->pcap_dumper = pcap_dump_open(dwdump->pcap_handle, 532 | dwdump->options.dumpfile); 533 | if (!dwdump->pcap_dumper) { 534 | pcap_perror(dwdump->pcap_handle, "pcap_dump_open"); 535 | goto err_dump_open; 536 | } 537 | 538 | err = dwdump_pcap_buf_init(dwdump); 539 | if (err) 540 | goto err_buf_init; 541 | 542 | err = dwdump_pcap_genl_init(dwdump); 543 | if (err) 544 | goto err_genl_init; 545 | 546 | return 0; 547 | 548 | err_genl_init: 549 | dwdump_pcap_buf_fini(dwdump); 550 | err_buf_init: 551 | pcap_dump_close(dwdump->pcap_dumper); 552 | err_dump_open: 553 | pcap_close(dwdump->pcap_handle); 554 | return -1; 555 | } 556 | 557 | static void dwdump_pcap_fini(struct dwdump *dwdump) 558 | { 559 | if (!dwdump->options.need_pcap) 560 | return; 561 | 562 | dwdump_pcap_buf_fini(dwdump); 563 | pcap_dump_close(dwdump->pcap_dumper); 564 | pcap_close(dwdump->pcap_handle); 565 | } 566 | 567 | static int dwdump_init(struct dwdump *dwdump) 568 | { 569 | int err; 570 | 571 | err = dwdump_data_init(dwdump); 572 | if (err) 573 | return err; 574 | 575 | err = dwdump_ctrl_init(dwdump); 576 | if (err) 577 | goto err_ctrl_init; 578 | 579 | err = dwdump_pcap_init(dwdump); 580 | if (err) 581 | goto err_pcap_init; 582 | 583 | err = dwdump_sighandler_install(); 584 | if (err) 585 | goto err_sighandler_install; 586 | 587 | return 0; 588 | 589 | err_sighandler_install: 590 | dwdump_pcap_fini(dwdump); 591 | err_pcap_init: 592 | dwdump_ctrl_fini(dwdump); 593 | err_ctrl_init: 594 | dwdump_data_fini(dwdump); 595 | return err; 596 | } 597 | 598 | static void dwdump_fini(struct dwdump *dwdump) 599 | { 600 | dwdump_pcap_fini(dwdump); 601 | dwdump_ctrl_fini(dwdump); 602 | dwdump_data_fini(dwdump); 603 | } 604 | 605 | static int dwdump_main(struct dwdump *dwdump) 606 | { 607 | int fd = nl_socket_get_fd(dwdump->dsk); 608 | 609 | if (dwdump->options.query) 610 | return dwdump_config_get(dwdump); 611 | 612 | if (dwdump->options.stats) 613 | return dwdump_stats_get(dwdump); 614 | 615 | if (dwdump->options.exit) 616 | return dwdump_monitor(dwdump, false); 617 | 618 | while (!stop) { 619 | int len; 620 | 621 | /* Use recv() instead of nl_recv() since interruption of 622 | * nl_recv() causes the operation to be retried. 623 | */ 624 | len = recv(fd, dwdump->pkt, dwdump->snaplen, 0); 625 | if (len < 0) { 626 | switch (errno) { 627 | case EINTR: 628 | continue; 629 | default: 630 | perror("recv"); 631 | return -1; 632 | } 633 | } 634 | 635 | dwdump_pcap_write(dwdump, (unsigned char *) dwdump->pkt, len); 636 | } 637 | 638 | return 0; 639 | } 640 | 641 | static int dwdump_origin_parse(struct dwdump *dwdump, const char *origin) 642 | { 643 | if (strcmp(origin, "sw") == 0) { 644 | dwdump->options.origin = DWDUMP_PKT_ORIGIN_SW; 645 | return 0; 646 | } else if (strcmp(origin, "hw") == 0) { 647 | dwdump->options.origin = DWDUMP_PKT_ORIGIN_HW; 648 | return 0; 649 | } else { 650 | fprintf(stderr, "Invalid origin: \'%s\'\n", origin); 651 | return -EINVAL; 652 | } 653 | } 654 | 655 | static void dwdump_usage(FILE *fp) 656 | { 657 | fprintf(fp, "Usage:\n"); 658 | fprintf(fp, "dwdump [ -w -t -q -l -p -s -b -e -o ]\n"); 659 | fprintf(fp, " -w dump packets to provided file. defaults to standard output\n"); 660 | fprintf(fp, " -t truncate packets to provided length. defaults to no truncation\n"); 661 | fprintf(fp, " -q query the kernel for current configuration and exit\n"); 662 | fprintf(fp, " -l set packet queue limit to provided limit\n"); 663 | fprintf(fp, " -p only listen on notified packets with no configuration\n"); 664 | fprintf(fp, " -s query kernel for statistics and exit\n"); 665 | fprintf(fp, " -b set the socket's receive buffer to provided size\n"); 666 | fprintf(fp, " -o monitor only originated drops. defaults to all\n"); 667 | fprintf(fp, " -e ask kernel to stop monitoring and exit\n"); 668 | } 669 | 670 | static int dwdump_opts_parse(struct dwdump *dwdump, int argc, char **argv) 671 | { 672 | static const struct option long_options[] = { 673 | { "write", required_argument, NULL, 'w' }, 674 | { "trunc", required_argument, NULL, 't' }, 675 | { "query", no_argument, NULL, 'q' }, 676 | { "limit", required_argument, NULL, 'l' }, 677 | { "passive", no_argument, NULL, 'p' }, 678 | { "stats", no_argument, NULL, 's' }, 679 | { "bufsize", required_argument, NULL, 'b' }, 680 | { "origin", required_argument, NULL, 'o' }, 681 | { "exit", no_argument, NULL, 'e' }, 682 | { "help", no_argument, NULL, 'h' }, 683 | { NULL, 0, NULL, 0 } 684 | }; 685 | static const char optstring[] = "w:t:ql:psb:o:eh"; 686 | int opt, err; 687 | 688 | /* Default values */ 689 | dwdump->options.dumpfile = "/dev/stdout"; 690 | dwdump->options.trunc_len = 0xffff; 691 | dwdump->options.queue_len = 1000; 692 | dwdump->options.rxbuf = 1024 * 1024; 693 | dwdump->options.origin = DWDUMP_PKT_ORIGIN_ALL; 694 | dwdump->options.need_pcap = true; 695 | dwdump->options.need_mon = true; 696 | 697 | while ((opt = getopt_long(argc, argv, optstring, 698 | long_options, NULL)) != -1) { 699 | switch (opt) { 700 | case 'w': 701 | dwdump->options.dumpfile = optarg; 702 | break; 703 | case 't': 704 | dwdump->options.trunc_len = atol(optarg); 705 | if (dwdump->options.trunc_len == 0 || 706 | dwdump->options.trunc_len > 0xffff) 707 | dwdump->options.trunc_len = 0xffff; 708 | break; 709 | case 'q': 710 | dwdump->options.query = true; 711 | dwdump->options.need_pcap = false; 712 | dwdump->options.need_mon = false; 713 | break; 714 | case 'l': 715 | dwdump->options.queue_len = atol(optarg); 716 | break; 717 | case 'p': 718 | dwdump->options.passive = true; 719 | dwdump->options.need_mon = false; 720 | break; 721 | case 's': 722 | dwdump->options.stats = true; 723 | dwdump->options.need_pcap = false; 724 | dwdump->options.need_mon = false; 725 | break; 726 | case 'b': 727 | dwdump->options.rxbuf = atol(optarg); 728 | break; 729 | case 'o': 730 | err = dwdump_origin_parse(dwdump, optarg); 731 | if (err) 732 | return err; 733 | break; 734 | case 'e': 735 | dwdump->options.exit = true; 736 | dwdump->options.need_pcap = false; 737 | dwdump->options.need_mon = false; 738 | break; 739 | case 'h': 740 | dwdump_usage(stdout); 741 | return -1; 742 | case '?': 743 | dwdump_usage(stderr); 744 | return -1; 745 | default: 746 | fprintf(stderr, "Unknown option: \'%c\'\n", opt); 747 | dwdump_usage(stderr); 748 | return -1; 749 | } 750 | } 751 | 752 | return 0; 753 | } 754 | 755 | int main(int argc, char **argv) 756 | { 757 | struct dwdump *dwdump; 758 | int err; 759 | 760 | dwdump = calloc(1, sizeof(*dwdump)); 761 | if (!dwdump) { 762 | perror("calloc"); 763 | goto err_dwdump_alloc; 764 | } 765 | 766 | err = dwdump_opts_parse(dwdump, argc, argv); 767 | if (err) 768 | goto err_opts_parse; 769 | 770 | err = dwdump_init(dwdump); 771 | if (err) 772 | goto err_dwdump_init; 773 | 774 | err = dwdump_main(dwdump); 775 | 776 | dwdump_fini(dwdump); 777 | free(dwdump); 778 | 779 | return err; 780 | 781 | err_dwdump_init: 782 | err_opts_parse: 783 | free(dwdump); 784 | err_dwdump_alloc: 785 | exit(EXIT_FAILURE); 786 | } 787 | -------------------------------------------------------------------------------- /src/lookup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, Neil Horman 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | /* 7 | * This is a translator. given an input address, this will convert it into a 8 | * function and offset. Unless overridden, it will automatically determine 9 | * translations using the following methods, in order of priority: 10 | * 1) /usr/lib/debug/ using libbfd 11 | * 2) /proc/kallsyms 12 | */ 13 | 14 | #include "config.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #ifdef HAVE_BFD_H 20 | #include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "lookup.h" 28 | 29 | #ifdef HAVE_BFD_H 30 | extern struct lookup_methods bfd_methods; 31 | #endif 32 | extern struct lookup_methods kallsym_methods; 33 | 34 | static int lookup_null_init(void) 35 | { 36 | printf("Initializing null lookup method\n"); 37 | return 0; 38 | } 39 | 40 | static int lookup_null_sym(void *pc, struct loc_result *location) 41 | { 42 | /* 43 | * In the null method, every lookup fails 44 | */ 45 | return 1; 46 | } 47 | 48 | static struct lookup_methods null_methods = { 49 | lookup_null_init, 50 | lookup_null_sym, 51 | }; 52 | 53 | static struct lookup_methods *methods = NULL; 54 | 55 | int init_lookup(lookup_init_method_t method) 56 | { 57 | int rc; 58 | switch (method) { 59 | case METHOD_NULL: 60 | /* 61 | * Don't actually do any lookups, 62 | * just pretend everything is 63 | * not found 64 | */ 65 | methods = &null_methods; 66 | break; 67 | case METHOD_AUTO: 68 | #ifdef HAVE_BFD_H 69 | methods = &bfd_methods; 70 | if (methods->lookup_init() == 0) 71 | return 0; 72 | #endif 73 | methods = &kallsym_methods; 74 | if (methods->lookup_init() == 0) 75 | return 0; 76 | methods = NULL; 77 | return -1; 78 | #ifdef HAVE_BFD_H 79 | case METHOD_DEBUGINFO: 80 | methods = &bfd_methods; 81 | break; 82 | #endif 83 | case METHOD_KALLSYMS: 84 | methods = &kallsym_methods; 85 | break; 86 | } 87 | 88 | rc = methods->lookup_init(); 89 | if (rc < 0) 90 | methods = NULL; 91 | return rc; 92 | } 93 | 94 | int lookup_symbol(void *pc, struct loc_result *loc) 95 | { 96 | if (loc == NULL) 97 | return 1; 98 | return methods->get_symbol(pc, loc); 99 | } 100 | -------------------------------------------------------------------------------- /src/lookup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, Neil Horman 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | /* 7 | * This is a translator. given an input address, this will convert it into a 8 | * function and offset. Unless overridden, it will automatically determine 9 | * translations using the following methods, in order of priority: 10 | * 1) /usr/lib/debug/ using libbfd 11 | * 2) /proc/kallsyms 12 | */ 13 | 14 | #include "config.h" 15 | 16 | #include 17 | #include 18 | 19 | 20 | /* 21 | * Initialization routine 22 | * INPUTS: 23 | * method - enum describing how to do translation 24 | * * METHOD_NULL : Just print pc values, not symbols 25 | * * METHOD_AUTO : automatic search for best method 26 | * * METHOD_DEBUGINFO : use debuginfo package 27 | * * METHOD_KALLSYMS : use /proc/kallsyms 28 | * returns: 29 | * * 0 : initalization succeeded 30 | * * < 0 : initalization failed 31 | */ 32 | typedef enum { 33 | METHOD_NULL = 0, 34 | METHOD_AUTO, 35 | #ifdef HAVE_BFD_H 36 | METHOD_DEBUGINFO, 37 | #endif 38 | METHOD_KALLSYMS 39 | } lookup_init_method_t; 40 | 41 | struct loc_result { 42 | const char *symbol; 43 | __u64 offset; 44 | }; 45 | 46 | int init_lookup(lookup_init_method_t method); 47 | int lookup_symbol(void *pc, struct loc_result *location); 48 | 49 | struct lookup_methods { 50 | int (*lookup_init)(void); 51 | int(*get_symbol)(void *pc, struct loc_result *location); 52 | }; 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/lookup_bfd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, Neil Horman 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | /* 7 | * This is a translator. given an input address, this will convert it into a 8 | * symbollic name using the bfd library 9 | */ 10 | 11 | #include "config.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "lookup.h" 23 | 24 | 25 | static int lookup_bfd_init(void) 26 | { 27 | struct utsname uts; 28 | struct stat sb; 29 | char *dbibuf; 30 | 31 | /* 32 | *Start by determining if we have the required debuginfo package 33 | *here 34 | */ 35 | if(uname(&uts)<0) 36 | return-1; 37 | 38 | dbibuf = malloc(strlen("/usr/lib/debug/lib/modules/") + sizeof(uts.release)); 39 | sprintf(dbibuf,"/usr/lib/debug/lib/modules/%s", uts.release); 40 | if (stat(dbibuf,&sb) < 0) { 41 | free(dbibuf); 42 | goto out_fail; 43 | } 44 | 45 | free(dbibuf); 46 | 47 | 48 | bfd_init(); 49 | return 0; 50 | 51 | out_fail: 52 | return-1; 53 | } 54 | 55 | static int lookup_bfd_sym(void *pc, struct loc_result *location) 56 | { 57 | return 1; 58 | } 59 | 60 | struct lookup_methods bfd_methods = { 61 | lookup_bfd_init, 62 | lookup_bfd_sym, 63 | }; 64 | -------------------------------------------------------------------------------- /src/lookup_kas.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, Neil Horman 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | /* 7 | * This is a translator. given an input address, this will convert it into a 8 | * symbolic name using /proc/kallsyms 9 | */ 10 | 11 | #include "config.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "lookup.h" 25 | 26 | struct symbol_entry { 27 | char *sym_name; 28 | uint64_t start; 29 | uint64_t end; 30 | LIST_ENTRY(symbol_entry) list; 31 | }; 32 | 33 | LIST_HEAD(sym_list, symbol_entry); 34 | 35 | /* 36 | * This is our cache of symbols that we've previously looked up 37 | */ 38 | static struct sym_list sym_list_head = {NULL}; 39 | 40 | 41 | static int lookup_kas_cache(uint64_t pc, struct loc_result *location) 42 | { 43 | struct symbol_entry *sym; 44 | 45 | LIST_FOREACH(sym, &sym_list_head, list) { 46 | if ((pc >= sym->start) && 47 | (pc <= sym->end)) { 48 | location->symbol = sym->sym_name; 49 | location->offset = (pc - sym->start); 50 | return 0; 51 | } 52 | } 53 | 54 | return 1; 55 | } 56 | 57 | static void kas_add_cache(__u64 start, __u64 end, char *name) 58 | { 59 | struct symbol_entry *sym = NULL; 60 | 61 | sym = malloc(sizeof(struct symbol_entry)); 62 | if (!sym) 63 | return; 64 | 65 | sym->start = start; 66 | sym->end = end; 67 | sym->sym_name = name; 68 | 69 | LIST_INSERT_HEAD(&sym_list_head, sym, list); 70 | return; 71 | } 72 | 73 | static void kas_update_cache(__u64 start, __u64 end, char *name) 74 | { 75 | struct symbol_entry *sym; 76 | /* look for any symbol that matches our start 77 | * if the new end is longer than the current end, extend it 78 | */ 79 | LIST_FOREACH(sym, &sym_list_head, list) { 80 | if (start == sym->start) { 81 | if (end > sym->end) { 82 | sym->end = end; 83 | } 84 | return; 85 | } 86 | } 87 | 88 | /* if we get here, we didn't find a symbol, and should add it */ 89 | kas_add_cache(start, end, name); 90 | } 91 | 92 | static int lookup_kas_proc(uint64_t pc, struct loc_result *location) 93 | { 94 | FILE *pf; 95 | uint64_t sppc; 96 | uint64_t min_delta, sdelta; 97 | uint64_t sym_base_addr; 98 | char *tgt_sym = NULL; 99 | char *name; 100 | 101 | pf = fopen("/proc/kallsyms", "r"); 102 | 103 | if (!pf) 104 | return 1; 105 | 106 | 107 | /* 108 | * We need to conduct a reverse price is right search here, we need to find the symbol that is less than 109 | * pc, but by the least amount. i.e. address 0xffffffff00010 is 10 more than symbol A, at 0xffffffff00000000, 110 | * but is only 8 more than symbol B at 0xffffffff00000002, therefore this drop occurs at symbol B+8 111 | */ 112 | min_delta = LLONG_MAX; 113 | sym_base_addr = 0; 114 | while (!feof(pf)) { 115 | /* 116 | * Each line of /proc/kallsyms is formatteded as: 117 | * - "%pK %c %s\n" (for kernel internal symbols), or 118 | * - "%pK %c %s\t[%s]\n" (for module-provided symbols) 119 | */ 120 | if (fscanf(pf, "%llx %*s %ms [ %*[^]] ]", (unsigned long long *)&sppc, &name) < 0) { 121 | if (ferror(pf)) { 122 | perror("Error Scanning File: "); 123 | break; 124 | } 125 | if (feof(pf)) { 126 | continue; 127 | } 128 | } 129 | 130 | /* don't bother with symbols that are above our target */ 131 | if (sppc > pc) { 132 | continue; 133 | } 134 | 135 | sdelta = pc - sppc; 136 | if (sdelta < min_delta) { 137 | min_delta = sdelta; 138 | if (tgt_sym) 139 | free(tgt_sym); 140 | tgt_sym = strdup(name); 141 | sym_base_addr = sppc; 142 | } 143 | free(name); 144 | } 145 | 146 | fclose(pf); 147 | 148 | if (sym_base_addr) { 149 | kas_update_cache(sym_base_addr, sym_base_addr + min_delta, tgt_sym); 150 | location->symbol = tgt_sym; 151 | location->offset = min_delta; 152 | return 0; 153 | } 154 | return 1; 155 | } 156 | 157 | static int lookup_kas_init(void) 158 | { 159 | printf("Initializing kallsyms db\n"); 160 | 161 | return 0; 162 | } 163 | 164 | static int lookup_kas_sym(void *pc, struct loc_result *location) 165 | { 166 | __u64 pcv; 167 | 168 | pcv = (uintptr_t)pc; 169 | 170 | if (!lookup_kas_cache(pcv, location)) { 171 | return 0; 172 | } 173 | 174 | return lookup_kas_proc(pcv, location); 175 | } 176 | 177 | struct lookup_methods kallsym_methods = { 178 | lookup_kas_init, 179 | lookup_kas_sym, 180 | }; 181 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, Neil Horman 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | /* 7 | * Opens our netlink socket. Returns the socket descriptor or < 0 on error 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "net_dropmon.h" 31 | #include "lookup.h" 32 | 33 | /* 34 | * This is just in place until the kernel changes get committed 35 | */ 36 | #ifndef NETLINK_DRPMON 37 | #define NETLINK_DRPMON 20 38 | #endif 39 | 40 | struct netlink_message { 41 | void *msg; 42 | struct nl_msg *nlbuf; 43 | int refcnt; 44 | LIST_ENTRY(netlink_message) ack_list_element; 45 | int seq; 46 | void (*ack_cb)(struct netlink_message *amsg, struct netlink_message *msg, int err); 47 | }; 48 | 49 | LIST_HEAD(ack_list, netlink_message); 50 | 51 | struct ack_list ack_list_head = {NULL}; 52 | 53 | unsigned long alimit = 0; 54 | unsigned long acount = 0; 55 | unsigned long trunc_len = 0; 56 | unsigned long queue_len = 0; 57 | bool monitor_sw = false; 58 | bool monitor_hw = false; 59 | 60 | void handle_dm_alert_msg(struct netlink_message *msg, int err); 61 | void handle_dm_packet_alert_msg(struct netlink_message *msg, int err); 62 | void handle_dm_config_new_msg(struct netlink_message *msg, int err); 63 | void handle_dm_stats_new_msg(struct netlink_message *msg, int err); 64 | void handle_dm_config_msg(struct netlink_message *amsg, struct netlink_message *msg, int err); 65 | void handle_dm_start_msg(struct netlink_message *amsg, struct netlink_message *msg, int err); 66 | void handle_dm_stop_msg(struct netlink_message *amsg, struct netlink_message *msg, int err); 67 | int disable_drop_monitor(); 68 | 69 | static void(*type_cb[_NET_DM_CMD_MAX])(struct netlink_message *, int err) = { 70 | NULL, 71 | handle_dm_alert_msg, 72 | NULL, 73 | NULL, 74 | NULL, 75 | handle_dm_packet_alert_msg, 76 | NULL, 77 | handle_dm_config_new_msg, 78 | NULL, 79 | handle_dm_stats_new_msg, 80 | }; 81 | 82 | static struct nl_sock *nsd; 83 | static int nsf; 84 | 85 | enum { 86 | STATE_IDLE = 0, 87 | STATE_ACTIVATING, 88 | STATE_RECEIVING, 89 | STATE_RQST_DEACTIVATE, 90 | STATE_RQST_ACTIVATE, 91 | STATE_DEACTIVATING, 92 | STATE_FAILED, 93 | STATE_EXIT, 94 | STATE_RQST_ALERT_MODE_SUMMARY, 95 | STATE_RQST_ALERT_MODE_PACKET, 96 | STATE_ALERT_MODE_SETTING, 97 | STATE_RQST_TRUNC_LEN, 98 | STATE_TRUNC_LEN_SETTING, 99 | STATE_RQST_QUEUE_LEN, 100 | STATE_QUEUE_LEN_SETTING, 101 | STATE_RQST_CONFIG, 102 | STATE_CONFIG_GETTING, 103 | STATE_RQST_STATS, 104 | STATE_STATS_GETTING, 105 | }; 106 | 107 | static int state = STATE_IDLE; 108 | 109 | static struct nla_policy net_dm_policy[NET_DM_ATTR_MAX + 1] = { 110 | [NET_DM_ATTR_ALERT_MODE] = { .type = NLA_U8 }, 111 | [NET_DM_ATTR_PC] = { .type = NLA_U64 }, 112 | [NET_DM_ATTR_SYMBOL] = { .type = NLA_STRING }, 113 | [NET_DM_ATTR_IN_PORT] = { .type = NLA_NESTED }, 114 | [NET_DM_ATTR_TIMESTAMP] = { .type = NLA_U64 }, 115 | [NET_DM_ATTR_PROTO] = { .type = NLA_U16 }, 116 | [NET_DM_ATTR_PAYLOAD] = { .type = NLA_UNSPEC }, 117 | [NET_DM_ATTR_TRUNC_LEN] = { .type = NLA_U32 }, 118 | [NET_DM_ATTR_ORIG_LEN] = { .type = NLA_U32 }, 119 | [NET_DM_ATTR_QUEUE_LEN] = { .type = NLA_U32 }, 120 | [NET_DM_ATTR_STATS] = { .type = NLA_NESTED }, 121 | [NET_DM_ATTR_HW_STATS] = { .type = NLA_NESTED }, 122 | [NET_DM_ATTR_ORIGIN] = { .type = NLA_U16 }, 123 | [NET_DM_ATTR_HW_TRAP_GROUP_NAME] = { .type = NLA_STRING }, 124 | [NET_DM_ATTR_HW_TRAP_NAME] = { .type = NLA_STRING }, 125 | [NET_DM_ATTR_HW_ENTRIES] = { .type = NLA_NESTED }, 126 | [NET_DM_ATTR_HW_ENTRY] = { .type = NLA_NESTED }, 127 | [NET_DM_ATTR_HW_TRAP_COUNT] = { .type = NLA_U32 }, 128 | }; 129 | 130 | static struct nla_policy net_dm_port_policy[NET_DM_ATTR_PORT_MAX + 1] = { 131 | [NET_DM_ATTR_PORT_NETDEV_IFINDEX] = { .type = NLA_U32 }, 132 | [NET_DM_ATTR_PORT_NETDEV_NAME] = { .type = NLA_STRING }, 133 | }; 134 | 135 | static struct nla_policy net_dm_stats_policy[NET_DM_ATTR_STATS_MAX + 1] = { 136 | [NET_DM_ATTR_STATS_DROPPED] = { .type = NLA_U64 }, 137 | }; 138 | 139 | int strtobool(const char *str, bool *p_val) 140 | { 141 | bool val; 142 | 143 | if (!strcmp(str, "true") || !strcmp(str, "1")) 144 | val = true; 145 | else if (!strcmp(str, "false") || !strcmp(str, "0")) 146 | val = false; 147 | else 148 | return -EINVAL; 149 | *p_val = val; 150 | return 0; 151 | } 152 | 153 | void sigint_handler(int signum) 154 | { 155 | if ((state == STATE_RECEIVING) || 156 | (state == STATE_RQST_DEACTIVATE)) { 157 | disable_drop_monitor(); 158 | state = STATE_DEACTIVATING; 159 | } else { 160 | printf("Got a sigint while not receiving\n"); 161 | } 162 | return; 163 | } 164 | 165 | struct nl_sock *setup_netlink_socket() 166 | { 167 | struct nl_sock *sd; 168 | int family; 169 | 170 | sd = nl_socket_alloc(); 171 | 172 | genl_connect(sd); 173 | 174 | family = genl_ctrl_resolve(sd, "NET_DM"); 175 | 176 | if (family < 0) { 177 | printf("Unable to find NET_DM family, dropwatch can't work\n"); 178 | goto out_close; 179 | } 180 | 181 | nsf = family; 182 | 183 | nl_close(sd); 184 | nl_socket_free(sd); 185 | 186 | sd = nl_socket_alloc(); 187 | nl_join_groups(sd, NET_DM_GRP_ALERT); 188 | 189 | nl_connect(sd, NETLINK_GENERIC); 190 | 191 | return sd; 192 | 193 | out_close: 194 | nl_close(sd); 195 | nl_socket_free(sd); 196 | return NULL; 197 | } 198 | 199 | struct netlink_message *alloc_netlink_msg(uint32_t type, uint16_t flags, size_t size) 200 | { 201 | struct netlink_message *msg; 202 | static uint32_t seq = 0; 203 | 204 | msg = (struct netlink_message *)malloc(sizeof(struct netlink_message)); 205 | 206 | if (!msg) 207 | return NULL; 208 | 209 | msg->refcnt = 1; 210 | msg->nlbuf = nlmsg_alloc(); 211 | msg->msg = genlmsg_put(msg->nlbuf, 0, seq, nsf, size, flags, type, 1); 212 | 213 | msg->ack_cb = NULL; 214 | msg->seq = seq++; 215 | 216 | return msg; 217 | } 218 | 219 | void set_ack_cb(struct netlink_message *msg, 220 | void (*cb)(struct netlink_message *, struct netlink_message *, int)) 221 | { 222 | if (msg->ack_cb) 223 | return; 224 | 225 | msg->ack_cb = cb; 226 | msg->refcnt++; 227 | LIST_INSERT_HEAD(&ack_list_head, msg, ack_list_element); 228 | } 229 | 230 | struct netlink_message *wrap_netlink_msg(struct nlmsghdr *buf) 231 | { 232 | struct netlink_message *msg; 233 | 234 | msg = (struct netlink_message *)malloc(sizeof(struct netlink_message)); 235 | if (msg) { 236 | msg->refcnt = 1; 237 | msg->msg = buf; 238 | msg->nlbuf = NULL; 239 | } 240 | 241 | return msg; 242 | } 243 | 244 | int free_netlink_msg(struct netlink_message *msg) 245 | { 246 | int refcnt; 247 | 248 | msg->refcnt--; 249 | 250 | refcnt = msg->refcnt; 251 | 252 | if (!refcnt) { 253 | if (msg->nlbuf) 254 | nlmsg_free(msg->nlbuf); 255 | else 256 | free(msg->msg); 257 | free(msg); 258 | } 259 | 260 | return refcnt; 261 | } 262 | 263 | int send_netlink_message(struct netlink_message *msg) 264 | { 265 | return nl_send(nsd, msg->nlbuf); 266 | } 267 | 268 | struct netlink_message *recv_netlink_message(int *err) 269 | { 270 | static struct nlmsghdr *buf; 271 | struct netlink_message *msg; 272 | struct genlmsghdr *glm; 273 | struct sockaddr_nl nla; 274 | int type; 275 | int rc; 276 | 277 | *err = 0; 278 | 279 | do { 280 | rc = nl_recv(nsd, &nla, (unsigned char **)&buf, NULL); 281 | if (rc < 0) { 282 | switch (errno) { 283 | case EINTR: 284 | /* 285 | * Take a pass through the state loop 286 | */ 287 | return NULL; 288 | break; 289 | default: 290 | perror("Receive operation failed:"); 291 | return NULL; 292 | break; 293 | } 294 | } 295 | } while (rc == 0); 296 | 297 | msg = wrap_netlink_msg(buf); 298 | 299 | type = ((struct nlmsghdr *)msg->msg)->nlmsg_type; 300 | 301 | /* 302 | * Note the NLMSG_ERROR is overloaded 303 | * Its also used to deliver ACKs 304 | */ 305 | if (type == NLMSG_ERROR) { 306 | struct netlink_message *am; 307 | struct nlmsgerr *errm = nlmsg_data(msg->msg); 308 | LIST_FOREACH(am, &ack_list_head, ack_list_element) { 309 | if (am->seq == errm->msg.nlmsg_seq) 310 | break; 311 | } 312 | 313 | if (am) { 314 | LIST_REMOVE(am, ack_list_element); 315 | am->ack_cb(msg, am, errm->error); 316 | free_netlink_msg(am); 317 | } else { 318 | printf("Got an unexpected ack for sequence %d\n", errm->msg.nlmsg_seq); 319 | } 320 | 321 | free_netlink_msg(msg); 322 | return NULL; 323 | } 324 | 325 | glm = nlmsg_data(msg->msg); 326 | type = glm->cmd; 327 | 328 | if ((type > NET_DM_CMD_MAX) || 329 | (type <= NET_DM_CMD_UNSPEC)) { 330 | printf("Received message of unknown type %d\n", 331 | type); 332 | free_netlink_msg(msg); 333 | return NULL; 334 | } 335 | 336 | return msg; 337 | } 338 | 339 | void process_rx_message(void) 340 | { 341 | struct netlink_message *msg; 342 | int err; 343 | int type; 344 | sigset_t bs; 345 | 346 | sigemptyset(&bs); 347 | sigaddset(&bs, SIGINT); 348 | sigprocmask(SIG_UNBLOCK, &bs, NULL); 349 | msg = recv_netlink_message(&err); 350 | sigprocmask(SIG_BLOCK, &bs, NULL); 351 | 352 | if (msg) { 353 | struct nlmsghdr *nlh = msg->msg; 354 | struct genlmsghdr *glh = nlmsg_data(nlh); 355 | type = glh->cmd; 356 | type_cb[type](msg, err); 357 | } 358 | return; 359 | } 360 | 361 | void print_nested_hw_entry(struct nlattr *hw_entry) 362 | { 363 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 364 | int err; 365 | 366 | err = nla_parse_nested(attrs, NET_DM_ATTR_MAX, hw_entry, net_dm_policy); 367 | if (err) 368 | return; 369 | 370 | if (!attrs[NET_DM_ATTR_HW_TRAP_NAME] || 371 | !attrs[NET_DM_ATTR_HW_TRAP_COUNT]) 372 | return; 373 | 374 | printf("%d drops at %s [hardware]\n", 375 | nla_get_u32(attrs[NET_DM_ATTR_HW_TRAP_COUNT]), 376 | nla_get_string(attrs[NET_DM_ATTR_HW_TRAP_NAME])); 377 | } 378 | 379 | void print_nested_hw_entries(struct nlattr *hw_entries) 380 | { 381 | struct nlattr *attr; 382 | int rem; 383 | 384 | nla_for_each_nested(attr, hw_entries, rem) { 385 | if (nla_type(attr) != NET_DM_ATTR_HW_ENTRY) 386 | continue; 387 | print_nested_hw_entry(attr); 388 | 389 | acount++; 390 | if (alimit && (acount == alimit)) { 391 | printf("Alert limit reached, deactivating!\n"); 392 | state = STATE_RQST_DEACTIVATE; 393 | } 394 | } 395 | } 396 | 397 | /* 398 | * These are the received message handlers 399 | */ 400 | void handle_dm_alert_msg(struct netlink_message *msg, int err) 401 | { 402 | int i; 403 | struct nlmsghdr *nlh = msg->msg; 404 | struct genlmsghdr *glh = nlmsg_data(nlh); 405 | struct loc_result res; 406 | struct net_dm_alert_msg *alert = nla_data(genlmsg_data(glh)); 407 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 408 | 409 | if (state != STATE_RECEIVING) 410 | goto out_free; 411 | 412 | err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); 413 | if (err) 414 | goto out_free; 415 | 416 | for (i=0; i < alert->entries; i++) { 417 | void *location; 418 | memcpy(&location, alert->points[i].pc, sizeof(void *)); 419 | if (lookup_symbol(location, &res)) 420 | printf ("%d drops at location %p [software]\n", alert->points[i].count, location); 421 | else 422 | printf ("%d drops at %s+%llx (%p) [software]\n", 423 | alert->points[i].count, res.symbol, (unsigned long long)res.offset, location); 424 | acount++; 425 | if (alimit && (acount == alimit)) { 426 | printf("Alert limit reached, deactivating!\n"); 427 | state = STATE_RQST_DEACTIVATE; 428 | } 429 | } 430 | 431 | if (attrs[NET_DM_ATTR_HW_ENTRIES]) 432 | print_nested_hw_entries(attrs[NET_DM_ATTR_HW_ENTRIES]); 433 | 434 | out_free: 435 | free_netlink_msg(msg); 436 | } 437 | 438 | void print_nested_port(struct nlattr *attr, const char *dir) 439 | { 440 | struct nlattr *attrs[NET_DM_ATTR_PORT_MAX + 1]; 441 | int err; 442 | 443 | err = nla_parse_nested(attrs, NET_DM_ATTR_PORT_MAX, attr, 444 | net_dm_port_policy); 445 | if (err) 446 | return; 447 | 448 | if (attrs[NET_DM_ATTR_PORT_NETDEV_IFINDEX]) 449 | printf("%s port ifindex: %d\n", dir, 450 | nla_get_u32(attrs[NET_DM_ATTR_PORT_NETDEV_IFINDEX])); 451 | 452 | if (attrs[NET_DM_ATTR_PORT_NETDEV_NAME]) 453 | printf("%s port name: %s\n", dir, 454 | nla_get_string(attrs[NET_DM_ATTR_PORT_NETDEV_NAME])); 455 | } 456 | 457 | void print_packet_origin(struct nlattr *attr) 458 | { 459 | const char *origin; 460 | uint16_t val; 461 | 462 | val = nla_get_u16(attr); 463 | switch (val) { 464 | case NET_DM_ORIGIN_SW: 465 | origin = "software"; 466 | break; 467 | case NET_DM_ORIGIN_HW: 468 | origin = "hardware"; 469 | break; 470 | default: 471 | origin = "unknown"; 472 | break; 473 | } 474 | 475 | printf("origin: %s\n", origin); 476 | } 477 | 478 | void handle_dm_packet_alert_msg(struct netlink_message *msg, int err) 479 | { 480 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 481 | 482 | if (state != STATE_RECEIVING) 483 | goto out_free; 484 | 485 | err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); 486 | if (err) 487 | goto out_free; 488 | 489 | if (attrs[NET_DM_ATTR_PC] && attrs[NET_DM_ATTR_SYMBOL]) 490 | printf("drop at: %s (0x%" PRIx64 ")\n", 491 | nla_get_string(attrs[NET_DM_ATTR_SYMBOL]), 492 | nla_get_u64(attrs[NET_DM_ATTR_PC])); 493 | else if (attrs[NET_DM_ATTR_HW_TRAP_GROUP_NAME] && 494 | attrs[NET_DM_ATTR_HW_TRAP_NAME]) 495 | printf("drop at: %s (%s)\n", 496 | nla_get_string(attrs[NET_DM_ATTR_HW_TRAP_NAME]), 497 | nla_get_string(attrs[NET_DM_ATTR_HW_TRAP_GROUP_NAME])); 498 | 499 | if (attrs[NET_DM_ATTR_ORIGIN]) 500 | print_packet_origin(attrs[NET_DM_ATTR_ORIGIN]); 501 | 502 | if (attrs[NET_DM_ATTR_IN_PORT]) 503 | print_nested_port(attrs[NET_DM_ATTR_IN_PORT], "input"); 504 | 505 | if (attrs[NET_DM_ATTR_FLOW_ACTION_COOKIE]) { 506 | unsigned char *cookie = nla_data(attrs[NET_DM_ATTR_FLOW_ACTION_COOKIE]); 507 | int cookie_len = nla_len(attrs[NET_DM_ATTR_FLOW_ACTION_COOKIE]); 508 | int i; 509 | 510 | printf("cookie: "); 511 | for (i = 0; i < cookie_len; i++) 512 | printf("%02x", cookie[i]); 513 | printf("\n"); 514 | } 515 | 516 | if (attrs[NET_DM_ATTR_TIMESTAMP]) { 517 | time_t tv_sec; 518 | struct tm *tm; 519 | uint64_t ts; 520 | char *tstr; 521 | 522 | ts = nla_get_u64(attrs[NET_DM_ATTR_TIMESTAMP]); 523 | tv_sec = ts / 1000000000; 524 | tm = localtime(&tv_sec); 525 | 526 | tstr = asctime(tm); 527 | tstr[strlen(tstr) - 1] = 0; 528 | printf("timestamp: %s %09" PRId64 " nsec\n", tstr, ts % 1000000000); 529 | } 530 | 531 | if (attrs[NET_DM_ATTR_PROTO]) 532 | printf("protocol: 0x%x\n", 533 | nla_get_u16(attrs[NET_DM_ATTR_PROTO])); 534 | 535 | if (attrs[NET_DM_ATTR_PAYLOAD]) 536 | printf("length: %u\n", nla_len(attrs[NET_DM_ATTR_PAYLOAD])); 537 | 538 | if (attrs[NET_DM_ATTR_ORIG_LEN]) 539 | printf("original length: %u\n", 540 | nla_get_u32(attrs[NET_DM_ATTR_ORIG_LEN])); 541 | 542 | if (attrs[NET_DM_ATTR_REASON]) 543 | printf("drop reason: %s\n", 544 | nla_get_string(attrs[NET_DM_ATTR_REASON])); 545 | 546 | printf("\n"); 547 | 548 | acount++; 549 | if (alimit && (acount == alimit)) { 550 | printf("Alert limit reached, deactivating!\n"); 551 | state = STATE_RQST_DEACTIVATE; 552 | } 553 | 554 | out_free: 555 | free_netlink_msg(msg); 556 | } 557 | 558 | void handle_dm_config_new_msg(struct netlink_message *msg, int err) 559 | { 560 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 561 | 562 | if (state != STATE_CONFIG_GETTING) 563 | goto out_free; 564 | 565 | err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); 566 | if (err) 567 | goto out_free; 568 | 569 | if (!attrs[NET_DM_ATTR_ALERT_MODE] || !attrs[NET_DM_ATTR_TRUNC_LEN] || 570 | !attrs[NET_DM_ATTR_QUEUE_LEN]) 571 | goto out_free; 572 | 573 | printf("Alert mode: "); 574 | switch (nla_get_u8(attrs[NET_DM_ATTR_ALERT_MODE])) { 575 | case NET_DM_ALERT_MODE_SUMMARY: 576 | printf("Summary\n"); 577 | break; 578 | case NET_DM_ALERT_MODE_PACKET: 579 | printf("Packet\n"); 580 | break; 581 | default: 582 | printf("Invalid alert mode\n"); 583 | break; 584 | } 585 | 586 | printf("Truncation length: %u\n", 587 | nla_get_u32(attrs[NET_DM_ATTR_TRUNC_LEN])); 588 | 589 | printf("Queue length: %u\n", nla_get_u32(attrs[NET_DM_ATTR_QUEUE_LEN])); 590 | 591 | out_free: 592 | state = STATE_IDLE; 593 | free_netlink_msg(msg); 594 | } 595 | 596 | void print_nested_stats(struct nlattr *attr) 597 | { 598 | struct nlattr *attrs[NET_DM_ATTR_STATS_MAX + 1]; 599 | int err; 600 | 601 | err = nla_parse_nested(attrs, NET_DM_ATTR_STATS_MAX, attr, 602 | net_dm_stats_policy); 603 | if (err) 604 | return; 605 | 606 | if (attrs[NET_DM_ATTR_STATS_DROPPED]) 607 | printf("Tail dropped: %" PRIu64 "\n", 608 | nla_get_u64(attrs[NET_DM_ATTR_STATS_DROPPED])); 609 | } 610 | 611 | void handle_dm_stats_new_msg(struct netlink_message *msg, int err) 612 | { 613 | struct nlattr *attrs[NET_DM_ATTR_MAX + 1]; 614 | 615 | if (state != STATE_STATS_GETTING) 616 | goto out_free; 617 | 618 | err = genlmsg_parse(msg->msg, 0, attrs, NET_DM_ATTR_MAX, net_dm_policy); 619 | if (err) 620 | goto out_free; 621 | 622 | if (attrs[NET_DM_ATTR_STATS]) { 623 | printf("Software statistics:\n"); 624 | print_nested_stats(attrs[NET_DM_ATTR_STATS]); 625 | } 626 | 627 | if (attrs[NET_DM_ATTR_HW_STATS]) { 628 | printf("Hardware statistics:\n"); 629 | print_nested_stats(attrs[NET_DM_ATTR_HW_STATS]); 630 | } 631 | 632 | out_free: 633 | state = STATE_IDLE; 634 | free_netlink_msg(msg); 635 | } 636 | 637 | void handle_dm_config_msg(struct netlink_message *amsg, struct netlink_message *msg, int err) 638 | { 639 | if (err != 0) { 640 | char *erm = strerror(-err); 641 | 642 | printf("Failed config request, error: %s\n", erm); 643 | state = STATE_FAILED; 644 | return; 645 | } 646 | 647 | switch (state) { 648 | case STATE_ALERT_MODE_SETTING: 649 | printf("Alert mode successfully set\n"); 650 | state = STATE_IDLE; 651 | break; 652 | case STATE_TRUNC_LEN_SETTING: 653 | printf("Truncation length successfully set\n"); 654 | state = STATE_IDLE; 655 | break; 656 | case STATE_QUEUE_LEN_SETTING: 657 | printf("Queue length successfully set\n"); 658 | state = STATE_IDLE; 659 | break; 660 | default: 661 | printf("Received acknowledgement for non-solicited config request\n"); 662 | state = STATE_FAILED; 663 | } 664 | } 665 | 666 | void handle_dm_start_msg(struct netlink_message *amsg, struct netlink_message *msg, int err) 667 | { 668 | if (err != 0) { 669 | char *erm = strerror(err*-1); 670 | printf("Failed activation request, error: %s\n", erm); 671 | state = STATE_FAILED; 672 | goto out; 673 | } 674 | 675 | if (state == STATE_ACTIVATING) { 676 | struct sigaction act; 677 | memset(&act, 0, sizeof(struct sigaction)); 678 | act.sa_handler = sigint_handler; 679 | act.sa_flags = SA_RESETHAND; 680 | 681 | printf("Kernel monitoring activated.\n"); 682 | printf("Issue Ctrl-C to stop monitoring\n"); 683 | sigaction(SIGINT, &act, NULL); 684 | 685 | state = STATE_RECEIVING; 686 | } else { 687 | printf("Odd, the kernel told us that it activated and we didn't ask\n"); 688 | state = STATE_FAILED; 689 | } 690 | out: 691 | return; 692 | } 693 | 694 | void handle_dm_stop_msg(struct netlink_message *amsg, struct netlink_message *msg, int err) 695 | { 696 | char *erm; 697 | 698 | if ((err == 0) || (err == -EAGAIN)) { 699 | printf("Got a stop message\n"); 700 | state = STATE_IDLE; 701 | } else { 702 | erm = strerror(err*-1); 703 | printf("Stop request failed, error: %s\n", erm); 704 | } 705 | } 706 | 707 | int enable_drop_monitor() 708 | { 709 | struct netlink_message *msg; 710 | 711 | msg = alloc_netlink_msg(NET_DM_CMD_START, NLM_F_REQUEST|NLM_F_ACK, 0); 712 | 713 | if (monitor_sw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_SW_DROPS)) 714 | goto nla_put_failure; 715 | 716 | if (monitor_hw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_HW_DROPS)) 717 | goto nla_put_failure; 718 | 719 | set_ack_cb(msg, handle_dm_start_msg); 720 | 721 | return send_netlink_message(msg); 722 | 723 | nla_put_failure: 724 | free_netlink_msg(msg); 725 | return -EMSGSIZE; 726 | } 727 | 728 | int disable_drop_monitor() 729 | { 730 | struct netlink_message *msg; 731 | 732 | msg = alloc_netlink_msg(NET_DM_CMD_STOP, NLM_F_REQUEST|NLM_F_ACK, 0); 733 | 734 | if (monitor_sw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_SW_DROPS)) 735 | goto nla_put_failure; 736 | 737 | if (monitor_hw && nla_put_flag(msg->nlbuf, NET_DM_ATTR_HW_DROPS)) 738 | goto nla_put_failure; 739 | 740 | set_ack_cb(msg, handle_dm_stop_msg); 741 | 742 | return send_netlink_message(msg); 743 | 744 | nla_put_failure: 745 | free_netlink_msg(msg); 746 | return -EMSGSIZE; 747 | } 748 | 749 | int set_alert_mode() 750 | { 751 | enum net_dm_alert_mode alert_mode; 752 | struct netlink_message *msg; 753 | 754 | switch (state) { 755 | case STATE_RQST_ALERT_MODE_SUMMARY: 756 | alert_mode = NET_DM_ALERT_MODE_SUMMARY; 757 | break; 758 | case STATE_RQST_ALERT_MODE_PACKET: 759 | alert_mode = NET_DM_ALERT_MODE_PACKET; 760 | break; 761 | default: 762 | return -EINVAL; 763 | } 764 | 765 | msg = alloc_netlink_msg(NET_DM_CMD_CONFIG, NLM_F_REQUEST|NLM_F_ACK, 0); 766 | if (!msg) 767 | return -ENOMEM; 768 | 769 | if (nla_put_u8(msg->nlbuf, NET_DM_ATTR_ALERT_MODE, alert_mode)) 770 | goto nla_put_failure; 771 | 772 | set_ack_cb(msg, handle_dm_config_msg); 773 | 774 | return send_netlink_message(msg); 775 | 776 | nla_put_failure: 777 | free_netlink_msg(msg); 778 | return -EMSGSIZE; 779 | } 780 | 781 | int set_trunc_len() 782 | { 783 | struct netlink_message *msg; 784 | 785 | msg = alloc_netlink_msg(NET_DM_CMD_CONFIG, NLM_F_REQUEST|NLM_F_ACK, 0); 786 | if (!msg) 787 | return -ENOMEM; 788 | 789 | if (nla_put_u32(msg->nlbuf, NET_DM_ATTR_TRUNC_LEN, trunc_len)) 790 | goto nla_put_failure; 791 | 792 | set_ack_cb(msg, handle_dm_config_msg); 793 | 794 | return send_netlink_message(msg); 795 | 796 | nla_put_failure: 797 | free_netlink_msg(msg); 798 | return -EMSGSIZE; 799 | } 800 | 801 | int set_queue_len() 802 | { 803 | struct netlink_message *msg; 804 | 805 | msg = alloc_netlink_msg(NET_DM_CMD_CONFIG, NLM_F_REQUEST|NLM_F_ACK, 0); 806 | if (!msg) 807 | return -ENOMEM; 808 | 809 | if (nla_put_u32(msg->nlbuf, NET_DM_ATTR_QUEUE_LEN, queue_len)) 810 | goto nla_put_failure; 811 | 812 | set_ack_cb(msg, handle_dm_config_msg); 813 | 814 | return send_netlink_message(msg); 815 | 816 | nla_put_failure: 817 | free_netlink_msg(msg); 818 | return -EMSGSIZE; 819 | } 820 | 821 | int get_config() 822 | { 823 | struct netlink_message *msg; 824 | 825 | msg = alloc_netlink_msg(NET_DM_CMD_CONFIG_GET, NLM_F_REQUEST, 0); 826 | if (!msg) 827 | return -ENOMEM; 828 | 829 | return send_netlink_message(msg); 830 | } 831 | 832 | int get_stats() 833 | { 834 | struct netlink_message *msg; 835 | 836 | msg = alloc_netlink_msg(NET_DM_CMD_STATS_GET, NLM_F_REQUEST, 0); 837 | if (!msg) 838 | return -ENOMEM; 839 | 840 | return send_netlink_message(msg); 841 | } 842 | 843 | void display_help() 844 | { 845 | printf("Command Syntax:\n"); 846 | printf("exit\t\t\t\t - Quit dropwatch\n"); 847 | printf("help\t\t\t\t - Display this message\n"); 848 | printf("set:\n"); 849 | printf("\talertlimit \t - capture only this many alert packets\n"); 850 | printf("\talertmode \t - set mode to \"summary\" or \"packet\"\n"); 851 | printf("\ttrunc \t\t - truncate packets to this length. "); 852 | printf("Only applicable when \"alertmode\" is set to \"packet\"\n"); 853 | printf("\tqueue \t\t - queue up to this many packets in the kernel. "); 854 | printf("Only applicable when \"alertmode\" is set to \"packet\"\n"); 855 | printf("\tsw \t - monitor software drops\n"); 856 | printf("\thw \t - monitor hardware drops\n"); 857 | printf("start\t\t\t\t - start capture\n"); 858 | printf("stop\t\t\t\t - stop capture\n"); 859 | printf("show\t\t\t\t - show existing configuration\n"); 860 | printf("stats\t\t\t\t - show statistics\n"); 861 | } 862 | 863 | void enter_command_line_mode() 864 | { 865 | char *input; 866 | int err; 867 | 868 | do { 869 | input = readline("dropwatch> "); 870 | 871 | if (input == NULL) { 872 | /* Someone closed stdin on us */ 873 | printf("Terminating dropwatch...\n"); 874 | state = STATE_EXIT; 875 | break; 876 | } 877 | 878 | if (!strcmp(input,"start")) { 879 | state = STATE_RQST_ACTIVATE; 880 | break; 881 | } 882 | 883 | if (!strcmp(input, "stop")) { 884 | state = STATE_RQST_DEACTIVATE; 885 | break; 886 | } 887 | 888 | if (!strcmp(input, "exit")) { 889 | state = STATE_EXIT; 890 | break; 891 | } 892 | 893 | if (!strcmp (input, "help")) { 894 | display_help(); 895 | goto next_input; 896 | } 897 | 898 | if (!strncmp(input, "set", 3)) { 899 | char *ninput = input+4; 900 | if (!strncmp(ninput, "alertlimit", 10)) { 901 | alimit = strtoul(ninput+10, NULL, 10); 902 | printf("setting alert capture limit to %lu\n", 903 | alimit); 904 | goto next_input; 905 | } else if (!strncmp(ninput, "alertmode", 9)) { 906 | ninput = ninput + 10; 907 | if (!strncmp(ninput, "summary", 7)) { 908 | state = STATE_RQST_ALERT_MODE_SUMMARY; 909 | break; 910 | } else if (!strncmp(ninput, "packet", 6)) { 911 | state = STATE_RQST_ALERT_MODE_PACKET; 912 | break; 913 | } 914 | } else if (!strncmp(ninput, "trunc", 5)) { 915 | trunc_len = strtoul(ninput + 6, NULL, 10); 916 | state = STATE_RQST_TRUNC_LEN; 917 | break; 918 | } else if (!strncmp(ninput, "queue", 5)) { 919 | queue_len = strtoul(ninput + 6, NULL, 10); 920 | state = STATE_RQST_QUEUE_LEN; 921 | break; 922 | } else if (!strncmp(ninput, "sw", 2)) { 923 | err = strtobool(ninput + 3, &monitor_sw); 924 | if (err) { 925 | printf("invalid boolean value\n"); 926 | state = STATE_FAILED; 927 | break; 928 | } 929 | printf("setting software drops monitoring to %d\n", 930 | monitor_sw); 931 | goto next_input; 932 | } else if (!strncmp(ninput, "hw", 2)) { 933 | err = strtobool(ninput + 3, &monitor_hw); 934 | if (err) { 935 | printf("invalid boolean value\n"); 936 | state = STATE_FAILED; 937 | break; 938 | } 939 | printf("setting hardware drops monitoring to %d\n", 940 | monitor_hw); 941 | goto next_input; 942 | } 943 | } 944 | 945 | if (!strncmp(input, "show", 4)) { 946 | state = STATE_RQST_CONFIG; 947 | break; 948 | } 949 | 950 | if (!strncmp(input, "stats", 5)) { 951 | state = STATE_RQST_STATS; 952 | break; 953 | } 954 | next_input: 955 | free(input); 956 | } while(1); 957 | 958 | free(input); 959 | } 960 | 961 | void enter_state_loop(void) 962 | { 963 | int should_rx = 0; 964 | 965 | while (1) { 966 | switch(state) { 967 | 968 | case STATE_IDLE: 969 | should_rx = 0; 970 | enter_command_line_mode(); 971 | break; 972 | case STATE_RQST_ACTIVATE: 973 | printf("Enabling monitoring...\n"); 974 | if (enable_drop_monitor() < 0) { 975 | perror("Unable to send activation msg:"); 976 | state = STATE_FAILED; 977 | } else { 978 | state = STATE_ACTIVATING; 979 | should_rx = 1; 980 | } 981 | break; 982 | case STATE_ACTIVATING: 983 | printf("Waiting for activation ack....\n"); 984 | break; 985 | case STATE_RECEIVING: 986 | break; 987 | case STATE_RQST_DEACTIVATE: 988 | printf("Deactivation requested, turning off monitoring\n"); 989 | if (disable_drop_monitor() < 0) { 990 | perror("Unable to send deactivation msg:"); 991 | state = STATE_FAILED; 992 | } else 993 | state = STATE_DEACTIVATING; 994 | should_rx = 1; 995 | break; 996 | case STATE_DEACTIVATING: 997 | printf("Waiting for deactivation ack...\n"); 998 | break; 999 | case STATE_EXIT: 1000 | case STATE_FAILED: 1001 | should_rx = 0; 1002 | return; 1003 | case STATE_RQST_ALERT_MODE_SUMMARY: 1004 | case STATE_RQST_ALERT_MODE_PACKET: 1005 | printf("Setting alert mode\n"); 1006 | if (set_alert_mode() < 0) { 1007 | perror("Failed to set alert mode"); 1008 | state = STATE_FAILED; 1009 | } else { 1010 | state = STATE_ALERT_MODE_SETTING; 1011 | should_rx = 1; 1012 | } 1013 | break; 1014 | case STATE_ALERT_MODE_SETTING: 1015 | printf("Waiting for alert mode setting ack...\n"); 1016 | break; 1017 | case STATE_RQST_TRUNC_LEN: 1018 | printf("Setting truncation length to %lu\n", 1019 | trunc_len); 1020 | if (set_trunc_len() < 0) { 1021 | perror("Failed to set truncation length"); 1022 | state = STATE_FAILED; 1023 | } else { 1024 | state = STATE_TRUNC_LEN_SETTING; 1025 | should_rx = 1; 1026 | } 1027 | break; 1028 | case STATE_TRUNC_LEN_SETTING: 1029 | printf("Waiting for truncation length setting ack...\n"); 1030 | break; 1031 | case STATE_RQST_QUEUE_LEN: 1032 | printf("Setting queue length to %lu\n", queue_len); 1033 | if (set_queue_len() < 0) { 1034 | perror("Failed to set queue length"); 1035 | state = STATE_FAILED; 1036 | } else { 1037 | state = STATE_QUEUE_LEN_SETTING; 1038 | should_rx = 1; 1039 | } 1040 | break; 1041 | case STATE_QUEUE_LEN_SETTING: 1042 | printf("Waiting for queue length setting ack...\n"); 1043 | break; 1044 | case STATE_RQST_CONFIG: 1045 | printf("Getting existing configuration\n"); 1046 | if (get_config() < 0) { 1047 | perror("Failed to get existing configuration"); 1048 | state = STATE_FAILED; 1049 | } else { 1050 | state = STATE_CONFIG_GETTING; 1051 | should_rx = 1; 1052 | } 1053 | break; 1054 | case STATE_CONFIG_GETTING: 1055 | printf("Waiting for existing configuration query response\n"); 1056 | break; 1057 | case STATE_RQST_STATS: 1058 | printf("Getting statistics\n"); 1059 | if (get_stats() < 0) { 1060 | perror("Failed to get statistics"); 1061 | state = STATE_FAILED; 1062 | } else { 1063 | state = STATE_STATS_GETTING; 1064 | should_rx = 1; 1065 | } 1066 | break; 1067 | case STATE_STATS_GETTING: 1068 | printf("Waiting for statistics query response\n"); 1069 | break; 1070 | default: 1071 | printf("Unknown state received! exiting!\n"); 1072 | state = STATE_FAILED; 1073 | should_rx = 0; 1074 | break; 1075 | } 1076 | 1077 | /* 1078 | * After we process our state loop, look to see if we have messages 1079 | */ 1080 | if (should_rx) 1081 | process_rx_message(); 1082 | } 1083 | } 1084 | 1085 | struct option options[] = { 1086 | {"lmethod", 1, 0, 'l'}, 1087 | {0, 0, 0, 0} 1088 | }; 1089 | 1090 | void usage() 1091 | { 1092 | printf("dropwatch [-l|--lmethod ]\n"); 1093 | } 1094 | 1095 | int main (int argc, char **argv) 1096 | { 1097 | int c, optind; 1098 | lookup_init_method_t meth = METHOD_NULL; 1099 | /* 1100 | * parse the options 1101 | */ 1102 | for(;;) { 1103 | c = getopt_long(argc, argv, "l:", options, &optind); 1104 | 1105 | /* are we done parsing ? */ 1106 | if (c == -1) 1107 | break; 1108 | 1109 | switch(c) { 1110 | 1111 | case '?': 1112 | usage(); 1113 | exit(1); 1114 | /* NOTREACHED */ 1115 | case 'l': 1116 | /* select the lookup method we want to use */ 1117 | if (!strncmp(optarg, "list", 4)) { 1118 | printf("Available lookup methods:\n"); 1119 | printf("kas - use /proc/kallsyms\n"); 1120 | exit(0); 1121 | } else if (!strncmp(optarg, "kas", 3)) { 1122 | meth = METHOD_KALLSYMS; 1123 | } else { 1124 | printf("Unknown lookup method %s\n", optarg); 1125 | exit(1); 1126 | } 1127 | break; 1128 | default: 1129 | printf("Unknown option\n"); 1130 | usage(); 1131 | exit(1); 1132 | /* NOTREACHED */ 1133 | } 1134 | } 1135 | 1136 | /* 1137 | * open up the netlink socket that we need to talk to our dropwatch socket 1138 | */ 1139 | nsd = setup_netlink_socket(); 1140 | 1141 | if (nsd == NULL) { 1142 | printf("Cleaning up on socket creation error\n"); 1143 | goto out; 1144 | } 1145 | 1146 | 1147 | /* 1148 | * Initialize our lookup library 1149 | */ 1150 | init_lookup(meth); 1151 | 1152 | enter_state_loop(); 1153 | printf("Shutting down ...\n"); 1154 | 1155 | nl_close(nsd); 1156 | exit(0); 1157 | out: 1158 | exit(1); 1159 | } 1160 | -------------------------------------------------------------------------------- /src/net_dropmon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-2.0-or-later 3 | */ 4 | #ifndef __NET_DROPMON_H 5 | #define __NET_DROPMON_H 6 | 7 | #include 8 | 9 | #ifndef SOL_NETLINK 10 | #define SOL_NETLINK 270 11 | #endif 12 | 13 | struct net_dm_drop_point { 14 | __u8 pc[8]; 15 | __u32 count; 16 | }; 17 | 18 | #define NET_DM_CFG_VERSION 0 19 | #define NET_DM_CFG_ALERT_COUNT 1 20 | #define NET_DM_CFG_ALERT_DELAY 2 21 | #define NET_DM_CFG_MAX 3 22 | 23 | struct net_dm_config_entry { 24 | __u32 type; 25 | __u64 data __attribute__((aligned(8))); 26 | }; 27 | 28 | struct net_dm_config_msg { 29 | __u32 entries; 30 | struct net_dm_config_entry options[0]; 31 | }; 32 | 33 | struct net_dm_alert_msg { 34 | __u32 entries; 35 | struct net_dm_drop_point points[0]; 36 | }; 37 | 38 | struct net_dm_user_msg { 39 | union { 40 | struct net_dm_config_msg user; 41 | struct net_dm_alert_msg alert; 42 | } u; 43 | }; 44 | 45 | 46 | /* These are the netlink message types for this protocol */ 47 | 48 | enum { 49 | NET_DM_CMD_UNSPEC = 0, 50 | NET_DM_CMD_ALERT, 51 | NET_DM_CMD_CONFIG, 52 | NET_DM_CMD_START, 53 | NET_DM_CMD_STOP, 54 | NET_DM_CMD_PACKET_ALERT, 55 | NET_DM_CMD_CONFIG_GET, 56 | NET_DM_CMD_CONFIG_NEW, 57 | NET_DM_CMD_STATS_GET, 58 | NET_DM_CMD_STATS_NEW, 59 | _NET_DM_CMD_MAX, 60 | }; 61 | 62 | #define NET_DM_CMD_MAX (_NET_DM_CMD_MAX - 1) 63 | 64 | /* 65 | * Our group identifiers 66 | */ 67 | #define NET_DM_GRP_ALERT 1 68 | 69 | enum net_dm_attr { 70 | NET_DM_ATTR_UNSPEC, 71 | 72 | NET_DM_ATTR_ALERT_MODE, /* u8 */ 73 | NET_DM_ATTR_PC, /* u64 */ 74 | NET_DM_ATTR_SYMBOL, /* string */ 75 | NET_DM_ATTR_IN_PORT, /* nested */ 76 | NET_DM_ATTR_TIMESTAMP, /* u64 */ 77 | NET_DM_ATTR_PROTO, /* u16 */ 78 | NET_DM_ATTR_PAYLOAD, /* binary */ 79 | NET_DM_ATTR_PAD, 80 | NET_DM_ATTR_TRUNC_LEN, /* u32 */ 81 | NET_DM_ATTR_ORIG_LEN, /* u32 */ 82 | NET_DM_ATTR_QUEUE_LEN, /* u32 */ 83 | NET_DM_ATTR_STATS, /* nested */ 84 | NET_DM_ATTR_HW_STATS, /* nested */ 85 | NET_DM_ATTR_ORIGIN, /* u16 */ 86 | NET_DM_ATTR_HW_TRAP_GROUP_NAME, /* string */ 87 | NET_DM_ATTR_HW_TRAP_NAME, /* string */ 88 | NET_DM_ATTR_HW_ENTRIES, /* nested */ 89 | NET_DM_ATTR_HW_ENTRY, /* nested */ 90 | NET_DM_ATTR_HW_TRAP_COUNT, /* u32 */ 91 | NET_DM_ATTR_SW_DROPS, /* flag */ 92 | NET_DM_ATTR_HW_DROPS, /* flag */ 93 | NET_DM_ATTR_FLOW_ACTION_COOKIE, /* binary */ 94 | NET_DM_ATTR_REASON, /* string */ 95 | 96 | __NET_DM_ATTR_MAX, 97 | NET_DM_ATTR_MAX = __NET_DM_ATTR_MAX - 1 98 | }; 99 | 100 | /** 101 | * enum net_dm_alert_mode - Alert mode. 102 | * @NET_DM_ALERT_MODE_SUMMARY: A summary of recent drops is sent to user space. 103 | * @NET_DM_ALERT_MODE_PACKET: Each dropped packet is sent to user space along 104 | * with metadata. 105 | */ 106 | enum net_dm_alert_mode { 107 | NET_DM_ALERT_MODE_SUMMARY, 108 | NET_DM_ALERT_MODE_PACKET, 109 | }; 110 | 111 | enum { 112 | NET_DM_ATTR_PORT_NETDEV_IFINDEX, /* u32 */ 113 | NET_DM_ATTR_PORT_NETDEV_NAME, /* string */ 114 | 115 | __NET_DM_ATTR_PORT_MAX, 116 | NET_DM_ATTR_PORT_MAX = __NET_DM_ATTR_PORT_MAX - 1 117 | }; 118 | 119 | enum { 120 | NET_DM_ATTR_STATS_DROPPED, /* u64 */ 121 | 122 | __NET_DM_ATTR_STATS_MAX, 123 | NET_DM_ATTR_STATS_MAX = __NET_DM_ATTR_STATS_MAX - 1 124 | }; 125 | 126 | enum net_dm_origin { 127 | NET_DM_ORIGIN_SW, 128 | NET_DM_ORIGIN_HW, 129 | }; 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /tests/Makefile.am: -------------------------------------------------------------------------------- 1 | check_SCRIPTS = rundropwatch.sh 2 | TESTS = rundropwatch.sh 3 | -------------------------------------------------------------------------------- /tests/rundropwatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | abort_dropwatch() { 4 | sleep 5 5 | killall -SIGINT dropwatch 6 | } 7 | 8 | abort_dropwatch & 9 | echo -e "set alertlimit 1\nstart\nstop\nexit" | ../src/dropwatch -l kas 10 | 11 | if [ $? -ne 0 ] 12 | then 13 | grep -q NET_DM ./rundropwatch.sh.log 14 | if [ $? -eq 0 ] 15 | then 16 | # This platform doesn't support NET_DM, skip this test 17 | exit 77 18 | fi 19 | fi 20 | --------------------------------------------------------------------------------