├── .github └── workflows │ ├── build.yml │ └── lint.yml ├── .mdl_style.rb ├── .mdlrc ├── BUGS ├── CONTRIB ├── Changelog ├── LICENSE ├── Makefile ├── README.md ├── RELEASE_PROCESS.md ├── SCREENSHOTS.md ├── WISHLIST ├── iptstate.8 ├── iptstate.cc ├── iptstate.spec ├── memleak.md ├── memuse.out └── screenshots ├── iptstate-2.0-filter-ss.png ├── iptstate-2.0-help-ss.png ├── iptstate-2.0-lookup-ss.png ├── iptstate-ss-filter.png ├── iptstate-ss-old.png ├── iptstate-ss-resolve.png └── iptstate-ss.png /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | fail-fast: false 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | - name: Install dependencies 18 | run: sudo apt install g++ make libnetfilter-conntrack-dev -y 19 | - name: Build 20 | run: make 21 | - name: Run 22 | run: ./iptstate --version 23 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | markdownlint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: checkout 14 | uses: actions/checkout@v2 15 | - name: MarkdownLint mdl Action 16 | uses: actionshub/markdownlint@1.2.0 17 | -------------------------------------------------------------------------------- /.mdl_style.rb: -------------------------------------------------------------------------------- 1 | all 2 | exclude_rule 'MD026' 3 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | style '.mdl_style.rb' 2 | -------------------------------------------------------------------------------- /BUGS: -------------------------------------------------------------------------------- 1 | IPTState Bugs 2 | 3 | KNOWN BUGS 4 | We keep track of bugs in our bugtracker on our Sourceforge page. However, before 5 | filing bugs, be aware of bugs in these other pieces of software that may affect 6 | your iptstate experience: 7 | 8 | - libnetfilter_conntrack 0.0.50 has a bug that prevents iptstate from 9 | deleting ICMP states. I wrote the following patch which the netfilter folks 10 | have already applied to their SVN tree: 11 | http://www.phildev.net/linux/patches/libnetfilter_conntrack-0.0.50_icmp_id.patch 12 | You can use it if this affects you. 13 | 14 | - There seems to be a small memory leak somewhere in ncurses. See 15 | http://www.phildev.net/iptstate/memleak.html 16 | for details. This won't effect most users much, but you want to be weary of 17 | leaving iptstate running on very busy firewalls for very long periods of time 18 | (a day or more). This bug has been reported to ncurses, see above URL. 19 | 20 | ABOUT BUGS 21 | If you find a bug in IPTState you should file a bug at our bugtracker: 22 | https://sourceforge.net/tracker/?group_id=52748&atid=467897 23 | OR mail our -devel list: 24 | https://lists.sourceforge.net/lists/listinfo/iptstate-devel 25 | 26 | If you are unsure if it's a bug or need support, please use the -devel mailing 27 | list. 28 | 29 | Sending a bug to your distro or some public forum is not going to let me know. 30 | I can't fix things I don't know about. So make sure I know! 31 | 32 | ABOUT MY RESPONSE 33 | If your bug is serious - i.e. compilation errors, a major a functionality is 34 | broken, or a security problem I'll usually give you pretty immediate attention. 35 | If it's a very minor bug, or a feature request, I'll respond as I have time. 36 | 37 | ABOUT PATCHES 38 | Patches are welcome but not necessary. I will fix bugs, don't worry. If you 39 | would like to contribute code, PLEASE use consistent style to the rest of the 40 | code. If you send a patch, it should be accompanied with a good explanation of 41 | what it does and why. If it fixes a bug, it should also be accompanied by a 42 | bug report in our bug tracker. 43 | 44 | ABOUT BUG REPORTS 45 | Please be sure to include these things in bug reports: 46 | - your iptstate version 47 | - your distribution and distribution version 48 | - your kernel version 49 | - your g++ version (if you built it yourself) 50 | - your make version (if you built it yourself) 51 | - your glibc version (if you built it yourself) 52 | - your ncurses version 53 | - your libnetfilter_conntrack version 54 | - any relevant output and/or errors 55 | 56 | Once again, distributions don't always forward bug reports upstream, so 57 | please send bug reports to me if you want bugs fixed. 58 | 59 | Thanks! 60 | 61 | Phil Dibowitz 62 | phil AT ipom DOT com 63 | -------------------------------------------------------------------------------- /CONTRIB: -------------------------------------------------------------------------------- 1 | IPTSTATE CONTRIBUTIONS 2 | 3 | IPTState is written and maintained by Phil Dibowitz 4 | I originally wrote the software not planning on releasing it - I wanted 5 | a feature similar to IP Filter's StateTop when I was using Linux. I 6 | decided to release it. It became much bigger than I expected in just a 7 | month. 8 | 9 | IPTState wouldn't be where it is today without many other people. This is 10 | an attempt to credit and thank them. 11 | 12 | IDEA 13 | Darren Reed wrote IP Filter. 14 | Frank Volf wrote the Statetop code for IP Filter. 15 | 16 | PACKAGERS 17 | I'm very grateful to those who package my software. I've tried to list 18 | maintainers here, but if you're not here, please, let me know and I'll 19 | add you. 20 | 21 | If you package my software _please let me know_, and even more importantly, 22 | _please_ pass on bugs to me! 23 | 24 | Mandriva: Garrick Staples 25 | Debian: Chris Taylor (was: Brian Nelson) 26 | Gentoo: (was: Eldad Zack) 27 | ALT Linux: Victor Forsyuk 28 | floppyfw: Cristian Ionescu-Idbohrn 29 | pkgsrc: Roland Illig 30 | Devil Linux: (was: Bruce Smith) 31 | Fedora/RedHat: Thomas Woerner 32 | OpenEmbedded: Jamie Lenehen 33 | ArchLinux (community repo): Andrea Zucchelli 34 | ipcop: Franck Bourdonnec 35 | Slackware (slackers.it repo): Corrado Franco 36 | 37 | OTHER 38 | Some people who helped me get this project started were Jullian Gomez, 39 | my fellow USCLUG'ers (Ted Faber in particular), and my friend Sanjay. 40 | 41 | BY VERSION 42 | Other contributions follow based on version. If you contributed something 43 | and you are not listed here, let me know, it's easy to forget to list 44 | people. Also note that if you're in the Changelog, you may not be here: 45 | 46 | 2.1 47 | - Thanks to Brian Nelson for find the -sSdD bug quickly before everyone 48 | had packaged 2.0! 49 | 50 | 2.0 51 | - Thanks to Ted Faber for his extensive coding knowledge 52 | - Thanks to Garrick Stapples for his help figuring out ncurses pads and scrolling 53 | - Thanks Bill Hudacek for the formatting bug report. 54 | 55 | 1.4 56 | - 57 | 58 | 1.3 59 | - Steve Augart finally proved the memory leak was in ncurses. Just in 60 | the nick of time before Debian kicked ipstate out. Extra special mega 61 | thanks to you Steve! Others who helped were Julian Seward, Todd Lyons, 62 | and many suggestions from various folks on the UUASC and USCLUG mailing 63 | lists. 64 | - Todd Lyons pointed out snprintf's in printline function should allow 65 | an extra char so as not to cut off newlines. 66 | 67 | 1.2.1 68 | - Thanks to Martin Geisler for pointing out the typo in the man page 69 | 70 | 1.2.0 71 | - Thanks to Garrick for all your help troubleshooting and beta testing. 72 | 73 | 1.1.0 74 | - Beat Bolli sent in a large patch that was a reference to finish up much 75 | of the work I was doing for this version such as fixing TTL sort, and 76 | various simplifications. His patch also reminded me to do the -R option, 77 | notified me of the 'make uninstall' bug, and gave a few cleanups. 78 | 79 | - Blars Blarson sent a patch to the Debian developers noting that I was 80 | only reading in 50 connections. He also sent in a patch to print a 81 | newline upon exiting state-top mode so the prompt isn't mixed in left- 82 | over output. 83 | 84 | - Many others made patches that did some of the above things in countless 85 | ways... 86 | 87 | 1.0.1 88 | - Thanks to Garrick Staples for the .spec file 89 | 90 | Phil Dibowitz 91 | phil AT ipom DOT com 92 | 93 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | 2.2.7 2 | - Released 10/16/21 3 | - Fix compiler warnings 4 | 5 | 2.2.6 6 | - Released 08/14/16 7 | - Fix `-b` option which didn't work in many cases 8 | - When we turn `lookup` mode on, automatically turn `skipdns` mode on 9 | - Move to dynamic memory for state entries. Fixes #3 10 | 11 | 2.2.5 12 | - Released 06/01/12 13 | - Full support for ICMP6 including code/type display and state deletion 14 | - Dynamically size "State" column" 15 | - If we can't resolve a protocol to a name, print the number instead of 16 | "UNKNOWN!" 17 | - Don't leave a space for ":" if there's no port 18 | 19 | 2.2.4 20 | - Released 06/01/12 21 | - Improved IPv6 support - truncate addresses if they don't fit and generally 22 | treat them like hostnames at display time 23 | - CONTRIB and man page fixes (Chris Taylor ) 24 | 25 | 2.2.3 26 | - Released 04/03/11 27 | - IPv6 support. Closes #2848930. 28 | - Handle filters as in6_addr and uints instead of strings 29 | - Fix loopback filtering support 30 | - Fix formatting for ICMP states. Closes #2969917. 31 | - Total style overhaul: move away from tabs, use 2 spaces, various other style 32 | cleanups 33 | - Documentation updates 34 | 35 | 2.2.2 36 | - Released 09/19/09 37 | - Fix includes 38 | - Add --version (closes bug 2792918) 39 | - Some minor code abstractions 40 | - Remove old /proc based code 41 | - Dropped "Proto" field minimum width to 3 chars (changing title to "Prt") 42 | to make more room for counters 43 | - If we can't fit counters and there's nothing we can truncate, show a 44 | warning and then disable counters rather than messing up the display 45 | - Some style cleanups 46 | 47 | 2.2.1 48 | - Released 03/18/07 49 | - Fix formatting bug (maxes not being cleared on each round) 50 | 51 | 2.2.0 52 | - Released 03/18/07 53 | - Added some logic to handle state tables larger than 32767 entries which 54 | breaks ncurses if you try to make a pad that large. 55 | - Cleanup the time.h includes 56 | - Port to new libnetfilter_conntrack library 57 | - Add support for byte/packet counters ('C' key) 58 | - Add support for deleting states ('x' key) 59 | - Move navigation help to top of interactive help so people can learn how 60 | to navigate without having to navigate to the bottom of the help 61 | - When --lookup is enabled, resolve port names as well ast hostnames 62 | (reported by Viliam Holub ) 63 | - Display the ICMP ID on ICMP states 64 | - Fix scrolling bug if totals or filters were enabled 65 | - General improvement of all scrolling calculations 66 | - Add 'B' as a way to sort by previous columbn (opposite of 'b') 67 | - Add ^d for pagedown and ^u for pageup 68 | 69 | 2.1 70 | - Released 10/05/06 71 | - Fixed bug where -s was doing what -S should do and -d was doing 72 | what -D should do. Thanks to Brian Nelson for catching this. 73 | - Add comments on the 3 functions that didn't have them in 2.0 74 | 75 | 2.0 76 | - Released 10/04/06 77 | - Moved man page to section 8 78 | - Significantly re-factored code 79 | - Fix long-protocol-names-break-formatting bug 80 | (reported by Bill Hudacek ) 81 | - Move all flag bools into a new flags_t struct 82 | - Move format-decisions to end 83 | - Move all counters into a new counters_t struct 84 | - Make the stable vector dynamic instead of a huge pre-allocation 85 | - Move many variables to #defines 86 | - Fix bug in "totals" line (numbers didn't always add up) 87 | - Add display of skipped entries on "totals" line 88 | - Move various char*'s to strings. 89 | - Move most snprintf()s to stringstreams. 90 | - Rewrite and significantly improve dynamic sizing of columns 91 | - Add a new interactive help window 92 | - Add srcpt and dstpt filtering 93 | - Add long options 94 | - Make interactive help scroll-able 95 | - Make main window scroll-able 96 | - Make having the main window be scrollable configurable and if not scrollable 97 | then use stdscr instead of a pad. Make this togglable interactively. 98 | - Redo command-line options so they match interactive options 99 | - Add ability to change all filters and the refresh rate interactively 100 | - Handle window resizes (SIGWINCH) properly 101 | - If we can't read ip_conntrack, error and exit rather than fail 102 | silently 103 | - Cleanup nicely if we get killed (SIGINT or SIGTERM) 104 | - Add color-coding of protocols 105 | 106 | 1.4 107 | - Released 04/16/05 108 | - Added display of filters 109 | - Added a "strip" target to the Makefile 110 | - Changed ip/port seperator to a colon instead of comma 111 | - Some string concat and Makefile cleanups from Roland Illig 112 | - Added new features to man page 113 | - Added filtering for source and destination addresses 114 | - Added filtering of DNS states option 115 | - Added tagging of truncated hostnames 116 | - Brought man page up-to-date 117 | - Got rid of deprecated warnings 118 | - Removed libgpm req from spec file. 119 | 120 | 1.3 121 | - Released 05/27/03 122 | - Steve Augart finally proved the 'memory leak' was in 123 | ncurses as I'd always suspected but was unable to prove 124 | Thanks Steve! 125 | - Increased snprintf boundaries in printline function to 126 | ensure newlines don't get cut off (Thanks to Todd Lyons) 127 | - Added dynamic sizing of iptstate based on term info. 128 | - Updated Makefile to only recompile if needed 129 | - Fixed gethostbyaddr() call to compile on more systems 130 | - Fixed truncation bug that ocassionally truncated one char 131 | too few 132 | - Added NOTES section to man page, plus other docs on new 133 | features 134 | - Fixed some man page bugs 135 | 136 | 1.2.1 137 | - Released 07/01/02 138 | - Fixes for GCC3 139 | - cast 'x' in 'log' so GCC knows which log I mean 140 | - add -Wno-deprecated to Makefile 141 | - Fix small bug in manpage that made -R not show up 142 | - Fix crash if protocol is not found in /etc/protocols 143 | - Update 'uninstall' in Makefile 144 | 145 | 1.2.0 146 | - Released 04/20/02 147 | - Various doc updates 148 | - Lots of code cleanups 149 | - Added documentation for interactive-mode options 150 | - Added interactive-mode toggles for -f -t -l 151 | - Added option to display totals 152 | - Added filtering of loopback 153 | - Added sorting by hostname 154 | - Added DNS hostname lookups 155 | - Improved SrcIP and DstIP sorting 156 | - Added sorting by port 157 | 158 | 1.1.0 159 | - Released 03/30/02 160 | - Will now read in all connections instead of just 50 161 | For single-line use, it will display them all as well. 162 | - Added command line flag for reverse sorting 163 | - Cleaned up reading of options 164 | - Fixed sorting of TTL/ cleaned up sorting code 165 | - Fixed uninstall in Makfile 166 | 167 | 1.0.1 168 | - Released 02/27/02 169 | - Added spec file so people can build RPMs if the like 170 | - Fix 'timeval' compile error for certain platforms. 171 | Bizzare bug, I'm guessing in GCC. On some platforms 172 | it wants to use sys/time.h. It will fail if you use 173 | sys/select.h or time.h. Go figure. 174 | - Take out src port and dst port for non tcp,udp cases 175 | - Give 'rate' an initial value 176 | - Fixed Big Endian Problem with command line arguments 177 | - Fixed Makefile (put LIBS at end) 178 | - Change "proto" field to look up by protocol number field of 179 | ip_conntrack instead of take it from the name field. 180 | This should now support pretty much any protocol. I 181 | was unaware that ip_conntrack printed 'unknown' for 182 | protos other than tcp, udp, and icmp. 183 | 184 | 1.0 185 | - Original version released 02/23/02 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | IP Tables State (iptstate) 2 | 3 | Copyright (C) 2002 - present Phil Dibowitz 4 | 5 | This software is provided 'as-is', without any express or 6 | implied warranty. In no event will the authors be held 7 | liable for any damages arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any 10 | purpose, including commercial applications, and to alter it 11 | and redistribute it freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you 14 | must not claim that you wrote the original software. If you use 15 | this software in a product, an acknowledgment in the product 16 | documentation would be appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and 19 | must not be misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. 23 | 24 | ----------------------------------- 25 | 26 | NOTE: If you are planning on packaging and/or submitting my software for/to a 27 | Linux distribution, I would appreciate a heads up. 28 | 29 | There is already an official maintainer for most popular distributions, see the 30 | CONTRIB file for details. 31 | 32 | Phil Dibowitz 33 | phil AT ipom DOT com 34 | 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2002 - present Phil Dibowitz. 3 | # 4 | # See iptstate.cc for copyright info 5 | # 6 | # Makefile for IPTState 7 | # 8 | 9 | ### USERS CAN CHANGE STUFF HERE 10 | 11 | PREFIX?=/usr 12 | SBIN?=$(PREFIX)/sbin 13 | INSTALL?=/usr/bin/install 14 | STRIP?=/usr/bin/strip 15 | MAN?=$(PREFIX)/share/man 16 | PKG_CONFIG?=pkg-config 17 | 18 | ### ADVANCED USERS AND PACKAGERS MIGHT WANT TO CHANGE THIS 19 | 20 | CXX?= g++ 21 | # All of our snprintf()s have size limits and are not a security issue, 22 | # but having to modulo every hours/second/minute variable in every snprintf 23 | # is the only way to work around format-truncation warning which is cumbersome 24 | # and hard to read. Hence -Wformat-truncation=0 25 | CXXFLAGS?= -g -Wall -O2 -Werror=format-security -Wformat-truncation=0 26 | CXXFILES?= iptstate.cc 27 | 28 | # THIS IS FOR NORMAL COMPILATION 29 | LIBS?= $(shell $(PKG_CONFIG) --libs ncurses libnetfilter_conntrack) 30 | 31 | ### YOU SHOULDN'T NEED TO CHANGE ANYTHING BELOW THIS 32 | 33 | all: iptstate 34 | 35 | 36 | iptstate: iptstate.cc Makefile 37 | @\ 38 | echo "+------------------------------------------------------------+" ;\ 39 | echo "| Welcome to IP Tables State by Phil Dibowitz |" ;\ 40 | echo "| |" ;\ 41 | echo "| PLEASE read the LICENSE and the README for important info. |" ;\ 42 | echo "| |" ;\ 43 | echo "| You may also wish to read the README for install info, |" ;\ 44 | echo "| the WISHLIST for upcoming features, BUGS for known bugs |" ;\ 45 | echo "| and info on bug reports, and the Changelog to find out |" ;\ 46 | echo "| what's new. |" ;\ 47 | echo "| |" ;\ 48 | echo "| Let's compile... |" ;\ 49 | echo "+------------------------------------------------------------+" ;\ 50 | echo ""; 51 | 52 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(CXXFILES) -o iptstate $(LIBS) 53 | @touch iptstate 54 | 55 | @\ 56 | echo "" ;\ 57 | echo "All done. Do 'make install' as root and you should be set to go!" ;\ 58 | echo "" 59 | 60 | strip: iptstate 61 | $(STRIP) iptstate 62 | @touch strip 63 | 64 | 65 | install: 66 | $(INSTALL) -D --mode=755 iptstate $(SBIN)/iptstate 67 | $(INSTALL) -D --mode=444 iptstate.8 $(MAN)/man8/iptstate.8 68 | 69 | 70 | clean: 71 | /bin/rm -rf iptstate 72 | /bin/rm -rf strip 73 | 74 | 75 | uninstall: 76 | /bin/rm -rf $(SBIN)/iptstate 77 | /bin/rm -rf $(MAN)/man1/iptstate.1 78 | /bin/rm -rf $(MAN)/man8/iptstate.8 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IP Tables State (iptstate) 2 | 3 | [![Lint](https://github.com/jaymzh/iptstate/workflows/Lint/badge.svg)](https://github.com/jaymzh/iptstate/actions?query=workflow%3ALint) 4 | [![Build](https://github.com/jaymzh/iptstate/workflows/Build/badge.svg)](https://github.com/jaymzh/iptstate/actions?query=workflow%3ABuild) 5 | 6 | Please see the LICENSE file for license information. 7 | 8 | ## WHAT IS IP TABLES STATE? 9 | 10 | IP Tables State (iptstate) was originally written to implement the "state top" 11 | feature of IP Filter (see "The Idea" below) in IP Tables. "State top" displays 12 | the states held by your stateful firewall in a top-like manner. 13 | 14 | Since IP Tables doesn't have a built in way to easily display this information 15 | even once, an option was added to just have it display the state table once. 16 | 17 | Features include: 18 | 19 | * Top-like realtime state table information 20 | * Sorting by any field 21 | * Reversible sorting 22 | * Single display of state table 23 | * Customizable refresh rate 24 | * Display filtering 25 | * Color-coding 26 | * Open Source (specifically I'm using the zlib license) 27 | * much more... 28 | 29 | You can checkout some [screenshots](SCREENSHOTS.md). 30 | 31 | ## INSTALATION 32 | 33 | IPTState is packaged in most Linux distributions. If your distribution is in 34 | the table below, your best bet is so use your distro's package manager to 35 | install it. 36 | 37 | [![Packaging status](https://repology.org/badge/vertical-allrepos/iptstate.svg?exclude_unsupported=1)](https://repology.org/project/iptstate/versions) 38 | 39 | Otherwise, you can build it from source. 40 | 41 | ### Building: prerequisites 42 | 43 | Make sure you have some version of curses installed (for most users this is 44 | probably ncurses). Note that if you are using vendor packages you will most 45 | likely need the packaged with '-dev' on the end of of it (i.e. ncurses-dev). 46 | 47 | Starting with version 2.2.0, you also need libnetfilter_conntrack version 0.0.50 48 | or later. These libraries also require nf_conntrack_netlink and nfnetlink support 49 | in your kernel. You will also need pkg-config at build-time. 50 | 51 | ### Building: the quick version: 52 | 53 | For most people the following should do all you need: 54 | 55 | ```shell 56 | make 57 | make install # this must be done as root 58 | ``` 59 | 60 | ### Building: the long version: 61 | 62 | #### Configuration 63 | 64 | The program is only one c++ source file, so the compile is very simple. For 65 | this reason there is no config file. The defaults in the Makefile should be 66 | fine, but if you want to change something you can change where iptstate gets 67 | installed by changing the "SBIN" variable in your environment. I can't imagine 68 | a reason but if you have 'install' installed in a weird place change the 69 | INSTALL variable in your environment. Other than that nothing should need 70 | tweaking. Obviously advanced users may wish to do other stuff, but we'll leave 71 | that as an excersize to the reader. 72 | 73 | #### Compiling 74 | 75 | The compiling should be as simple as running 'make.' If this doesn't work, feel 76 | free to drop me an email, BUT MAKE SURE you put "IPTSTATE:" in the subject. In 77 | the email include: Distribution, kernel version, make version, gcc version, 78 | libc version, and the error messages. 79 | 80 | Package maintainers may wish to override CXXFLAGS, and can do so like so: 81 | 82 | ```shell 83 | # CXXFLAGS=-O3 make 84 | ``` 85 | 86 | and/or use "make strip" which will build iptstate and then strip it. 87 | 88 | If you get errors like: 89 | 90 | ```shell 91 | iptstate.cc:286: passing `in_addr *' as argument 92 | 1 of `gethostbyaddr(const char *, size_t, int)' 93 | ``` 94 | 95 | then you need to upgrade your glibc. This is an important thing to keep 96 | up-to-date anyway. 97 | 98 | #### Installing 99 | 100 | IPTState installs in /usr/sbin. This is because it should be a utility for the 101 | superuser. You need root access (or CAP_NET_ADMIN) for iptstate to get the data 102 | it needs anyway. Installing should be as simple as 'make install' as root. If 103 | this fails, feel free to do: 104 | 105 | ```shell 106 | # cp iptstate /usr/sbin/iptstate 107 | # chmod 755 /usr/sbin/iptstate 108 | # chown root:bin /usr/sbin/iptstate 109 | # cp iptstate.8 /usr/share/man/man8/iptstate.8 110 | # chmod 444 /usr/share/man/man1/iptstate.8 111 | ``` 112 | 113 | And that should do it. If 'make install' fails feel free to drop me an email 114 | provided you put "IPTSTATE:" in the subject. Please see the BUGS file on how to 115 | send proper bug reports. 116 | 117 | ## USAGE 118 | 119 | IPTables State is extremely simple to use. Most of the time what you'll want is 120 | just the command 'iptstate' as root. This will launch you into the 'statetop' 121 | mode. In here, your state table is being sorted by Source IP. To change the 122 | sorting, on the fly, type 'b.' This will rotate through the various sorting 123 | possibilities. You can quit by typing 'q.' You can also change the sorting with 124 | the -b ("sort BY") option. The -b option takes d (Destination IP), D 125 | (Destination Port), S (Source IP), p (protocol), s (state), and t (TTL) as it's 126 | possible options. To sort by Source IP, just don't specify -b. 127 | 128 | You can also change the refresh rate of the statetop by -R followed by an 129 | integer. The integer represents the refresh rate in seconds. 130 | 131 | To get help, hit 'h' from withint iptstate, or run iptstate with the '--help' 132 | option. 133 | 134 | To get a quick look at what's going across your firewall, try iptstate -1. This 135 | is "single run" mode. It will just print out your state table at the moment you 136 | requested it. This is where -b comes in handy. Again, the default sort is by 137 | Source IP. 138 | 139 | NOTE WELL: This is not meant to be a comprehensive guide. There are many other 140 | features - check the man page, the -h option, and the interactive help page 141 | within iptstate for more information. But this should give you the basics. 142 | 143 | ## DESIRED FEATURES 144 | 145 | There is a list of features I plan and don't plan to implement in the WISHLIST 146 | file. 147 | 148 | ## THE IDEA 149 | 150 | The idea of statetop comes from IP Filter by Darren Reed. 151 | 152 | This package's main purpose is to provide a state-top type interface for IP 153 | Tables. I've added in the "single run" option since there's no nice way to do 154 | that with IP Tables either. 155 | 156 | ## THE AUTHOR 157 | 158 | IPTState was written by me, Phil Dibowitz. My day job is large-scale system 159 | administration and automation. Outside of work I maintain several open source 160 | projects. You can find out more about me at [PhilDev](http://www.phildev.net/). 161 | 162 | Phil Dibowitz 163 | phil AT ipom DOT com 164 | -------------------------------------------------------------------------------- /RELEASE_PROCESS.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | ## Bump version 4 | 5 | ```shell 6 | vi iptstate.cc iptstate.spec 7 | ``` 8 | 9 | ## Add appropriate Changelog entries 10 | 11 | ```shell 12 | vi Changelog 13 | ``` 14 | 15 | ## Commit & Push 16 | 17 | ## Tag a release 18 | 19 | ```shell 20 | version="2.2.7" # update accordingly 21 | git tag -a "v$version" -m "iptstate version $version" 22 | git push origin --tags 23 | ``` 24 | 25 | ## Make a tarball 26 | 27 | ```shell 28 | git archive --format=tar --prefix=iptstate-$version/ v$version \ 29 | > /tmp/iptstate-$version-prep.tar 30 | cd /tmp && tar xf iptstate-$version-prep.tar 31 | cd /tmp/iptstate-$version 32 | # poke around 33 | cd .. 34 | mv iptstate-$version-prep.tar iptstate-$version.tar 35 | bzip2 iptstate-$version.tar 36 | ``` 37 | 38 | ## Sign 39 | 40 | ```shell 41 | gpg -ab /tmp/iptstate-$version.tar.bz2 42 | ``` 43 | 44 | ## Upload 45 | 46 | ## Update website 47 | -------------------------------------------------------------------------------- /SCREENSHOTS.md: -------------------------------------------------------------------------------- 1 | # Screenshots 2 | 3 | ![IPTState 2.0 doing hostname lookups, with color-coding on (all one color 4 | because there's only TCP in this 5 | picture)](screenshots/iptstate-2.0-lookup-ss.png) 6 | 7 | ![IPTState 2.0 filtered to only show connections with a destination port of 22, 8 | doing color-coding, and host-name 9 | lookups.](screenshots/iptstate-2.0-filter-ss.png) 10 | 11 | ![IPTState 2.0 filtered to only show connections with a destination port of 22, 12 | doing color-coding, and host-name 13 | lookups.](screenshots/iptstate-2.0-help-ss.png) 14 | 15 | ![Original IPTState 1.2.0 screenshot](screenshots/iptstate-ss-old.png) 16 | -------------------------------------------------------------------------------- /WISHLIST: -------------------------------------------------------------------------------- 1 | IPTSTATE WISHLIST 2 | Wishlist features should be filed in the Wishlist tracker on our sourceforge page. 3 | 4 | This is an overview of the common ones and where they are on the priority list. 5 | 6 | Features coming soon: 7 | - Filtering hostnames 8 | - Display NAT'd IP 9 | 10 | Features that may come later: 11 | - Secondary sorting 12 | - Display packets/bytes per interval instead of just total 13 | - Display the "CONNMARK" value of each connection 14 | 15 | Features I probably won't add because I'm not that interested, or 16 | don't feel they belong, or would be too much work, or some 17 | combination therein. 18 | - Full regex filtering 19 | 20 | 21 | Phil Dibowitz 22 | phil AT ipom DOT com 23 | -------------------------------------------------------------------------------- /iptstate.8: -------------------------------------------------------------------------------- 1 | .\" Process this file with 2 | .\" groff -man -Tascii iptstate.8 3 | .\" 4 | .TH IPTSTATE 8 "JUNE 2012" "" "" 5 | .\" 6 | .\" Man page written by Phil Dibowitz 7 | .\" 8 | .\" IPTState is copyright by Phil Dibowitz. Please see the README and LICENSE. 9 | .\" 10 | .SH NAME 11 | .B iptstate 12 | \- A top-like display of IP Tables state table entries 13 | 14 | .SH SYNOPSIS 15 | .B iptstate 16 | .RB [< options >] 17 | 18 | .SH DESCRIPTION 19 | .B iptstate 20 | displays information held in the IP Tables state table in real-time in a top-like format. 21 | Output can be sorted by any field, or any field reversed. Users can choose to have the output only print once and exit, rather than the top-like system. Refresh rate is configurable, IPs can be resolved to names, output can be formatted, the display can be filtered, and color coding are among some of the many features. 22 | 23 | .SH COMMAND\-LINE OPTIONS 24 | .TP 25 | .B -c, --no-color 26 | Toggle color-code by protocol 27 | .TP 28 | .B -C, --counters 29 | Toggle display of bytes/packets counters 30 | .TP 31 | .B -d, --dst-filter \fIIP[/NETMASK]\fP 32 | Only show states with a destination of \fIIP\fP and with optional \fINETMASK\fP. 33 | .br 34 | Note, that this must be an IP, hostname matching is not yet supported. 35 | .TP 36 | .B -D --dstpt-filter \fIport\fP 37 | Only show states with a destination port of \fIport\fP 38 | .TP 39 | .B -h, --help 40 | Show help message 41 | .TP 42 | .B -i, --invert-filters 43 | Invert filters to display non-matching results 44 | .TP 45 | .B -l, --lookup 46 | Show hostnames instead of IP addresses. Enabling this will also enable \fB-L\fP to prevent an ever-growing number of DNS requests. 47 | .TP 48 | .B -m, --mark-truncated 49 | Mark truncated hostnames with a '+' 50 | .TP 51 | .B -o, --no-dynamic 52 | Toggle dynamic formatting 53 | .TP 54 | .B -L, --no-dns 55 | Skip outgoing DNS lookup states 56 | .TP 57 | .B -f, --no-loopback 58 | Filter states on loopback 59 | .TP 60 | .B -p, --no-scroll 61 | No scrolling (don't use a "pad"). See \fBSCROLLING AND PADS\fP for more information. 62 | .TP 63 | .B -r, --reverse 64 | Reverse sort order 65 | .TP 66 | .B -R, --rate \fIseconds\fP 67 | Refresh rate, followed by rate in \fIseconds\fP. Note that this is for statetop mode, and not applicable for single-run mode (\-\-single). 68 | .TP 69 | .B -1, --single 70 | Single run (no curses) 71 | .TP 72 | .B -b, --sort \fIcolumn\fP 73 | This determines what column to sort by. Options: 74 | .br 75 | .B " S" 76 | Source Port 77 | .br 78 | .B " d" 79 | Destination IP (or Name) 80 | .br 81 | .B " D" 82 | Destination Port 83 | .br 84 | .B " p" 85 | Protocol 86 | .br 87 | .B " s" 88 | State 89 | .br 90 | .B " t" 91 | TTL 92 | .br 93 | .B " b" 94 | Bytes 95 | .br 96 | .B " P" 97 | Packets 98 | .br 99 | To sort by Source IP (or Name), don't use \-b. Sorting by bytes/packets is only available for kernels that support it, and only when compiled against libnetfilter_conntrack (the default). 100 | .TP 101 | .B -s, --src-filter \fIIP[/NETMASK]\fP 102 | Only show states with a source of \fIIP\fP and with optional \fINETMASK\fP. 103 | .br 104 | Note, that this must be an IP, hostname matching is not yet supported. 105 | .TP 106 | .B -S, --srcpt-filter \fIport\fP 107 | Only show states with a source port of \fIport\fP 108 | .TP 109 | .B -t, --totals 110 | Toggle display of totals 111 | 112 | .SH INTERACTIVE OPTIONS 113 | As of version 2.0, all command-line options are now available interactively using the same key as the short-option. For example, \fB--sort\fP is also \fB-b\fP, so while \fBiptstate\fP is running, hitting \fBb\fP will change the sorting to the next column. Similarly, \fBt\fP toggles the display of totals, and so on. 114 | .PP 115 | There are also extra interactive options: \fBB\fP - change sorting to previous column (opposite of \fBb\fP); \fBq\fP - quit; and \fBx\fP - delete the currently highlighted state from the netfilter conntrack table. 116 | .PP 117 | Additionally, the following keys are used to navigate within \fBiptstate\fP: 118 | .TP 119 | \fBUp\fP or \fBj\fP - Move up one line 120 | .TP 121 | \fBDown\fP or \fBk\fP - Move down one line 122 | .TP 123 | \fBLeft\fP or \fBh\fP - Move left one column 124 | .TP 125 | \fBRight\fP or \fBl\fP - Move right one column 126 | .TP 127 | \fBPageUp\fP or \fB^u\fP - Move up one page 128 | .TP 129 | \fBPageDown\fP or \fB^d\fP - Move down one page 130 | .TP 131 | \fBHome\fP - Go to the top 132 | .TP 133 | \fBEnd\fP - Go to the end 134 | .PP 135 | In many cases, \fBiptstate\fP needs to prompt you in order to change something. For example, if you want to set or change the source-ip filter, when you hit \fBs\fP, \fBiptstate\fP will pop up a prompt at the top of the window to ask you what you want to set it to. 136 | .PP 137 | Note that like many UNIX applications, ctrl-G will tell \fBiptstate\fP "nevermind" - it'll remove the prompt and forget you ever hit \fBs\fP. 138 | .PP 139 | In most cases, a blank response means "clear" - clear the source IP filter, for example. 140 | .PP 141 | At anytime while \fBiptstate\fP is running, you can hit \fBh\fP to get to the \fBinteractive help\fP which will display all the current settings to you as well give you a list of all interactive commands available. 142 | .PP 143 | While running, \fBspace\fP will immediately update the display. \fBIptstate\fP should gracefully handle all window resizes, but if it doesn't, you can force it to re-calculate and re-draw the screen with a \fBctrl-L\fP. 144 | .PP 145 | Note that hitting \fBl\fP to enable hostname resolution while in interactive mode will also enable \fBL\fP to skip all DNS entries (to prevent an ever-growing number of DNS requests). 146 | 147 | .SH SCROLLING AND PADS 148 | For almost any user, there is no reason to turn off scrolling. The ability to turn this off - and especially the ability to toggle this interactively - is done more for theoretical completeness than anything else. 149 | .TP 150 | But, nonetheless, here are the details. Typically in a curses application you create a "window." Windows don't scroll, however. They are, at most, the size of your terminal. Windows provide double-buffering to make refreshing as fast and seemless as possible. However, to enable scrolling, one has to use "pads" instead of windows. Pads can be bigger than the current terminal. Then all necessary data is written to the pad, and "scrolling" becomes a function of just showing the right part of that pad on the screen. 151 | .TP 152 | However, pads do not have the double-buffering feature that windows have. Thus, there _might_ be some case where for some user using some very strange machine, having scrolling enabled could cause poor refreshing. Given the nature of the way \fBiptstate\fP uses the screen though, I find this highly unlikely. In addition, the scrolling method uses a little more memory. However, \fBiptstate\fP is not a memory intensive application, so this shouldn't be a problem even on low-memory systems. 153 | .TP 154 | Nonetheless, if this does negatively affect you, the option to turn it off is there. 155 | 156 | .SH EXIT STATUS 157 | Anything other than 0 indicates and error. A list of current exit statuses are below: 158 | .TP 159 | .B 0 160 | Success 161 | .TP 162 | .B 1 163 | Bad command-line arguments 164 | .TP 165 | .B 2 166 | Error communicating with the netfilter subsystem. 167 | .TP 168 | .B 3 169 | Terminal too narrow 170 | 171 | .SH BUGS 172 | We don't support filtering on resolved names. IPv6 support is new and the dynamic formatting doesn't yet always handle IPv6 addresses as well as it should. 173 | 174 | .SH BUG REPORTS 175 | All bugs should be reported to Phil Dibowitz . Please see the \fBREADME\fR and \fBBUGS\fR for more information on bug reports. Please read the \fBWISHLIST\fR before sending in features you hope to see. 176 | 177 | .SH NOTES 178 | \fBiptstate\fP does a lot of work to try to fit everything on the screen in an easy-to-read way. However, in some cases, hostnames may need to be truncated (in lookup mode). Similarly, IPv6 addresses may need to be truncated. The truncation of names happens from the right for source because you most likely know your own domain name, and from the left for destination because knowing your users are connection to "mail.a." doesn't help much. However, for addresses, this is reversed. 179 | .PP 180 | \fBiptstate\fP does not automatically handle window-resizes while in the \fBinteractive help\fP screen. If you do resize while in this window, you should return to the main window, hit \fBctrl-L\fP to re-calculate and re-draw the screen, and then, if you choose, return to the \fBinteractive help\fP. 181 | .PP 182 | \fBiptstate\fP currently uses libnetfilter_conntrack to access the netfilter connection state table. However, older versions read out of /proc/net/ip_conntrack, and the current version can still be compiled to do this. This deprecated method can be racy on SMP systems, and can hurt performance on very heavily loaded firewalls. This deprecated method should be avoided - support will be removed in future versions. 183 | 184 | .SH SEE ALSO 185 | .BR iptables (8) 186 | .PP 187 | 188 | .SH AUTHOR 189 | \fBiptstate\fP was written by Phil Dibowitz 190 | .br 191 | http://www.phildev.net/iptstate/ 192 | -------------------------------------------------------------------------------- /iptstate.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * vim:textwidth=80:tabstop=2:shiftwidth=2:expandtab:ai 3 | * 4 | * iptstate.cc 5 | * IPTables State 6 | * 7 | * ----------------------------------- 8 | * 9 | * Copyright (C) 2002 - present Phil Dibowitz 10 | * 11 | * This software is provided 'as-is', without any express or 12 | * implied warranty. In no event will the authors be held 13 | * liable for any damages arising from the use of this software. 14 | * 15 | * Permission is granted to anyone to use this software for any 16 | * purpose, including commercial applications, and to alter it 17 | * and redistribute it freely, subject to the following restrictions: 18 | * 19 | * 1. The origin of this software must not be misrepresented; you 20 | * must not claim that you wrote the original software. If you use 21 | * this software in a product, an acknowledgment in the product 22 | * documentation would be appreciated but is not required. 23 | * 24 | * 2. Altered source versions must be plainly marked as such, and 25 | * must not be misrepresented as being the original software. 26 | * 27 | * 3. This notice may not be removed or altered from any source 28 | * distribution. 29 | * 30 | * ----------------------------------- 31 | * 32 | * The idea of statetop comes from IP Filter by Darren Reed. 33 | * 34 | * This package's main purpose is to provide a state-top type 35 | * interface for IP Tables. I've added in the "single run" 36 | * option since there's no nice way to do that either. 37 | * 38 | * NOTE: If you are planning on packaging and/or submitting my software for/to 39 | * a Linux distribution, I would appreciate a heads up. 40 | * 41 | */ 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | // There are no C++-ified versions of these. 57 | #include 58 | #include 59 | extern "C" { 60 | #include 61 | }; 62 | #define __STDC_FORMAT_MACROS 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | using namespace std; 70 | 71 | #define VERSION "2.2.7" 72 | /* 73 | * MAXCONS is set to 16k, the default number of states in iptables. Generally 74 | * speaking the ncurses pad is this many lines long, but since ncurses 75 | * uses a short for their dimensions, a pad can never be longer than 32767. 76 | * Thus we define both of these values and NLINES as the lesser of the two. 77 | */ 78 | #define MAXCONS 16384 79 | #define MAXLINES 32767 80 | #if MAXCONS < MAXLINES 81 | #define NLINES MAXCONS 82 | #else 83 | #define NLINES MAXLINES 84 | #endif 85 | #define MAXFIELDS 20 86 | // This is the default format string if we don't dynamically determine it 87 | #define DEFAULT_FORMAT "%-21s %-21s %-7s %-12s %-9s\n" 88 | // The following MUST be the same as the above 89 | #define DEFAULT_SRC 21 90 | #define DEFAULT_DST 21 91 | #define DEFAULT_PROTO 7 92 | #define DEFAULT_STATE 12 93 | #define DEFAULT_TTL 9 94 | // This is the format string for the "totals" line, always. 95 | #define TOTALS_FORMAT \ 96 | "Total States: %i -- TCP: %i UDP: %i ICMP: %i Other: %i (Filtered: %i)\n" 97 | // Options for truncating from the front or the back 98 | #define TRUNC_FRONT 0 99 | #define TRUNC_END 1 100 | // maxlength for string we pass to inet_ntop() 101 | #define NAMELEN 100 102 | // Sorting options 103 | #define SORT_SRC 0 104 | #define SORT_SRC_PT 1 105 | #define SORT_DST 2 106 | #define SORT_DST_PT 3 107 | #define SORT_PROTO 4 108 | #define SORT_STATE 5 109 | #define SORT_TTL 6 110 | #define SORT_BYTES 7 111 | #define SORT_PACKETS 8 112 | #define SORT_MAX 8 113 | 114 | /* 115 | * GLOBAL CONSTANTS 116 | */ 117 | 118 | /* 119 | * GLOBAL VARS 120 | */ 121 | int sort_factor = 1; 122 | bool need_resize = false; 123 | 124 | /* shameless stolen from libnetfilter_conntrack_tcp.c */ 125 | static const char *states[] = { 126 | "NONE", 127 | "SYN_SENT", 128 | "SYN_RECV", 129 | "ESTABLISHED", 130 | "FIN_WAIT", 131 | "CLOSE_WAIT", 132 | "LAST_ACK", 133 | "TIME_WAIT", 134 | "CLOSE", 135 | "LISTEN" 136 | }; 137 | 138 | 139 | /* 140 | * STRUCTS 141 | */ 142 | // One state-table entry 143 | struct tentry_t { 144 | string proto, state, ttl, sname, dname, spname, dpname; 145 | in6_addr src, dst; 146 | uint8_t family; 147 | unsigned long srcpt, dstpt, bytes, packets, s; 148 | }; 149 | // x/y of the terminal window 150 | struct screensize_t { 151 | unsigned int x, y; 152 | }; 153 | // Struct 'o flags 154 | struct flags_t { 155 | bool single, totals, lookup, skiplb, staticsize, skipdns, tag_truncate, 156 | filter_src, filter_dst, filter_srcpt, filter_dstpt, filter_inv, noscroll, nocolor, 157 | counters; 158 | }; 159 | // Struct 'o counters 160 | struct counters_t { 161 | unsigned int total, tcp, udp, icmp, other, skipped; 162 | }; 163 | // Various filters to be applied pending the right flags in flags_t 164 | struct filters_t { 165 | in6_addr src, dst; 166 | bool has_srcnet, has_dstnet; 167 | uint8_t srcnet, dstnet; 168 | uint8_t srcfam, dstfam; 169 | unsigned long srcpt, dstpt; 170 | }; 171 | // The max-length of fields in the stable table 172 | struct max_t { 173 | unsigned int src, dst, proto, state, ttl; 174 | unsigned long bytes, packets; 175 | }; 176 | struct hook_data { 177 | vector *stable; 178 | flags_t *flags; 179 | max_t *max; 180 | counters_t *counts; 181 | const filters_t *filters; 182 | }; 183 | 184 | 185 | /* 186 | * GENERAL HELPER FUNCTIONS 187 | */ 188 | 189 | /* 190 | * split a string into two strings based on the first occurance 191 | * of any character 192 | */ 193 | void split(char s, string line, string &p1, string &p2) 194 | { 195 | int pos = line.find(s); 196 | p1 = line.substr(0, pos); 197 | p2 = line.substr(pos+1, line.size()-pos); 198 | } 199 | 200 | /* 201 | * split a string into an array of strings based on 202 | * any character 203 | */ 204 | void splita(char s, string line, vector &result) 205 | { 206 | int pos, size; 207 | int i=0; 208 | string temp, temp1; 209 | temp = line; 210 | while ((temp.find(s) != string::npos) && (i < MAXFIELDS-1)) { 211 | pos = temp.find(s); 212 | result[i] = temp.substr(0, pos); 213 | size = temp.size(); 214 | temp = temp.substr(pos+1, size-pos-1); 215 | if (result[i] != "") { 216 | i++; 217 | } 218 | } 219 | result[i] = temp; 220 | } 221 | 222 | /* 223 | * This determines the length of an integer (i.e. number of digits) 224 | */ 225 | unsigned int digits(unsigned long x) 226 | { 227 | return (unsigned int) floor(log10((double)x))+1; 228 | } 229 | 230 | /* 231 | * Check to ensure an IP & netmask are valid 232 | */ 233 | bool check_ip(const char *arg, in6_addr *addr, uint8_t *family, uint8_t *netmask, bool *has_netmask) 234 | { 235 | const char *p_arg; 236 | char ip[NAMELEN]; 237 | 238 | // Check for netmask prefix 239 | p_arg = strrchr(arg, '/'); 240 | if (p_arg == NULL) { 241 | memcpy(ip, arg, (strlen(arg) + 1)); 242 | *has_netmask = false; 243 | } else { 244 | size_t ip_len = strlen(arg) - strlen(p_arg); 245 | memcpy(ip, arg, ip_len); 246 | ip[ip_len] = '\0'; 247 | 248 | const char *net_arg = arg + ip_len + 1; 249 | if (net_arg[0] == '\0') 250 | return false; 251 | int net_int = atoi(net_arg); 252 | if (net_int < 0 || net_int > 128) 253 | return false; // Max IPv6 prefix length 254 | *has_netmask = true; 255 | *netmask = (uint8_t) net_int; 256 | } 257 | 258 | // Get IP 259 | int ret; 260 | ret = inet_pton(AF_INET6, ip, addr); 261 | if (ret) { 262 | *family = AF_INET6; 263 | return true; 264 | } 265 | ret = inet_pton(AF_INET, ip, addr); 266 | if (ret) { 267 | if (*has_netmask && *netmask > 32) 268 | return false; // Max IPv4 prefix length 269 | *family = AF_INET; 270 | return true; 271 | } 272 | return false; 273 | } 274 | 275 | /* 276 | * Compare IPv4/6 addresses with a netmask 277 | * https://gist.github.com/duedal/b83303b4988a4afb2a75 278 | */ 279 | bool match_netmask(uint8_t family, const in6_addr &address, const in6_addr &network, uint8_t bits) { 280 | if (family == AF_INET) { 281 | if (bits == 0) { 282 | // C99 6.5.7 (3): u32 << 32 is undefined behaviour 283 | return true; 284 | } 285 | 286 | struct in_addr addr4, net4; 287 | memcpy(&addr4, &address, sizeof(in_addr)); 288 | memcpy(&net4, &network, sizeof(in_addr)); 289 | return !((addr4.s_addr ^ net4.s_addr) & htonl(0xFFFFFFFFu << (32 - bits))); 290 | } else { 291 | const uint32_t *a = address.s6_addr32; 292 | const uint32_t *n = network.s6_addr32; 293 | 294 | int bits_whole, bits_incomplete; 295 | bits_whole = bits >> 5; // number of whole u32 296 | bits_incomplete = bits & 0x1F; // number of bits in incomplete u32 297 | if (bits_whole) { 298 | if (memcmp(a, n, bits_whole << 2)) { 299 | return false; 300 | } 301 | } 302 | if (bits_incomplete) { 303 | uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete)); 304 | if ((a[bits_whole] ^ n[bits_whole]) & mask) { 305 | return false; 306 | } 307 | } 308 | return true; 309 | } 310 | return false; 311 | } 312 | 313 | /* 314 | * The help 315 | */ 316 | void version() 317 | { 318 | cout << "IPTables State Top Version " << VERSION << endl << endl; 319 | } 320 | 321 | void help() 322 | { 323 | cout << "IPTables State Top Version " << VERSION << endl; 324 | cout << "Usage: iptstate []\n\n"; 325 | cout << " -c, --no-color\n"; 326 | cout << "\tToggle color-code by protocol\n\n"; 327 | cout << " -C, --counters\n"; 328 | cout << "\tToggle display of bytes/packets counters\n\n"; 329 | cout << " -d, --dst-filter [/]\n"; 330 | cout << "\tOnly show states with a destination of and optional \n"; 331 | cout << "\tNote: Hostname matching is not yet supported.\n\n"; 332 | cout << " -D --dstpt-filter \n"; 333 | cout << "\tOnly show states with a destination port of \n\n"; 334 | cout << " -h, --help\n"; 335 | cout << "\tThis help message\n\n"; 336 | cout << " -i, --invert-filters\n"; 337 | cout << "\tInvert filters to display non-matching results\n\n"; 338 | cout << " -l, --lookup\n"; 339 | cout << "\tShow hostnames instead of IP addresses. Enabling this will also" 340 | << " enable\n\t-L to prevent an ever-growing number of DNS requests.\n\n"; 341 | cout << " -m, --mark-truncated\n"; 342 | cout << "\tMark truncated hostnames with a '+'\n\n"; 343 | cout << " -o, --no-dynamic\n"; 344 | cout << "\tToggle dynamic formatting\n\n"; 345 | cout << " -L, --no-dns\n"; 346 | cout << "\tSkip outgoing DNS lookup states\n\n"; 347 | cout << " -f, --no-loopback\n"; 348 | cout << "\tFilter states on loopback\n\n"; 349 | cout << " -p, --no-scroll\n"; 350 | cout << "\tNo scrolling (don't use a \"pad\")\n\n"; 351 | cout << " -r, --reverse\n"; 352 | cout << "\tReverse sort order\n\n"; 353 | cout << " -R, --rate \n"; 354 | cout << "\tRefresh rate, followed by rate in seconds\n"; 355 | cout << "\tNote: For statetop, not applicable for -s\n\n"; 356 | cout << " -1, --single\n"; 357 | cout << "\tSingle run (no curses)\n\n"; 358 | cout << " -b, --sort \n"; 359 | cout << "\tThis determines what column to sort by. Options:\n"; 360 | cout << "\t d: Destination IP (or Name)\n"; 361 | cout << "\t p: Protocol\n"; 362 | cout << "\t s: State\n"; 363 | cout << "\t t: TTL\n"; 364 | cout << "\t b: Bytes\n"; 365 | cout << "\t P: Packets\n"; 366 | cout << "\tTo sort by Source IP (or Name), don't use -b.\n"; 367 | cout << "\tNote that bytes/packets are only available when" 368 | << " supported in the kernel,\n"; 369 | cout << "\tand enabled with -C\n\n"; 370 | cout << " -s, --src-filter [/]\n"; 371 | cout << "\tOnly show states with a source of and optional \n"; 372 | cout << "\tNote: Hostname matching is not yet supported.\n\n"; 373 | cout << " -S, --srcpt-filter \n"; 374 | cout << "\tOnly show states with a source port of \n\n"; 375 | cout << " -t, --totals\n"; 376 | cout << "\tToggle display of totals\n\n"; 377 | cout << "See man iptstate(8) or the interactive help for more" 378 | << " information.\n"; 379 | exit(0); 380 | } 381 | 382 | /* 383 | * Resolve hostnames 384 | */ 385 | void resolve_host(const uint8_t &family, const in6_addr &ip, string &name) 386 | { 387 | struct hostent *hostinfo = NULL; 388 | 389 | if ((hostinfo = gethostbyaddr((char *)&ip, sizeof(ip), family)) != NULL) { 390 | name = hostinfo->h_name; 391 | } else { 392 | char str[NAMELEN]; 393 | name = inet_ntop(family, (void *)&ip, str, NAMELEN-1) ; 394 | } 395 | } 396 | 397 | void resolve_port(const unsigned int &port, string &name, const string &proto) 398 | { 399 | struct servent *portinfo = NULL; 400 | 401 | if ((portinfo = getservbyport(htons(port), proto.c_str())) != NULL) { 402 | name = portinfo->s_name; 403 | } else { 404 | ostringstream buf; 405 | buf.str(""); 406 | buf << port; 407 | name = buf.str(); 408 | } 409 | } 410 | 411 | /* 412 | * If lookup mode is on, we lookup the names and put them in the structure. 413 | * 414 | * If lookup mode is not on, we generate strings of the addresses and put 415 | * those in the structure. 416 | * 417 | * Finally, we update the max_t structure. 418 | * 419 | * NOTE: We stringify addresses largely because in the IPv6 case we need 420 | * to treat them like truncate-able strings. 421 | */ 422 | void stringify_entry(tentry_t *entry, max_t &max, const flags_t &flags) 423 | { 424 | unsigned int size = 0; 425 | ostringstream buffer; 426 | char tmp[NAMELEN]; 427 | 428 | bool have_port = entry->proto == "tcp" || entry->proto == "udp"; 429 | if (!have_port) { 430 | entry->spname = entry->dpname = ""; 431 | } 432 | 433 | if (flags.lookup) { 434 | resolve_host(entry->family, entry->src, entry->sname); 435 | resolve_host(entry->family, entry->dst, entry->dname); 436 | if (have_port) { 437 | resolve_port(entry->srcpt, entry->spname, entry->proto); 438 | resolve_port(entry->dstpt, entry->dpname, entry->proto); 439 | } 440 | } else { 441 | buffer << inet_ntop(entry->family, (void*)&(entry->src), tmp, NAMELEN-1); 442 | entry->sname = buffer.str(); 443 | buffer.str(""); 444 | buffer << inet_ntop(entry->family, (void*)&(entry->dst), tmp, NAMELEN-1); 445 | entry->dname = buffer.str(); 446 | buffer.str(""); 447 | if (have_port) { 448 | buffer << entry->srcpt; 449 | entry->spname = buffer.str(); 450 | buffer.str(""); 451 | buffer << entry->dstpt; 452 | entry->dpname = buffer.str(); 453 | buffer.str(""); 454 | } 455 | } 456 | 457 | size = entry->sname.size() + entry->spname.size() + 1; 458 | if (size > max.src) 459 | max.src = size; 460 | 461 | size = entry->dname.size() + entry->dpname.size() + 1; 462 | if (size > max.dst) 463 | max.dst = size; 464 | } 465 | 466 | 467 | /* 468 | * SORT FUNCTIONS 469 | */ 470 | bool src_sort(tentry_t *one, tentry_t *two) 471 | { 472 | /* 473 | * memcmp() will properly sort v4 or v6 addresses, but not cross-family 474 | * (presumably because of garbage in the top 96 bytes when you store 475 | * a v4 address in a in6_addr), so we sort by family and then memcmp() 476 | * within the same family. 477 | */ 478 | if (one->family == two->family) { 479 | return memcmp(one->src.s6_addr, two->src.s6_addr, 16) * sort_factor < 0; 480 | } else if (one->family == AF_INET) { 481 | return sort_factor > 0; 482 | } else { 483 | return sort_factor < 0; 484 | } 485 | } 486 | 487 | bool srcname_sort(tentry_t *one, tentry_t *two) 488 | { 489 | return one->sname.compare(two->sname) * sort_factor < 0; 490 | } 491 | 492 | bool dst_sort(tentry_t *one, tentry_t *two) 493 | { 494 | // See src_sort() for details 495 | if (one->family == two->family) { 496 | return memcmp(one->dst.s6_addr, two->dst.s6_addr, 16) * sort_factor < 0; 497 | } else if (one->family == AF_INET) { 498 | return sort_factor > 0; 499 | } else { 500 | return sort_factor < 0; 501 | } 502 | } 503 | 504 | bool dstname_sort(tentry_t *one, tentry_t *two) 505 | { 506 | return one->dname.compare(two->dname) * sort_factor < 0; 507 | } 508 | 509 | /* 510 | * int comparison that takes care of sort_factor 511 | * used for ports, bytes, etc... 512 | */ 513 | bool cmpint(int one, int two) 514 | { 515 | return (sort_factor > 0) ? one < two : one > two; 516 | } 517 | 518 | bool srcpt_sort(tentry_t *one, tentry_t *two) 519 | { 520 | return cmpint(one->srcpt, two->srcpt); 521 | } 522 | 523 | bool dstpt_sort(tentry_t *one, tentry_t *two) 524 | { 525 | return cmpint(one->dstpt, two->dstpt); 526 | } 527 | 528 | bool proto_sort(tentry_t *one, tentry_t *two) 529 | { 530 | return one->proto.compare(two->proto) * sort_factor < 0; 531 | } 532 | 533 | bool state_sort(tentry_t *one, tentry_t *two) 534 | { 535 | return one->state.compare(two->state) * sort_factor < 0; 536 | } 537 | 538 | bool ttl_sort(tentry_t *one, tentry_t *two) 539 | { 540 | return one->ttl.compare(two->ttl) * sort_factor < 0; 541 | } 542 | 543 | bool bytes_sort(tentry_t *one, tentry_t *two) 544 | { 545 | return cmpint(one->bytes, two->bytes); 546 | } 547 | 548 | bool packets_sort(tentry_t *one, tentry_t *two) 549 | { 550 | return cmpint(one->packets, two->packets); 551 | } 552 | 553 | /* 554 | * CURSES HELPER FUNCTIONS 555 | */ 556 | 557 | /* 558 | * Finish-up for curses environment 559 | */ 560 | void end_curses() 561 | { 562 | curs_set(1); 563 | nocbreak(); 564 | endwin(); 565 | cout << endl; 566 | } 567 | 568 | /* 569 | * SIGWINCH signal handler. 570 | */ 571 | void winch_handler(int sig) 572 | { 573 | sigset_t mask_set; 574 | sigset_t old_set; 575 | // Reset signal handler 576 | signal(28, winch_handler); 577 | // ignore this signal for a bit 578 | sigfillset(&mask_set); 579 | sigprocmask(SIG_SETMASK, &mask_set, &old_set); 580 | 581 | need_resize = true; 582 | } 583 | 584 | /* 585 | * SIGKILL signal handler 586 | */ 587 | void kill_handler(int sig) 588 | { 589 | end_curses(); 590 | printf("Caught signal %d, cleaning up.\n", sig); 591 | exit(0); 592 | } 593 | 594 | /* 595 | * Start-up for curses environment 596 | * 597 | * NOTE: That by default we create a pad. A pad is a special type of window that 598 | * can be bigger than the screen. See the comments in interactive_help() 599 | * below for how to use it and how it works. 600 | * 601 | * However, pad's lack the double-buffering and other features of standard 602 | * ncurses windows and thus can appear slower. Thus we allow the user to 603 | * downgrade to standard windows if they choose. See the comments 604 | * switch_scroll() for more details. 605 | * 606 | */ 607 | static WINDOW* start_curses(flags_t &flags) 608 | { 609 | int y, x; 610 | initscr(); 611 | cbreak(); 612 | noecho(); 613 | halfdelay(1); 614 | 615 | /* 616 | * If we're starting curses, we care about SIGWNCH, SIGINT, and SIGTERM 617 | * so this seems like as good a place as any to setup our signal 618 | * handler. 619 | */ 620 | // Resize 621 | signal(28, winch_handler); 622 | // Shutdown 623 | signal(2, kill_handler); 624 | signal(15, kill_handler); 625 | 626 | if (has_colors()) { 627 | start_color(); 628 | // for tcp 629 | init_pair(1, COLOR_GREEN, COLOR_BLACK); 630 | // for udp 631 | init_pair(2, COLOR_YELLOW, COLOR_BLACK); 632 | // for icmp 633 | init_pair(3, COLOR_RED, COLOR_BLACK); 634 | // for prompts 635 | init_pair(4, COLOR_BLACK, COLOR_RED); 636 | // for the currently selected row 637 | init_pair(5, COLOR_BLACK, COLOR_GREEN); 638 | init_pair(6, COLOR_BLACK, COLOR_YELLOW); 639 | init_pair(7, COLOR_BLACK, COLOR_RED); 640 | } else { 641 | flags.nocolor = true; 642 | } 643 | 644 | if (!flags.noscroll) { 645 | getmaxyx(stdscr, y, x); 646 | return newpad(NLINES, x); 647 | } 648 | return stdscr; 649 | } 650 | 651 | /* 652 | * Figure out the best way to get the screensize_t, and then do it 653 | */ 654 | screensize_t get_size(const bool &single) 655 | { 656 | int maxx = 0, maxy = 0; 657 | if (!single) { 658 | getmaxyx(stdscr, maxy, maxx); 659 | } else { 660 | // https://stackoverflow.com/questions/1022957/ 661 | struct winsize w; 662 | ioctl(0, TIOCGWINSZ, &w); 663 | maxx = w.ws_col; 664 | 665 | if (getenv("COLS")) 666 | maxx = atoi(getenv("COLS")); 667 | } 668 | 669 | screensize_t a; 670 | a.x = maxx; 671 | a.y = maxy; 672 | 673 | return a; 674 | } 675 | 676 | /* 677 | * Error function for screen being too small. 678 | */ 679 | void term_too_small() 680 | { 681 | end_curses(); 682 | cout << "I'm sorry, your terminal must be atleast 72 columns" 683 | << "wide to run iptstate\n"; 684 | exit(3); 685 | } 686 | 687 | /* 688 | * This is one of those "well, I should impliment it to be complete, but 689 | * I doubt it'll get used very often features." It was a nice-thing-to-do 690 | * to impliment the ability for iptstate to use stdscr instead of a pad 691 | * as this provides the doulbe-buffering and other features that pads 692 | * do not. This is probably useful to a small subset of users. It's pretty 693 | * unlikely people will want to interactively want to change this during 694 | * runtime, but since I implimented noscroll, it's only proper to impliment 695 | * interactive toggling. 696 | * 697 | * TECH NOTE: 698 | * This is just a note for myself so I remember why this is the way it is. 699 | * 700 | * The syntax WINDOW *&mainwin is right, thought it's doing what you'd 701 | * expect WINDOW &*mainwin to do... except that's invalid. So it's just a 702 | * &foo pass on a WINDOW*. 703 | */ 704 | void switch_scroll(flags_t &flags, WINDOW *&mainwin) 705 | { 706 | int x, y; 707 | if (flags.noscroll) { 708 | getmaxyx(stdscr, y, x); 709 | // remove stuff from the bottom window 710 | erase(); 711 | // build pad 712 | wmove(mainwin, 0, 0); 713 | mainwin = newpad(NLINES, x); 714 | wmove(mainwin, 0, 0); 715 | keypad(mainwin,1); 716 | halfdelay(1); 717 | } else { 718 | // delete pad 719 | delwin(mainwin); 720 | mainwin = stdscr; 721 | keypad(mainwin,1); 722 | halfdelay(1); 723 | } 724 | 725 | flags.noscroll = !flags.noscroll; 726 | } 727 | 728 | /* 729 | * Prompt the user for something, and get an answer. 730 | */ 731 | void get_input(WINDOW *win, string &input, const string &prompt, 732 | const flags_t &flags) 733 | { 734 | 735 | /* 736 | * This function is here so that we can prompt and get an answer 737 | * and the user can get an echo of what they're inputting. This is 738 | * already a non-straight-forward thing to do in cbreak() mode, but 739 | * it turns out that using pads makes it even more difficult. 740 | * 741 | * It's worth noting that I tried doin a simple waddch() and then 742 | * prefresh as one would expect, but it didn't echo the chars. 743 | * Because we're using pads I have to do a waddchar() and then 744 | * a prefresh(). 745 | * 746 | * Note, that the documentation says that if we're using waddchar() 747 | * we shouldn't need any refresh, but it doesn't echo without it. 748 | * This is probably because waddch() calls wrefresh() instead of 749 | * prefresh(). 750 | */ 751 | 752 | input = ""; 753 | int x, y; 754 | getmaxyx(stdscr, y, x); 755 | WINDOW *cmd = subpad(win, 1, x, 0, 0); 756 | if (!flags.nocolor) 757 | wattron(cmd, COLOR_PAIR(4)); 758 | keypad(cmd, true); 759 | wprintw(cmd, "%s", prompt.c_str()); 760 | wclrtoeol(cmd); 761 | prefresh(cmd, 0, 0, 0, 0, 0, x); 762 | 763 | 764 | int ch; 765 | int charcount = 0; 766 | echo(); 767 | nodelay(cmd,0); 768 | 769 | while (1) { 770 | ch = wgetch(cmd); 771 | switch (ch) { 772 | case '\n': 773 | // 7 is ^G 774 | case 7: 775 | if (ch == 7) 776 | input = ""; 777 | if (!flags.nocolor) 778 | wattroff(cmd, COLOR_PAIR(4)); 779 | delwin(cmd); 780 | noecho(); 781 | wmove(win, 0, 0); 782 | return; 783 | break; 784 | // on most platforms KEY_BACKSPACE will catch 785 | // all backspaces... 786 | case KEY_BACKSPACE: 787 | // but on some platforms ncurses fails, so ensure we catch both 8 (0x8) and 127 (0x7e) 788 | // which are the two valid backspace keycodes 789 | case 8: 790 | case 127: 791 | if (charcount > 0) { 792 | input = input.substr(0, input.size()-1); 793 | wechochar(cmd, '\b'); 794 | wechochar(cmd, ' '); 795 | wechochar(cmd, '\b'); 796 | charcount--; 797 | } 798 | break; 799 | case ERR: 800 | continue; 801 | break; 802 | default: 803 | input += ch; 804 | charcount++; 805 | wechochar(cmd, ch); 806 | } 807 | prefresh(cmd, 0, 0, 0, 0, 0, x); 808 | } 809 | } 810 | 811 | /* 812 | * Create a window with noticable colors (if colors are enabled) 813 | * and print a warning. Means curses_warning. 814 | */ 815 | void c_warn(WINDOW *win, const string &warning, const flags_t &flags) 816 | { 817 | 818 | /* 819 | * This function is here so that we can warn a user in curses, 820 | * usually about bad input. 821 | */ 822 | 823 | int x, y; 824 | getmaxyx(stdscr, y, x); 825 | WINDOW *warn = subpad(win, 1, x, 0, 0); 826 | if (!flags.nocolor) 827 | wattron(warn, COLOR_PAIR(4)); 828 | wprintw(warn, "%s", warning.c_str()); 829 | wprintw(warn, " Press any key to continue..."); 830 | wclrtoeol(warn); 831 | prefresh(warn, 0, 0, 0, 0, 0, x); 832 | while ((y = getch())) { 833 | if (y != ERR) { 834 | break; 835 | } 836 | prefresh(warn, 0, 0, 0, 0, 0, x); 837 | } 838 | if (!flags.nocolor) 839 | wattroff(warn, COLOR_PAIR(4)); 840 | delwin(warn); 841 | noecho(); 842 | wmove(win, 0, 0); 843 | return; 844 | } 845 | 846 | /* 847 | * Initialize the max_t structure with some sane defaults. We'll grow 848 | * them later as needed. 849 | */ 850 | void initialize_maxes(max_t &max, flags_t &flags) 851 | { 852 | /* 853 | * For NO lookup: 854 | * src/dst IP can be no bigger than 21 chars: 855 | * IP (max of 15) + colon (1) + port (max of 5) = 21 856 | * 857 | * For lookup: 858 | * if it's a name, we start with the width of the header, and we can 859 | * grow from there as needed. 860 | */ 861 | if (flags.lookup) { 862 | max.src = 6; 863 | max.dst = 11; 864 | } else { 865 | max.src = max.dst = 21; 866 | } 867 | /* 868 | * The proto starts at 3, since tcp/udp are the most common, but will 869 | * grow if we see bigger proto strings such as "ICMP". 870 | */ 871 | max.proto = 3; 872 | /* 873 | * "ESTABLISHED" is generally the longest state, we almost always have 874 | * several, so we'll start with this. It also looks really bad if state 875 | * is changing size a lot, so we start with a common minumum. 876 | */ 877 | max.state = 11; 878 | // TTL we statically make 7: xxx:xx:xx 879 | max.ttl = 9; 880 | 881 | // Start with something sane 882 | max.bytes = 2; 883 | max.packets = 2; 884 | } 885 | 886 | /* 887 | * The actual work of handling a resize. 888 | */ 889 | void handle_resize(WINDOW *&win, const flags_t &flags, screensize_t &ssize) 890 | { 891 | if (flags.noscroll) { 892 | endwin(); 893 | refresh(); 894 | return; 895 | } 896 | 897 | /* 898 | * OK, the above case without pads is easy. But pads is tricker. 899 | * In order to properly handle SIGWINCH we need to: 900 | * 901 | * - Tear down the pad (delwin) 902 | * - Reset the terminal settings to non-visual mode (endwin) 903 | * - Return to visual mode (refresh) 904 | * - Get the new size (getmaxyx) 905 | * - Rebuild the pad 906 | * 907 | * Note that we don't get the new size without the endwin/refresh 908 | * and thus the new pad doesn't get built right, and everything wraps. 909 | * 910 | * This order must be preserved. 911 | */ 912 | 913 | /* 914 | * Tear down... 915 | */ 916 | delwin(win); 917 | endwin(); 918 | /* 919 | * Start up... 920 | */ 921 | refresh(); 922 | getmaxyx(stdscr, ssize.y, ssize.x); 923 | win = newpad(NLINES, ssize.x); 924 | keypad(win, true); 925 | wmove(win, 0, 0); 926 | 927 | return; 928 | } 929 | 930 | /* 931 | * Take in a 'curr' value, and delete a given conntrack 932 | */ 933 | void delete_state(WINDOW *&win, const tentry_t *entry, const flags_t &flags) 934 | { 935 | struct nfct_handle *cth; 936 | struct nf_conntrack *ct; 937 | cth = nfct_open(CONNTRACK, 0); 938 | ct = nfct_new(); 939 | int ret; 940 | string response; 941 | char str[NAMELEN]; 942 | string src, dst; 943 | src = inet_ntop(entry->family, (void *)&(entry->src), str, NAMELEN-1); 944 | dst = inet_ntop(entry->family, (void *)&(entry->dst), str, NAMELEN-1); 945 | 946 | ostringstream msg; 947 | msg.str(""); 948 | msg << "Deleting state: "; 949 | if (entry->proto == "tcp" || entry->proto == "udp") { 950 | msg << src << ":" << entry->srcpt << " -> " << dst << ":" << entry->dstpt; 951 | } else { 952 | msg << src << " -> " << dst; 953 | } 954 | msg << " -- Are you sure? (y/n)"; 955 | get_input(win, response, msg.str(), flags); 956 | 957 | if (response != "y" && response != "Y" && response != "yes" && 958 | response != "YES" && response != "Yes") { 959 | c_warn(win, "NOT deleting state.", flags); 960 | return; 961 | } 962 | 963 | nfct_set_attr_u8(ct, ATTR_ORIG_L3PROTO, entry->family); 964 | 965 | if (entry->family == AF_INET) { 966 | nfct_set_attr(ct, ATTR_ORIG_IPV4_SRC, (void *)&(entry->src.s6_addr)); 967 | nfct_set_attr(ct, ATTR_ORIG_IPV4_DST, (void *)&(entry->dst.s6_addr)); 968 | } else if (entry->family == AF_INET6) { 969 | nfct_set_attr(ct, ATTR_ORIG_IPV6_SRC, (void *)&(entry->src.s6_addr)); 970 | nfct_set_attr(ct, ATTR_ORIG_IPV6_DST, (void *)&(entry->dst.s6_addr)); 971 | } 972 | 973 | // undo our space optimization so the kernel can find the state. 974 | protoent *pn; 975 | if (entry->proto == "icmp6") 976 | pn = getprotobyname("ipv6-icmp"); 977 | else 978 | pn = getprotobyname(entry->proto.c_str()); 979 | 980 | nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, pn->p_proto); 981 | 982 | if (entry->proto == "tcp" || entry->proto == "udp") { 983 | nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, htons(entry->srcpt)); 984 | nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, htons(entry->dstpt)); 985 | } else if (entry->proto == "icmp" || entry->proto == "icmp6") { 986 | string type, code, id, tmp; 987 | split('/', entry->state, type, tmp); 988 | split(' ', tmp, code, tmp); 989 | split('(', tmp, tmp, id); 990 | split(')', id, id, tmp); 991 | 992 | nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, atoi(type.c_str())); 993 | nfct_set_attr_u8(ct, ATTR_ICMP_CODE, atoi(code.c_str())); 994 | nfct_set_attr_u16(ct, ATTR_ICMP_ID, atoi(id.c_str())); 995 | } 996 | 997 | ret = nfct_query(cth, NFCT_Q_DESTROY, ct); 998 | if (ret < 0) { 999 | string msg = "Failed to delete state: "; 1000 | msg += strerror(errno); 1001 | c_warn(win, msg, flags); 1002 | } 1003 | 1004 | } 1005 | 1006 | 1007 | /* 1008 | * CORE FUNCTIONS 1009 | */ 1010 | 1011 | /* 1012 | * Callback for conntrack 1013 | */ 1014 | int conntrack_hook(enum nf_conntrack_msg_type nf_type, struct nf_conntrack *ct, 1015 | void *tmp) 1016 | { 1017 | 1018 | /* 1019 | * start by getting our struct back 1020 | */ 1021 | struct hook_data *data = static_cast(tmp); 1022 | 1023 | /* 1024 | * and pull out the pieces 1025 | */ 1026 | vector *stable = data->stable; 1027 | flags_t *flags = data->flags; 1028 | max_t *max = data->max; 1029 | counters_t *counts = data->counts; 1030 | const filters_t *filters = data->filters; 1031 | 1032 | // our table entry 1033 | tentry_t *entry = new tentry_t; 1034 | 1035 | // some vars 1036 | struct protoent* pe = NULL; 1037 | int seconds, minutes, hours; 1038 | char ttlc[11]; 1039 | ostringstream buffer; 1040 | 1041 | /* 1042 | * Clear the entry 1043 | */ 1044 | entry->sname = ""; 1045 | entry->dname = ""; 1046 | entry->srcpt = 0; 1047 | entry->dstpt = 0; 1048 | entry->proto = ""; 1049 | entry->ttl = ""; 1050 | entry->state = ""; 1051 | 1052 | /* 1053 | * First, we read stuff into the array that's always the 1054 | * same regardless of protocol 1055 | */ 1056 | 1057 | short int pr = nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO); 1058 | pe = getprotobynumber(pr); 1059 | if (pe == NULL) { 1060 | buffer << pr; 1061 | entry->proto = buffer.str(); 1062 | buffer.str(""); 1063 | } else { 1064 | entry->proto = pe->p_name; 1065 | /* 1066 | * if proto is "ipv6-icmp" we can just say "icmp6" to save space... 1067 | * it's more common/standard anyway 1068 | */ 1069 | if (entry->proto == "ipv6-icmp") 1070 | entry->proto = "icmp6"; 1071 | } 1072 | 1073 | // ttl 1074 | seconds = nfct_get_attr_u32(ct, ATTR_TIMEOUT); 1075 | minutes = seconds/60; 1076 | hours = minutes/60; 1077 | minutes = minutes%60; 1078 | seconds = seconds%60; 1079 | // Format it with snprintf and store it in the table 1080 | snprintf(ttlc, 11, "%3i:%02i:%02i", hours, minutes, seconds); 1081 | entry->ttl = ttlc; 1082 | 1083 | entry->family = nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO); 1084 | // Everything has addresses 1085 | if (entry->family == AF_INET) { 1086 | memcpy(entry->src.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV4_SRC), 1087 | sizeof(uint8_t[16])); 1088 | memcpy(entry->dst.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV4_DST), 1089 | sizeof(uint8_t[16])); 1090 | } else if (entry->family == AF_INET6) { 1091 | memcpy(entry->src.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC), 1092 | sizeof(uint8_t[16])); 1093 | memcpy(entry->dst.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV6_DST), 1094 | sizeof(uint8_t[16])); 1095 | } else { 1096 | fprintf(stderr, "UNKNOWN FAMILY!\n"); 1097 | exit(1); 1098 | } 1099 | 1100 | // Counters (summary, in + out) 1101 | entry->bytes = nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_BYTES) + 1102 | nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_BYTES); 1103 | entry->packets = nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_PACKETS) + 1104 | nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_PACKETS); 1105 | 1106 | if (digits(entry->bytes) > max->bytes) { 1107 | max->bytes = digits(entry->bytes); 1108 | } 1109 | if (digits(entry->packets) > max->packets) { 1110 | max->packets = digits(entry->packets); 1111 | } 1112 | 1113 | if (entry->proto.size() > max->proto) 1114 | max->proto = entry->proto.size(); 1115 | 1116 | // OK, proto dependent stuff 1117 | if (entry->proto == "tcp" || entry->proto == "udp") { 1118 | entry->srcpt = htons(nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); 1119 | entry->dstpt = htons(nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); 1120 | } 1121 | 1122 | if (entry->proto == "tcp") { 1123 | entry->state = states[nfct_get_attr_u8(ct, ATTR_TCP_STATE)]; 1124 | counts->tcp++; 1125 | } else if (entry->proto == "udp") { 1126 | entry->state = ""; 1127 | counts->udp++; 1128 | } else if (entry->proto == "icmp" || entry->proto == "icmp6") { 1129 | buffer.str(""); 1130 | buffer << (int)nfct_get_attr_u8(ct, ATTR_ICMP_TYPE) << "/" 1131 | << (int)nfct_get_attr_u8(ct, ATTR_ICMP_CODE) << " (" 1132 | << nfct_get_attr_u16(ct, ATTR_ICMP_ID) << ")"; 1133 | entry->state = buffer.str(); 1134 | counts->icmp++; 1135 | if (entry->state.size() > max->state) 1136 | max->state = entry->state.size(); 1137 | } else { 1138 | counts->other++; 1139 | } 1140 | 1141 | /* 1142 | * FILTERING 1143 | */ 1144 | 1145 | /* 1146 | * FIXME: Filtering needs to be pulled into it's own function. 1147 | */ 1148 | struct in_addr lb; 1149 | struct in6_addr lb6; 1150 | inet_pton(AF_INET, "127.0.0.1", &lb); 1151 | inet_pton(AF_INET6, "::1", &lb6); 1152 | size_t entrysize = entry->family == AF_INET 1153 | ? sizeof(in_addr) 1154 | : sizeof(in6_addr); 1155 | if (flags->skiplb && (entry->family == AF_INET 1156 | ? !memcmp(&(entry->src), &lb, sizeof(in_addr)) 1157 | : !memcmp(&(entry->src), &lb6, sizeof(in6_addr)))) { 1158 | counts->skipped++; 1159 | return NFCT_CB_CONTINUE; 1160 | } 1161 | 1162 | if (flags->skipdns && (entry->dstpt == 53)) { 1163 | counts->skipped++; 1164 | return NFCT_CB_CONTINUE; 1165 | } 1166 | 1167 | if (flags->filter_src && !filters->has_srcnet) { 1168 | if ((flags->filter_inv && !memcmp(&(entry->src), &(filters->src), entrysize)) || 1169 | (!flags->filter_inv && memcmp(&(entry->src), &(filters->src), entrysize))) { 1170 | counts->skipped++; 1171 | return NFCT_CB_CONTINUE; 1172 | } 1173 | } 1174 | 1175 | if (flags->filter_src && filters->has_srcnet) { 1176 | if ((flags->filter_inv && match_netmask(entry->family, entry->src, filters->src, filters->srcnet)) || 1177 | (!flags->filter_inv && !match_netmask(entry->family, entry->src, filters->src, filters->srcnet))) { 1178 | counts->skipped++; 1179 | return NFCT_CB_CONTINUE; 1180 | } 1181 | } 1182 | 1183 | if (flags->filter_srcpt) { 1184 | if ((flags->filter_inv && entry->srcpt == filters->srcpt) || 1185 | (!flags->filter_inv && entry->srcpt != filters->srcpt)) { 1186 | counts->skipped++; 1187 | return NFCT_CB_CONTINUE; 1188 | } 1189 | } 1190 | 1191 | if (flags->filter_dst && !filters->has_dstnet) { 1192 | if ((flags->filter_inv && !memcmp(&(entry->dst), &(filters->dst), entrysize)) || 1193 | (!flags->filter_inv && memcmp(&(entry->dst), &(filters->dst), entrysize))) { 1194 | counts->skipped++; 1195 | return NFCT_CB_CONTINUE; 1196 | } 1197 | } 1198 | 1199 | if (flags->filter_dst && filters->has_dstnet) { 1200 | if ((flags->filter_inv && match_netmask(entry->family, entry->dst, filters->dst, filters->dstnet)) || 1201 | (!flags->filter_inv && !match_netmask(entry->family, entry->dst, filters->dst, filters->dstnet))) { 1202 | counts->skipped++; 1203 | return NFCT_CB_CONTINUE; 1204 | } 1205 | } 1206 | 1207 | if (flags->filter_dstpt) { 1208 | if ((flags->filter_inv && entry->dstpt == filters->dstpt) || 1209 | (!flags->filter_inv && entry->dstpt != filters->dstpt)) { 1210 | counts->skipped++; 1211 | return NFCT_CB_CONTINUE; 1212 | } 1213 | } 1214 | 1215 | /* 1216 | * RESOLVE 1217 | */ 1218 | 1219 | // Resolve names - if necessary - or generate strings of address, 1220 | // and calculate max sizes 1221 | stringify_entry(entry, *max, *flags); 1222 | 1223 | /* 1224 | * Add this to the array 1225 | */ 1226 | stable->push_back(entry); 1227 | 1228 | return NFCT_CB_CONTINUE; 1229 | } 1230 | 1231 | /** 1232 | * Nuke the tentry_t's we made before deleting the vector of pointers 1233 | */ 1234 | void clear_table(vector &stable) 1235 | { 1236 | for (tentry_t* entry: stable) { 1237 | delete entry; 1238 | } 1239 | stable.clear(); 1240 | } 1241 | 1242 | 1243 | /* 1244 | * This is the core of this program - build a table of states. 1245 | * 1246 | * For the new libnetfilter_conntrack code, the bulk of build_table was moved 1247 | * to the conntrack callback function. 1248 | */ 1249 | void build_table(flags_t &flags, const filters_t &filters, vector 1250 | &stable, counters_t &counts, max_t &max) 1251 | { 1252 | /* 1253 | * Variables 1254 | */ 1255 | int res=0; 1256 | vector fields(MAXFIELDS); 1257 | static struct nfct_handle *cth; 1258 | u_int8_t family = AF_UNSPEC; 1259 | 1260 | /* 1261 | * This is the ugly struct for the nfct hook, that holds pointers to 1262 | * all of the things the callback will need to fill our table 1263 | */ 1264 | struct hook_data hook; 1265 | hook.stable = &stable; 1266 | hook.flags = &flags; 1267 | hook.max = &max; 1268 | hook.counts = &counts; 1269 | hook.filters = &filters; 1270 | 1271 | /* 1272 | * Initialization 1273 | */ 1274 | clear_table(stable); 1275 | 1276 | counts.tcp = counts.udp = counts.icmp = counts.other = counts.skipped = 0; 1277 | 1278 | cth = nfct_open(CONNTRACK, 0); 1279 | if (!cth) { 1280 | end_curses(); 1281 | printf("ERROR: couldn't establish conntrack connection\n"); 1282 | exit(2); 1283 | } 1284 | nfct_callback_register(cth, NFCT_T_ALL, conntrack_hook, (void *)&hook); 1285 | res = nfct_query(cth, NFCT_Q_DUMP, &family); 1286 | if (res < 0) { 1287 | end_curses(); 1288 | printf("ERROR: Couldn't retreive conntrack table: %s\n", strerror(errno)); 1289 | exit(2); 1290 | } 1291 | nfct_close(cth); 1292 | } 1293 | 1294 | /* 1295 | * This sorts the table based on the current sorting preference 1296 | */ 1297 | void sort_table(const int &sortby, const bool &lookup, const int &sort_factor, 1298 | vector &stable, string &sorting) 1299 | { 1300 | switch (sortby) { 1301 | case SORT_SRC: 1302 | if (lookup) { 1303 | std::sort(stable.begin(), stable.end(), srcname_sort); 1304 | sorting = "SrcName"; 1305 | } else { 1306 | std::sort(stable.begin(), stable.end(), src_sort); 1307 | sorting = "SrcIP"; 1308 | } 1309 | break; 1310 | 1311 | case SORT_SRC_PT: 1312 | std::sort(stable.begin(), stable.end(), srcpt_sort); 1313 | sorting = "SrcPort"; 1314 | break; 1315 | 1316 | case SORT_DST: 1317 | if (lookup) { 1318 | std::sort(stable.begin(), stable.end(), dstname_sort); 1319 | sorting = "DstName"; 1320 | } else { 1321 | std::sort(stable.begin(), stable.end(), dst_sort); 1322 | sorting = "DstIP"; 1323 | } 1324 | break; 1325 | 1326 | case SORT_DST_PT: 1327 | std::sort(stable.begin(), stable.end(), dstpt_sort); 1328 | sorting = "DstPort"; 1329 | break; 1330 | 1331 | case SORT_PROTO: 1332 | std::sort(stable.begin(), stable.end(), proto_sort); 1333 | sorting = "Prt"; 1334 | break; 1335 | 1336 | case SORT_STATE: 1337 | std::sort(stable.begin(), stable.end(), state_sort); 1338 | sorting = "State"; 1339 | break; 1340 | 1341 | case SORT_TTL: 1342 | std::sort(stable.begin(), stable.end(), ttl_sort); 1343 | sorting = "TTL"; 1344 | break; 1345 | 1346 | case SORT_BYTES: 1347 | std::sort(stable.begin(), stable.end(), bytes_sort); 1348 | sorting = "Bytes"; 1349 | break; 1350 | 1351 | case SORT_PACKETS: 1352 | std::sort(stable.begin(), stable.end(), packets_sort); 1353 | sorting = "Packets"; 1354 | break; 1355 | 1356 | default: 1357 | //we should never get here 1358 | sorting = "??unknown??"; 1359 | break; 1360 | 1361 | } //switch 1362 | 1363 | if (sort_factor == -1) 1364 | sorting = sorting + " reverse"; 1365 | 1366 | } 1367 | 1368 | void print_headers(const flags_t &flags, const string &format, 1369 | const string &sorting, const filters_t &filters, 1370 | const counters_t &counts, const screensize_t &ssize, 1371 | int table_size, WINDOW *mainwin) 1372 | { 1373 | if (flags.single) { 1374 | cout << "IP Tables State Top -- Sort by: " << sorting << endl; 1375 | } else { 1376 | wmove(mainwin, 0, 0); 1377 | wclrtoeol(mainwin); 1378 | wmove(mainwin,0, ssize.x/2-15); 1379 | wattron(mainwin, A_BOLD); 1380 | wprintw(mainwin, "IPTState - IPTables State Top\n"); 1381 | 1382 | wprintw(mainwin, "Version: "); 1383 | wattroff(mainwin, A_BOLD); 1384 | wprintw(mainwin, "%-13s", VERSION); 1385 | 1386 | wattron(mainwin, A_BOLD); 1387 | wprintw(mainwin, "Sort: "); 1388 | wattroff(mainwin, A_BOLD); 1389 | wprintw(mainwin, "%-16s", sorting.c_str()); 1390 | 1391 | wattron(mainwin, A_BOLD); 1392 | wprintw(mainwin, "b"); 1393 | wattroff(mainwin, A_BOLD); 1394 | wprintw(mainwin, "%-19s", ": change sorting"); 1395 | 1396 | wattron(mainwin, A_BOLD); 1397 | wprintw(mainwin, "h"); 1398 | wattroff(mainwin, A_BOLD); 1399 | wprintw(mainwin, "%-s\n", ": help"); 1400 | } 1401 | 1402 | /* 1403 | * If enabled, print totals 1404 | */ 1405 | if (flags.totals) { 1406 | if (flags.single) 1407 | printf(TOTALS_FORMAT, table_size+counts.skipped, counts.tcp, counts.udp, 1408 | counts.icmp, counts.other, counts.skipped); 1409 | else 1410 | wprintw(mainwin, TOTALS_FORMAT, table_size+counts.skipped, counts.tcp, 1411 | counts.udp, counts.icmp, counts.other, counts.skipped); 1412 | } 1413 | 1414 | /* 1415 | * If any, print filters 1416 | */ 1417 | char tmp[NAMELEN]; 1418 | if (flags.filter_src || flags.filter_dst || flags.filter_srcpt 1419 | || flags.filter_dstpt) { 1420 | 1421 | if (flags.single) { 1422 | printf("Filters: "); 1423 | } else { 1424 | wattron(mainwin, A_BOLD); 1425 | wprintw(mainwin, "Filters: "); 1426 | wattroff(mainwin, A_BOLD); 1427 | } 1428 | 1429 | bool printed_a_filter = false; 1430 | 1431 | if (flags.filter_src) { 1432 | inet_ntop(filters.srcfam, &filters.src, tmp, NAMELEN-1); 1433 | if (flags.single) 1434 | printf("src: %s", tmp); 1435 | else 1436 | wprintw(mainwin, "src: %s", tmp); 1437 | if (filters.has_srcnet) { 1438 | if (flags.single) 1439 | printf("/%" PRIu8, filters.srcnet); 1440 | else 1441 | wprintw(mainwin, "/%" PRIu8, filters.srcnet); 1442 | } 1443 | printed_a_filter = true; 1444 | } 1445 | if (flags.filter_srcpt) { 1446 | if (printed_a_filter) { 1447 | if (flags.single) 1448 | printf(", "); 1449 | else 1450 | waddstr(mainwin, ", "); 1451 | } 1452 | if (flags.single) 1453 | printf("sport: %lu", filters.srcpt); 1454 | else 1455 | wprintw(mainwin, "sport: %lu", filters.srcpt); 1456 | printed_a_filter = true; 1457 | } 1458 | if (flags.filter_dst) { 1459 | if (printed_a_filter) { 1460 | if (flags.single) 1461 | printf(", "); 1462 | else 1463 | waddstr(mainwin, ", "); 1464 | } 1465 | inet_ntop(filters.dstfam, &filters.dst, tmp, NAMELEN-1); 1466 | if (flags.single) 1467 | printf("dst: %s", tmp); 1468 | else 1469 | wprintw(mainwin, "dst: %s", tmp); 1470 | if (filters.has_dstnet) { 1471 | if (flags.single) 1472 | printf("/%" PRIu8, filters.dstnet); 1473 | else 1474 | wprintw(mainwin, "/%" PRIu8, filters.dstnet); 1475 | } 1476 | printed_a_filter = true; 1477 | } 1478 | if (flags.filter_dstpt) { 1479 | if (printed_a_filter) { 1480 | if (flags.single) 1481 | printf(", "); 1482 | else 1483 | waddstr(mainwin, ", "); 1484 | } 1485 | if (flags.single) 1486 | printf("dport: %lu", filters.dstpt); 1487 | else 1488 | wprintw(mainwin, "dport: %lu", filters.dstpt); 1489 | printed_a_filter = true; 1490 | } 1491 | if (flags.filter_inv) { 1492 | if (flags.single) { 1493 | printf(" (Inverted)"); 1494 | } else { 1495 | wprintw(mainwin, " (Inverted)"); 1496 | } 1497 | } 1498 | if (flags.single) 1499 | printf("\n"); 1500 | else 1501 | wprintw(mainwin, "\n"); 1502 | } 1503 | 1504 | /* 1505 | * Print column headers 1506 | */ 1507 | if (flags.single) { 1508 | if (flags.counters) 1509 | printf(format.c_str(), "Source", "Destination", "Prt", "State", "TTL", 1510 | "B", "P"); 1511 | else 1512 | printf(format.c_str(), "Source", "Destination", "Prt", "State", "TTL"); 1513 | } else { 1514 | wattron(mainwin, A_BOLD); 1515 | if (flags.counters) 1516 | wprintw(mainwin, format.c_str(), "Source", "Destination", "Prt", 1517 | "State", "TTL", "B", "P"); 1518 | else 1519 | wprintw(mainwin, format.c_str(), "Source", "Destination", "Prt", 1520 | "State", "TTL"); 1521 | wattroff(mainwin, A_BOLD); 1522 | } 1523 | 1524 | } 1525 | 1526 | 1527 | void truncate(string &string, int length, bool mark, char direction) 1528 | { 1529 | int s = (direction == 'f') ? string.size() - length : 0; 1530 | 1531 | string = string.substr(s, length); 1532 | if (mark) { 1533 | int m = (direction == 'f') ? 0 : string.size() - 1; 1534 | string[m] = '+'; 1535 | } 1536 | } 1537 | 1538 | /* 1539 | * Based on the format pre-chosen, truncate src/dst as needed, and then 1540 | * generate the host:port strings and drop them off in the src/dst string 1541 | * objects passed in. 1542 | */ 1543 | void format_src_dst(tentry_t *table, string &src, string &dst, 1544 | const flags_t &flags, const max_t &max) 1545 | { 1546 | ostringstream buffer; 1547 | bool have_port = table->proto == "tcp" || table->proto == "udp"; 1548 | char direction; 1549 | unsigned int length; 1550 | 1551 | // What length would we currently use? 1552 | length = table->sname.size(); 1553 | if (have_port) 1554 | length += table->spname.size() + 1; 1555 | 1556 | // If it's too long, figure out how room we have and truncate it 1557 | if (length > max.src) { 1558 | length = max.src; 1559 | if (have_port) 1560 | length -= 1 + table->spname.size(); 1561 | direction = (flags.lookup) ? 'e' : 'f'; 1562 | truncate(table->sname, length, flags.tag_truncate, direction); 1563 | } 1564 | 1565 | // ... and repeat 1566 | length = table->dname.size(); 1567 | if (have_port) 1568 | length += table->dpname.size() + 1; 1569 | if (length > max.dst) { 1570 | length = max.dst; 1571 | if (have_port) 1572 | length -= 1 + table->dpname.size(); 1573 | direction = (flags.lookup) ? 'f' : 'e'; 1574 | truncate(table->dname, length, flags.tag_truncate, direction); 1575 | } 1576 | 1577 | buffer << table->sname; 1578 | if (have_port) 1579 | buffer << ":" << table->spname; 1580 | src = buffer.str(); 1581 | buffer.str(""); 1582 | buffer << table->dname; 1583 | if (have_port) 1584 | buffer << ":" << table->dpname; 1585 | dst = buffer.str(); 1586 | buffer.str(""); 1587 | } 1588 | 1589 | /* 1590 | * An abstraction of priting a line for both single/curses modes 1591 | */ 1592 | void printline(tentry_t *table, const flags_t &flags, const string &format, 1593 | const max_t &max, WINDOW *mainwin, const bool curr) 1594 | { 1595 | ostringstream buffer; 1596 | buffer.str(""); 1597 | string src, dst, b, p; 1598 | 1599 | // Generate strings for src/dest, truncating and marking as necessary 1600 | format_src_dst(table, src, dst, flags, max); 1601 | 1602 | if (flags.counters) { 1603 | buffer << table->bytes; 1604 | b = buffer.str(); 1605 | buffer.str(""); 1606 | buffer << table->packets; 1607 | p = buffer.str(); 1608 | buffer.str(""); 1609 | } 1610 | 1611 | if (flags.single) { 1612 | if (flags.counters) 1613 | printf(format.c_str(), src.c_str(), dst.c_str(), table->proto.c_str(), 1614 | table->state.c_str(), table->ttl.c_str(), b.c_str(), p.c_str()); 1615 | else 1616 | printf(format.c_str(), src.c_str(), dst.c_str(), table->proto.c_str(), 1617 | table->state.c_str(), table->ttl.c_str()); 1618 | } else { 1619 | int color = 0; 1620 | if (!flags.nocolor) { 1621 | if (table->proto == "tcp") 1622 | color = 1; 1623 | else if (table->proto == "udp") 1624 | color = 2; 1625 | else if (table->proto == "icmp" || table->proto == "icmp6") 1626 | color = 3; 1627 | if (curr) 1628 | color += 4; 1629 | wattron(mainwin, COLOR_PAIR(color)); 1630 | } 1631 | if (flags.counters) 1632 | wprintw(mainwin, format.c_str(), src.c_str(), dst.c_str(), 1633 | table->proto.c_str(), table->state.c_str(), table->ttl.c_str(), 1634 | b.c_str(), p.c_str()); 1635 | else 1636 | wprintw(mainwin, format.c_str(), src.c_str(), dst.c_str(), 1637 | table->proto.c_str(), table->state.c_str(), table->ttl.c_str()); 1638 | 1639 | if (!flags.nocolor && color != 0) 1640 | wattroff(mainwin, COLOR_PAIR(color)); 1641 | } 1642 | } 1643 | 1644 | /* 1645 | * This does all the work of actually printing the table including 1646 | * various bits of formatting. It handles both curses and non-curses runs. 1647 | */ 1648 | void print_table(vector &stable, const flags_t &flags, 1649 | const string &format, const string &sorting, 1650 | const filters_t &filters, const counters_t &counts, 1651 | const screensize_t &ssize, const max_t &max, WINDOW *mainwin, 1652 | unsigned int &curr) 1653 | { 1654 | /* 1655 | * Print headers 1656 | */ 1657 | print_headers(flags, format, sorting, filters, counts, ssize, stable.size(), 1658 | mainwin); 1659 | 1660 | /* 1661 | * Print the state table 1662 | */ 1663 | unsigned int limit = (stable.size() < NLINES) ? stable.size() : NLINES; 1664 | for (unsigned int tmpint=0; tmpint < limit; tmpint++) { 1665 | printline(stable[tmpint], flags, format, max, mainwin, (curr == tmpint)); 1666 | if (!flags.single && flags.noscroll 1667 | && (tmpint >= ssize.y-4 || (flags.totals && tmpint >= ssize.y-5))) 1668 | break; 1669 | 1670 | } 1671 | 1672 | /* 1673 | * We don't want to lave things on the screen we didn't draw 1674 | * this time. 1675 | */ 1676 | if (!flags.single) 1677 | wclrtobot(mainwin); 1678 | 1679 | } 1680 | 1681 | /* 1682 | * Dynamically build a format to fit the most amount of data on the screen 1683 | */ 1684 | void determine_format(WINDOW *mainwin, max_t &max, screensize_t &ssize, 1685 | string &format, flags_t &flags) 1686 | { 1687 | 1688 | /* 1689 | * NOTE: When doing proper dynamic format building, we fill the 1690 | * entire screen, so curses puts in a newline for us. However 1691 | * with "staticsize" we must add a newline. Also with "single" 1692 | * mode we must add it as well since there's no curses there. 1693 | * 1694 | * Thus DEFAULT_FORMAT (only used for staticsize) has it, and 1695 | * at the bottom of this function we add a \n if flags.single 1696 | * is set. 1697 | */ 1698 | if (flags.staticsize) { 1699 | format = DEFAULT_FORMAT; 1700 | max.src = DEFAULT_SRC; 1701 | max.dst = DEFAULT_DST; 1702 | max.proto = DEFAULT_PROTO; 1703 | max.state = DEFAULT_STATE; 1704 | max.ttl = DEFAULT_TTL; 1705 | return; 1706 | } 1707 | 1708 | ssize = get_size(flags.single); 1709 | 1710 | /* The screen must be 85 chars wide to be able to fit in 1711 | * counters when we display IP addresses... 1712 | * 1713 | * in lookup mode, we can truncate names, but in IP mode, 1714 | * truncation makes no sense, so we just disable counters if 1715 | * we run into this. 1716 | */ 1717 | if (ssize.x < 85 && flags.counters && !flags.lookup) { 1718 | string prompt = "Window too narrow for counters! Disabling."; 1719 | if (flags.single) 1720 | cerr << prompt << endl; 1721 | else 1722 | c_warn(mainwin, prompt, flags); 1723 | flags.counters = false; 1724 | } 1725 | 1726 | /* what's left is the above three, plus 4 spaces 1727 | * (one between each of 5 fields) 1728 | */ 1729 | unsigned int left = ssize.x - max.ttl - max.state - max.proto - 4; 1730 | if (flags.counters) 1731 | left -= (max.bytes + max.packets + 2); 1732 | 1733 | /* 1734 | * The rest is *prolly* going to be divided between src 1735 | * and dst, so we see if that works. If 'left' is odd though 1736 | * we give the extra space to src. 1737 | */ 1738 | unsigned int src, dst; 1739 | src = dst = left / 2; 1740 | bool left_odd = false; 1741 | if ((left % 2) == 1) { 1742 | left_odd = true; 1743 | src++; 1744 | } 1745 | if ((max.src + max.dst) < left) { 1746 | /* 1747 | * This means we can fit without an truncation, but it doesn't 1748 | * necessarily mean that we can just give half to src and half 1749 | * to dst... so lets figure that out. 1750 | */ 1751 | 1752 | if (max.src < src && max.dst < dst) { 1753 | /* 1754 | * This case applies if: 1755 | * we're even and they both fit in left/2 1756 | * OR 1757 | * we're odd and dst fits in left/2 1758 | * and src fits in left/2+1 1759 | * 1760 | * Since we've already calculated src/dst that way 1761 | * we just combine this check as they both require 1762 | * the same outcome. 1763 | */ 1764 | } else if (left_odd && (src < left / 2) && (dst < left / 2 + 1)) { 1765 | /* 1766 | * If src can fit in left/2 and dst in left/2+1 1767 | * then we switch them. 1768 | */ 1769 | src = dst; 1770 | dst++; 1771 | } else if (max.src > max.dst) { 1772 | /* 1773 | * If we're here, we can fit them, but we can't fit them 1774 | * and still keep the two columns relatively equal. Ah 1775 | * well. 1776 | * 1777 | * Either max gets the bigger chunk and everything else 1778 | * to dst... 1779 | */ 1780 | src = max.src; 1781 | dst = left - max.src; 1782 | } else { 1783 | /* 1784 | * ...or the other way around 1785 | */ 1786 | dst = max.dst; 1787 | src = left - max.dst; 1788 | } 1789 | } else if (max.src < src) { 1790 | /* 1791 | * If we're here, we do have to truncate, but if one column is 1792 | * very small, we should not give it more space than it needs. 1793 | */ 1794 | src = max.src; 1795 | dst = left - max.src; 1796 | } else if (max.dst < dst) { 1797 | /* 1798 | * same as above. 1799 | */ 1800 | dst = max.dst; 1801 | src = left - max.dst; 1802 | } 1803 | 1804 | /* 1805 | * If nothing matched, then they're both bigger than left/2, so we'll 1806 | * leave the default we set above. 1807 | */ 1808 | 1809 | ostringstream buffer; 1810 | buffer << "\%-" << src << "s \%-" << dst << "s \%-" << max.proto << "s \%-" 1811 | << max.state << "s \%-" << max.ttl << "s"; 1812 | 1813 | if (flags.counters) 1814 | buffer << " \%-" << max.bytes << "s \%-" << max.packets << "s"; 1815 | 1816 | if (flags.single) 1817 | buffer << "\n"; 1818 | 1819 | format = buffer.str(); 1820 | 1821 | max.dst = dst; 1822 | max.src = src; 1823 | } 1824 | 1825 | /* 1826 | * Interactive help 1827 | */ 1828 | void interactive_help(const string &sorting, const flags_t &flags, 1829 | const filters_t &filters) 1830 | { 1831 | 1832 | /* 1833 | * This is the max we need the pad to be, and thus how 1834 | * big we're going to create the pad. 1835 | * 1836 | * In many cases we'd make the pad very very large and not 1837 | * worry about it. However, in this case, we want to draw 1838 | * a "box" around the window and if the pad is huge then 1839 | * the box will get drawn around that. 1840 | * 1841 | * So... we have 41 lines of help, plus a top and bottom border, 1842 | * thus maxrows is 43. We also need to account for the filter settings 1843 | * that are only being displayed when enabled. 1844 | * 1845 | * Our help text is not wider than 80, so we'll set that standard 1846 | * width. 1847 | * 1848 | * If the screen is bigger than this, we deal with it below. 1849 | */ 1850 | unsigned int maxrows = 43; 1851 | unsigned int maxcols = 80; 1852 | 1853 | // Acount for dynamic filter settings 1854 | maxrows += flags.filter_src + flags.filter_srcpt + flags.filter_dst + flags.filter_dstpt; 1855 | 1856 | /* 1857 | * The actual screen size 1858 | */ 1859 | screensize_t ssize = get_size(flags.single); 1860 | 1861 | /* 1862 | * If the biggest we think we'll need is smaller than the screen, 1863 | * then lets grow the pad to the size of the screen so that the 1864 | * main window isn't peeking through. 1865 | */ 1866 | if (maxrows < ssize.y) 1867 | maxrows = ssize.y; 1868 | if (maxcols < ssize.x) 1869 | maxcols = ssize.x; 1870 | 1871 | /* 1872 | * Where we are withing the pad (for printing). We can't just print 1873 | * newlines and expect it to work. Cause, well, it doesn't. You have 1874 | * to tell it where on the pad to print, specifically. 1875 | */ 1876 | unsigned int x, y; 1877 | x = y = 0; 1878 | 1879 | /* 1880 | * The current position on the pad we're showing (top left) 1881 | */ 1882 | unsigned int px, py; 1883 | px = py = 0; 1884 | 1885 | /* 1886 | * As noted above, we create the biggest pad we might need 1887 | */ 1888 | static WINDOW *helpwin; 1889 | helpwin = newpad(maxrows, maxcols); 1890 | 1891 | /* 1892 | * Create a box, and then add one to "x" and "y" so we don't write 1893 | * on the line, 1894 | */ 1895 | box(helpwin, ACS_VLINE, ACS_HLINE); 1896 | x++; 1897 | y++; 1898 | 1899 | 1900 | /* 1901 | * we want arrow keys to work 1902 | */ 1903 | keypad(helpwin, true); 1904 | 1905 | // Prolly not needed 1906 | wmove(helpwin, 0, 0); 1907 | 1908 | // Print opener 1909 | wattron(helpwin, A_BOLD); 1910 | mvwaddstr(helpwin, y++, x, "IPTState "); 1911 | waddstr(helpwin, VERSION); 1912 | wattroff(helpwin, A_BOLD); 1913 | // this is \n 1914 | y++; 1915 | 1916 | // We don't want anything except the title up against the 1917 | // border 1918 | x++; 1919 | 1920 | string nav = "Up/j, Down/k, Left/h, Right/l, PageUp/^u, PageDown/^d, "; 1921 | nav += " Home, or End"; 1922 | // Print instructions first 1923 | mvwaddstr(helpwin, y++, x, "Navigation:"); 1924 | mvwaddstr(helpwin, y++, x, nav.c_str()); 1925 | mvwaddstr(helpwin, y++, x, " Press any other key to continue..."); 1926 | y++; 1927 | 1928 | // Print settings 1929 | mvwaddstr(helpwin, y++, x, "Current settings:"); 1930 | 1931 | mvwaddstr(helpwin, y++, x, " Sorting by: "); 1932 | wattron(helpwin, A_BOLD); 1933 | waddstr(helpwin, sorting.c_str()); 1934 | wattroff(helpwin, A_BOLD); 1935 | 1936 | mvwaddstr(helpwin, y++, x, " Dynamic formatting: "); 1937 | wattron(helpwin, A_BOLD); 1938 | waddstr(helpwin,(!flags.staticsize) ? "yes" : "no"); 1939 | wattroff(helpwin, A_BOLD); 1940 | 1941 | mvwaddstr(helpwin, y++, x, " Skip loopback states: "); 1942 | wattron(helpwin, A_BOLD); 1943 | waddstr(helpwin,(flags.skiplb) ? "yes" : "no"); 1944 | wattroff(helpwin, A_BOLD); 1945 | 1946 | mvwaddstr(helpwin, y++, x, " Resolve hostnames: "); 1947 | wattron(helpwin, A_BOLD); 1948 | waddstr(helpwin,(flags.lookup) ? "yes" : "no"); 1949 | wattroff(helpwin, A_BOLD); 1950 | 1951 | mvwaddstr(helpwin, y++, x, " Mark truncated hostnames: "); 1952 | wattron(helpwin, A_BOLD); 1953 | waddstr(helpwin,(flags.tag_truncate) ? "yes" : "no"); 1954 | wattroff(helpwin, A_BOLD); 1955 | 1956 | mvwaddstr(helpwin, y++, x, " Colors: "); 1957 | wattron(helpwin, A_BOLD); 1958 | waddstr(helpwin,(!flags.nocolor) ? "yes" : "no"); 1959 | wattroff(helpwin, A_BOLD); 1960 | 1961 | mvwaddstr(helpwin, y++, x, " Skip outgoing DNS lookup states: "); 1962 | wattron(helpwin, A_BOLD); 1963 | waddstr(helpwin,(flags.skipdns) ? "yes" : "no"); 1964 | wattroff(helpwin, A_BOLD); 1965 | 1966 | mvwaddstr(helpwin, y++, x, " Enable scroll: "); 1967 | wattron(helpwin, A_BOLD); 1968 | waddstr(helpwin,(!flags.noscroll) ? "yes" : "no"); 1969 | wattroff(helpwin, A_BOLD); 1970 | 1971 | mvwaddstr(helpwin, y++, x, " Display totals: "); 1972 | wattron(helpwin, A_BOLD); 1973 | waddstr(helpwin,(flags.totals) ? "yes" : "no"); 1974 | wattroff(helpwin, A_BOLD); 1975 | 1976 | mvwaddstr(helpwin, y++, x, " Display counters: "); 1977 | wattron(helpwin, A_BOLD); 1978 | waddstr(helpwin,(flags.counters) ? "yes" : "no"); 1979 | wattroff(helpwin, A_BOLD); 1980 | 1981 | mvwaddstr(helpwin, y++, x, " Invert filters: "); 1982 | wattron(helpwin, A_BOLD); 1983 | waddstr(helpwin,(flags.filter_inv) ? "yes" : "no"); 1984 | wattroff(helpwin, A_BOLD); 1985 | 1986 | char tmp[NAMELEN]; 1987 | if (flags.filter_src) { 1988 | inet_ntop(filters.srcfam, &filters.src, tmp, NAMELEN-1); 1989 | mvwaddstr(helpwin, y++, x, " Source filter: "); 1990 | wattron(helpwin, A_BOLD); 1991 | waddstr(helpwin, tmp); 1992 | if (filters.has_srcnet) 1993 | wprintw(helpwin, "/%" PRIu8, filters.srcnet); 1994 | wattroff(helpwin, A_BOLD); 1995 | } 1996 | if (flags.filter_dst) { 1997 | inet_ntop(filters.dstfam, &filters.dst, tmp, NAMELEN-1); 1998 | mvwaddstr(helpwin, y++, x, " Destination filter: "); 1999 | wattron(helpwin, A_BOLD); 2000 | waddstr(helpwin, tmp); 2001 | if (filters.has_dstnet) 2002 | wprintw(helpwin, "/%" PRIu8, filters.dstnet); 2003 | wattroff(helpwin, A_BOLD); 2004 | } 2005 | if (flags.filter_srcpt) { 2006 | mvwaddstr(helpwin, y++, x, " Source port filter: "); 2007 | wattron(helpwin, A_BOLD); 2008 | wprintw(helpwin, "%lu", filters.srcpt); 2009 | wattroff(helpwin, A_BOLD); 2010 | } 2011 | if (flags.filter_dstpt) { 2012 | mvwaddstr(helpwin, y++, x, " Destination port filter: "); 2013 | wattron(helpwin, A_BOLD); 2014 | wprintw(helpwin, "%lu", filters.dstpt); 2015 | wattroff(helpwin, A_BOLD); 2016 | } 2017 | 2018 | y++; 2019 | 2020 | // Print commands 2021 | mvwaddstr(helpwin, y++, x, "Interactive commands:"); 2022 | 2023 | wattron(helpwin, A_BOLD); 2024 | mvwaddstr(helpwin, y++, x, " c"); 2025 | wattroff(helpwin, A_BOLD); 2026 | waddstr(helpwin, "\tUse colors"); 2027 | 2028 | wattron(helpwin, A_BOLD); 2029 | mvwaddstr(helpwin, y++, x, " C"); 2030 | wattroff(helpwin, A_BOLD); 2031 | waddstr(helpwin, "\tToggle display of bytes/packets counters"); 2032 | 2033 | wattron(helpwin, A_BOLD); 2034 | mvwaddstr(helpwin, y++, x, " b"); 2035 | wattroff(helpwin, A_BOLD); 2036 | waddstr(helpwin, "\tSort by next column"); 2037 | 2038 | wattron(helpwin, A_BOLD); 2039 | mvwaddstr(helpwin, y++, x, " B"); 2040 | wattroff(helpwin, A_BOLD); 2041 | waddstr(helpwin, "\tSort by previous column"); 2042 | 2043 | wattron(helpwin, A_BOLD); 2044 | mvwaddstr(helpwin, y++, x, " d"); 2045 | wattroff(helpwin, A_BOLD); 2046 | waddstr(helpwin, "\tChange destination filter"); 2047 | 2048 | wattron(helpwin, A_BOLD); 2049 | mvwaddstr(helpwin, y++, x, " D"); 2050 | wattroff(helpwin, A_BOLD); 2051 | waddstr(helpwin, "\tChange destination port filter"); 2052 | 2053 | wattron(helpwin, A_BOLD); 2054 | mvwaddstr(helpwin, y++, x, " f"); 2055 | wattroff(helpwin, A_BOLD); 2056 | waddstr(helpwin, "\tToggle display of loopback states"); 2057 | 2058 | wattron(helpwin, A_BOLD); 2059 | mvwaddstr(helpwin, y++, x, " h"); 2060 | wattroff(helpwin, A_BOLD); 2061 | waddstr(helpwin, "\tDisplay this help message"); 2062 | 2063 | wattron(helpwin, A_BOLD); 2064 | mvwaddstr(helpwin, y++, x, " i"); 2065 | wattroff(helpwin, A_BOLD); 2066 | waddstr(helpwin, "\tInvert filters to display non-matching results"); 2067 | 2068 | wattron(helpwin, A_BOLD); 2069 | mvwaddstr(helpwin, y++, x, " l"); 2070 | wattroff(helpwin, A_BOLD); 2071 | waddstr(helpwin, "\tToggle DNS lookups"); 2072 | 2073 | wattron(helpwin, A_BOLD); 2074 | mvwaddstr(helpwin, y++, x, " L"); 2075 | wattroff(helpwin, A_BOLD); 2076 | waddstr(helpwin, "\tToggle display of outgoing DNS states"); 2077 | 2078 | wattron(helpwin, A_BOLD); 2079 | mvwaddstr(helpwin, y++, x, " m"); 2080 | wattroff(helpwin, A_BOLD); 2081 | waddstr(helpwin, "\tToggle marking truncated hostnames with a '+'"); 2082 | 2083 | wattron(helpwin, A_BOLD); 2084 | mvwaddstr(helpwin, y++, x, " o"); 2085 | wattroff(helpwin, A_BOLD); 2086 | waddstr(helpwin, "\tToggle dynamic or old formatting"); 2087 | 2088 | wattron(helpwin, A_BOLD); 2089 | mvwaddstr(helpwin, y++, x, " p"); 2090 | wattroff(helpwin, A_BOLD); 2091 | waddstr(helpwin, "\tToggle scrolling"); 2092 | 2093 | wattron(helpwin, A_BOLD); 2094 | mvwaddstr(helpwin, y++, x, " q"); 2095 | wattroff(helpwin, A_BOLD); 2096 | waddstr(helpwin, "\tQuit"); 2097 | 2098 | wattron(helpwin, A_BOLD); 2099 | mvwaddstr(helpwin, y++, x, " r"); 2100 | wattroff(helpwin, A_BOLD); 2101 | waddstr(helpwin, "\tToggle reverse sorting"); 2102 | 2103 | wattron(helpwin, A_BOLD); 2104 | mvwaddstr(helpwin, y++, x, " R"); 2105 | wattroff(helpwin, A_BOLD); 2106 | waddstr(helpwin, "\tChange the refresh rate"); 2107 | 2108 | wattron(helpwin, A_BOLD); 2109 | mvwaddstr(helpwin, y++, x, " s"); 2110 | wattroff(helpwin, A_BOLD); 2111 | waddstr(helpwin, "\tChange source filter"); 2112 | 2113 | wattron(helpwin, A_BOLD); 2114 | mvwaddstr(helpwin, y++, x, " S"); 2115 | wattroff(helpwin, A_BOLD); 2116 | waddstr(helpwin, "\tChange source port filter"); 2117 | 2118 | wattron(helpwin, A_BOLD); 2119 | mvwaddstr(helpwin, y++, x, " t"); 2120 | wattroff(helpwin, A_BOLD); 2121 | waddstr(helpwin, "\tToggle display of totals"); 2122 | 2123 | wattron(helpwin, A_BOLD); 2124 | mvwaddstr(helpwin, y++, x, " x"); 2125 | wattroff(helpwin, A_BOLD); 2126 | waddstr(helpwin, "\tDelete the currently highlighted state from netfilter"); 2127 | 2128 | y++; 2129 | 2130 | wmove(helpwin, 0, 0); 2131 | 2132 | /* 2133 | * refresh from wherever we are the pad 2134 | * and the top of the window to the bottom of the window. 2135 | */ 2136 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2137 | // kill line buffering 2138 | cbreak(); 2139 | // nodelay with a 0 here causes getch() to block until key is pressed. 2140 | nodelay( helpwin, 0 ); 2141 | int c; 2142 | while ((c = wgetch(helpwin))) { 2143 | switch (c) { 2144 | case ERR: 2145 | continue; 2146 | break; 2147 | case KEY_DOWN: 2148 | case 'j': 2149 | /* 2150 | * py is the top of the window, 2151 | * ssize.y is the height of the window, 2152 | * so py+ssize.y is the bottom of the window. 2153 | * 2154 | * Since y is the bottom of the text we've 2155 | * written, if 2156 | * py+ssize.y == y 2157 | * then the bottom of the screen as at the 2158 | * bottom of the text, no more scrolling. 2159 | */ 2160 | if (py + ssize.y < y) 2161 | py++; 2162 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2163 | break; 2164 | case KEY_UP: 2165 | case 'k': 2166 | if (py > 0) 2167 | py--; 2168 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2169 | break; 2170 | case KEY_RIGHT: 2171 | case 'l': 2172 | /* 2173 | * px is the left of the window, 2174 | * ssize.x is the width of the window, 2175 | * so px+ssize.x os the right side of the window. 2176 | * 2177 | * So if px+ssize.x == 80 (more than the width 2178 | * of our text), no more scrolling. 2179 | */ 2180 | if (px + ssize.x < 80) 2181 | px++; 2182 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2183 | break; 2184 | case KEY_LEFT: 2185 | case 'h': 2186 | if (px > 0) 2187 | px--; 2188 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2189 | break; 2190 | case KEY_HOME: 2191 | case KEY_SHOME: 2192 | case KEY_FIND: 2193 | px = py = 0; 2194 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2195 | break; 2196 | case KEY_END: 2197 | py = y-ssize.y; 2198 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2199 | break; 2200 | case 4: 2201 | case KEY_NPAGE: 2202 | case KEY_SNEXT: 2203 | if (flags.noscroll) 2204 | break; 2205 | /* 2206 | * If the screen is bigger than the text, 2207 | * ignore 2208 | */ 2209 | if (y < ssize.y) 2210 | break; 2211 | /* 2212 | * Otherwise, if the bottom of the screen 2213 | * (current position + screen size 2214 | * == py + ssize.y) 2215 | * were to go down one screen (thus: 2216 | * py + ssize.y*2), 2217 | * and that is bigger than the whole text, just 2218 | * go to the bottom. 2219 | * 2220 | * Otherwise, go down a screen size. 2221 | */ 2222 | if ((py + (ssize.y * 2)) > y) { 2223 | py = y-ssize.y; 2224 | } else { 2225 | py += ssize.y; 2226 | } 2227 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2228 | break; 2229 | case 21: 2230 | case KEY_PPAGE: 2231 | case KEY_SPREVIOUS: 2232 | if (flags.noscroll) 2233 | break; 2234 | /* 2235 | * If we're at the top, ignore this. 2236 | */ 2237 | if (py == 0) 2238 | break; 2239 | /* 2240 | * Otherwise if we're less than a page from the 2241 | * top, go to the top, else, go up a page. 2242 | */ 2243 | if (py < ssize.y) 2244 | py = 0; 2245 | else 2246 | py -= ssize.y; 2247 | prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2248 | break; 2249 | 2250 | case 'q': 2251 | default: 2252 | goto out; 2253 | break; 2254 | } 2255 | if (need_resize) { 2256 | goto out; 2257 | } 2258 | } 2259 | out: 2260 | // once a key is pressed, tear down the help window. 2261 | delwin(helpwin); 2262 | refresh(); 2263 | halfdelay(1); 2264 | } 2265 | 2266 | /* 2267 | * MAIN 2268 | */ 2269 | int main(int argc, char *argv[]) 2270 | { 2271 | // Use the locale specified by the environment 2272 | setlocale(LC_ALL, ""); 2273 | 2274 | // Variables 2275 | string line, src, dst, srcpt, dstpt, proto, code, type, state, ttl, mins, 2276 | secs, hrs, sorting, tmpstring, format, prompt; 2277 | ostringstream ostream; 2278 | vector stable; 2279 | int tmpint = 0, sortby = 0, rate = 1, hdrs = 0; 2280 | unsigned int py = 0, px = 0, curr_state = 0; 2281 | timeval selecttimeout; 2282 | fd_set readfd; 2283 | flags_t flags; 2284 | counters_t counts; 2285 | screensize_t ssize; 2286 | filters_t filters; 2287 | max_t max; 2288 | 2289 | /* 2290 | * Initialize 2291 | */ 2292 | flags.single = flags.totals = flags.lookup = flags.skiplb = flags.staticsize 2293 | = flags.skipdns = flags.tag_truncate = flags.filter_src 2294 | = flags.filter_dst = flags.filter_srcpt = flags.filter_dstpt 2295 | = flags.noscroll = flags.nocolor = flags.counters = flags.filter_inv = false; 2296 | ssize.x = ssize.y = 0; 2297 | counts.tcp = counts.udp = counts.icmp = counts.other = counts.skipped = 0; 2298 | filters.src = filters.dst = in6addr_any; 2299 | filters.srcpt = filters.dstpt = 0; 2300 | max.src = max.dst = max.proto = max.state = max.ttl = 0; 2301 | px = py = 0; 2302 | 2303 | static struct option long_options[] = { 2304 | {"counters", no_argument , 0, 'C'}, 2305 | {"dst-filter", required_argument, 0, 'd'}, 2306 | {"dstpt-filter", required_argument, 0, 'D'}, 2307 | {"help", no_argument, 0, 'h'}, 2308 | {"invert-filters", no_argument, 0, 'i'}, 2309 | {"lookup", no_argument, 0, 'l'}, 2310 | {"mark-truncated", no_argument, 0, 'm'}, 2311 | {"no-color", no_argument, 0, 'c'}, 2312 | {"no-dynamic", no_argument, 0, 'o'}, 2313 | {"no-dns", no_argument, 0, 'L'}, 2314 | {"no-loopback", no_argument, 0, 'f'}, 2315 | {"no-scroll", no_argument, 0, 'p'}, 2316 | {"rate", required_argument, 0, 'R'}, 2317 | {"reverse", no_argument, 0, 'r'}, 2318 | {"single", no_argument, 0, '1'}, 2319 | {"sort", required_argument, 0, 'b'}, 2320 | {"src-filter", required_argument, 0, 's'}, 2321 | {"srcpt-filter", required_argument, 0, 'S'}, 2322 | {"totals", no_argument, 0, 't'}, 2323 | {"version", no_argument, 0, 'v'}, 2324 | {0, 0, 0,0} 2325 | }; 2326 | int option_index = 0; 2327 | 2328 | // Command Line Arguments 2329 | while ((tmpint = getopt_long(argc, argv, "Cd:D:hilmcoLfpR:r1b:s:S:tv", 2330 | long_options, &option_index)) != EOF) { 2331 | switch (tmpint) { 2332 | case 0: 2333 | /* Apparently this test is needed?! Seems lame! */ 2334 | if (long_options[option_index].flag != 0) 2335 | break; 2336 | 2337 | /* 2338 | * Long-only options go here, like so: 2339 | * 2340 | * tmpstring = long_options[option_index].name; 2341 | * if (tmpstring == "srcpt-filter") { 2342 | * ... 2343 | * } else if (...) { 2344 | * ... 2345 | * } 2346 | * 2347 | */ 2348 | 2349 | break; 2350 | // --counters 2351 | case 'C': 2352 | flags.counters = true; 2353 | break; 2354 | // --dst-filter 2355 | case 'd': 2356 | if (optarg == NULL) 2357 | break; 2358 | // See check_ip() note above 2359 | if (!check_ip(optarg, &filters.dst, &filters.dstfam, &filters.dstnet, &filters.has_dstnet)) { 2360 | cerr << "Invalid IP address: " << optarg 2361 | << endl; 2362 | exit(1); 2363 | } 2364 | flags.filter_dst = true; 2365 | break; 2366 | // --dstpt-filter 2367 | case 'D': 2368 | /* 2369 | * even though this won't be an IP address 2370 | * aton() won't complain about anything that's 2371 | * just digits, so it's an easy check. 2372 | */ 2373 | if (optarg == NULL) 2374 | break; 2375 | flags.filter_dstpt = true; 2376 | filters.dstpt = atoi(optarg); 2377 | break; 2378 | // --invert-filters 2379 | case 'i': 2380 | flags.filter_inv = true; 2381 | break; 2382 | // --help 2383 | case 'h': 2384 | help(); 2385 | break; 2386 | // --lookup 2387 | case 'l': 2388 | flags.lookup = true; 2389 | // and also set skipdns for sanity 2390 | flags.skipdns = true; 2391 | break; 2392 | // --mark-truncated 2393 | case 'm': 2394 | flags.tag_truncate = true; 2395 | break; 2396 | // --color 2397 | case 'c': 2398 | flags.nocolor = false; 2399 | break; 2400 | // --no-dynamic 2401 | case 'o': 2402 | flags.staticsize = true; 2403 | break; 2404 | // --no-dns 2405 | case 'L': 2406 | flags.skipdns = true; 2407 | break; 2408 | // --no-loopback 2409 | case 'f': 2410 | flags.skiplb = true; 2411 | break; 2412 | // --no-scroll 2413 | case 'p': 2414 | flags.noscroll = true; 2415 | break; 2416 | // --reverse 2417 | case 'r': 2418 | sort_factor = -1; 2419 | break; 2420 | // --rate 2421 | case 'R': 2422 | rate = atoi(optarg); 2423 | break; 2424 | // --sort 2425 | case 'b': 2426 | if (*optarg == 'd') 2427 | sortby = SORT_DST; 2428 | else if (*optarg == 'D') { 2429 | sortby = SORT_DST_PT; 2430 | } else if (*optarg == 'S') 2431 | sortby = SORT_SRC_PT; 2432 | else if (*optarg == 'p') 2433 | sortby = SORT_PROTO; 2434 | else if (*optarg == 's') 2435 | sortby = SORT_STATE; 2436 | else if (*optarg == 't') 2437 | sortby = SORT_TTL; 2438 | else if (*optarg == 'b' && flags.counters) 2439 | sortby = SORT_BYTES; 2440 | else if (*optarg == 'P' && flags.counters) 2441 | sortby = SORT_PACKETS; 2442 | break; 2443 | // --single 2444 | case '1': 2445 | flags.single = true; 2446 | break; 2447 | // --src-filter 2448 | case 's': 2449 | if (optarg == NULL) 2450 | break; 2451 | if (!check_ip(optarg, &filters.src, &filters.srcfam, &filters.srcnet, &filters.has_srcnet)) { 2452 | cerr << "Invalid IP address: " << optarg << endl; 2453 | exit(1); 2454 | } 2455 | flags.filter_src = true; 2456 | break; 2457 | // --srcpt-filter 2458 | case 'S': 2459 | if (optarg == NULL) 2460 | break; 2461 | flags.filter_srcpt = true; 2462 | filters.srcpt = atoi(optarg); 2463 | break; 2464 | // --totals 2465 | case 't': 2466 | flags.totals = true; 2467 | break; 2468 | // --version 2469 | case 'v': 2470 | version(); 2471 | exit(0); 2472 | break; 2473 | // catch-all 2474 | default: 2475 | // getopts should already have printed a message 2476 | exit(1); 2477 | break; 2478 | } 2479 | } 2480 | 2481 | if (rate < 0 || rate > 60) { 2482 | rate = 1; 2483 | } 2484 | 2485 | // Initialize Curses Stuff 2486 | static WINDOW *mainwin = NULL; 2487 | if (!flags.single) { 2488 | mainwin = start_curses(flags); 2489 | keypad(mainwin, true); 2490 | } 2491 | 2492 | /* 2493 | * We want to keep going until the user stops us 2494 | * unless they use single run mode 2495 | * in which case, we'll deal with that down below 2496 | */ 2497 | while (1) { 2498 | 2499 | /* 2500 | * We get the screensize_t up-front so we can die if the 2501 | * screen doesn't meet our minimum requirements without making 2502 | * the user wait while we gather and process all the data. 2503 | * We'll do it again afterwards just in case 2504 | */ 2505 | 2506 | ssize = get_size(flags.single); 2507 | 2508 | if (ssize.x < 72) { 2509 | term_too_small(); 2510 | } 2511 | 2512 | // And our header size 2513 | hdrs = 3; 2514 | if (flags.totals) { 2515 | hdrs++; 2516 | } 2517 | if (flags.filter_src || flags.filter_dst || flags.filter_srcpt 2518 | || flags.filter_dstpt) { 2519 | hdrs++; 2520 | } 2521 | 2522 | // clear maxes 2523 | initialize_maxes(max, flags); 2524 | 2525 | // Build our table 2526 | build_table(flags, filters, stable, counts, max); 2527 | 2528 | /* 2529 | * Now that we have the new table, make sure our page/cursor 2530 | * positions still make sense. 2531 | */ 2532 | if (curr_state > stable.size() - 1) { 2533 | curr_state = stable.size() - 1; 2534 | } 2535 | 2536 | /* 2537 | * The bottom of the screen is stable.size()+hdrs+1 2538 | * (the +1 is so we can have a blank line at the end) 2539 | * but we want to never have py be more than that - ssize.y 2540 | * so we're showing a page full of states. 2541 | */ 2542 | int bottom = stable.size() + hdrs + 1 - ssize.y; 2543 | if (bottom < 0) 2544 | bottom = 0; 2545 | if (py > (unsigned)bottom) 2546 | py = bottom; 2547 | 2548 | /* 2549 | * Originally I strived to do this the "right" way by calling 2550 | * nfct_is_set(ct, ATTR_ORIG_COUNGERS) to determine if 2551 | * counters were enabled. BUT, if counters are not enabled, 2552 | * nfct_get_attr() returns NULL, so this test is just as 2553 | * valid. 2554 | * 2555 | * Conversely checking is_set and then get_attr() inside our 2556 | * callback is twice the calls per-state if they are enabled, 2557 | * for no additional benefit. 2558 | */ 2559 | if (flags.counters && stable.size() > 0 && stable[0]->bytes == 0) { 2560 | prompt = "Counters requested, but not enabled in the"; 2561 | prompt += " kernel!"; 2562 | flags.counters = 0; 2563 | if (flags.single) 2564 | cerr << prompt << endl; 2565 | else 2566 | c_warn(mainwin, prompt, flags); 2567 | } 2568 | 2569 | // Sort our table 2570 | sort_table(sortby, flags.lookup, sort_factor, stable, sorting); 2571 | 2572 | /* 2573 | * From here on out 'max' is no longer "the maximum size of 2574 | * this field throughout the table", but is instead the actual 2575 | * size to print each field. 2576 | * 2577 | * BTW, we do "get_size" again here incase the window changed 2578 | * while we were off parsing and sorting data. 2579 | */ 2580 | determine_format(mainwin, max, ssize, format, flags); 2581 | 2582 | /* 2583 | * Now we print out the table in whichever format we're 2584 | * configured for 2585 | */ 2586 | print_table(stable, flags, format, sorting, filters, counts, ssize, max, 2587 | mainwin, curr_state); 2588 | 2589 | // Exit if we're only supposed to run once 2590 | if (flags.single) 2591 | exit(0); 2592 | 2593 | // Otherwise refresh the curses display 2594 | if (flags.noscroll) { 2595 | refresh(); 2596 | } else { 2597 | prefresh(mainwin, py, px, 0, 0, ssize.y-1, ssize.x-1); 2598 | } 2599 | 2600 | //check for key presses for one second 2601 | //or whatever the user said 2602 | selecttimeout.tv_sec = rate; 2603 | selecttimeout.tv_usec = 0; 2604 | // I don't care about fractions of seconds. I don't want them. 2605 | FD_ZERO(&readfd); 2606 | FD_SET(0, &readfd); 2607 | select(1,&readfd, NULL, NULL, &selecttimeout); 2608 | if (FD_ISSET(0, &readfd)) { 2609 | tmpint = wgetch(mainwin); 2610 | switch (tmpint) { 2611 | // This is ^L 2612 | case 12: 2613 | handle_resize(mainwin, flags, ssize); 2614 | break; 2615 | /* 2616 | * This is at the top because the rest are in 2617 | * order of longopts, and q isn't in longopts 2618 | */ 2619 | case 'q': 2620 | goto out; 2621 | break; 2622 | /* 2623 | * General option toggles 2624 | */ 2625 | case 'c': 2626 | /* 2627 | * we only want to pay attention to this 2628 | * command if colors are available 2629 | */ 2630 | if (has_colors()) 2631 | flags.nocolor = !flags.nocolor; 2632 | break; 2633 | case 'C': 2634 | flags.counters = !flags.counters; 2635 | if (sortby >= SORT_BYTES) 2636 | sortby = SORT_BYTES-1; 2637 | break; 2638 | case 'h': 2639 | interactive_help(sorting, flags, filters); 2640 | break; 2641 | case 'i': 2642 | flags.filter_inv = !flags.filter_inv; 2643 | break; 2644 | case 'l': 2645 | flags.lookup = !flags.lookup; 2646 | /* 2647 | * If we just turned on lookup, also turn on filtering DNS states. 2648 | * They can turn it off if they want, but generally this is the safer 2649 | * approach. 2650 | */ 2651 | if (flags.lookup) { 2652 | flags.skipdns = true; 2653 | } 2654 | break; 2655 | case 'm': 2656 | flags.tag_truncate = !flags.tag_truncate; 2657 | break; 2658 | case 'o': 2659 | flags.staticsize = !flags.staticsize; 2660 | break; 2661 | case 'L': 2662 | flags.skipdns = !flags.skipdns; 2663 | break; 2664 | case 'f': 2665 | flags.skiplb = !flags.skiplb; 2666 | break; 2667 | case 'p': 2668 | switch_scroll(flags, mainwin); 2669 | break; 2670 | case 'r': 2671 | sort_factor = -sort_factor; 2672 | break; 2673 | case 'b': 2674 | if (sortby < SORT_MAX) { 2675 | sortby++; 2676 | if (!flags.counters && sortby >= SORT_BYTES) 2677 | sortby = 0; 2678 | } else { 2679 | sortby = 0; 2680 | } 2681 | break; 2682 | case 'B': 2683 | if (sortby > 0) { 2684 | sortby--; 2685 | } else { 2686 | if (flags.counters) 2687 | sortby=SORT_MAX; 2688 | else 2689 | sortby=SORT_BYTES-1; 2690 | } 2691 | break; 2692 | case 't': 2693 | flags.totals = !flags.totals; 2694 | break; 2695 | /* 2696 | * Update-filters 2697 | */ 2698 | case 'd': 2699 | prompt = "New Destination Filter? (leave blank"; 2700 | prompt += " for none): "; 2701 | get_input(mainwin, tmpstring, prompt, flags); 2702 | if (tmpstring == "") { 2703 | flags.filter_dst = false; 2704 | filters.dst = in6addr_any; 2705 | } else { 2706 | if (!check_ip(tmpstring.c_str(), &filters.dst, &filters.dstfam, &filters.dstnet, &filters.has_dstnet)) { 2707 | prompt = "Invalid IP,"; 2708 | prompt += " ignoring!"; 2709 | c_warn(mainwin, prompt, flags); 2710 | } else { 2711 | flags.filter_dst = true; 2712 | } 2713 | } 2714 | break; 2715 | case 'D': 2716 | prompt = "New dstpt filter? (leave blank for"; 2717 | prompt += " none): "; 2718 | get_input(mainwin, tmpstring, prompt, flags); 2719 | if (tmpstring == "") { 2720 | flags.filter_dstpt = false; 2721 | filters.dstpt = 0; 2722 | } else { 2723 | flags.filter_dstpt = true; 2724 | filters.dstpt = atoi(tmpstring.c_str()); 2725 | } 2726 | wmove(mainwin, 0, 0); 2727 | wclrtoeol(mainwin); 2728 | break; 2729 | case 'R': 2730 | prompt = "Rate: "; 2731 | get_input(mainwin, tmpstring, prompt, flags); 2732 | if (tmpstring != "") { 2733 | int i = atoi(tmpstring.c_str()); 2734 | if (i < 1) { 2735 | prompt = "Invalid rate,"; 2736 | prompt += " ignoring!"; 2737 | c_warn(mainwin, prompt, flags); 2738 | } else { 2739 | rate = i; 2740 | } 2741 | } 2742 | break; 2743 | case 's': 2744 | prompt = "New src filter? (leave blank for"; 2745 | prompt += " none): "; 2746 | get_input(mainwin, tmpstring, prompt, flags); 2747 | if (tmpstring == "") { 2748 | flags.filter_src = false; 2749 | filters.src = in6addr_any; 2750 | } else { 2751 | if (!check_ip(tmpstring.c_str(), &filters.src, &filters.srcfam, &filters.srcnet, &filters.has_srcnet)) { 2752 | prompt = "Invalid IP,"; 2753 | prompt += " ignoring!"; 2754 | c_warn(mainwin, prompt, flags); 2755 | } else { 2756 | flags.filter_src = true; 2757 | } 2758 | } 2759 | wmove(mainwin, 0, 0); 2760 | wclrtoeol(mainwin); 2761 | break; 2762 | case 'S': 2763 | prompt = "New srcpt filter? (leave blank for"; 2764 | prompt += " none): "; 2765 | get_input(mainwin, tmpstring, prompt, flags); 2766 | if (tmpstring == "") { 2767 | flags.filter_srcpt = false; 2768 | filters.srcpt = 0; 2769 | } else { 2770 | flags.filter_srcpt = true; 2771 | filters.srcpt = atoi(tmpstring.c_str()); 2772 | } 2773 | wmove(mainwin, 0, 0); 2774 | wclrtoeol(mainwin); 2775 | break; 2776 | case 'x': 2777 | delete_state(mainwin, stable[curr_state], flags); 2778 | break; 2779 | /* 2780 | * Window navigation 2781 | */ 2782 | case KEY_DOWN: 2783 | case 'j': 2784 | if (flags.noscroll) 2785 | break; 2786 | /* 2787 | * GENERAL NOTE: 2788 | * py is the top of the window, 2789 | * ssize.y is the height of the window, 2790 | * so py+ssize.y is the bottom of the window. 2791 | * 2792 | * BOTTOM OF SCROLLING: 2793 | * Since stable.size()+hdrs+1 is 2794 | * the bottom of the text we've written, if 2795 | * py+ssize.y == stable.size()+hdrs+1 2796 | * then the bottom of the screen as at the 2797 | * bottom of the text, no more scrolling. 2798 | * 2799 | * However, we only want to scroll the page 2800 | * when the cursor is at the bottom, i.e. 2801 | * when curr_state+4 == py+ssize.y 2802 | */ 2803 | 2804 | /* 2805 | * If we have room to scroll down AND if cur is 2806 | * at the bottom of a page scroll down. 2807 | */ 2808 | if ((py + ssize.y <= stable.size() + hdrs + 1) 2809 | && (curr_state + 4 == py + ssize.y)) 2810 | py++; 2811 | 2812 | /* 2813 | * As long as the cursor isn't at the end, 2814 | * move it down one. 2815 | */ 2816 | if (curr_state < stable.size() - 1) 2817 | curr_state++; 2818 | prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2819 | break; 2820 | case KEY_UP: 2821 | case 'k': 2822 | if (flags.noscroll) 2823 | break; 2824 | 2825 | /* 2826 | * This one is tricky. 2827 | * 2828 | * First thing we need to know is when the cursor 2829 | * is at the top of the page. This is simply when 2830 | * curr_state+hdrs+1 cursor location), is 2831 | * exactly one more than the top of the window 2832 | * (py), * i.e. when curr_state+hdrs+1 == py+1. 2833 | * 2834 | * PAGE SCROLLING: 2835 | * IF we're not page-scrolled all the way up 2836 | * (i.e. py > 0) 2837 | * AND the cursor is at the top of the page 2838 | * OR the cursor is at the top of the list, 2839 | * AND we're not yet at the top (showing 2840 | * the headers). 2841 | * THEN we scroll up. 2842 | * 2843 | * CURSOR SCROLLING: 2844 | * Unlike KEY_DOWN, we don't break just because 2845 | * the cursor can't move anymore - on the way 2846 | * ip we may still have page-scrolling to do. So 2847 | * test to make sure we're not at state 0, and 2848 | * if so, we scroll up. 2849 | */ 2850 | 2851 | /* 2852 | * Basically: 2853 | * IF the cursor bumps the top of the screen 2854 | * OR we need to scroll up for headers 2855 | */ 2856 | if ((py > 0 && (curr_state + hdrs + 1) == (py + 1)) 2857 | || (curr_state == 0 && py > 0)) 2858 | py--; 2859 | 2860 | if (curr_state > 0) 2861 | curr_state--; 2862 | prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2863 | break; 2864 | // 4 is ^d 2865 | case 4: 2866 | case KEY_NPAGE: 2867 | case KEY_SNEXT: 2868 | if (flags.noscroll) 2869 | break; 2870 | /* 2871 | * If the screen is bigger than the text, 2872 | * and the cursor is at the bottom, ignore. 2873 | */ 2874 | if (stable.size() + hdrs + 1 < ssize.y && curr_state == stable.size()) 2875 | break; 2876 | 2877 | /* 2878 | * Otherwise, if the bottom of the screen 2879 | * (current position + screen size 2880 | * == py + ssize.y) 2881 | * were to go down one screen (thus: 2882 | * py + ssize.y*2), 2883 | * and that is bigger than the whole pad, just 2884 | * go to the bottom. 2885 | * 2886 | * Otherwise, go down a screen size. 2887 | */ 2888 | if (py + ssize.y * 2 > stable.size() + hdrs + 1) { 2889 | py = stable.size() + hdrs + 1 - ssize.y; 2890 | } else { 2891 | py += ssize.y; 2892 | } 2893 | 2894 | /* 2895 | * For the cursor, we try to move it down one 2896 | * screen as well, but if that's too far, 2897 | * we bring it up to the largest number it can 2898 | * be. 2899 | */ 2900 | curr_state += ssize.y; 2901 | if (curr_state > stable.size()) { 2902 | curr_state = stable.size(); 2903 | } 2904 | prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2905 | break; 2906 | // 21 is ^u 2907 | case 21: 2908 | case KEY_PPAGE: 2909 | case KEY_SPREVIOUS: 2910 | if (flags.noscroll) 2911 | break; 2912 | /* 2913 | * If we're at the top, ignore 2914 | */ 2915 | if (py == 0 && curr_state == 0) 2916 | break; 2917 | /* 2918 | * Otherwise if we're less than a page from the 2919 | * top, go to the top, else go up a page. 2920 | */ 2921 | if (py < ssize.y) { 2922 | py = 0; 2923 | } else { 2924 | py -= ssize.y; 2925 | } 2926 | 2927 | /* 2928 | * We bring the cursor up a page too, unless 2929 | * that's too far. 2930 | */ 2931 | if (curr_state < ssize.y) { 2932 | curr_state = 0; 2933 | } else { 2934 | curr_state -= ssize.y; 2935 | } 2936 | prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2937 | break; 2938 | case KEY_HOME: 2939 | if (flags.noscroll) 2940 | break; 2941 | px = py = curr_state = 0; 2942 | prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2943 | break; 2944 | case KEY_END: 2945 | if (flags.noscroll) 2946 | break; 2947 | py = stable.size() + hdrs + 1 - ssize.y; 2948 | if (py < 0) 2949 | py = 0; 2950 | curr_state = stable.size(); 2951 | prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); 2952 | break; 2953 | } 2954 | } 2955 | /* 2956 | * If we got a sigwinch, we need to redraw 2957 | */ 2958 | if (need_resize) { 2959 | handle_resize(mainwin, flags, ssize); 2960 | need_resize = false; 2961 | } 2962 | } // end while(1) 2963 | 2964 | out: 2965 | clear_table(stable); 2966 | 2967 | /* 2968 | * The user has broken out of the loop, take down the curses 2969 | */ 2970 | end_curses(); 2971 | 2972 | // And we're done 2973 | return(0); 2974 | 2975 | } // end main 2976 | -------------------------------------------------------------------------------- /iptstate.spec: -------------------------------------------------------------------------------- 1 | %define name iptstate 2 | %define version 2.2.7 3 | %define release 1 4 | 5 | Name: %{name} 6 | Summary: Display IP Tables state table information in a "top"-like interface 7 | Version: %{version} 8 | Release: %{release} 9 | Group: Monitoring 10 | License: zlib License 11 | Source: http://www.phildev.net/iptstate/%{name}-%{version}.tar.gz 12 | URL: http://www.phildev.net/iptstate/ 13 | BuildRoot: %{_tmppath}/%{name}-buildroot 14 | BuildRequires: libncurses.so.5 libnetfilter_conntrack.so.1 15 | 16 | %description 17 | IP Tables State (iptstate) was originally written to 18 | impliment the "state top" feature of IP Filter. 19 | "State top" displays the states held by your stateful 20 | firewall in a "top"-like manner. 21 | 22 | Since IP Tables doesn't have a built in way to easily 23 | display this information even once, an option was 24 | added to just display the state table once and exit. 25 | 26 | %prep 27 | rm -rf $RPM_BUILD_ROOT 28 | %setup 29 | 30 | %build 31 | make 32 | 33 | %install 34 | make install PREFIX=$RPM_BUILD_ROOT/usr 35 | 36 | %clean 37 | rm -rf $RPM_BUILD_ROOT 38 | 39 | %files 40 | %defattr(755, root, bin, 755) 41 | /usr/sbin/%{name} 42 | /usr/share/man/man8/%{name}.8* 43 | %doc README BUGS Changelog LICENSE CONTRIB WISHLIST 44 | 45 | -------------------------------------------------------------------------------- /memleak.md: -------------------------------------------------------------------------------- 1 | # IP Tables State - The leak 2 | 3 | Back on April 3, 2002, Casey Webster submitted a [Debian Bug 4 | Report](http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=141044&repeatmerged=yes) 5 | about a memory leak he found in IPTState. There was certainly a leak SOMEWHERE, 6 | because when he ran it, it increasingly ate up memory. But looking over my code 7 | I used no dynamic data structures. 8 | 9 | I started researching and for a small amount of time thought it was a bug in 10 | libc... but it turned out that wasn't true. I then found increasing evidence 11 | the bug was in ncurses... but nothing hard. I could never prove it. This was a 12 | function of my own shortcomings. 13 | 14 | On October 26, 2002, my Debian Maintainer, Brian Nelson emailed me to tell me 15 | that Debian was doing a purge. At the end of the week all software with 16 | outstanding bugs would be kicked out -- and IPTState was on the list. "Oh 17 | Crap!" I thought. This is perhaps the busiest month I'd ever had. I didn't have 18 | time to figure this out, I'd already put several hours into it and come up with 19 | nothing. 20 | 21 | Being open source software I called for help. I asked everyone at UUASC and 22 | USCLUG to do what they could. I even asked for help from the author of Valgrind 23 | who agreed to help. And many people started looking into it! A huge thanks goes 24 | out to all of you. 25 | 26 | On October 28, 2002, at 12:47am, Steve Augart emailed me with his findings: 27 | 28 | ```text 29 | Date: Mon, 28 Oct 2002 00:47:29 -0800 30 | From: Steven Augart 31 | To: Brian Nelson 32 | Cc: Phil Dibowitz 33 | Subject: Re: iptstate in debian 34 | 35 | Well, I've been over it (my God, has it really been two and half hours 36 | that I spent?) and it's a bug in ncurses. 37 | 38 | If someone wants to look at it, ftp to ftp.augart.com and grab the two 39 | tar files in the directory iptstate. That contains the debugging versions 40 | I built, specifically: 41 | 42 | I made a new version, called "iptstate-castrated.cc". It takes a new flag, 43 | -N, which means to not use Curses (kind of like -s, but not just running 44 | once). If you run it with -r 0 (meaning to never pause) and -N, you'll find 45 | that the image never ever bloats. 46 | 47 | Since the ncurses calls all look legitimate, it must be a bug in that interface. 48 | I watched the # of allocated blocks go up during a long run of it. So, every 49 | so often ncurses must lose a memory block. 50 | 51 | I've attached a shred-malloc trace. You'll see that it initially is at 52 | 239/240 allocated blocks, and goes up to 240/241 around the 230th iteration. 53 | (I generated the trace by redirecting stderr to memuse.out). 54 | 55 | You can generate a similar trace easily by running iptstate-castrated with -r 0 56 | and redirecting stderr to a file. 57 | 58 | The shred_malloc package is also there, in libSAugart-advanced.tar.gz. 59 | 60 | It's already all compiled under SuSE Linux 8.0 (glibc 2.2.5, g++ 2.95.3). 61 | 62 | Just because it's a bug in ncurses doesn't mean that the debian folks 63 | are going to be willing to give iptstate a break. I would recommend 64 | building ncurses with -ggdb3 and running one of the memory debuggers 65 | you tried, one that lists what blocks are allocated and from where. Get a 66 | dump after 10 iterations or so. 67 | 68 | Then run iptstate again with -r 0 and get a dump after about 2000 iterations. 69 | Compare the two. 70 | 71 | Anyway, that's what I found. 72 | Please forward this note to the other maintainers. 73 | ``` 74 | 75 | You can find the output he mentions at [memuse.out](memuse.out). 76 | 77 | As he said in the letter you can find his modified source, as well as his 78 | memory debugging software in the iptstate directory of 79 | [ftp.augart.com](ftp://ftp.augart.com/iptstate/). 80 | 81 | In conclusion, this seems to be a bug in ncurses. My software does not leak 82 | when it doesn't use ncuses, but leaks very slowly when using ncurses. Brian 83 | hopefully will be notifying the ncurses maintainers as well as closing this bug 84 | \- and iptstate should be able to stay in Debian (and hopefully move into 85 | testing). 86 | 87 | With that I'd like to say special thanks to Steve Augart, Julian Seward, Todd 88 | Lyons, and **everyone** at UUASC and USCLUG who pitched in to help out. There 89 | were many of you, and I will try and add more names to this list, but for now 90 | I'm trying to post it before I fall asleep. But know that even if you tried, 91 | and didn't find anything and thus didn't email me, I still appreciate your 92 | effort. 93 | -------------------------------------------------------------------------------- /screenshots/iptstate-2.0-filter-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-2.0-filter-ss.png -------------------------------------------------------------------------------- /screenshots/iptstate-2.0-help-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-2.0-help-ss.png -------------------------------------------------------------------------------- /screenshots/iptstate-2.0-lookup-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-2.0-lookup-ss.png -------------------------------------------------------------------------------- /screenshots/iptstate-ss-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-ss-filter.png -------------------------------------------------------------------------------- /screenshots/iptstate-ss-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-ss-old.png -------------------------------------------------------------------------------- /screenshots/iptstate-ss-resolve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-ss-resolve.png -------------------------------------------------------------------------------- /screenshots/iptstate-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymzh/iptstate/b6e08271c9621e81fa5ddee750f62229b4050635/screenshots/iptstate-ss.png --------------------------------------------------------------------------------