/* * 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