diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..041e38c --- /dev/null +++ b/BUGS @@ -0,0 +1,62 @@ +IPTState Bugs + +KNOWN BUGS +We keep track of bugs in our bugtracker on our Sourceforge page. However, before +filing bugs, be aware of bugs in these other pieces of software that may affect +your iptstate experience: + + - libnetfilter_conntrack 0.0.50 has a bug that prevents iptstate from + deleting ICMP states. I wrote the following patch which the netfilter folks + have already applied to their SVN tree: + http://www.phildev.net/linux/patches/libnetfilter_conntrack-0.0.50_icmp_id.patch + You can use it if this affects you. + + - There seems to be a small memory leak somewhere in ncurses. See + http://www.phildev.net/iptstate/memleak.html + for details. This won't effect most users much, but you want to be weary of + leaving iptstate running on very busy firewalls for very long periods of time + (a day or more). This bug has been reported to ncurses, see above URL. + +ABOUT BUGS +If you find a bug in IPTState you should file a bug at our bugtracker: + https://sourceforge.net/tracker/?group_id=52748&atid=467897 +OR mail our -devel list: + https://lists.sourceforge.net/lists/listinfo/iptstate-devel + +If you are unsure if it's a bug or need support, please use the -devel mailing +list. + +Sending a bug to your distro or some public forum is not going to let me know. +I can't fix things I don't know about. So make sure I know! + +ABOUT MY RESPONSE +If your bug is serious - i.e. compilation errors, a major a functionality is +broken, or a security problem I'll usually give you pretty immediate attention. +If it's a very minor bug, or a feature request, I'll respond as I have time. + +ABOUT PATCHES +Patches are welcome but not necessary. I will fix bugs, don't worry. If you +would like to contribute code, PLEASE use consistent style to the rest of the +code. If you send a patch, it should be accompanied with a good explanation of +what it does and why. If it fixes a bug, it should also be accompanied by a +bug report in our bug tracker. + +ABOUT BUG REPORTS +Please be sure to include these things in bug reports: + - your iptstate version + - your distribution and distribution version + - your kernel version + - your g++ version (if you built it yourself) + - your make version (if you built it yourself) + - your glibc version (if you built it yourself) + - your ncurses version + - your libnetfilter_conntrack version + - any relevant output and/or errors + +Once again, distributions don't always forward bug reports upstream, so +please send bug reports to me if you want bugs fixed. + +Thanks! + +Phil Dibowitz +phil AT ipom DOT com diff --git a/CONTRIB b/CONTRIB new file mode 100644 index 0000000..ffc91a2 --- /dev/null +++ b/CONTRIB @@ -0,0 +1,92 @@ +IPTSTATE CONTRIBUTIONS + +IPTState is written and maintained by Phil Dibowitz +I originally wrote the software not planning on releasing it - I wanted +a feature similar to IP Filter's StateTop when I was using Linux. I +decided to release it. It became much bigger than I expected in just a +month. + +IPTState wouldn't be where it is today without many other people. This is +an attempt to credit and thank them. + +IDEA +Darren Reed wrote IP Filter. +Frank Volf wrote the Statetop code for IP Filter. + +PACKAGERS +I'm very grateful to those who package my software. I've tried to list +maintainers here, but if you're not here, please, let me know and I'll +add you. + +If you package my software _please let me know_, and even more importantly, +_please_ pass on bugs to me! + +Mandriva: Garrick Staples +Debian: Chris Taylor (was: Brian Nelson) +Gentoo: (was: Eldad Zack) +ALT Linux: Victor Forsyuk +floppyfw: Cristian Ionescu-Idbohrn +pkgsrc: Roland Illig +Devil Linux: (was: Bruce Smith) +Fedora/RedHat: Thomas Woerner +OpenEmbedded: Jamie Lenehen +ArchLinux (community repo): Andrea Zucchelli +ipcop: Franck Bourdonnec +Slackware (slackers.it repo): Corrado Franco + +OTHER +Some people who helped me get this project started were Jullian Gomez, +my fellow USCLUG'ers (Ted Faber in particular), and my friend Sanjay. + +BY VERSION +Other contributions follow based on version. If you contributed something +and you are not listed here, let me know, it's easy to forget to list +people. Also note that if you're in the Changelog, you may not be here: + +2.1 +- Thanks to Brian Nelson for find the -sSdD bug quickly before everyone + had packaged 2.0! + +2.0 +- Thanks to Ted Faber for his extensive coding knowledge +- Thanks to Garrick Stapples for his help figuring out ncurses pads and scrolling +- Thanks Bill Hudacek for the formatting bug report. + +1.4 +- + +1.3 +- Steve Augart finally proved the memory leak was in ncurses. Just in +the nick of time before Debian kicked ipstate out. Extra special mega +thanks to you Steve! Others who helped were Julian Seward, Todd Lyons, +and many suggestions from various folks on the UUASC and USCLUG mailing +lists. +- Todd Lyons pointed out snprintf's in printline function should allow +an extra char so as not to cut off newlines. + +1.2.1 +- Thanks to Martin Geisler for pointing out the typo in the man page + +1.2.0 +- Thanks to Garrick for all your help troubleshooting and beta testing. + +1.1.0 +- Beat Bolli sent in a large patch that was a reference to finish up much +of the work I was doing for this version such as fixing TTL sort, and +various simplifications. His patch also reminded me to do the -R option, +notified me of the 'make uninstall' bug, and gave a few cleanups. + +- Blars Blarson sent a patch to the Debian developers noting that I was +only reading in 50 connections. He also sent in a patch to print a +newline upon exiting state-top mode so the prompt isn't mixed in left- +over output. + +- Many others made patches that did some of the above things in countless +ways... + +1.0.1 +- Thanks to Garrick Staples for the .spec file + +Phil Dibowitz +phil AT ipom DOT com + diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..28a88c9 --- /dev/null +++ b/Changelog @@ -0,0 +1,181 @@ +2.2.6 +- Released 08/14/16 +- Fix `-b` option which didn't work in many cases +- When we turn `lookup` mode on, automatically turn `skipdns` mode on +- Move to dynamic memory for state entries. Fixes #3 + +2.2.5 +- Released 06/01/12 +- Full support for ICMP6 including code/type display and state deletion +- Dynamically size "State" column" +- If we can't resolve a protocol to a name, print the number instead of + "UNKNOWN!" +- Don't leave a space for ":" if there's no port + +2.2.4 +- Released 06/01/12 +- Improved IPv6 support - truncate addresses if they don't fit and generally + treat them like hostnames at display time +- CONTRIB and man page fixes (Chris Taylor ) + +2.2.3 +- Released 04/03/11 +- IPv6 support. Closes #2848930. +- Handle filters as in6_addr and uints instead of strings +- Fix loopback filtering support +- Fix formatting for ICMP states. Closes #2969917. +- Total style overhaul: move away from tabs, use 2 spaces, various other style + cleanups +- Documentation updates + +2.2.2 +- Released 09/19/09 +- Fix includes +- Add --version (closes bug 2792918) +- Some minor code abstractions +- Remove old /proc based code +- Dropped "Proto" field minimum width to 3 chars (changing title to "Prt") + to make more room for counters +- If we can't fit counters and there's nothing we can truncate, show a + warning and then disable counters rather than messing up the display +- Some style cleanups + +2.2.1 +- Released 03/18/07 +- Fix formatting bug (maxes not being cleared on each round) + +2.2.0 +- Released 03/18/07 +- Added some logic to handle state tables larger than 32767 entries which + breaks ncurses if you try to make a pad that large. +- Cleanup the time.h includes +- Port to new libnetfilter_conntrack library +- Add support for byte/packet counters ('C' key) +- Add support for deleting states ('x' key) +- Move navigation help to top of interactive help so people can learn how + to navigate without having to navigate to the bottom of the help +- When --lookup is enabled, resolve port names as well ast hostnames + (reported by Viliam Holub ) +- Display the ICMP ID on ICMP states +- Fix scrolling bug if totals or filters were enabled +- General improvement of all scrolling calculations +- Add 'B' as a way to sort by previous columbn (opposite of 'b') +- Add ^d for pagedown and ^u for pageup + +2.1 +- Released 10/05/06 +- Fixed bug where -s was doing what -S should do and -d was doing + what -D should do. Thanks to Brian Nelson for catching this. +- Add comments on the 3 functions that didn't have them in 2.0 + +2.0 +- Released 10/04/06 +- Moved man page to section 8 +- Significantly re-factored code +- Fix long-protocol-names-break-formatting bug + (reported by Bill Hudacek ) +- Move all flag bools into a new flags_t struct +- Move format-decisions to end +- Move all counters into a new counters_t struct +- Make the stable vector dynamic instead of a huge pre-allocation +- Move many variables to #defines +- Fix bug in "totals" line (numbers didn't always add up) +- Add display of skipped entries on "totals" line +- Move various char*'s to strings. +- Move most snprintf()s to stringstreams. +- Rewrite and significantly improve dynamic sizing of columns +- Add a new interactive help window +- Add srcpt and dstpt filtering +- Add long options +- Make interactive help scroll-able +- Make main window scroll-able +- Make having the main window be scrollable configurable and if not scrollable + then use stdscr instead of a pad. Make this togglable interactively. +- Redo command-line options so they match interactive options +- Add ability to change all filters and the refresh rate interactively +- Handle window resizes (SIGWINCH) properly +- If we can't read ip_conntrack, error and exit rather than fail + silently +- Cleanup nicely if we get killed (SIGINT or SIGTERM) +- Add color-coding of protocols + +1.4 +- Released 04/16/05 +- Added display of filters +- Added a "strip" target to the Makefile +- Changed ip/port seperator to a colon instead of comma +- Some string concat and Makefile cleanups from Roland Illig +- Added new features to man page +- Added filtering for source and destination addresses +- Added filtering of DNS states option +- Added tagging of truncated hostnames +- Brought man page up-to-date +- Got rid of deprecated warnings +- Removed libgpm req from spec file. + +1.3 +- Released 05/27/03 +- Steve Augart finally proved the 'memory leak' was in + ncurses as I'd always suspected but was unable to prove + Thanks Steve! +- Increased snprintf boundaries in printline function to + ensure newlines don't get cut off (Thanks to Todd Lyons) +- Added dynamic sizing of iptstate based on term info. +- Updated Makefile to only recompile if needed +- Fixed gethostbyaddr() call to compile on more systems +- Fixed truncation bug that ocassionally truncated one char + too few +- Added NOTES section to man page, plus other docs on new + features +- Fixed some man page bugs + +1.2.1 +- Released 07/01/02 +- Fixes for GCC3 + - cast 'x' in 'log' so GCC knows which log I mean + - add -Wno-deprecated to Makefile +- Fix small bug in manpage that made -R not show up +- Fix crash if protocol is not found in /etc/protocols +- Update 'uninstall' in Makefile + +1.2.0 +- Released 04/20/02 +- Various doc updates +- Lots of code cleanups +- Added documentation for interactive-mode options +- Added interactive-mode toggles for -f -t -l +- Added option to display totals +- Added filtering of loopback +- Added sorting by hostname +- Added DNS hostname lookups +- Improved SrcIP and DstIP sorting +- Added sorting by port + +1.1.0 +- Released 03/30/02 +- Will now read in all connections instead of just 50 + For single-line use, it will display them all as well. +- Added command line flag for reverse sorting +- Cleaned up reading of options +- Fixed sorting of TTL/ cleaned up sorting code +- Fixed uninstall in Makfile + +1.0.1 +- Released 02/27/02 +- Added spec file so people can build RPMs if the like +- Fix 'timeval' compile error for certain platforms. + Bizzare bug, I'm guessing in GCC. On some platforms + it wants to use sys/time.h. It will fail if you use + sys/select.h or time.h. Go figure. +- Take out src port and dst port for non tcp,udp cases +- Give 'rate' an initial value +- Fixed Big Endian Problem with command line arguments +- Fixed Makefile (put LIBS at end) +- Change "proto" field to look up by protocol number field of + ip_conntrack instead of take it from the name field. + This should now support pretty much any protocol. I + was unaware that ip_conntrack printed 'unknown' for + protos other than tcp, udp, and icmp. + +1.0 +- Original version released 02/23/02 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..df70b8e --- /dev/null +++ b/LICENSE @@ -0,0 +1,34 @@ + IP Tables State (iptstate) + + Copyright (C) 2002 - present Phil Dibowitz + + This software is provided 'as-is', without any express or + implied warranty. In no event will the authors be held + liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it + and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + ----------------------------------- + +NOTE: If you are planning on packaging and/or submitting my software for/to a +Linux distribution, I would appreciate a heads up. + +There is already an official maintainer for most popular distributions, see the +CONTRIB file for details. + +Phil Dibowitz +phil AT ipom DOT com + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..68db0fc --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# +# Copyright (C) 2002 - present Phil Dibowitz. +# +# See iptstate.cc for copyright info +# +# Makefile for IPTState +# + +### USERS CAN CHANGE STUFF HERE + +PREFIX?=/usr +SBIN?=$(PREFIX)/sbin +INSTALL?=/usr/bin/install +STRIP?=/usr/bin/strip +MAN?=$(PREFIX)/share/man + +### ADVANCED USERS AND PACKAGERS MIGHT WANT TO CHANGE THIS + +CXX?= g++ +CXXFLAGS?= -g -Wall -O2 +CXXFILES?= iptstate.cc + +# THIS IS FOR NORMAL COMPILATION +LIBS?= -lncurses -lnetfilter_conntrack +CPPFLAGS= + +### YOU SHOULDN'T NEED TO CHANGE ANYTHING BELOW THIS + +all: iptstate + + +iptstate: iptstate.cc Makefile + @\ + echo "+------------------------------------------------------------+" ;\ + echo "| Welcome to IP Tables State by Phil Dibowitz |" ;\ + echo "| |" ;\ + echo "| PLEASE read the LICENSE and the README for important info. |" ;\ + echo "| |" ;\ + echo "| You may also wish to read the README for install info, |" ;\ + echo "| the WISHLIST for upcoming features, BUGS for known bugs |" ;\ + echo "| and info on bug reports, and the Changelog to find out |" ;\ + echo "| what's new. |" ;\ + echo "| |" ;\ + echo "| Let's compile... |" ;\ + echo "+------------------------------------------------------------+" ;\ + echo ""; + + $(CXX) $(CXXFLAGS) $(CXXFILES) -o iptstate $(LIBS) $(CPPFLAGS) + @touch iptstate + + @\ + echo "" ;\ + echo "All done. Do 'make install' as root and you should be set to go!" ;\ + echo "" + +strip: iptstate + $(STRIP) iptstate + @touch strip + + +install: + $(INSTALL) -D --mode=755 iptstate $(SBIN)/iptstate + $(INSTALL) -D --mode=444 iptstate.8 $(MAN)/man8/iptstate.8 + + +clean: + /bin/rm -rf iptstate + /bin/rm -rf strip + + +uninstall: + /bin/rm -rf $(SBIN)/iptstate + /bin/rm -rf $(MAN)/man1/iptstate.1 + /bin/rm -rf $(MAN)/man8/iptstate.8 + diff --git a/README.md b/README.md new file mode 100644 index 0000000..de16451 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# IP Tables State (iptstate) + +Please see the LICENSE file for license information. + +## WHAT IS IP TABLES STATE? + +IP Tables State (iptstate) was originally written to implement the "state top" +feature of IP Filter (see "The Idea" below) in IP Tables. "State top" displays +the states held by your stateful firewall in a top-like manner. + +Since IP Tables doesn't have a built in way to easily display this information +even once, an option was added to just have it display the state table once. + +Features include: +* Top-like realtime state table information +* Sorting by any field +* Reversible sorting +* Single display of state table +* Customizable refresh rate +* Display filtering +* Color-coding +* Open Source (specifically I'm using the zlib license) +* much more... + +## PRE-INSTALATION + +Make sure you have some version of curses installed (for most users this is +probably ncurses). Note that if you are using vendor packages you will most +likely need the packaged with '-dev' on the end of of it (i.e. ncurses-dev). + +Starting with version 2.2.0 you also need libnetfilter_conntrack version 0.0.50 +or later. These libraries also require nf_conntrack_netlink and nfnetlink +support in your kernel. + +## INSTALLATION + +### The quick version: + +For most people the following should do all you need: + + make + make install # this must be done as root + +### The long version: + +#### Configuration + +The program is only one c++ source file, so the compile is very simple. For +this reason there is no config file. The defaults in the Makefile should be +fine, but if you want to change something you can change where iptstate gets +installed by changing the "SBIN" variable in your environment. I can't imagine +a reason but if you have 'install' installed in a weird place change the +INSTALL variable in your environment. Other than that nothing should need +tweaking. Obviously advanced users may wish to do other stuff, but we'll leave +that as an excersize to the reader. + +#### Compiling + +The compiling should be as simple as running 'make.' If this doesn't work, feel +free to drop me an email, BUT MAKE SURE you put "IPTSTATE:" in the subject. In +the email include: Distribution, kernel version, make version, gcc version, +libc version, and the error messages. + +Package maintainers may wish to override CXXFLAGS, and can do so like so: + + # CXXFLAGS=-O3 make + +and/or use "make strip" which will build iptstate and then strip it. + +If you get errors like: + + iptstate.cc:286: passing `in_addr *' as argument + 1 of `gethostbyaddr(const char *, size_t, int)' + +then you need to upgrade your glibc. This is an important thing to keep +up-to-date anyway. + +#### Installing + +IPTState installs in /usr/sbin. This is because it should be a utility for the +superuser. You need root access (or CAP_NET_ADMIN) for iptstate to get the data +it needs anyway. Installing should be as simple as 'make install' as root. If +this fails, feel free to do: + + # cp iptstate /usr/sbin/iptstate + # chmod 755 /usr/sbin/iptstate + # chown root:bin /usr/sbin/iptstate + # cp iptstate.8 /usr/share/man/man8/iptstate.8 + # chmod 444 /usr/share/man/man1/iptstate.8 + +And that should do it. If 'make install' fails feel free to drop me an email +provided you put "IPTSTATE:" in the subject. Please see the BUGS file on how to +send proper bug reports. + +## USAGE + +IPTables State is extremely simple to use. Most of the time what you'll want is +just the command 'iptstate' as root. This will launch you into the 'statetop' +mode. In here, your state table is being sorted by Source IP. To change the +sorting, on the fly, type 'b.' This will rotate through the various sorting +possibilities. You can quit by typing 'q.' You can also change the sorting with +the -b ("sort BY") option. The -b option takes d (Destination IP), D +(Destination Port), S (Source IP), p (protocol), s (state), and t (TTL) as it's +possible options. To sort by Source IP, just don't specify -b. + +You can also change the refresh rate of the statetop by -R followed by an +integer. The integer represents the refresh rate in seconds. + +To get help, hit 'h' from withint iptstate, or run iptstate with the '--help' +option. + +To get a quick look at what's going across your firewall, try iptstate -1. This +is "single run" mode. It will just print out your state table at the moment you +requested it. This is where -b comes in handy. Again, the default sort is by +Source IP. + +NOTE WELL: This is not meant to be a comprehensive guide. There are many other +features - check the man page, the -h option, and the interactive help page +within iptstate for more information. But this should give you the basics. + +## DESIRED FEATURES + +There is a list of features I plan and don't plan to implement in the WISHLIST +file. + +## THE IDEA + +The idea of statetop comes from IP Filter by Darren Reed. + +This package's main purpose is to provide a state-top type interface for IP +Tables. I've added in the "single run" option since there's no nice way to do +that with IP Tables either. + +## THE AUTHOR + +IPTState was written by me, Phil Dibowitz. My day job is large-scale system +administration and automation. Outside of work I maintain several open source +projects. You can find out more about me at http://www.phildev.net/ + +Phil Dibowitz +phil AT ipom DOT com diff --git a/WISHLIST b/WISHLIST new file mode 100644 index 0000000..f3cc884 --- /dev/null +++ b/WISHLIST @@ -0,0 +1,23 @@ +IPTSTATE WISHLIST +Wishlist features should be filed in the Wishlist tracker on our sourceforge page. + +This is an overview of the common ones and where they are on the priority list. + +Features coming soon: +- Filtering hostnames +- Inverse filtering +- Display NAT'd IP + +Features that may come later: +- Secondary sorting +- Display packets/bytes per interval instead of just total +- Display the "CONNMARK" value of each connection + +Features I probably won't add because I'm not that interested, or +don't feel they belong, or would be too much work, or some +combination therein. +- Full regex filtering + + +Phil Dibowitz +phil AT ipom DOT com diff --git a/iptstate.8 b/iptstate.8 new file mode 100644 index 0000000..a2414d3 --- /dev/null +++ b/iptstate.8 @@ -0,0 +1,185 @@ +.\" Process this file with +.\" groff -man -Tascii iptstate.8 +.\" +.TH IPTSTATE 8 "JUNE 2012" "" "" +.\" +.\" Man page written by Phil Dibowitz +.\" +.\" IPTState is copyright by Phil Dibowitz. Please see the README and LICENSE. +.\" +.SH NAME +.B iptstate +\- A top-like display of IP Tables state table entries + +.SH SYNOPSIS +.B iptstate +.RB [< options >] + +.SH DESCRIPTION +.B iptstate +displays information held in the IP Tables state table in real-time in a top-like format. +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. + +.SH COMMAND\-LINE OPTIONS +.TP +.B -c, --no-color +Toggle color-code by protocol +.TP +.B -C, --counters +Toggle display of bytes/packets counters +.TP +.B -d, --dst-filter \fIIP\fP +Only show states with a destination of \fIIP\fP +Note, that this must be an IP, hostname matching is not yet supported. +.TP +.B -D --dstpt-filter \fIport\fP +Only show states with a destination port of \fIport\fP +.TP +.B -h, --help +Show help message +.TP +.B -l, --lookup +Show hostnames instead of IP addresses. Enabling this will also enable \fB-L\fP to prevent an ever-growing number of DNS requests. +.TP +.B -m, --mark-truncated +Mark truncated hostnames with a '+' +.TP +.B -o, --no-dynamic +Toggle dynamic formatting +.TP +.B -L, --no-dns +Skip outgoing DNS lookup states +.TP +.B -f, --no-loopback +Filter states on loopback +.TP +.B -p, --no-scroll +No scrolling (don't use a "pad"). See \fBSCROLLING AND PADS\fP for more information. +.TP +.B -r, --reverse +Reverse sort order +.TP +.B -R, --rate \fIseconds\fP +Refresh rate, followed by rate in \fIseconds\fP. Note that this is for statetop mode, and not applicable for single-run mode (\-\-single). +.TP +.B -1, --single +Single run (no curses) +.TP +.B -b, --sort \fIcolumn\fP +This determines what column to sort by. Options: +.br +.B " S" +Source Port +.br +.B " d" +Destination IP (or Name) +.br +.B " D" +Destination Port +.br +.B " p" +Protocol +.br +.B " s" +State +.br +.B " t" +TTL +.br +.B " b" +Bytes +.br +.B " P" +Packets +.br +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). +.TP +.B -s, --src-filter \fIIP\fP +Only show states with a source of \fIIP\fP. Note, that this must be an IP, hostname matching is not yet supported. +.TP +.B -S, --srcpt-filter \fIport\fP +Only show states with a source port of \fIport\fP +.TP +.B -t, --totals +Toggle display of totals + +.SH INTERACTIVE OPTIONS +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. +.PP +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. +.PP +Additionally, the following keys are used to navigate within \fBiptstate\fP: +.TP +\fBUp\fP or \fBj\fP - Move up one line +.TP +\fBDown\fP or \fBk\fP - Move down one line +.TP +\fBLeft\fP or \fBh\fP - Move left one column +.TP +\fBRight\fP or \fBl\fP - Move right one column +.TP +\fBPageUp\fP or \fB^u\fP - Move up one page +.TP +\fBPageDown\fP or \fB^d\fP - Move down one page +.TP +\fBHome\fP - Go to the top +.TP +\fBEnd\fP - Go to the end +.PP +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. +.PP +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. +.PP +In most cases, a blank response means "clear" - clear the source IP filter, for example. +.PP +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. +.PP +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. +.PP +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). + +.SH SCROLLING AND PADS +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. +.TP +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. +.TP +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. +.TP +Nonetheless, if this does negatively affect you, the option to turn it off is there. + +.SH EXIT STATUS +Anything other than 0 indicates and error. A list of current exit statuses are below: +.TP +.B 0 +Success +.TP +.B 1 +Bad command-line arguments +.TP +.B 2 +Error communicating with the netfilter subsystem. +.TP +.B 3 +Terminal too narrow + +.SH BUGS +We don't support filtering on resolved names, and we don't support filtering on networks. IPv6 support is new and the dynamic formatting doesn't yet always handle IPv6 addresses as well as it should. + +.SH BUG REPORTS +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. + +.SH NOTES +\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. +.PP +\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. +.PP +\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. + +.SH SEE ALSO +.BR iptables (8) +.PP + +.SH AUTHOR +\fBiptstate\fP was written by Phil Dibowitz +.br +http://www.phildev.net/iptstate/ diff --git a/iptstate.cc b/iptstate.cc new file mode 100644 index 0000000..78e7e16 --- /dev/null +++ b/iptstate.cc @@ -0,0 +1,2816 @@ +/* + * vim:textwidth=80:tabstop=2:shiftwidth=2:expandtab:ai + * + * iptstate.cc + * IPTables State + * + * ----------------------------------- + * + * Copyright (C) 2002 - present Phil Dibowitz + * + * This software is provided 'as-is', without any express or + * implied warranty. In no event will the authors be held + * liable for any damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it + * and redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * ----------------------------------- + * + * The idea of statetop comes from IP Filter by Darren Reed. + * + * This package's main purpose is to provide a state-top type + * interface for IP Tables. I've added in the "single run" + * option since there's no nice way to do that either. + * + * NOTE: If you are planning on packaging and/or submitting my software for/to + * a Linux distribution, I would appreciate a heads up. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// There are no C++-ified versions of these. +#include +#include +extern "C" { + #include +}; +#include +#include +#include +using namespace std; + +#define VERSION "2.2.6" +/* + * MAXCONS is set to 16k, the default number of states in iptables. Generally + * speaking the ncurses pad is this many lines long, but since ncurses + * uses a short for their dimensions, a pad can never be longer than 32767. + * Thus we define both of these values and NLINES as the lesser of the two. + */ +#define MAXCONS 16384 +#define MAXLINES 32767 +#if MAXCONS < MAXLINES + #define NLINES MAXCONS +#else + #define NLINES MAXLINES +#endif +#define MAXFIELDS 20 +// This is the default format string if we don't dynamically determine it +#define DEFAULT_FORMAT "%-21s %-21s %-7s %-12s %-9s\n" +// The following MUST be the same as the above +#define DEFAULT_SRC 21 +#define DEFAULT_DST 21 +#define DEFAULT_PROTO 7 +#define DEFAULT_STATE 12 +#define DEFAULT_TTL 9 +// This is the format string for the "totals" line, always. +#define TOTALS_FORMAT \ + "Total States: %i -- TCP: %i UDP: %i ICMP: %i Other: %i (Filtered: %i)\n" +// Options for truncating from the front or the back +#define TRUNC_FRONT 0 +#define TRUNC_END 1 +// maxlength for string we pass to inet_ntop() +#define NAMELEN 100 +// Sorting options +#define SORT_SRC 0 +#define SORT_SRC_PT 1 +#define SORT_DST 2 +#define SORT_DST_PT 3 +#define SORT_PROTO 4 +#define SORT_STATE 5 +#define SORT_TTL 6 +#define SORT_BYTES 7 +#define SORT_PACKETS 8 +#define SORT_MAX 8 + +/* + * GLOBAL CONSTANTS + */ + +/* + * GLOBAL VARS + */ +int sort_factor = 1; +bool need_resize = false; + +/* shameless stolen from libnetfilter_conntrack_tcp.c */ +static const char *states[] = { + "NONE", + "SYN_SENT", + "SYN_RECV", + "ESTABLISHED", + "FIN_WAIT", + "CLOSE_WAIT", + "LAST_ACK", + "TIME_WAIT", + "CLOSE", + "LISTEN" +}; + + +/* + * STRUCTS + */ +// One state-table entry +struct tentry_t { + string proto, state, ttl, sname, dname, spname, dpname; + in6_addr src, dst; + uint8_t family; + unsigned long srcpt, dstpt, bytes, packets, s; +}; +// x/y of the terminal window +struct screensize_t { + unsigned int x, y; +}; +// Struct 'o flags +struct flags_t { + bool single, totals, lookup, skiplb, staticsize, skipdns, tag_truncate, + filter_src, filter_dst, filter_srcpt, filter_dstpt, noscroll, nocolor, + counters; +}; +// Struct 'o counters +struct counters_t { + unsigned int total, tcp, udp, icmp, other, skipped; +}; +// Various filters to be applied pending the right flags in flags_t +struct filters_t { + in6_addr src, dst; + uint8_t srcfam, dstfam; + unsigned long srcpt, dstpt; +}; +// The max-length of fields in the stable table +struct max_t { + unsigned int src, dst, proto, state, ttl; + unsigned long bytes, packets; +}; +struct hook_data { + vector *stable; + flags_t *flags; + max_t *max; + counters_t *counts; + const filters_t *filters; +}; + + +/* + * GENERAL HELPER FUNCTIONS + */ + +/* + * split a string into two strings based on the first occurance + * of any character + */ +void split(char s, string line, string &p1, string &p2) +{ + int pos = line.find(s); + p1 = line.substr(0, pos); + p2 = line.substr(pos+1, line.size()-pos); +} + +/* + * split a string into an array of strings based on + * any character + */ +void splita(char s, string line, vector &result) +{ + int pos, size; + int i=0; + string temp, temp1; + temp = line; + while ((temp.find(s) != string::npos) && (i < MAXFIELDS-1)) { + pos = temp.find(s); + result[i] = temp.substr(0, pos); + size = temp.size(); + temp = temp.substr(pos+1, size-pos-1); + if (result[i] != "") { + i++; + } + } + result[i] = temp; +} + +/* + * This determines the length of an integer (i.e. number of digits) + */ +unsigned int digits(unsigned long x) +{ + return (unsigned int) floor(log10((double)x))+1; +} + +/* + * Check to ensure an IP is valid + */ +bool check_ip(const char *arg, in6_addr *addr, uint8_t *family) +{ + int ret; + ret = inet_pton(AF_INET6, arg, addr); + if (ret) { + *family = AF_INET6; + return true; + } + ret = inet_pton(AF_INET, arg, addr); + if (ret) { + *family = AF_INET; + return true; + } + return false; +} + +/* + * The help + */ +void version() +{ + cout << "IPTables State Top Version " << VERSION << endl << endl; +} + +void help() +{ + cout << "IPTables State Top Version " << VERSION << endl; + cout << "Usage: iptstate []\n\n"; + cout << " -c, --no-color\n"; + cout << "\tToggle color-code by protocol\n\n"; + cout << " -C, --counters\n"; + cout << "\tToggle display of bytes/packets counters\n\n"; + cout << " -d, --dst-filter \n"; + cout << "\tOnly show states with a destination of \n"; + cout << "\tNote, that this must be an IP, hostname matching is" + << " not yet supported.\n\n"; + cout << " -D --dstpt-filter \n"; + cout << "\tOnly show states with a destination port of \n\n"; + cout << " -h, --help\n"; + cout << "\tThis help message\n\n"; + cout << " -l, --lookup\n"; + cout << "\tShow hostnames instead of IP addresses. Enabling this will also" + << " enable\n\t-L to prevent an ever-growing number of DNS requests.\n\n"; + cout << " -m, --mark-truncated\n"; + cout << "\tMark truncated hostnames with a '+'\n\n"; + cout << " -o, --no-dynamic\n"; + cout << "\tToggle dynamic formatting\n\n"; + cout << " -L, --no-dns\n"; + cout << "\tSkip outgoing DNS lookup states\n\n"; + cout << " -f, --no-loopback\n"; + cout << "\tFilter states on loopback\n\n"; + cout << " -p, --no-scroll\n"; + cout << "\tNo scrolling (don't use a \"pad\")\n\n"; + cout << " -r, --reverse\n"; + cout << "\tReverse sort order\n\n"; + cout << " -R, --rate \n"; + cout << "\tRefresh rate, followed by rate in seconds\n"; + cout << "\tNote: For statetop, not applicable for -s\n\n"; + cout << " -1, --single\n"; + cout << "\tSingle run (no curses)\n\n"; + cout << " -b, --sort \n"; + cout << "\tThis determines what column to sort by. Options:\n"; + cout << "\t d: Destination IP (or Name)\n"; + cout << "\t p: Protocol\n"; + cout << "\t s: State\n"; + cout << "\t t: TTL\n"; + cout << "\t b: Bytes\n"; + cout << "\t P: Packets\n"; + cout << "\tTo sort by Source IP (or Name), don't use -b.\n"; + cout << "\tNote that bytes/packets are only available when" + << " supported in the kernel,\n"; + cout << "\tand enabled with -C\n\n"; + cout << " -s, --src-filter \n"; + cout << "\tOnly show states with a source of \n"; + cout << "\tNote, that this must be an IP, hostname matching is" + << " not yet supported.\n\n"; + cout << " -S, --srcpt-filter \n"; + cout << "\tOnly show states with a source port of \n\n"; + cout << " -t, --totals\n"; + cout << "\tToggle display of totals\n\n"; + cout << "See man iptstate(8) or the interactive help for more" + << " information.\n"; + exit(0); +} + +/* + * Resolve hostnames + */ +void resolve_host(const uint8_t &family, const in6_addr &ip, string &name) +{ + struct hostent *hostinfo = NULL; + + if ((hostinfo = gethostbyaddr((char *)&ip, sizeof(ip), family)) != NULL) { + name = hostinfo->h_name; + } else { + char str[NAMELEN]; + name = inet_ntop(family, (void *)&ip, str, NAMELEN-1) ; + } +} + +void resolve_port(const unsigned int &port, string &name, const string &proto) +{ + struct servent *portinfo = NULL; + + if ((portinfo = getservbyport(htons(port), proto.c_str())) != NULL) { + name = portinfo->s_name; + } else { + ostringstream buf; + buf.str(""); + buf << port; + name = buf.str(); + } +} + +/* + * If lookup mode is on, we lookup the names and put them in the structure. + * + * If lookup mode is not on, we generate strings of the addresses and put + * those in the structure. + * + * Finally, we update the max_t structure. + * + * NOTE: We stringify addresses largely because in the IPv6 case we need + * to treat them like truncate-able strings. + */ +void stringify_entry(tentry_t *entry, max_t &max, const flags_t &flags) +{ + unsigned int size = 0; + ostringstream buffer; + char tmp[NAMELEN]; + + bool have_port = entry->proto == "tcp" || entry->proto == "udp"; + if (!have_port) { + entry->spname = entry->dpname = ""; + } + + if (flags.lookup) { + resolve_host(entry->family, entry->src, entry->sname); + resolve_host(entry->family, entry->dst, entry->dname); + if (have_port) { + resolve_port(entry->srcpt, entry->spname, entry->proto); + resolve_port(entry->dstpt, entry->dpname, entry->proto); + } + } else { + buffer << inet_ntop(entry->family, (void*)&(entry->src), tmp, NAMELEN-1); + entry->sname = buffer.str(); + buffer.str(""); + buffer << inet_ntop(entry->family, (void*)&(entry->dst), tmp, NAMELEN-1); + entry->dname = buffer.str(); + buffer.str(""); + if (have_port) { + buffer << entry->srcpt; + entry->spname = buffer.str(); + buffer.str(""); + buffer << entry->dstpt; + entry->dpname = buffer.str(); + buffer.str(""); + } + } + + size = entry->sname.size() + entry->spname.size() + 1; + if (size > max.src) + max.src = size; + + size = entry->dname.size() + entry->dpname.size() + 1; + if (size > max.dst) + max.dst = size; +} + + +/* + * SORT FUNCTIONS + */ +bool src_sort(tentry_t *one, tentry_t *two) +{ + /* + * memcmp() will properly sort v4 or v6 addresses, but not cross-family + * (presumably because of garbage in the top 96 bytes when you store + * a v4 address in a in6_addr), so we sort by family and then memcmp() + * within the same family. + */ + if (one->family == two->family) { + return memcmp(one->src.s6_addr, two->src.s6_addr, 16) * sort_factor < 0; + } else if (one->family == AF_INET) { + return sort_factor > 0; + } else { + return sort_factor < 0; + } +} + +bool srcname_sort(tentry_t *one, tentry_t *two) +{ + return one->sname.compare(two->sname) * sort_factor < 0; +} + +bool dst_sort(tentry_t *one, tentry_t *two) +{ + // See src_sort() for details + if (one->family == two->family) { + return memcmp(one->dst.s6_addr, two->dst.s6_addr, 16) * sort_factor < 0; + } else if (one->family == AF_INET) { + return sort_factor > 0; + } else { + return sort_factor < 0; + } +} + +bool dstname_sort(tentry_t *one, tentry_t *two) +{ + return one->dname.compare(two->dname) * sort_factor < 0; +} + +/* + * int comparison that takes care of sort_factor + * used for ports, bytes, etc... + */ +bool cmpint(int one, int two) +{ + return (sort_factor > 0) ? one < two : one > two; +} + +bool srcpt_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->srcpt, two->srcpt); +} + +bool dstpt_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->dstpt, two->dstpt); +} + +bool proto_sort(tentry_t *one, tentry_t *two) +{ + return one->proto.compare(two->proto) * sort_factor < 0; +} + +bool state_sort(tentry_t *one, tentry_t *two) +{ + return one->state.compare(two->state) * sort_factor < 0; +} + +bool ttl_sort(tentry_t *one, tentry_t *two) +{ + return one->ttl.compare(two->ttl) * sort_factor < 0; +} + +bool bytes_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->bytes, two->bytes); +} + +bool packets_sort(tentry_t *one, tentry_t *two) +{ + return cmpint(one->packets, two->packets); +} + +/* + * CURSES HELPER FUNCTIONS + */ + +/* + * Finish-up for curses environment + */ +void end_curses() +{ + curs_set(1); + nocbreak(); + endwin(); + cout << endl; +} + +/* + * SIGWINCH signal handler. + */ +void winch_handler(int sig) +{ + sigset_t mask_set; + sigset_t old_set; + // Reset signal handler + signal(28, winch_handler); + // ignore this signal for a bit + sigfillset(&mask_set); + sigprocmask(SIG_SETMASK, &mask_set, &old_set); + + need_resize = true; +} + +/* + * SIGKILL signal handler + */ +void kill_handler(int sig) +{ + end_curses(); + printf("Caught signal %d, cleaning up.\n", sig); + exit(0); +} + +/* + * Start-up for curses environment + * + * NOTE: That by default we create a pad. A pad is a special type of window that + * can be bigger than the screen. See the comments in interactive_help() + * below for how to use it and how it works. + * + * However, pad's lack the double-buffering and other features of standard + * ncurses windows and thus can appear slower. Thus we allow the user to + * downgrade to standard windows if they choose. See the comments + * switch_scroll() for more details. + * + */ +static WINDOW* start_curses(flags_t &flags) +{ + int y, x; + initscr(); + cbreak(); + noecho(); + halfdelay(1); + + /* + * If we're starting curses, we care about SIGWNCH, SIGINT, and SIGTERM + * so this seems like as good a place as any to setup our signal + * handler. + */ + // Resize + signal(28, winch_handler); + // Shutdown + signal(2, kill_handler); + signal(15, kill_handler); + + if (has_colors()) { + start_color(); + // for tcp + init_pair(1, COLOR_GREEN, COLOR_BLACK); + // for udp + init_pair(2, COLOR_YELLOW, COLOR_BLACK); + // for icmp + init_pair(3, COLOR_RED, COLOR_BLACK); + // for prompts + init_pair(4, COLOR_BLACK, COLOR_RED); + // for the currently selected row + init_pair(5, COLOR_BLACK, COLOR_GREEN); + init_pair(6, COLOR_BLACK, COLOR_YELLOW); + init_pair(7, COLOR_BLACK, COLOR_RED); + } else { + flags.nocolor = true; + } + + if (!flags.noscroll) { + getmaxyx(stdscr, y, x); + return newpad(NLINES, x); + } + return stdscr; +} + +/* + * Figure out the best way to get the screensize_t, and then do it + */ +screensize_t get_size(const bool &single) +{ + int maxx = 0, maxy = 0; + if (!single) { + getmaxyx(stdscr, maxy, maxx); + } else { + maxx = 72; + if (getenv("COLS")) + maxx=atoi(getenv("COLS")); + } + + screensize_t a; + a.x = maxx; + a.y = maxy; + + return a; +} + +/* + * Error function for screen being too small. + */ +void term_too_small() +{ + end_curses(); + cout << "I'm sorry, your terminal must be atleast 72 columns" + << "wide to run iptstate\n"; + exit(3); +} + +/* + * This is one of those "well, I should impliment it to be complete, but + * I doubt it'll get used very often features." It was a nice-thing-to-do + * to impliment the ability for iptstate to use stdscr instead of a pad + * as this provides the doulbe-buffering and other features that pads + * do not. This is probably useful to a small subset of users. It's pretty + * unlikely people will want to interactively want to change this during + * runtime, but since I implimented noscroll, it's only proper to impliment + * interactive toggling. + * + * TECH NOTE: + * This is just a note for myself so I remember why this is the way it is. + * + * The syntax WINDOW *&mainwin is right, thought it's doing what you'd + * expect WINDOW &*mainwin to do... except that's invalid. So it's just a + * &foo pass on a WINDOW*. + */ +void switch_scroll(flags_t &flags, WINDOW *&mainwin) +{ + int x, y; + if (flags.noscroll) { + getmaxyx(stdscr, y, x); + // remove stuff from the bottom window + erase(); + // build pad + wmove(mainwin, 0, 0); + mainwin = newpad(NLINES, x); + wmove(mainwin, 0, 0); + keypad(mainwin,1); + halfdelay(1); + } else { + // delete pad + delwin(mainwin); + mainwin = stdscr; + keypad(mainwin,1); + halfdelay(1); + } + + flags.noscroll = !flags.noscroll; +} + +/* + * Prompt the user for something, and get an answer. + */ +void get_input(WINDOW *win, string &input, const string &prompt, + const flags_t &flags) +{ + + /* + * This function is here so that we can prompt and get an answer + * and the user can get an echo of what they're inputting. This is + * already a non-straight-forward thing to do in cbreak() mode, but + * it turns out that using pads makes it even more difficult. + * + * It's worth noting that I tried doin a simple waddch() and then + * prefresh as one would expect, but it didn't echo the chars. + * Because we're using pads I have to do a waddchar() and then + * a prefresh(). + * + * Note, that the documentation says that if we're using waddchar() + * we shouldn't need any refresh, but it doesn't echo without it. + * This is probably because waddch() calls wrefresh() instead of + * prefresh(). + */ + + input = ""; + int x, y; + getmaxyx(stdscr, y, x); + WINDOW *cmd = subpad(win, 1, x, 0, 0); + if (!flags.nocolor) + wattron(cmd, COLOR_PAIR(4)); + keypad(cmd, true); + wprintw(cmd, prompt.c_str()); + wclrtoeol(cmd); + prefresh(cmd, 0, 0, 0, 0, 0, x); + + + int ch; + int charcount = 0; + echo(); + nodelay(cmd,0); + + while (1) { + ch = wgetch(cmd); + switch (ch) { + case '\n': + // 7 is ^G + case 7: + if (ch == 7) + input = ""; + if (!flags.nocolor) + wattroff(cmd, COLOR_PAIR(4)); + delwin(cmd); + noecho(); + wmove(win, 0, 0); + return; + break; + // 8 is shift-backspace - just incase + case KEY_BACKSPACE: + case 8: + if (charcount > 0) { + input = input.substr(0, input.size()-1); + wechochar(cmd, '\b'); + wechochar(cmd, ' '); + wechochar(cmd, '\b'); + charcount--; + } + break; + case ERR: + continue; + break; + default: + input += ch; + charcount++; + wechochar(cmd, ch); + } + prefresh(cmd, 0, 0, 0, 0, 0, x); + } +} + +/* + * Create a window with noticable colors (if colors are enabled) + * and print a warning. Means curses_warning. + */ +void c_warn(WINDOW *win, const string &warning, const flags_t &flags) +{ + + /* + * This function is here so that we can warn a user in curses, + * usually about bad input. + */ + + int x, y; + getmaxyx(stdscr, y, x); + WINDOW *warn = subpad(win, 1, x, 0, 0); + if (!flags.nocolor) + wattron(warn, COLOR_PAIR(4)); + wprintw(warn, warning.c_str()); + wprintw(warn, " Press any key to continue..."); + wclrtoeol(warn); + prefresh(warn, 0, 0, 0, 0, 0, x); + while ((y = getch())) { + if (y != ERR) { + break; + } + prefresh(warn, 0, 0, 0, 0, 0, x); + } + if (!flags.nocolor) + wattroff(warn, COLOR_PAIR(4)); + delwin(warn); + noecho(); + wmove(win, 0, 0); + return; +} + +/* + * Initialize the max_t structure with some sane defaults. We'll grow + * them later as needed. + */ +void initialize_maxes(max_t &max, flags_t &flags) +{ + /* + * For NO lookup: + * src/dst IP can be no bigger than 21 chars: + * IP (max of 15) + colon (1) + port (max of 5) = 21 + * + * For lookup: + * if it's a name, we start with the width of the header, and we can + * grow from there as needed. + */ + if (flags.lookup) { + max.src = 6; + max.dst = 11; + } else { + max.src = max.dst = 21; + } + /* + * The proto starts at 3, since tcp/udp are the most common, but will + * grow if we see bigger proto strings such as "ICMP". + */ + max.proto = 3; + /* + * "ESTABLISHED" is generally the longest state, we almost always have + * several, so we'll start with this. It also looks really bad if state + * is changing size a lot, so we start with a common minumum. + */ + max.state = 11; + // TTL we statically make 7: xxx:xx:xx + max.ttl = 9; + + // Start with something sane + max.bytes = 2; + max.packets = 2; +} + +/* + * The actual work of handling a resize. + */ +void handle_resize(WINDOW *&win, const flags_t &flags, screensize_t &ssize) +{ + if (flags.noscroll) { + endwin(); + refresh(); + return; + } + + /* + * OK, the above case without pads is easy. But pads is tricker. + * In order to properly handle SIGWINCH we need to: + * + * - Tear down the pad (delwin) + * - Reset the terminal settings to non-visual mode (endwin) + * - Return to visual mode (refresh) + * - Get the new size (getmaxyx) + * - Rebuild the pad + * + * Note that we don't get the new size without the endwin/refresh + * and thus the new pad doesn't get built right, and everything wraps. + * + * This order must be preserved. + */ + + /* + * Tear down... + */ + delwin(win); + endwin(); + /* + * Start up... + */ + refresh(); + getmaxyx(stdscr, ssize.y, ssize.x); + win = newpad(NLINES, ssize.x); + keypad(win, true); + wmove(win, 0, 0); + + return; +} + +/* + * Take in a 'curr' value, and delete a given conntrack + */ +void delete_state(WINDOW *&win, const tentry_t *entry, const flags_t &flags) +{ + struct nfct_handle *cth; + struct nf_conntrack *ct; + cth = nfct_open(CONNTRACK, 0); + ct = nfct_new(); + int ret; + string response; + char str[NAMELEN]; + string src, dst; + src = inet_ntop(entry->family, (void *)&(entry->src), str, NAMELEN-1); + dst = inet_ntop(entry->family, (void *)&(entry->dst), str, NAMELEN-1); + + ostringstream msg; + msg.str(""); + msg << "Deleting state: "; + if (entry->proto == "tcp" || entry->proto == "udp") { + msg << src << ":" << entry->srcpt << " -> " << dst << ":" << entry->dstpt; + } else { + msg << src << " -> " << dst; + } + msg << " -- Are you sure? (y/n)"; + get_input(win, response, msg.str(), flags); + + if (response != "y" && response != "Y" && response != "yes" && + response != "YES" && response != "Yes") { + c_warn(win, "NOT deleting state.", flags); + return; + } + + nfct_set_attr_u8(ct, ATTR_ORIG_L3PROTO, entry->family); + + if (entry->family == AF_INET) { + nfct_set_attr(ct, ATTR_ORIG_IPV4_SRC, (void *)&(entry->src.s6_addr)); + nfct_set_attr(ct, ATTR_ORIG_IPV4_DST, (void *)&(entry->dst.s6_addr)); + } else if (entry->family == AF_INET6) { + nfct_set_attr(ct, ATTR_ORIG_IPV6_SRC, (void *)&(entry->src.s6_addr)); + nfct_set_attr(ct, ATTR_ORIG_IPV6_DST, (void *)&(entry->dst.s6_addr)); + } + + // undo our space optimization so the kernel can find the state. + protoent *pn; + if (entry->proto == "icmp6") + pn = getprotobyname("ipv6-icmp"); + else + pn = getprotobyname(entry->proto.c_str()); + + nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, pn->p_proto); + + if (entry->proto == "tcp" || entry->proto == "udp") { + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, htons(entry->srcpt)); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, htons(entry->dstpt)); + } else if (entry->proto == "icmp" || entry->proto == "icmp6") { + string type, code, id, tmp; + split('/', entry->state, type, tmp); + split(' ', tmp, code, tmp); + split('(', tmp, tmp, id); + split(')', id, id, tmp); + + nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, atoi(type.c_str())); + nfct_set_attr_u8(ct, ATTR_ICMP_CODE, atoi(code.c_str())); + nfct_set_attr_u16(ct, ATTR_ICMP_ID, atoi(id.c_str())); + } + + ret = nfct_query(cth, NFCT_Q_DESTROY, ct); + if (ret < 0) { + string msg = "Failed to delete state: "; + msg += strerror(errno); + c_warn(win, msg.c_str(), flags); + } + +} + + +/* + * CORE FUNCTIONS + */ + +/* + * Callback for conntrack + */ +int conntrack_hook(enum nf_conntrack_msg_type nf_type, struct nf_conntrack *ct, + void *tmp) +{ + + /* + * start by getting our struct back + */ + struct hook_data *data = static_cast(tmp); + + /* + * and pull out the pieces + */ + vector *stable = data->stable; + flags_t *flags = data->flags; + max_t *max = data->max; + counters_t *counts = data->counts; + const filters_t *filters = data->filters; + + // our table entry + tentry_t *entry = new tentry_t; + + // some vars + struct protoent* pe = NULL; + int seconds, minutes, hours; + char ttlc[11]; + ostringstream buffer; + + /* + * Clear the entry + */ + entry->sname = ""; + entry->dname = ""; + entry->srcpt = 0; + entry->dstpt = 0; + entry->proto = ""; + entry->ttl = ""; + entry->state = ""; + + /* + * First, we read stuff into the array that's always the + * same regardless of protocol + */ + + short int pr = nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO); + pe = getprotobynumber(pr); + if (pe == NULL) { + buffer << pr; + entry->proto = buffer.str(); + buffer.str(""); + } else { + entry->proto = pe->p_name; + /* + * if proto is "ipv6-icmp" we can just say "icmp6" to save space... + * it's more common/standard anyway + */ + if (entry->proto == "ipv6-icmp") + entry->proto = "icmp6"; + } + + // ttl + seconds = nfct_get_attr_u32(ct, ATTR_TIMEOUT); + minutes = seconds/60; + hours = minutes/60; + minutes = minutes%60; + seconds = seconds%60; + // Format it with snprintf and store it in the table + snprintf(ttlc,11, "%3i:%02i:%02i", hours, minutes, seconds); + entry->ttl = ttlc; + + entry->family = nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO); + // Everything has addresses + if (entry->family == AF_INET) { + memcpy(entry->src.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV4_SRC), + sizeof(uint8_t[16])); + memcpy(entry->dst.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV4_DST), + sizeof(uint8_t[16])); + } else if (entry->family == AF_INET6) { + memcpy(entry->src.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC), + sizeof(uint8_t[16])); + memcpy(entry->dst.s6_addr, nfct_get_attr(ct, ATTR_ORIG_IPV6_DST), + sizeof(uint8_t[16])); + } else { + fprintf(stderr, "UNKNOWN FAMILY!\n"); + exit(1); + } + + // Counters (summary, in + out) + entry->bytes = nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_BYTES) + + nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_BYTES); + entry->packets = nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_PACKETS) + + nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_PACKETS); + + if (digits(entry->bytes) > max->bytes) { + max->bytes = digits(entry->bytes); + } + if (digits(entry->packets) > max->packets) { + max->packets = digits(entry->packets); + } + + if (entry->proto.size() > max->proto) + max->proto = entry->proto.size(); + + // OK, proto dependent stuff + if (entry->proto == "tcp" || entry->proto == "udp") { + entry->srcpt = htons(nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + entry->dstpt = htons(nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + } + + if (entry->proto == "tcp") { + entry->state = states[nfct_get_attr_u8(ct, ATTR_TCP_STATE)]; + counts->tcp++; + } else if (entry->proto == "udp") { + entry->state = ""; + counts->udp++; + } else if (entry->proto == "icmp" || entry->proto == "icmp6") { + buffer.str(""); + buffer << (int)nfct_get_attr_u8(ct, ATTR_ICMP_TYPE) << "/" + << (int)nfct_get_attr_u8(ct, ATTR_ICMP_CODE) << " (" + << nfct_get_attr_u16(ct, ATTR_ICMP_ID) << ")"; + entry->state = buffer.str(); + counts->icmp++; + if (entry->state.size() > max->state) + max->state = entry->state.size(); + } else { + counts->other++; + } + + /* + * FILTERING + */ + + /* + * FIXME: Filtering needs to be pulled into it's own function. + */ + struct in_addr lb; + struct in6_addr lb6; + inet_pton(AF_INET, "127.0.0.1", &lb); + inet_pton(AF_INET6, "::1", &lb6); + size_t entrysize = entry->family == AF_INET + ? sizeof(in_addr) + : sizeof(in6_addr); + if (flags->skiplb && (entry->family == AF_INET + ? !memcmp(&(entry->src), &lb, sizeof(in_addr)) + : !memcmp(&(entry->src), &lb6, sizeof(in6_addr)))) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->skipdns && (entry->dstpt == 53)) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_src && (memcmp(&(entry->src), &(filters->src), entrysize))) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_srcpt && (entry->srcpt != filters->srcpt)) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_dst && (memcmp(&(entry->dst), &(filters->dst), entrysize))) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + if (flags->filter_dstpt && (entry->dstpt != filters->dstpt)) { + counts->skipped++; + return NFCT_CB_CONTINUE; + } + + /* + * RESOLVE + */ + + // Resolve names - if necessary - or generate strings of address, + // and calculate max sizes + stringify_entry(entry, *max, *flags); + + /* + * Add this to the array + */ + stable->push_back(entry); + + return NFCT_CB_CONTINUE; +} + +/* + * This is the core of this program - build a table of states. + * + * For the new libnetfilter_conntrack code, the bulk of build_table was moved + * to the conntrack callback function. + */ +void build_table(flags_t &flags, const filters_t &filters, vector + &stable, counters_t &counts, max_t &max) +{ + /* + * Variables + */ + int res=0; + vector fields(MAXFIELDS); + static struct nfct_handle *cth; + u_int8_t family = AF_UNSPEC; + + /* + * This is the ugly struct for the nfct hook, that holds pointers to + * all of the things the callback will need to fill our table + */ + struct hook_data hook; + hook.stable = &stable; + hook.flags = &flags; + hook.max = &max; + hook.counts = &counts; + hook.filters = &filters; + + /* + * Initialization + */ + // Nuke the tentry_t's we made before deleting the vector of pointers + for ( + vector::iterator it = stable.begin(); + it != stable.end(); + it++ + ) { + delete *it; + } + stable.clear(); + counts.tcp = counts.udp = counts.icmp = counts.other = counts.skipped = 0; + + cth = nfct_open(CONNTRACK, 0); + if (!cth) { + end_curses(); + printf("ERROR: couldn't establish conntrack connection\n"); + exit(2); + } + nfct_callback_register(cth, NFCT_T_ALL, conntrack_hook, (void *)&hook); + res = nfct_query(cth, NFCT_Q_DUMP, &family); + if (res < 0) { + end_curses(); + printf("ERROR: Couldn't retreive conntrack table: %s\n", strerror(errno)); + exit(2); + } + nfct_close(cth); +} + +/* + * This sorts the table based on the current sorting preference + */ +void sort_table(const int &sortby, const bool &lookup, const int &sort_factor, + vector &stable, string &sorting) +{ + switch (sortby) { + case SORT_SRC: + if (lookup) { + std::sort(stable.begin(), stable.end(), srcname_sort); + sorting = "SrcName"; + } else { + std::sort(stable.begin(), stable.end(), src_sort); + sorting = "SrcIP"; + } + break; + + case SORT_SRC_PT: + std::sort(stable.begin(), stable.end(), srcpt_sort); + sorting = "SrcPort"; + break; + + case SORT_DST: + if (lookup) { + std::sort(stable.begin(), stable.end(), dstname_sort); + sorting = "DstName"; + } else { + std::sort(stable.begin(), stable.end(), dst_sort); + sorting = "DstIP"; + } + break; + + case SORT_DST_PT: + std::sort(stable.begin(), stable.end(), dstpt_sort); + sorting = "DstPort"; + break; + + case SORT_PROTO: + std::sort(stable.begin(), stable.end(), proto_sort); + sorting = "Prt"; + break; + + case SORT_STATE: + std::sort(stable.begin(), stable.end(), state_sort); + sorting = "State"; + break; + + case SORT_TTL: + std::sort(stable.begin(), stable.end(), ttl_sort); + sorting = "TTL"; + break; + + case SORT_BYTES: + std::sort(stable.begin(), stable.end(), bytes_sort); + sorting = "Bytes"; + break; + + case SORT_PACKETS: + std::sort(stable.begin(), stable.end(), packets_sort); + sorting = "Packets"; + break; + + default: + //we should never get here + sorting = "??unknown??"; + break; + + } //switch + + if (sort_factor == -1) + sorting = sorting + " reverse"; + +} + +void print_headers(const flags_t &flags, const string &format, + const string &sorting, const filters_t &filters, + const counters_t &counts, const screensize_t &ssize, + int table_size, WINDOW *mainwin) +{ + if (flags.single) { + cout << "IP Tables State Top -- Sort by: " << sorting << endl; + } else { + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + wmove(mainwin,0, ssize.x/2-15); + wattron(mainwin, A_BOLD); + wprintw(mainwin, "IPTState - IPTables State Top\n"); + + wprintw(mainwin, "Version: "); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-13s", VERSION); + + wattron(mainwin, A_BOLD); + wprintw(mainwin, "Sort: "); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-16s", sorting.c_str()); + + wattron(mainwin, A_BOLD); + wprintw(mainwin, "b"); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-19s", ": change sorting"); + + wattron(mainwin, A_BOLD); + wprintw(mainwin, "h"); + wattroff(mainwin, A_BOLD); + wprintw(mainwin, "%-s\n", ": help"); + } + + /* + * If enabled, print totals + */ + if (flags.totals) { + if (flags.single) + printf(TOTALS_FORMAT, table_size+counts.skipped, counts.tcp, counts.udp, + counts.icmp, counts.other, counts.skipped); + else + wprintw(mainwin, TOTALS_FORMAT, table_size+counts.skipped, counts.tcp, + counts.udp, counts.icmp, counts.other, counts.skipped); + } + + /* + * If any, print filters + */ + char tmp[NAMELEN]; + if (flags.filter_src || flags.filter_dst || flags.filter_srcpt + || flags.filter_dstpt) { + + if (flags.single) { + printf("Filters: "); + } else { + wattron(mainwin, A_BOLD); + wprintw(mainwin, "Filters: "); + wattroff(mainwin, A_BOLD); + } + + bool printed_a_filter = false; + + if (flags.filter_src) { + inet_ntop(filters.srcfam, &filters.src, tmp, NAMELEN-1); + if (flags.single) + printf("src: %s", tmp); + else + wprintw(mainwin, "src: %s", tmp); + printed_a_filter = true; + } + if (flags.filter_srcpt) { + if (printed_a_filter) { + if (flags.single) + printf(", "); + else + waddstr(mainwin, ", "); + } + if (flags.single) + printf("sport: %lu", filters.srcpt); + else + wprintw(mainwin, "sport: %lu", filters.srcpt); + printed_a_filter = true; + } + if (flags.filter_dst) { + if (printed_a_filter) { + if (flags.single) + printf(", "); + else + waddstr(mainwin, ", "); + } + inet_ntop(filters.dstfam, &filters.dst, tmp, NAMELEN-1); + if (flags.single) + printf("dst: %s", tmp); + else + wprintw(mainwin, "dst: %s", tmp); + printed_a_filter = true; + } + if (flags.filter_dstpt) { + if (printed_a_filter) { + if (flags.single) + printf(", "); + else + waddstr(mainwin, ", "); + } + if (flags.single) + printf("dport: %lu", filters.dstpt); + else + wprintw(mainwin, "dport: %lu", filters.dstpt); + printed_a_filter = true; + } + if (flags.single) + printf("\n"); + else + wprintw(mainwin, "\n"); + } + + /* + * Print column headers + */ + if (flags.single) { + if (flags.counters) + printf(format.c_str(), "Source", "Destination", "Prt", "State", "TTL", + "B", "P"); + else + printf(format.c_str(), "Source", "Destination", "Prt", "State", "TTL"); + } else { + wattron(mainwin, A_BOLD); + if (flags.counters) + wprintw(mainwin, format.c_str(), "Source", "Destination", "Prt", + "State", "TTL", "B", "P"); + else + wprintw(mainwin, format.c_str(), "Source", "Destination", "Prt", + "State", "TTL"); + wattroff(mainwin, A_BOLD); + } + +} + + +void truncate(string &string, int length, bool mark, char direction) +{ + int s = (direction == 'f') ? string.size() - length : 0; + + string = string.substr(s, length); + if (mark) { + int m = (direction == 'f') ? 0 : string.size() - 1; + string[m] = '+'; + } +} + +/* + * Based on the format pre-chosen, truncate src/dst as needed, and then + * generate the host:port strings and drop them off in the src/dst string + * objects passed in. + */ +void format_src_dst(tentry_t *table, string &src, string &dst, + const flags_t &flags, const max_t &max) +{ + ostringstream buffer; + bool have_port = table->proto == "tcp" || table->proto == "udp"; + char direction; + unsigned int length; + + // What length would we currently use? + length = table->sname.size(); + if (have_port) + length += table->spname.size() + 1; + + // If it's too long, figure out how room we have and truncate it + if (length > max.src) { + length = max.src; + if (have_port) + length -= 1 + table->spname.size(); + direction = (flags.lookup) ? 'e' : 'f'; + truncate(table->sname, length, flags.tag_truncate, direction); + } + + // ... and repeat + length = table->dname.size(); + if (have_port) + length += table->dpname.size() + 1; + if (length > max.dst) { + length = max.dst; + if (have_port) + length -= 1 + table->dpname.size(); + direction = (flags.lookup) ? 'f' : 'e'; + truncate(table->dname, length, flags.tag_truncate, direction); + } + + buffer << table->sname; + if (have_port) + buffer << ":" << table->spname; + src = buffer.str(); + buffer.str(""); + buffer << table->dname; + if (have_port) + buffer << ":" << table->dpname; + dst = buffer.str(); + buffer.str(""); +} + +/* + * An abstraction of priting a line for both single/curses modes + */ +void printline(tentry_t *table, const flags_t &flags, const string &format, + const max_t &max, WINDOW *mainwin, const bool curr) +{ + ostringstream buffer; + buffer.str(""); + string src, dst, b, p; + + // Generate strings for src/dest, truncating and marking as necessary + format_src_dst(table, src, dst, flags, max); + + if (flags.counters) { + buffer << table->bytes; + b = buffer.str(); + buffer.str(""); + buffer << table->packets; + p = buffer.str(); + buffer.str(""); + } + + if (flags.single) { + if (flags.counters) + printf(format.c_str(), src.c_str(), dst.c_str(), table->proto.c_str(), + table->state.c_str(), table->ttl.c_str(), b.c_str(), p.c_str()); + else + printf(format.c_str(), src.c_str(), dst.c_str(), table->proto.c_str(), + table->state.c_str(), table->ttl.c_str()); + } else { + int color = 0; + if (!flags.nocolor) { + if (table->proto == "tcp") + color = 1; + else if (table->proto == "udp") + color = 2; + else if (table->proto == "icmp" || table->proto == "icmp6") + color = 3; + if (curr) + color += 4; + wattron(mainwin, COLOR_PAIR(color)); + } + if (flags.counters) + wprintw(mainwin, format.c_str(), src.c_str(), dst.c_str(), + table->proto.c_str(), table->state.c_str(), table->ttl.c_str(), + b.c_str(), p.c_str()); + else + wprintw(mainwin, format.c_str(), src.c_str(), dst.c_str(), + table->proto.c_str(), table->state.c_str(), table->ttl.c_str()); + + if (!flags.nocolor && color != 0) + wattroff(mainwin, COLOR_PAIR(color)); + } +} + +/* + * This does all the work of actually printing the table including + * various bits of formatting. It handles both curses and non-curses runs. + */ +void print_table(vector &stable, const flags_t &flags, + const string &format, const string &sorting, + const filters_t &filters, const counters_t &counts, + const screensize_t &ssize, const max_t &max, WINDOW *mainwin, + unsigned int &curr) +{ + /* + * Print headers + */ + print_headers(flags, format, sorting, filters, counts, ssize, stable.size(), + mainwin); + + /* + * Print the state table + */ + unsigned int limit = (stable.size() < NLINES) ? stable.size() : NLINES; + for (unsigned int tmpint=0; tmpint < limit; tmpint++) { + printline(stable[tmpint], flags, format, max, mainwin, (curr == tmpint)); + if (!flags.single && flags.noscroll + && (tmpint >= ssize.y-4 || (flags.totals && tmpint >= ssize.y-5))) + break; + + } + + /* + * We don't want to lave things on the screen we didn't draw + * this time. + */ + if (!flags.single) + wclrtobot(mainwin); + +} + +/* + * Dynamically build a format to fit the most amount of data on the screen + */ +void determine_format(WINDOW *mainwin, max_t &max, screensize_t &ssize, + string &format, flags_t &flags) +{ + + /* + * NOTE: When doing proper dynamic format building, we fill the + * entire screen, so curses puts in a newline for us. However + * with "staticsize" we must add a newline. Also with "single" + * mode we must add it as well since there's no curses there. + * + * Thus DEFAULT_FORMAT (only used for staticsize) has it, and + * at the bottom of this function we add a \n if flags.single + * is set. + */ + if (flags.staticsize) { + format = DEFAULT_FORMAT; + max.src = DEFAULT_SRC; + max.dst = DEFAULT_DST; + max.proto = DEFAULT_PROTO; + max.state = DEFAULT_STATE; + max.ttl = DEFAULT_TTL; + return; + } + + ssize = get_size(flags.single); + + /* The screen must be 85 chars wide to be able to fit in + * counters when we display IP addresses... + * + * in lookup mode, we can truncate names, but in IP mode, + * truncation makes no sense, so we just disable counters if + * we run into this. + */ + if (ssize.x < 85 && flags.counters && !flags.lookup) { + string prompt = "Window too narrow for counters! Disabling."; + c_warn(mainwin, prompt, flags); + flags.counters = false; + } + + /* what's left is the above three, plus 4 spaces + * (one between each of 5 fields) + */ + unsigned int left = ssize.x - max.ttl - max.state - max.proto - 4; + if (flags.counters) + left -= (max.bytes + max.packets + 2); + + /* + * The rest is *prolly* going to be divided between src + * and dst, so we see if that works. If 'left' is odd though + * we give the extra space to src. + */ + unsigned int src, dst; + src = dst = left / 2; + bool left_odd = false; + if ((left % 2) == 1) { + left_odd = true; + src++; + } + if ((max.src + max.dst) < left) { + /* + * This means we can fit without an truncation, but it doesn't + * necessarily mean that we can just give half to src and half + * to dst... so lets figure that out. + */ + + if (max.src < src && max.dst < dst) { + /* + * This case applies if: + * we're even and they both fit in left/2 + * OR + * we're odd and dst fits in left/2 + * and src fits in left/2+1 + * + * Since we've already calculated src/dst that way + * we just combine this check as they both require + * the same outcome. + */ + } else if (left_odd && (src < left / 2) && (dst < left / 2 + 1)) { + /* + * If src can fit in left/2 and dst in left/2+1 + * then we switch them. + */ + src = dst; + dst++; + } else if (max.src > max.dst) { + /* + * If we're here, we can fit them, but we can't fit them + * and still keep the two columns relatively equal. Ah + * well. + * + * Either max gets the bigger chunk and everything else + * to dst... + */ + src = max.src; + dst = left - max.src; + } else { + /* + * ...or the other way around + */ + dst = max.dst; + src = left - max.dst; + } + } else if (max.src < src) { + /* + * If we're here, we do have to truncate, but if one column is + * very small, we should not give it more space than it needs. + */ + src = max.src; + dst = left - max.src; + } else if (max.dst < dst) { + /* + * same as above. + */ + dst = max.dst; + src = left - max.dst; + } + + /* + * If nothing matched, then they're both bigger than left/2, so we'll + * leave the default we set above. + */ + + ostringstream buffer; + buffer << "\%-" << src << "s \%-" << dst << "s \%-" << max.proto << "s \%-" + << max.state << "s \%-" << max.ttl << "s"; + + if (flags.counters) + buffer << " \%-" << max.bytes << "s \%-" << max.packets << "s"; + + if (flags.single) + buffer << "\n"; + + format = buffer.str(); + + max.dst = dst; + max.src = src; +} + +/* + * Interactive help + */ +void interactive_help(const string &sorting, const flags_t &flags, + const filters_t &filters) +{ + + /* + * This is the max we need the pad to be, and thus how + * big we're going to create the pad. + * + * In many cases we'd make the pad very very large and not + * worry about it. However, in this case: + * 1. We know exactly how big we need it to be, and it's + * not going to change interactively. + * 2. We want to draw a "box" around the window and if the + * pad is huge then the box will get drawn around that. + * + * So... we have 32 lines of help, plus a top and bottom border, + * thus maxrows is 34. + * + * Our help text is not wider than 80, so we'll se that standard + * width. + * + * If the screen is bigger than this, we deal with it below. + */ + unsigned int maxrows = 41; + unsigned int maxcols = 80; + + /* + * The actual screen size + */ + screensize_t ssize = get_size(flags.single); + + /* + * If the biggest we think we'll need is smaller than the screen, + * then lets grow the pad to the size of the screen so that the + * main window isn't peeking through. + */ + if (maxrows < ssize.y) + maxrows = ssize.y; + if (maxcols < ssize.x) + maxcols = ssize.x; + + /* + * Where we are withing the pad (for printing). We can't just print + * newlines and expect it to work. Cause, well, it doesn't. You have + * to tell it where on the pad to print, specifically. + */ + unsigned int x, y; + x = y = 0; + + /* + * The current position on the pad we're showing (top left) + */ + unsigned int px, py; + px = py = 0; + + /* + * As noted above, we create the biggest pad we might need + */ + static WINDOW *helpwin; + helpwin = newpad(maxrows, maxcols); + + /* + * Create a box, and then add one to "x" and "y" so we don't write + * on the line, + */ + box(helpwin, ACS_VLINE, ACS_HLINE); + x++; + y++; + + + /* + * we want arrow keys to work + */ + keypad(helpwin, true); + + // Prolly not needed + wmove(helpwin, 0, 0); + + // Print opener + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, "IPTState "); + waddstr(helpwin, VERSION); + wattroff(helpwin, A_BOLD); + // this is \n + y++; + + // We don't want anything except the title up against the + // border + x++; + + string nav = "Up/j, Down/k, Left/h, Right/l, PageUp/^u, PageDown/^d, "; + nav += " Home, or End"; + // Print instructions first + mvwaddstr(helpwin, y++, x, "Navigation:"); + mvwaddstr(helpwin, y++, x, nav.c_str()); + mvwaddstr(helpwin, y++, x, " Press any other key to continue..."); + y++; + + // Print settings + mvwaddstr(helpwin, y++, x, "Current settings:"); + + mvwaddstr(helpwin, y++, x, " Sorting by: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin, sorting.c_str()); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Dynamic formatting: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(!flags.staticsize) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Skip loopback states: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.skiplb) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Resolve hostnames: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.lookup) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Mark truncated hostnames: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.tag_truncate) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Colors: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(!flags.nocolor) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Skip outgoing DNS lookup states: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.skipdns) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Enable scroll: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(!flags.noscroll) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Display totals: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.totals) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + mvwaddstr(helpwin, y++, x, " Display counters: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin,(flags.counters) ? "yes" : "no"); + wattroff(helpwin, A_BOLD); + + char tmp[NAMELEN]; + if (flags.filter_src) { + inet_ntop(filters.srcfam, &filters.src, tmp, + filters.srcfam == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)); + mvwaddstr(helpwin, y++, x, " Source filter: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin, tmp); + wattroff(helpwin, A_BOLD); + } + if (flags.filter_dst) { + inet_ntop(filters.dstfam, &filters.dst, tmp, + filters.dstfam == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)); + mvwaddstr(helpwin, y++, x, " Destination filter: "); + wattron(helpwin, A_BOLD); + waddstr(helpwin, tmp); + wattroff(helpwin, A_BOLD); + } + if (flags.filter_srcpt) { + mvwaddstr(helpwin, y++, x, " Source port filter: "); + wattron(helpwin, A_BOLD); + wprintw(helpwin, "%lu", filters.srcpt); + wattroff(helpwin, A_BOLD); + } + if (flags.filter_dstpt) { + mvwaddstr(helpwin, y++, x, " Destination port filter: "); + wattron(helpwin, A_BOLD); + wprintw(helpwin, "%lu", filters.dstpt); + wattroff(helpwin, A_BOLD); + } + + y++; + + // Print commands + mvwaddstr(helpwin, y++, x, "Interactive commands:"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " c"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tUse colors"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " C"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of bytes/packets counters"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " b"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tSort by next column"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " B"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tSort by previous column"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " d"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange destination filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " D"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange destination port filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " f"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of loopback states"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " h"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tDisplay this help message"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " l"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle DNS lookups"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " L"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of outgoing DNS states"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " m"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle marking truncated hostnames with a '+'"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " o"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle dynamic or old formatting"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " p"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle scrolling"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " q"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tQuit"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " r"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle reverse sorting"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " R"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange the refresh rate"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " s"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange source filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " S"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tChange source port filter"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " t"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tToggle display of totals"); + + wattron(helpwin, A_BOLD); + mvwaddstr(helpwin, y++, x, " x"); + wattroff(helpwin, A_BOLD); + waddstr(helpwin, "\tDelete the currently highlighted state from netfilter"); + + y++; + + wmove(helpwin, 0, 0); + + /* + * refresh from wherever we are the pad + * and the top of the window to the bottom of the window. + */ + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + // kill line buffering + cbreak(); + // nodelay with a 0 here causes getch() to block until key is pressed. + nodelay( helpwin, 0 ); + int c; + while ((c = wgetch(helpwin))) { + switch (c) { + case ERR: + continue; + break; + case KEY_DOWN: + case 'j': + /* + * py is the top of the window, + * ssize.y is the height of the window, + * so py+ssize.y is the bottom of the window. + * + * Since y is the bottom of the text we've + * written, if + * py+ssize.y == y + * then the bottom of the screen as at the + * bottom of the text, no more scrolling. + */ + if (py + ssize.y < y) + py++; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_UP: + case 'k': + if (py > 0) + py--; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_RIGHT: + case 'l': + /* + * px is the left of the window, + * ssize.x is the width of the window, + * so px+ssize.x os the right side of the window. + * + * So if px+ssize.x == 80 (more than the width + * of our text), no more scrolling. + */ + if (px + ssize.x < 80) + px++; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_LEFT: + case 'h': + if (px > 0) + px--; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_HOME: + case KEY_SHOME: + case KEY_FIND: + px = py = 0; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_END: + py = y-ssize.y; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case 4: + case KEY_NPAGE: + case KEY_SNEXT: + if (flags.noscroll) + break; + /* + * If the screen is bigger than the text, + * ignore + */ + if (y < ssize.y) + break; + /* + * Otherwise, if the bottom of the screen + * (current position + screen size + * == py + ssize.y) + * were to go down one screen (thus: + * py + ssize.y*2), + * and that is bigger than the whole text, just + * go to the bottom. + * + * Otherwise, go down a screen size. + */ + if ((py + (ssize.y * 2)) > y) { + py = y-ssize.y; + } else { + py += ssize.y; + } + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case 21: + case KEY_PPAGE: + case KEY_SPREVIOUS: + if (flags.noscroll) + break; + /* + * If we're at the top, ignore this. + */ + if (py == 0) + break; + /* + * Otherwise if we're less than a page from the + * top, go to the top, else, go up a page. + */ + if (py < ssize.y) + py = 0; + else + py -= ssize.y; + prefresh(helpwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + + case 'q': + default: + goto out; + break; + } + if (need_resize) { + goto out; + } + } +out: + // once a key is pressed, tear down the help window. + delwin(helpwin); + refresh(); + halfdelay(1); +} + + +/* + * MAIN + */ +int main(int argc, char *argv[]) +{ + + // Variables + string line, src, dst, srcpt, dstpt, proto, code, type, state, ttl, mins, + secs, hrs, sorting, tmpstring, format, prompt; + ostringstream ostream; + vector stable; + int tmpint = 0, sortby = 0, rate = 1, hdrs = 0; + unsigned int py = 0, px = 0, curr_state = 0; + timeval selecttimeout; + fd_set readfd; + flags_t flags; + counters_t counts; + screensize_t ssize; + filters_t filters; + max_t max; + + /* + * Initialize + */ + flags.single = flags.totals = flags.lookup = flags.skiplb = flags.staticsize + = flags.skipdns = flags.tag_truncate = flags.filter_src + = flags.filter_dst = flags.filter_srcpt = flags.filter_dstpt + = flags.noscroll = flags.nocolor = flags.counters = false; + ssize.x = ssize.y = 0; + counts.tcp = counts.udp = counts.icmp = counts.other = counts.skipped = 0; + filters.src = filters.dst = in6addr_any; + filters.srcpt = filters.dstpt = 0; + max.src = max.dst = max.proto = max.state = max.ttl = 0; + px = py = 0; + + static struct option long_options[] = { + {"counters", no_argument , 0, 'C'}, + {"dst-filter", required_argument, 0, 'd'}, + {"dstpt-filter", required_argument, 0, 'D'}, + {"help", no_argument, 0, 'h'}, + {"lookup", no_argument, 0, 'l'}, + {"mark-truncated", no_argument, 0, 'm'}, + {"no-color", no_argument, 0, 'c'}, + {"no-dynamic", no_argument, 0, 'o'}, + {"no-dns", no_argument, 0, 'L'}, + {"no-loopback", no_argument, 0, 'f'}, + {"no-scroll", no_argument, 0, 'p'}, + {"rate", required_argument, 0, 'R'}, + {"reverse", no_argument, 0, 'r'}, + {"single", no_argument, 0, '1'}, + {"sort", required_argument, 0, 'b'}, + {"src-filter", required_argument, 0, 's'}, + {"srcpt-filter", required_argument, 0, 'S'}, + {"totals", no_argument, 0, 't'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0,0} + }; + int option_index = 0; + + // Command Line Arguments + while ((tmpint = getopt_long(argc, argv, "Cd:D:hlmcoLfpR:r1b:s:S:tv", + long_options, &option_index)) != EOF) { + printf("loop: %d\n", tmpint); + switch (tmpint) { + case 0: + /* Apparently this test is needed?! Seems lame! */ + if (long_options[option_index].flag != 0) + break; + + /* + * Long-only options go here, like so: + * + * tmpstring = long_options[option_index].name; + * if (tmpstring == "srcpt-filter") { + * ... + * } else if (...) { + * ... + * } + * + */ + + break; + // --counters + case 'C': + flags.counters = true; + break; + // --dst-filter + case 'd': + if (optarg == NULL) + break; + // See check_ip() note above + if (!check_ip(optarg, &filters.dst, &filters.dstfam)) { + cerr << "Invalid IP address: " << optarg + << endl; + exit(1); + } + flags.filter_dst = true; + break; + // --dstpt-filter + case 'D': + /* + * even though this won't be an IP address + * aton() won't complain about anything that's + * just digits, so it's an easy check. + */ + if (optarg == NULL) + break; + flags.filter_dstpt = true; + filters.dstpt = atoi(optarg); + break; + // --help + case 'h': + help(); + break; + // --lookup + case 'l': + flags.lookup = true; + // and also set skipdns for sanity + flags.skipdns = true; + break; + // --mark-truncated + case 'm': + flags.tag_truncate = true; + break; + // --color + case 'c': + flags.nocolor = false; + break; + // --no-dynamic + case 'o': + flags.staticsize = true; + break; + // --no-dns + case 'L': + flags.skipdns = true; + break; + // --no-loopback + case 'f': + flags.skiplb = true; + break; + // --no-scroll + case 'p': + flags.noscroll = true; + break; + // --reverse + case 'r': + sort_factor = -1; + break; + // --rate + case 'R': + rate = atoi(optarg); + break; + // --sort + case 'b': + if (*optarg == 'd') + sortby = SORT_DST; + else if (*optarg == 'D') { + sortby = SORT_DST_PT; + } else if (*optarg == 'S') + sortby = SORT_SRC_PT; + else if (*optarg == 'p') + sortby = SORT_PROTO; + else if (*optarg == 's') + sortby = SORT_STATE; + else if (*optarg == 't') + sortby = SORT_TTL; + else if (*optarg == 'b' && flags.counters) + sortby = SORT_BYTES; + else if (*optarg == 'P' && flags.counters) + sortby = SORT_PACKETS; + break; + // --single + case '1': + flags.single = true; + break; + // --src-filter + case 's': + if (optarg == NULL) + break; + if (!check_ip(optarg, &filters.src, &filters.srcfam)) { + cerr << "Invalid IP address: " << optarg << endl; + exit(1); + } + flags.filter_src = true; + break; + // --srcpt-filter + case 'S': + if (optarg == NULL) + break; + flags.filter_srcpt = true; + filters.srcpt = atoi(optarg); + break; + // --totals + case 't': + flags.totals = true; + break; + // --version + case 'v': + version(); + exit(0); + break; + // catch-all + default: + // getopts should already have printed a message + exit(1); + break; + } + } + + if (rate < 0 || rate > 60) { + rate = 1; + } + + // Initialize Curses Stuff + static WINDOW *mainwin = NULL; + if (!flags.single) { + mainwin = start_curses(flags); + keypad(mainwin, true); + } + + /* + * We want to keep going until the user stops us + * unless they use single run mode + * in which case, we'll deal with that down below + */ + while (1) { + + /* + * We get the screensize_t up-front so we can die if the + * screen doesn't meet our minimum requirements without making + * the user wait while we gather and process all the data. + * We'll do it again afterwards just in case + */ + + ssize = get_size(flags.single); + + if (ssize.x < 72) { + term_too_small(); + } + + // And our header size + hdrs = 3; + if (flags.totals) { + hdrs++; + } + if (flags.filter_src || flags.filter_dst || flags.filter_srcpt + || flags.filter_dstpt) { + hdrs++; + } + + // clear maxes + initialize_maxes(max, flags); + + // Build our table + build_table(flags, filters, stable, counts, max); + + /* + * Now that we have the new table, make sure our page/cursor + * positions still make sense. + */ + if (curr_state > stable.size() - 1) { + curr_state = stable.size() - 1; + } + + /* + * The bottom of the screen is stable.size()+hdrs+1 + * (the +1 is so we can have a blank line at the end) + * but we want to never have py be more than that - ssize.y + * so we're showing a page full of states. + */ + int bottom = stable.size() + hdrs + 1 - ssize.y; + if (bottom < 0) + bottom = 0; + if (py > (unsigned)bottom) + py = bottom; + + /* + * Originally I strived to do this the "right" way by calling + * nfct_is_set(ct, ATTR_ORIG_COUNGERS) to determine if + * counters were enabled. BUT, if counters are not enabled, + * nfct_get_attr() returns NULL, so this test is just as + * valid. + * + * Conversely checking is_set and then get_attr() inside our + * callback is twice the calls per-state if they are enabled, + * for no additional benefit. + */ + if (flags.counters && stable.size() > 0 && stable[0]->bytes == 0) { + prompt = "Counters requested, but not enabled in the"; + prompt += " kernel!"; + flags.counters = 0; + c_warn(mainwin, prompt, flags); + } + + // Sort our table + sort_table(sortby, flags.lookup, sort_factor, stable, sorting); + + /* + * From here on out 'max' is no longer "the maximum size of + * this field throughout the table", but is instead the actual + * size to print each field. + * + * BTW, we do "get_size" again here incase the window changed + * while we were off parsing and sorting data. + */ + determine_format(mainwin, max, ssize, format, flags); + + /* + * Now we print out the table in whichever format we're + * configured for + */ + print_table(stable, flags, format, sorting, filters, counts, ssize, max, + mainwin, curr_state); + + // Exit if we're only supposed to run once + if (flags.single) + exit(0); + + // Otherwise refresh the curses display + if (flags.noscroll) { + refresh(); + } else { + prefresh(mainwin, py, px, 0, 0, ssize.y-1, ssize.x-1); + } + + //check for key presses for one second + //or whatever the user said + selecttimeout.tv_sec = rate; + selecttimeout.tv_usec = 0; + // I don't care about fractions of seconds. I don't want them. + FD_ZERO(&readfd); + FD_SET(0, &readfd); + select(1,&readfd, NULL, NULL, &selecttimeout); + if (FD_ISSET(0, &readfd)) { + tmpint = wgetch(mainwin); + switch (tmpint) { + // This is ^L + case 12: + handle_resize(mainwin, flags, ssize); + break; + /* + * This is at the top because the rest are in + * order of longopts, and q isn't in longopts + */ + case 'q': + goto out; + break; + /* + * General option toggles + */ + case 'c': + /* + * we only want to pay attention to this + * command if colors are available + */ + if (has_colors()) + flags.nocolor = !flags.nocolor; + break; + case 'C': + flags.counters = !flags.counters; + if (sortby >= SORT_BYTES) + sortby = SORT_BYTES-1; + break; + case 'h': + interactive_help(sorting, flags, filters); + break; + case 'l': + flags.lookup = !flags.lookup; + /* + * If we just turned on lookup, also turn on filtering DNS states. + * They can turn it off if they want, but generally this is the safer + * approach. + */ + if (flags.lookup) { + flags.skipdns = true; + } + break; + case 'm': + flags.tag_truncate = !flags.tag_truncate; + break; + case 'o': + flags.staticsize = !flags.staticsize; + break; + case 'L': + flags.skipdns = !flags.skipdns; + break; + case 'f': + flags.skiplb = !flags.skiplb; + break; + case 'p': + switch_scroll(flags, mainwin); + break; + case 'r': + sort_factor = -sort_factor; + break; + case 'b': + if (sortby < SORT_MAX) { + sortby++; + if (!flags.counters && sortby >= SORT_BYTES) + sortby = 0; + } else { + sortby = 0; + } + break; + case 'B': + if (sortby > 0) { + sortby--; + } else { + if (flags.counters) + sortby=SORT_MAX; + else + sortby=SORT_BYTES-1; + } + break; + case 't': + flags.totals = !flags.totals; + break; + /* + * Update-filters + */ + case 'd': + prompt = "New Destination Filter? (leave blank"; + prompt += " for none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_dst = false; + filters.dst = in6addr_any; + } else { + if (!check_ip(tmpstring.c_str(), &filters.dst, &filters.dstfam)) { + prompt = "Invalid IP,"; + prompt += " ignoring!"; + c_warn(mainwin, prompt, flags); + } else { + flags.filter_dst = true; + } + } + break; + case 'D': + prompt = "New dstpt filter? (leave blank for"; + prompt += " none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_dstpt = false; + filters.dstpt = 0; + } else { + flags.filter_dstpt = true; + filters.dstpt = atoi(tmpstring.c_str()); + } + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + break; + case 'R': + prompt = "Rate: "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring != "") { + int i = atoi(tmpstring.c_str()); + if (i < 1) { + prompt = "Invalid rate,"; + prompt += " ignoring!"; + c_warn(mainwin, prompt, flags); + } else { + rate = i; + } + } + break; + case 's': + prompt = "New src filter? (leave blank for"; + prompt += " none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_src = false; + filters.src = in6addr_any; + } else { + if (!check_ip(tmpstring.c_str(), &filters.src, &filters.srcfam)) { + prompt = "Invalid IP,"; + prompt += " ignoring!"; + c_warn(mainwin, prompt, flags); + } else { + flags.filter_src = true; + } + } + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + break; + case 'S': + prompt = "New srcpt filter? (leave blank for"; + prompt += " none): "; + get_input(mainwin, tmpstring, prompt, flags); + if (tmpstring == "") { + flags.filter_srcpt = false; + filters.srcpt = 0; + } else { + flags.filter_srcpt = true; + filters.srcpt = atoi(tmpstring.c_str()); + } + wmove(mainwin, 0, 0); + wclrtoeol(mainwin); + break; + case 'x': + delete_state(mainwin, stable[curr_state], flags); + break; + /* + * Window navigation + */ + case KEY_DOWN: + case 'j': + if (flags.noscroll) + break; + /* + * GENERAL NOTE: + * py is the top of the window, + * ssize.y is the height of the window, + * so py+ssize.y is the bottom of the window. + * + * BOTTOM OF SCROLLING: + * Since stable.size()+hdrs+1 is + * the bottom of the text we've written, if + * py+ssize.y == stable.size()+hdrs+1 + * then the bottom of the screen as at the + * bottom of the text, no more scrolling. + * + * However, we only want to scroll the page + * when the cursor is at the bottom, i.e. + * when curr_state+4 == py+ssize.y + */ + + /* + * If we have room to scroll down AND if cur is + * at the bottom of a page scroll down. + */ + if ((py + ssize.y <= stable.size() + hdrs + 1) + && (curr_state + 4 == py + ssize.y)) + py++; + + /* + * As long as the cursor isn't at the end, + * move it down one. + */ + if (curr_state < stable.size() - 1) + curr_state++; + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_UP: + case 'k': + if (flags.noscroll) + break; + + /* + * This one is tricky. + * + * First thing we need to know is when the cursor + * is at the top of the page. This is simply when + * curr_state+hdrs+1 cursor location), is + * exactly one more than the top of the window + * (py), * i.e. when curr_state+hdrs+1 == py+1. + * + * PAGE SCROLLING: + * IF we're not page-scrolled all the way up + * (i.e. py > 0) + * AND the cursor is at the top of the page + * OR the cursor is at the top of the list, + * AND we're not yet at the top (showing + * the headers). + * THEN we scroll up. + * + * CURSOR SCROLLING: + * Unlike KEY_DOWN, we don't break just because + * the cursor can't move anymore - on the way + * ip we may still have page-scrolling to do. So + * test to make sure we're not at state 0, and + * if so, we scroll up. + */ + + /* + * Basically: + * IF the cursor bumps the top of the screen + * OR we need to scroll up for headers + */ + if ((py > 0 && (curr_state + hdrs + 1) == (py + 1)) + || (curr_state == 0 && py > 0)) + py--; + + if (curr_state > 0) + curr_state--; + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + // 4 is ^d + case 4: + case KEY_NPAGE: + case KEY_SNEXT: + if (flags.noscroll) + break; + /* + * If the screen is bigger than the text, + * and the cursor is at the bottom, ignore. + */ + if (stable.size() + hdrs + 1 < ssize.y && curr_state == stable.size()) + break; + + /* + * Otherwise, if the bottom of the screen + * (current position + screen size + * == py + ssize.y) + * were to go down one screen (thus: + * py + ssize.y*2), + * and that is bigger than the whole pad, just + * go to the bottom. + * + * Otherwise, go down a screen size. + */ + if (py + ssize.y * 2 > stable.size() + hdrs + 1) { + py = stable.size() + hdrs + 1 - ssize.y; + } else { + py += ssize.y; + } + + /* + * For the cursor, we try to move it down one + * screen as well, but if that's too far, + * we bring it up to the largest number it can + * be. + */ + curr_state += ssize.y; + if (curr_state > stable.size()) { + curr_state = stable.size(); + } + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + // 21 is ^u + case 21: + case KEY_PPAGE: + case KEY_SPREVIOUS: + if (flags.noscroll) + break; + /* + * If we're at the top, ignore + */ + if (py == 0 && curr_state == 0) + break; + /* + * Otherwise if we're less than a page from the + * top, go to the top, else go up a page. + */ + if (py < ssize.y) { + py = 0; + } else { + py -= ssize.y; + } + + /* + * We bring the cursor up a page too, unless + * that's too far. + */ + if (curr_state < ssize.y) { + curr_state = 0; + } else { + curr_state -= ssize.y; + } + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_HOME: + if (flags.noscroll) + break; + px = py = curr_state = 0; + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + case KEY_END: + if (flags.noscroll) + break; + py = stable.size() + hdrs + 1 - ssize.y; + if (py < 0) + py = 0; + curr_state = stable.size(); + prefresh(mainwin, py, px, 0, 0, ssize.y - 1, ssize.x - 1); + break; + } + } + /* + * If we got a sigwinch, we need to redraw + */ + if (need_resize) { + handle_resize(mainwin, flags, ssize); + need_resize = false; + } + } // end while(1) + + out: + + /* + * The user has broken out of the loop, take down the curses + */ + end_curses(); + + // And we're done + return(0); + +} // end main diff --git a/iptstate.spec b/iptstate.spec new file mode 100644 index 0000000..a597544 --- /dev/null +++ b/iptstate.spec @@ -0,0 +1,44 @@ +%define name iptstate +%define version 2.2.6 +%define release 1 + +Name: %{name} +Summary: Display IP Tables state table information in a "top"-like interface +Version: %{version} +Release: %{release} +Group: Monitoring +License: zlib License +Source: http://www.phildev.net/iptstate/%{name}-%{version}.tar.gz +URL: http://www.phildev.net/iptstate/ +BuildRoot: %{_tmppath}/%{name}-buildroot +BuildRequires: libncurses.so.5 libnetfilter_conntrack.so.1 + +%description + IP Tables State (iptstate) was originally written to + impliment the "state top" feature of IP Filter. + "State top" displays the states held by your stateful + firewall in a "top"-like manner. + + Since IP Tables doesn't have a built in way to easily + display this information even once, an option was + added to just display the state table once and exit. + +%prep +rm -rf $RPM_BUILD_ROOT +%setup + +%build +make + +%install +make install PREFIX=$RPM_BUILD_ROOT/usr + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(755, root, bin, 755) +/usr/sbin/%{name} +/usr/share/man/man8/%{name}.8* +%doc README BUGS Changelog LICENSE CONTRIB WISHLIST +