/* * (C) 2005-2012 by Pablo Neira Ayuso * (C) 2012 by Intra2net AG * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Note: * Yes, portions of this code has been stolen from iptables ;) * Special thanks to the the Netfilter Core Team. * Thanks to Javier de Miguel Rodriguez * for introducing me to advanced firewalling stuff. * * --pablo 13/04/2005 * * 2005-04-16 Harald Welte : * Add support for conntrack accounting and conntrack mark * 2005-06-23 Harald Welte : * Add support for expect creation * 2005-09-24 Harald Welte : * Remove remaints of "-A" * 2007-04-22 Pablo Neira Ayuso : * Ported to the new libnetfilter_conntrack API * 2008-04-13 Pablo Neira Ayuso : * Way more flexible update and delete operations * * Part of this code has been funded by Sophos Astaro */ #include "conntrack.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ARPA_INET_H #include #endif #include #include #include #include #include #include #include struct u32_mask { uint32_t value; uint32_t mask; }; /* These are the template objects that are used to send commands. */ static struct { struct nf_conntrack *ct; struct nf_expect *exp; /* Expectations require the expectation tuple and the mask. */ struct nf_conntrack *exptuple, *mask; /* Allows filtering/setting specific bits in the ctmark */ struct u32_mask mark; /* Allow to filter by mark from kernel-space. */ struct nfct_filter_dump_mark filter_mark_kernel; } tmpl; static int alloc_tmpl_objects(void) { tmpl.ct = nfct_new(); tmpl.exptuple = nfct_new(); tmpl.mask = nfct_new(); tmpl.exp = nfexp_new(); memset(&tmpl.mark, 0, sizeof(tmpl.mark)); return tmpl.ct != NULL && tmpl.exptuple != NULL && tmpl.mask != NULL && tmpl.exp != NULL; } static void free_tmpl_objects(void) { if (tmpl.ct) nfct_destroy(tmpl.ct); if (tmpl.exptuple) nfct_destroy(tmpl.exptuple); if (tmpl.mask) nfct_destroy(tmpl.mask); if (tmpl.exp) nfexp_destroy(tmpl.exp); } enum ct_command { CT_NONE = 0, CT_LIST_BIT = 0, CT_LIST = (1 << CT_LIST_BIT), CT_CREATE_BIT = 1, CT_CREATE = (1 << CT_CREATE_BIT), CT_UPDATE_BIT = 2, CT_UPDATE = (1 << CT_UPDATE_BIT), CT_DELETE_BIT = 3, CT_DELETE = (1 << CT_DELETE_BIT), CT_GET_BIT = 4, CT_GET = (1 << CT_GET_BIT), CT_FLUSH_BIT = 5, CT_FLUSH = (1 << CT_FLUSH_BIT), CT_EVENT_BIT = 6, CT_EVENT = (1 << CT_EVENT_BIT), CT_VERSION_BIT = 7, CT_VERSION = (1 << CT_VERSION_BIT), CT_HELP_BIT = 8, CT_HELP = (1 << CT_HELP_BIT), EXP_LIST_BIT = 9, EXP_LIST = (1 << EXP_LIST_BIT), EXP_CREATE_BIT = 10, EXP_CREATE = (1 << EXP_CREATE_BIT), EXP_DELETE_BIT = 11, EXP_DELETE = (1 << EXP_DELETE_BIT), EXP_GET_BIT = 12, EXP_GET = (1 << EXP_GET_BIT), EXP_FLUSH_BIT = 13, EXP_FLUSH = (1 << EXP_FLUSH_BIT), EXP_EVENT_BIT = 14, EXP_EVENT = (1 << EXP_EVENT_BIT), CT_COUNT_BIT = 15, CT_COUNT = (1 << CT_COUNT_BIT), EXP_COUNT_BIT = 16, EXP_COUNT = (1 << EXP_COUNT_BIT), CT_STATS_BIT = 17, CT_STATS = (1 << CT_STATS_BIT), EXP_STATS_BIT = 18, EXP_STATS = (1 << EXP_STATS_BIT), }; /* If you add a new command, you have to update NUMBER_OF_CMD in conntrack.h */ enum ct_options { CT_OPT_ORIG_SRC_BIT = 0, CT_OPT_ORIG_SRC = (1 << CT_OPT_ORIG_SRC_BIT), CT_OPT_ORIG_DST_BIT = 1, CT_OPT_ORIG_DST = (1 << CT_OPT_ORIG_DST_BIT), CT_OPT_ORIG = (CT_OPT_ORIG_SRC | CT_OPT_ORIG_DST), CT_OPT_REPL_SRC_BIT = 2, CT_OPT_REPL_SRC = (1 << CT_OPT_REPL_SRC_BIT), CT_OPT_REPL_DST_BIT = 3, CT_OPT_REPL_DST = (1 << CT_OPT_REPL_DST_BIT), CT_OPT_REPL = (CT_OPT_REPL_SRC | CT_OPT_REPL_DST), CT_OPT_PROTO_BIT = 4, CT_OPT_PROTO = (1 << CT_OPT_PROTO_BIT), CT_OPT_TUPLE_ORIG = (CT_OPT_ORIG | CT_OPT_PROTO), CT_OPT_TUPLE_REPL = (CT_OPT_REPL | CT_OPT_PROTO), CT_OPT_TIMEOUT_BIT = 5, CT_OPT_TIMEOUT = (1 << CT_OPT_TIMEOUT_BIT), CT_OPT_STATUS_BIT = 6, CT_OPT_STATUS = (1 << CT_OPT_STATUS_BIT), CT_OPT_ZERO_BIT = 7, CT_OPT_ZERO = (1 << CT_OPT_ZERO_BIT), CT_OPT_EVENT_MASK_BIT = 8, CT_OPT_EVENT_MASK = (1 << CT_OPT_EVENT_MASK_BIT), CT_OPT_EXP_SRC_BIT = 9, CT_OPT_EXP_SRC = (1 << CT_OPT_EXP_SRC_BIT), CT_OPT_EXP_DST_BIT = 10, CT_OPT_EXP_DST = (1 << CT_OPT_EXP_DST_BIT), CT_OPT_MASK_SRC_BIT = 11, CT_OPT_MASK_SRC = (1 << CT_OPT_MASK_SRC_BIT), CT_OPT_MASK_DST_BIT = 12, CT_OPT_MASK_DST = (1 << CT_OPT_MASK_DST_BIT), CT_OPT_NATRANGE_BIT = 13, CT_OPT_NATRANGE = (1 << CT_OPT_NATRANGE_BIT), CT_OPT_MARK_BIT = 14, CT_OPT_MARK = (1 << CT_OPT_MARK_BIT), CT_OPT_ID_BIT = 15, CT_OPT_ID = (1 << CT_OPT_ID_BIT), CT_OPT_FAMILY_BIT = 16, CT_OPT_FAMILY = (1 << CT_OPT_FAMILY_BIT), CT_OPT_SRC_NAT_BIT = 17, CT_OPT_SRC_NAT = (1 << CT_OPT_SRC_NAT_BIT), CT_OPT_DST_NAT_BIT = 18, CT_OPT_DST_NAT = (1 << CT_OPT_DST_NAT_BIT), CT_OPT_OUTPUT_BIT = 19, CT_OPT_OUTPUT = (1 << CT_OPT_OUTPUT_BIT), CT_OPT_SECMARK_BIT = 20, CT_OPT_SECMARK = (1 << CT_OPT_SECMARK_BIT), CT_OPT_BUFFERSIZE_BIT = 21, CT_OPT_BUFFERSIZE = (1 << CT_OPT_BUFFERSIZE_BIT), CT_OPT_ANY_NAT_BIT = 22, CT_OPT_ANY_NAT = (1 << CT_OPT_ANY_NAT_BIT), CT_OPT_ZONE_BIT = 23, CT_OPT_ZONE = (1 << CT_OPT_ZONE_BIT), }; /* If you add a new option, you have to update NUMBER_OF_OPT in conntrack.h */ /* Update this mask to allow to filter based on new options. */ #define CT_COMPARISON (CT_OPT_PROTO | CT_OPT_ORIG | CT_OPT_REPL | \ CT_OPT_MARK | CT_OPT_SECMARK | CT_OPT_STATUS | \ CT_OPT_ID | CT_OPT_ZONE) static const char *optflags[NUMBER_OF_OPT] = { [CT_OPT_ORIG_SRC_BIT] = "src", [CT_OPT_ORIG_DST_BIT] = "dst", [CT_OPT_REPL_SRC_BIT] = "reply-src", [CT_OPT_REPL_DST_BIT] = "reply-dst", [CT_OPT_PROTO_BIT] = "protonum", [CT_OPT_TIMEOUT_BIT] = "timeout", [CT_OPT_STATUS_BIT] = "status", [CT_OPT_ZERO_BIT] = "zero", [CT_OPT_EVENT_MASK_BIT] = "event-mask", [CT_OPT_EXP_SRC_BIT] = "tuple-src", [CT_OPT_EXP_DST_BIT] = "tuple-dst", [CT_OPT_MASK_SRC_BIT] = "mask-src", [CT_OPT_MASK_DST_BIT] = "mask-dst", [CT_OPT_NATRANGE_BIT] = "nat-range", [CT_OPT_MARK_BIT] = "mark", [CT_OPT_ID_BIT] = "id", [CT_OPT_FAMILY_BIT] = "family", [CT_OPT_SRC_NAT_BIT] = "src-nat", [CT_OPT_DST_NAT_BIT] = "dst-nat", [CT_OPT_OUTPUT_BIT] = "output", [CT_OPT_SECMARK_BIT] = "secmark", [CT_OPT_BUFFERSIZE_BIT] = "buffer-size", [CT_OPT_ANY_NAT_BIT] = "any-nat", [CT_OPT_ZONE_BIT] = "zone", }; static struct option original_opts[] = { {"dump", 2, 0, 'L'}, {"create", 2, 0, 'I'}, {"delete", 2, 0, 'D'}, {"update", 2, 0, 'U'}, {"get", 2, 0, 'G'}, {"flush", 2, 0, 'F'}, {"event", 2, 0, 'E'}, {"counter", 2, 0, 'C'}, {"stats", 0, 0, 'S'}, {"version", 0, 0, 'V'}, {"help", 0, 0, 'h'}, {"orig-src", 1, 0, 's'}, {"src", 1, 0, 's'}, {"orig-dst", 1, 0, 'd'}, {"dst", 1, 0, 'd'}, {"reply-src", 1, 0, 'r'}, {"reply-dst", 1, 0, 'q'}, {"protonum", 1, 0, 'p'}, {"timeout", 1, 0, 't'}, {"status", 1, 0, 'u'}, {"zero", 0, 0, 'z'}, {"event-mask", 1, 0, 'e'}, {"tuple-src", 1, 0, '['}, {"tuple-dst", 1, 0, ']'}, {"mask-src", 1, 0, '{'}, {"mask-dst", 1, 0, '}'}, {"nat-range", 1, 0, 'a'}, /* deprecated */ {"mark", 1, 0, 'm'}, {"secmark", 1, 0, 'c'}, {"id", 2, 0, 'i'}, /* deprecated */ {"family", 1, 0, 'f'}, {"src-nat", 2, 0, 'n'}, {"dst-nat", 2, 0, 'g'}, {"output", 1, 0, 'o'}, {"buffer-size", 1, 0, 'b'}, {"any-nat", 2, 0, 'j'}, {"zone", 1, 0, 'w'}, {0, 0, 0, 0} }; static const char *getopt_str = "L::I::U::D::G::E::F::hVs:d:r:q:" "p:t:u:e:a:z[:]:{:}:m:i:f:o:n::" "g::c:b:C::Sj::w:"; /* Table of legal combinations of commands and options. If any of the * given commands make an option legal, that option is legal (applies to * CMD_LIST and CMD_ZERO only). * Key: * 0 illegal * 1 compulsory * 2 optional * 3 undecided, see flag combination checkings in generic_opt_check() */ static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = /* Well, it's better than "Re: Linux vs FreeBSD" */ { /* s d r q p t u z e [ ] { } a m i f n g o c b j w*/ /*CT_LIST*/ {2,2,2,2,2,0,2,2,0,0,0,0,0,0,2,0,2,2,2,2,2,0,2,2}, /*CT_CREATE*/ {3,3,3,3,1,1,2,0,0,0,0,0,0,2,2,0,0,2,2,0,0,0,0,2}, /*CT_UPDATE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0}, /*CT_DELETE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,2}, /*CT_GET*/ {3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0}, /*CT_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*CT_EVENT*/ {2,2,2,2,2,0,0,0,2,0,0,0,0,0,2,0,0,2,2,2,2,2,2,2}, /*VERSION*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*HELP*/ {0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_LIST*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0}, /*EXP_CREATE*/{1,1,2,2,1,1,2,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_DELETE*/{1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_GET*/ {1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_EVENT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0}, /*CT_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*CT_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, }; static const int cmd2type[][2] = { ['L'] = { CT_LIST, EXP_LIST }, ['I'] = { CT_CREATE, EXP_CREATE }, ['D'] = { CT_DELETE, EXP_DELETE }, ['G'] = { CT_GET, EXP_GET }, ['F'] = { CT_FLUSH, EXP_FLUSH }, ['E'] = { CT_EVENT, EXP_EVENT }, ['V'] = { CT_VERSION, CT_VERSION }, ['h'] = { CT_HELP, CT_HELP }, ['C'] = { CT_COUNT, EXP_COUNT }, ['S'] = { CT_STATS, EXP_STATS }, }; static const int opt2type[] = { ['s'] = CT_OPT_ORIG_SRC, ['d'] = CT_OPT_ORIG_DST, ['r'] = CT_OPT_REPL_SRC, ['q'] = CT_OPT_REPL_DST, ['{'] = CT_OPT_MASK_SRC, ['}'] = CT_OPT_MASK_DST, ['['] = CT_OPT_EXP_SRC, [']'] = CT_OPT_EXP_DST, ['n'] = CT_OPT_SRC_NAT, ['g'] = CT_OPT_DST_NAT, ['m'] = CT_OPT_MARK, ['c'] = CT_OPT_SECMARK, ['i'] = CT_OPT_ID, ['j'] = CT_OPT_ANY_NAT, ['w'] = CT_OPT_ZONE, }; static const int opt2family_attr[][2] = { ['s'] = { ATTR_ORIG_IPV4_SRC, ATTR_ORIG_IPV6_SRC }, ['d'] = { ATTR_ORIG_IPV4_DST, ATTR_ORIG_IPV6_DST }, ['r'] = { ATTR_REPL_IPV4_SRC, ATTR_REPL_IPV6_SRC }, ['q'] = { ATTR_REPL_IPV4_DST, ATTR_REPL_IPV6_DST }, ['{'] = { ATTR_ORIG_IPV4_SRC, ATTR_ORIG_IPV6_SRC }, ['}'] = { ATTR_ORIG_IPV4_DST, ATTR_ORIG_IPV6_DST }, ['['] = { ATTR_ORIG_IPV4_SRC, ATTR_ORIG_IPV6_SRC }, [']'] = { ATTR_ORIG_IPV4_DST, ATTR_ORIG_IPV6_DST }, }; static const int opt2attr[] = { ['s'] = ATTR_ORIG_L3PROTO, ['d'] = ATTR_ORIG_L3PROTO, ['r'] = ATTR_REPL_L3PROTO, ['q'] = ATTR_REPL_L3PROTO, ['m'] = ATTR_MARK, ['c'] = ATTR_SECMARK, ['i'] = ATTR_ID, ['w'] = ATTR_ZONE, }; static char exit_msg[NUMBER_OF_CMD][64] = { [CT_LIST_BIT] = "%d flow entries have been shown.\n", [CT_CREATE_BIT] = "%d flow entries have been created.\n", [CT_UPDATE_BIT] = "%d flow entries have been updated.\n", [CT_DELETE_BIT] = "%d flow entries have been deleted.\n", [CT_GET_BIT] = "%d flow entries have been shown.\n", [CT_EVENT_BIT] = "%d flow events have been shown.\n", [EXP_LIST_BIT] = "%d expectations have been shown.\n", [EXP_DELETE_BIT] = "%d expectations have been shown.\n", }; static const char usage_commands[] = "Commands:\n" " -L [table] [options]\t\tList conntrack or expectation table\n" " -G [table] parameters\t\tGet conntrack or expectation\n" " -D [table] parameters\t\tDelete conntrack or expectation\n" " -I [table] parameters\t\tCreate a conntrack or expectation\n" " -U [table] parameters\t\tUpdate a conntrack\n" " -E [table] [options]\t\tShow events\n" " -F [table]\t\t\tFlush table\n" " -C [table]\t\t\tShow counter\n" " -S\t\t\t\tShow statistics\n"; static const char usage_tables[] = "Tables: conntrack, expect\n"; static const char usage_conntrack_parameters[] = "Conntrack parameters and options:\n" " -n, --src-nat ip\t\t\tsource NAT ip\n" " -g, --dst-nat ip\t\t\tdestination NAT ip\n" " -j, --any-nat ip\t\t\tsource or destination NAT ip\n" " -m, --mark mark\t\t\tSet mark\n" " -c, --secmark secmark\t\t\tSet selinux secmark\n" " -e, --event-mask eventmask\t\tEvent mask, eg. NEW,DESTROY\n" " -z, --zero \t\t\t\tZero counters while listing\n" " -o, --output type[,...]\t\tOutput format, eg. xml\n"; static const char usage_expectation_parameters[] = "Expectation parameters and options:\n" " --tuple-src ip\tSource address in expect tuple\n" " --tuple-dst ip\tDestination address in expect tuple\n" " --mask-src ip\t\tSource mask address\n" " --mask-dst ip\t\tDestination mask address\n"; static const char usage_parameters[] = "Common parameters and options:\n" " -s, --orig-src ip\t\tSource address from original direction\n" " -d, --orig-dst ip\t\tDestination address from original direction\n" " -r, --reply-src ip\t\tSource addres from reply direction\n" " -q, --reply-dst ip\t\tDestination address from reply direction\n" " -p, --protonum proto\t\tLayer 4 Protocol, eg. 'tcp'\n" " -f, --family proto\t\tLayer 3 Protocol, eg. 'ipv6'\n" " -t, --timeout timeout\t\tSet timeout\n" " -u, --status status\t\tSet status, eg. ASSURED\n" " -w, --zone value\t\tSet conntrack zone\n" " -b, --buffer-size\t\tNetlink socket buffer size\n" ; #define OPTION_OFFSET 256 static struct nfct_handle *cth, *ith; static struct option *opts = original_opts; static unsigned int global_option_offset = 0; #define ADDR_VALID_FLAGS_MAX 2 static unsigned int addr_valid_flags[ADDR_VALID_FLAGS_MAX] = { CT_OPT_ORIG_SRC | CT_OPT_ORIG_DST, CT_OPT_REPL_SRC | CT_OPT_REPL_DST, }; static LIST_HEAD(proto_list); static unsigned int options; void register_proto(struct ctproto_handler *h) { if (strcmp(h->version, VERSION) != 0) { fprintf(stderr, "plugin `%s': version %s (I'm %s)\n", h->name, h->version, VERSION); exit(1); } list_add(&h->head, &proto_list); } extern struct ctproto_handler ct_proto_unknown; static struct ctproto_handler *findproto(char *name, int *pnum) { struct ctproto_handler *cur; struct protoent *pent; int protonum; /* is it in the list of supported protocol? */ list_for_each_entry(cur, &proto_list, head) { if (strcmp(cur->name, name) == 0) { *pnum = cur->protonum; return cur; } } /* using the protocol name for an unsupported protocol? */ if ((pent = getprotobyname(name))) { *pnum = pent->p_proto; return &ct_proto_unknown; } /* using a protocol number? */ protonum = atoi(name); if (protonum > 0 && protonum <= IPPROTO_MAX) { /* try lookup by number, perhaps this protocol is supported */ list_for_each_entry(cur, &proto_list, head) { if (cur->protonum == protonum) { *pnum = protonum; return cur; } } *pnum = protonum; return &ct_proto_unknown; } return NULL; } static void extension_help(struct ctproto_handler *h, int protonum) { const char *name; if (h == &ct_proto_unknown) { struct protoent *pent; pent = getprotobynumber(protonum); if (!pent) name = h->name; else name = pent->p_name; } else { name = h->name; } fprintf(stdout, "Proto `%s' help:\n", name); h->help(); } static void __attribute__((noreturn)) exit_tryhelp(int status) { fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", PROGNAME, PROGNAME); exit(status); } static void free_options(void) { if (opts != original_opts) { free(opts); opts = original_opts; global_option_offset = 0; } } void __attribute__((noreturn)) exit_error(enum exittype status, const char *msg, ...) { va_list args; free_options(); va_start(args, msg); fprintf(stderr,"%s v%s (conntrack-tools): ", PROGNAME, VERSION); vfprintf(stderr, msg, args); fprintf(stderr, "\n"); va_end(args); if (status == PARAMETER_PROBLEM) exit_tryhelp(status); /* release template objects that were allocated in the setup stage. */ free_tmpl_objects(); exit(status); } static int bit2cmd(int command) { int i; for (i = 0; i < NUMBER_OF_CMD; i++) if (command & (1<= 0) return 1; /* report a partial matching to suggest something */ return 0; } static struct option * merge_options(struct option *oldopts, const struct option *newopts, unsigned int *option_offset) { unsigned int num_old, num_new, i; struct option *merge; for (num_old = 0; oldopts[num_old].name; num_old++); for (num_new = 0; newopts[num_new].name; num_new++); global_option_offset += OPTION_OFFSET; *option_offset = global_option_offset; merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); if (merge == NULL) return NULL; memcpy(merge, oldopts, num_old * sizeof(struct option)); for (i = 0; i < num_new; i++) { merge[num_old + i] = newopts[i]; merge[num_old + i].val += *option_offset; } memset(merge + num_old + num_new, 0, sizeof(struct option)); return merge; } /* From linux/errno.h */ #define ENOTSUPP 524 /* Operation is not supported */ /* Translates errno numbers into more human-readable form than strerror. */ static const char * err2str(int err, enum ct_command command) { unsigned int i; struct table_struct { enum ct_command act; int err; const char *message; } table [] = { { CT_LIST, ENOTSUPP, "function not implemented" }, { 0xFFFF, EINVAL, "invalid parameters" }, { CT_CREATE, EEXIST, "Such conntrack exists, try -U to update" }, { CT_CREATE|CT_GET|CT_DELETE, ENOENT, "such conntrack doesn't exist" }, { CT_CREATE|CT_GET, ENOMEM, "not enough memory" }, { CT_GET, EAFNOSUPPORT, "protocol not supported" }, { CT_CREATE, ETIME, "conntrack has expired" }, { EXP_CREATE, ENOENT, "master conntrack not found" }, { EXP_CREATE, EINVAL, "invalid parameters" }, { ~0U, EPERM, "sorry, you must be root or get " "CAP_NET_ADMIN capability to do this"} }; for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { if ((table[i].act & command) && table[i].err == err) return table[i].message; } return strerror(err); } static int mark_cmp(const struct u32_mask *m, const struct nf_conntrack *ct) { return nfct_attr_is_set(ct, ATTR_MARK) && (nfct_get_attr_u32(ct, ATTR_MARK) & m->mask) == m->value; } #define PARSE_STATUS 0 #define PARSE_EVENT 1 #define PARSE_OUTPUT 2 #define PARSE_MAX 3 enum { _O_XML = (1 << 0), _O_EXT = (1 << 1), _O_TMS = (1 << 2), _O_ID = (1 << 3), _O_KTMS = (1 << 4), }; enum { CT_EVENT_F_NEW = (1 << 0), CT_EVENT_F_UPD = (1 << 1), CT_EVENT_F_DEL = (1 << 2), CT_EVENT_F_ALL = CT_EVENT_F_NEW | CT_EVENT_F_UPD | CT_EVENT_F_DEL, }; static struct parse_parameter { const char *parameter[6]; size_t size; unsigned int value[6]; } parse_array[PARSE_MAX] = { { {"ASSURED", "SEEN_REPLY", "UNSET", "FIXED_TIMEOUT", "EXPECTED"}, 5, { IPS_ASSURED, IPS_SEEN_REPLY, 0, IPS_FIXED_TIMEOUT, IPS_EXPECTED} }, { {"ALL", "NEW", "UPDATES", "DESTROY"}, 4, { CT_EVENT_F_ALL, CT_EVENT_F_NEW, CT_EVENT_F_UPD, CT_EVENT_F_DEL } }, { {"xml", "extended", "timestamp", "id", "ktimestamp"}, 5, { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS }, }, }; static int do_parse_parameter(const char *str, size_t str_length, unsigned int *value, int parse_type) { size_t i; int ret = 0; struct parse_parameter *p = &parse_array[parse_type]; if (strncasecmp(str, "SRC_NAT", str_length) == 0) { fprintf(stderr, "WARNING: ignoring SRC_NAT, " "use --src-nat instead\n"); return 1; } if (strncasecmp(str, "DST_NAT", str_length) == 0) { fprintf(stderr, "WARNING: ignoring DST_NAT, " "use --dst-nat instead\n"); return 1; } for (i = 0; i < p->size; i++) if (strncasecmp(str, p->parameter[i], str_length) == 0) { *value |= p->value[i]; ret = 1; break; } return ret; } static void parse_parameter(const char *arg, unsigned int *status, int parse_type) { const char *comma; while ((comma = strchr(arg, ',')) != NULL) { if (comma == arg || !do_parse_parameter(arg, comma-arg, status, parse_type)) exit_error(PARAMETER_PROBLEM,"Bad parameter `%s'", arg); arg = comma+1; } if (strlen(arg) == 0 || !do_parse_parameter(arg, strlen(arg), status, parse_type)) exit_error(PARAMETER_PROBLEM, "Bad parameter `%s'", arg); } static void parse_u32_mask(const char *arg, struct u32_mask *m) { char *end; m->value = (uint32_t) strtoul(arg, &end, 0); if (*end == '/') m->mask = (uint32_t) strtoul(end+1, NULL, 0); else m->mask = ~0; } static void add_command(unsigned int *cmd, const int newcmd) { if (*cmd) exit_error(PARAMETER_PROBLEM, "Invalid commands combination"); *cmd |= newcmd; } static unsigned int check_type(int argc, char *argv[]) { char *table = NULL; /* Nasty bug or feature in getopt_long ? * It seems that it behaves badly with optional arguments. * Fortunately, I just stole the fix from iptables ;) */ if (optarg) return 0; else if (optind < argc && argv[optind][0] != '-' && argv[optind][0] != '!') table = argv[optind++]; if (!table) return 0; if (strncmp("expect", table, strlen(table)) == 0) return 1; else if (strncmp("conntrack", table, strlen(table)) == 0) return 0; else exit_error(PARAMETER_PROBLEM, "unknown type `%s'", table); return 0; } static void set_family(int *family, int new) { if (*family == AF_UNSPEC) *family = new; else if (*family != new) exit_error(PARAMETER_PROBLEM, "mismatched address family"); } struct addr_parse { struct in_addr addr; struct in6_addr addr6; unsigned int family; }; static int parse_inetaddr(const char *cp, struct addr_parse *parse) { if (inet_aton(cp, &parse->addr)) return AF_INET; #ifdef HAVE_INET_PTON_IPV6 else if (inet_pton(AF_INET6, cp, &parse->addr6) > 0) return AF_INET6; #endif return AF_UNSPEC; } union ct_address { uint32_t v4; uint32_t v6[4]; }; static int parse_addr(const char *cp, union ct_address *address) { struct addr_parse parse; int ret; if ((ret = parse_inetaddr(cp, &parse)) == AF_INET) address->v4 = parse.addr.s_addr; else if (ret == AF_INET6) memcpy(address->v6, &parse.addr6, sizeof(parse.addr6)); return ret; } static void nat_parse(char *arg, struct nf_conntrack *obj, int type) { char *colon, *error; union ct_address parse; colon = strchr(arg, ':'); if (colon) { uint16_t port; *colon = '\0'; port = (uint16_t)atoi(colon+1); if (port == 0) { if (strlen(colon+1) == 0) { exit_error(PARAMETER_PROBLEM, "No port specified after `:'"); } else { exit_error(PARAMETER_PROBLEM, "Port `%s' not valid", colon+1); } } error = strchr(colon+1, ':'); if (error) exit_error(PARAMETER_PROBLEM, "Invalid port:port syntax"); if (type == CT_OPT_SRC_NAT) nfct_set_attr_u16(tmpl.ct, ATTR_SNAT_PORT, ntohs(port)); else if (type == CT_OPT_DST_NAT) nfct_set_attr_u16(tmpl.ct, ATTR_DNAT_PORT, ntohs(port)); else if (type == CT_OPT_ANY_NAT) { nfct_set_attr_u16(tmpl.ct, ATTR_SNAT_PORT, ntohs(port)); nfct_set_attr_u16(tmpl.ct, ATTR_DNAT_PORT, ntohs(port)); } } if (parse_addr(arg, &parse) == AF_UNSPEC) { if (strlen(arg) == 0) { exit_error(PARAMETER_PROBLEM, "No IP specified"); } else { exit_error(PARAMETER_PROBLEM, "Invalid IP address `%s'", arg); } } if (type == CT_OPT_SRC_NAT || type == CT_OPT_ANY_NAT) nfct_set_attr_u32(tmpl.ct, ATTR_SNAT_IPV4, parse.v4); else if (type == CT_OPT_DST_NAT || type == CT_OPT_ANY_NAT) nfct_set_attr_u32(tmpl.ct, ATTR_DNAT_IPV4, parse.v4); } static void usage(char *prog) { fprintf(stdout, "Command line interface for the connection " "tracking system. Version %s\n", VERSION); fprintf(stdout, "Usage: %s [commands] [options]\n", prog); fprintf(stdout, "\n%s", usage_commands); fprintf(stdout, "\n%s", usage_tables); fprintf(stdout, "\n%s", usage_conntrack_parameters); fprintf(stdout, "\n%s", usage_expectation_parameters); fprintf(stdout, "\n%s\n", usage_parameters); } static unsigned int output_mask; static int filter_mark(const struct nf_conntrack *ct) { if ((options & CT_OPT_MARK) && !mark_cmp(&tmpl.mark, ct)) return 1; return 0; } static int filter_nat(const struct nf_conntrack *obj, const struct nf_conntrack *ct) { int check_srcnat = options & CT_OPT_SRC_NAT ? 1 : 0; int check_dstnat = options & CT_OPT_DST_NAT ? 1 : 0; int has_srcnat = 0, has_dstnat = 0; uint32_t ip; uint16_t port; if (options & CT_OPT_ANY_NAT) check_srcnat = check_dstnat = 1; if (check_srcnat) { int check_address = 0, check_port = 0; if (nfct_attr_is_set(obj, ATTR_SNAT_IPV4)) { check_address = 1; ip = nfct_get_attr_u32(obj, ATTR_SNAT_IPV4); if (nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT) && ip == nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)) has_srcnat = 1; } if (nfct_attr_is_set(obj, ATTR_SNAT_PORT)) { int ret = 0; check_port = 1; port = nfct_get_attr_u16(obj, ATTR_SNAT_PORT); if (nfct_getobjopt(ct, NFCT_GOPT_IS_SPAT) && port == nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)) ret = 1; /* the address matches but the port does not. */ if (check_address && has_srcnat && !ret) has_srcnat = 0; if (!check_address && ret) has_srcnat = 1; } if (!check_address && !check_port && (nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT) || nfct_getobjopt(ct, NFCT_GOPT_IS_SPAT))) has_srcnat = 1; } if (check_dstnat) { int check_address = 0, check_port = 0; if (nfct_attr_is_set(obj, ATTR_DNAT_IPV4)) { check_address = 1; ip = nfct_get_attr_u32(obj, ATTR_DNAT_IPV4); if (nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT) && ip == nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC)) has_dstnat = 1; } if (nfct_attr_is_set(obj, ATTR_DNAT_PORT)) { int ret = 0; check_port = 1; port = nfct_get_attr_u16(obj, ATTR_DNAT_PORT); if (nfct_getobjopt(ct, NFCT_GOPT_IS_DPAT) && port == nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC)) ret = 1; /* the address matches but the port does not. */ if (check_address && has_dstnat && !ret) has_dstnat = 0; if (!check_address && ret) has_dstnat = 1; } if (!check_address && !check_port && (nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT) || nfct_getobjopt(ct, NFCT_GOPT_IS_DPAT))) has_dstnat = 1; } if (options & CT_OPT_ANY_NAT) return !(has_srcnat || has_dstnat); else if ((options & CT_OPT_SRC_NAT) && (options & CT_OPT_DST_NAT)) return !(has_srcnat && has_dstnat); else if (options & CT_OPT_SRC_NAT) return !has_srcnat; else if (options & CT_OPT_DST_NAT) return !has_dstnat; return 0; } static int counter; static int dump_xml_header_done = 1; static void __attribute__((noreturn)) event_sighandler(int s) { if (dump_xml_header_done == 0) { printf("\n"); fflush(stdout); } fprintf(stderr, "%s v%s (conntrack-tools): ", PROGNAME, VERSION); fprintf(stderr, "%d flow events have been shown.\n", counter); nfct_close(cth); exit(0); } static void __attribute__((noreturn)) exp_event_sighandler(int s) { if (dump_xml_header_done == 0) { printf("\n"); fflush(stdout); } fprintf(stderr, "%s v%s (conntrack-tools): ", PROGNAME, VERSION); fprintf(stderr, "%d expectation events have been shown.\n", counter); nfct_close(cth); exit(0); } static int event_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { char buf[1024]; struct nf_conntrack *obj = data; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (filter_mark(ct)) return NFCT_CB_CONTINUE; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return NFCT_CB_CONTINUE; if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { dump_xml_header_done = 0; printf("\n" "\n"); } } if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_TMS) { if (!(output_mask & _O_XML)) { struct timeval tv; gettimeofday(&tv, NULL); printf("[%-8ld.%-6ld]\t", tv.tv_sec, tv.tv_usec); } else op_flags |= NFCT_OF_TIME; } if (output_mask & _O_KTMS) op_flags |= NFCT_OF_TIMESTAMP; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, type, op_type, op_flags); printf("%s\n", buf); fflush(stdout); counter++; return NFCT_CB_CONTINUE; } static int dump_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { char buf[1024]; struct nf_conntrack *obj = data; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (filter_mark(ct)) return NFCT_CB_CONTINUE; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return NFCT_CB_CONTINUE; if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { dump_xml_header_done = 0; printf("\n" "\n"); } } if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_KTMS) op_flags |= NFCT_OF_TIMESTAMP; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); counter++; return NFCT_CB_CONTINUE; } static int delete_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { int res; char buf[1024]; struct nf_conntrack *obj = data; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (filter_mark(ct)) return NFCT_CB_CONTINUE; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return NFCT_CB_CONTINUE; res = nfct_query(ith, NFCT_Q_DESTROY, ct); if (res < 0) exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_DELETE)); if (output_mask & _O_XML) op_type = NFCT_O_XML; if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); counter++; return NFCT_CB_CONTINUE; } static int print_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { char buf[1024]; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (output_mask & _O_XML) op_type = NFCT_O_XML; if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); return NFCT_CB_CONTINUE; } static void copy_mark(struct nf_conntrack *tmp, const struct nf_conntrack *ct, const struct u32_mask *m) { if (options & CT_OPT_MARK) { uint32_t mark = nfct_get_attr_u32(ct, ATTR_MARK); mark = (mark & ~m->mask) ^ m->value; nfct_set_attr_u32(tmp, ATTR_MARK, mark); } } static void copy_status(struct nf_conntrack *tmp, const struct nf_conntrack *ct) { if (options & CT_OPT_STATUS) { /* copy existing flags, we only allow setting them. */ uint32_t status = nfct_get_attr_u32(ct, ATTR_STATUS); status |= nfct_get_attr_u32(tmp, ATTR_STATUS); nfct_set_attr_u32(tmp, ATTR_STATUS, status); } } static int update_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { int res; struct nf_conntrack *obj = data, *tmp; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (nfct_attr_is_set(obj, ATTR_ID) && nfct_attr_is_set(ct, ATTR_ID) && nfct_get_attr_u32(obj, ATTR_ID) != nfct_get_attr_u32(ct, ATTR_ID)) return NFCT_CB_CONTINUE; if (options & CT_OPT_TUPLE_ORIG && !nfct_cmp(obj, ct, NFCT_CMP_ORIG)) return NFCT_CB_CONTINUE; if (options & CT_OPT_TUPLE_REPL && !nfct_cmp(obj, ct, NFCT_CMP_REPL)) return NFCT_CB_CONTINUE; tmp = nfct_new(); if (tmp == NULL) exit_error(OTHER_PROBLEM, "out of memory"); nfct_copy(tmp, ct, NFCT_CP_ORIG); nfct_copy(tmp, obj, NFCT_CP_META); copy_mark(tmp, ct, &tmpl.mark); copy_status(tmp, ct); /* do not send NFCT_Q_UPDATE if ct appears unchanged */ if (nfct_cmp(tmp, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) { nfct_destroy(tmp); return NFCT_CB_CONTINUE; } res = nfct_query(ith, NFCT_Q_UPDATE, tmp); if (res < 0) { nfct_destroy(tmp); exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_UPDATE)); } nfct_callback_register(ith, NFCT_T_ALL, print_cb, NULL); res = nfct_query(ith, NFCT_Q_GET, tmp); if (res < 0) { nfct_destroy(tmp); /* the entry has vanish in middle of the update */ if (errno == ENOENT) { nfct_callback_unregister(ith); return NFCT_CB_CONTINUE; } exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_UPDATE)); } nfct_destroy(tmp); nfct_callback_unregister(ith); counter++; return NFCT_CB_CONTINUE; } static int dump_exp_cb(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { char buf[1024]; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { dump_xml_header_done = 0; printf("\n" "\n"); } } if (output_mask & _O_TMS) { if (!(output_mask & _O_XML)) { struct timeval tv; gettimeofday(&tv, NULL); printf("[%-8ld.%-6ld]\t", tv.tv_sec, tv.tv_usec); } else op_flags |= NFCT_OF_TIME; } nfexp_snprintf(buf,sizeof(buf), exp, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); counter++; return NFCT_CB_CONTINUE; } static int event_exp_cb(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { char buf[1024]; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { dump_xml_header_done = 0; printf("\n" "\n"); } } if (output_mask & _O_TMS) { if (!(output_mask & _O_XML)) { struct timeval tv; gettimeofday(&tv, NULL); printf("[%-8ld.%-6ld]\t", tv.tv_sec, tv.tv_usec); } else op_flags |= NFCT_OF_TIME; } nfexp_snprintf(buf,sizeof(buf), exp, type, op_type, op_flags); printf("%s\n", buf); fflush(stdout); counter++; return NFCT_CB_CONTINUE; } static int count_exp_cb(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { counter++; return NFCT_CB_CONTINUE; } #ifndef CT_STATS_PROC #define CT_STATS_PROC "/proc/net/stat/nf_conntrack" #endif /* As of 2.6.29, we have 16 entries, this is enough */ #ifndef CT_STATS_ENTRIES_MAX #define CT_STATS_ENTRIES_MAX 64 #endif /* maximum string length currently is 13 characters */ #ifndef CT_STATS_STRING_MAX #define CT_STATS_STRING_MAX 64 #endif static int display_proc_conntrack_stats(void) { int ret = 0; FILE *fd; char buf[4096], *token, *nl; char output[CT_STATS_ENTRIES_MAX][CT_STATS_STRING_MAX]; unsigned int value[CT_STATS_ENTRIES_MAX], i, max; fd = fopen(CT_STATS_PROC, "r"); if (fd == NULL) return -1; if (fgets(buf, sizeof(buf), fd) == NULL) { ret = -1; goto out_err; } /* trim off trailing \n */ nl = strchr(buf, '\n'); if (nl != NULL) *nl = '\0'; token = strtok(buf, " "); for (i=0; token != NULL && inlmsg_type = (subsys << 8) | type; nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP; nlh->nlmsg_seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); nfh->nfgen_family = AF_INET; nfh->version = NFNETLINK_V0; nfh->res_id = 0; return nlh; } static void nfct_mnl_socket_close(void) { mnl_socket_close(sock.mnl); } static int nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; int res; nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type); res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len); if (res < 0) return res; res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); while (res > 0) { res = mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid, cb, NULL); if (res <= MNL_CB_STOP) break; res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); } return res; } static int nfct_mnl_get(uint16_t subsys, uint16_t type, mnl_cb_t cb) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; int res; nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type); res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len); if (res < 0) return res; res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); if (res < 0) return res; return mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid, cb, NULL); } static int nfct_stats_attr_cb(const struct nlattr *attr, void *data) { const struct nlattr **tb = data; int type = mnl_attr_get_type(attr); if (mnl_attr_type_valid(attr, CTA_STATS_MAX) < 0) return MNL_CB_OK; if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { perror("mnl_attr_validate"); return MNL_CB_ERROR; } tb[type] = attr; return MNL_CB_OK; } static int nfct_stats_cb(const struct nlmsghdr *nlh, void *data) { struct nlattr *tb[CTA_STATS_MAX+1] = {}; struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); const char *attr2name[CTA_STATS_MAX+1] = { [CTA_STATS_SEARCHED] = "searched", [CTA_STATS_FOUND] = "found", [CTA_STATS_NEW] = "new", [CTA_STATS_INVALID] = "invalid", [CTA_STATS_IGNORE] = "ignore", [CTA_STATS_DELETE] = "delete", [CTA_STATS_DELETE_LIST] = "delete_list", [CTA_STATS_INSERT] = "insert", [CTA_STATS_INSERT_FAILED] = "insert_failed", [CTA_STATS_DROP] = "drop", [CTA_STATS_EARLY_DROP] = "early_drop", [CTA_STATS_ERROR] = "error", [CTA_STATS_SEARCH_RESTART] = "search_restart", }; int i; mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_attr_cb, tb); printf("cpu=%-4u\t", ntohs(nfg->res_id)); for (i=0; ires_id)); for (i=0; iopts, &h->option_offset); if (opts == NULL) exit_error(OTHER_PROBLEM, "out of memory"); nfct_set_attr_u8(tmpl.ct, ATTR_L4PROTO, protonum); break; case 't': options |= CT_OPT_TIMEOUT; nfct_set_attr_u32(tmpl.ct, ATTR_TIMEOUT, atol(optarg)); nfexp_set_attr_u32(tmpl.exp, ATTR_EXP_TIMEOUT, atol(optarg)); break; case 'u': options |= CT_OPT_STATUS; parse_parameter(optarg, &status, PARSE_STATUS); nfct_set_attr_u32(tmpl.ct, ATTR_STATUS, status); break; case 'e': options |= CT_OPT_EVENT_MASK; parse_parameter(optarg, &event_mask, PARSE_EVENT); break; case 'o': options |= CT_OPT_OUTPUT; parse_parameter(optarg, &output_mask, PARSE_OUTPUT); break; case 'z': options |= CT_OPT_ZERO; break; case 'n': case 'g': case 'j': { char *tmp = NULL; options |= opt2type[c]; if (optarg) continue; else if (optind < argc && argv[optind][0] != '-' && argv[optind][0] != '!') tmp = argv[optind++]; if (tmp == NULL) continue; set_family(&family, AF_INET); nat_parse(tmp, tmpl.ct, opt2type[c]); break; } case 'w': options |= opt2type[c]; nfct_set_attr_u16(tmpl.ct, opt2attr[c], strtoul(optarg, NULL, 0)); break; case 'i': case 'c': options |= opt2type[c]; nfct_set_attr_u32(tmpl.ct, opt2attr[c], strtoul(optarg, NULL, 0)); break; case 'm': options |= opt2type[c]; parse_u32_mask(optarg, &tmpl.mark); tmpl.filter_mark_kernel.val = tmpl.mark.value; tmpl.filter_mark_kernel.mask = tmpl.mark.mask; break; case 'a': fprintf(stderr, "WARNING: ignoring -%c, " "deprecated option.\n", c); break; case 'f': options |= CT_OPT_FAMILY; if (strncmp(optarg, "ipv4", strlen("ipv4")) == 0) set_family(&family, AF_INET); else if (strncmp(optarg, "ipv6", strlen("ipv6")) == 0) set_family(&family, AF_INET6); else exit_error(PARAMETER_PROBLEM, "`%s' unsupported protocol", optarg); break; case 'b': socketbuffersize = atol(optarg); options |= CT_OPT_BUFFERSIZE; break; case '?': if (optopt) exit_error(PARAMETER_PROBLEM, "option `%s' requires an " "argument", argv[optind-1]); else exit_error(PARAMETER_PROBLEM, "unknown option `%s'", argv[optind-1]); break; default: if (h && h->parse_opts &&!h->parse_opts(c - h->option_offset, tmpl.ct, tmpl.exptuple, tmpl.mask, &l4flags)) exit_error(PARAMETER_PROBLEM, "parse error"); break; } } /* default family */ if (family == AF_UNSPEC) family = AF_INET; /* we cannot check this combination with generic_opt_check. */ if (options & CT_OPT_ANY_NAT && ((options & CT_OPT_SRC_NAT) || (options & CT_OPT_DST_NAT))) { exit_error(PARAMETER_PROBLEM, "cannot specify `--src-nat' or " "`--dst-nat' with `--any-nat'"); } cmd = bit2cmd(command); res = generic_opt_check(options, NUMBER_OF_OPT, commands_v_options[cmd], optflags, addr_valid_flags, ADDR_VALID_FLAGS_MAX, &partial); if (!res) { switch(partial) { case -1: case 0: exit_error(PARAMETER_PROBLEM, "you have to specify " "`--src' and `--dst'"); break; case 1: exit_error(PARAMETER_PROBLEM, "you have to specify " "`--reply-src' and " "`--reply-dst'"); break; } } if (!(command & CT_HELP) && h && h->final_check) h->final_check(l4flags, cmd, tmpl.ct); switch(command) { struct nfct_filter_dump *filter_dump; case CT_LIST: cth = nfct_open(CONNTRACK, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); if (options & CT_COMPARISON && options & CT_OPT_ZERO) exit_error(PARAMETER_PROBLEM, "Can't use -z with " "filtering parameters"); nfct_callback_register(cth, NFCT_T_ALL, dump_cb, tmpl.ct); filter_dump = nfct_filter_dump_create(); if (filter_dump == NULL) exit_error(OTHER_PROBLEM, "OOM"); nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK, &tmpl.filter_mark_kernel); nfct_filter_dump_set_attr_u8(filter_dump, NFCT_FILTER_DUMP_L3NUM, family); if (options & CT_OPT_ZERO) res = nfct_query(cth, NFCT_Q_DUMP_FILTER_RESET, filter_dump); else res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump); nfct_filter_dump_destroy(filter_dump); if (dump_xml_header_done == 0) { printf("\n"); fflush(stdout); } nfct_close(cth); break; case EXP_LIST: cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); nfexp_callback_register(cth, NFCT_T_ALL, dump_exp_cb, NULL); res = nfexp_query(cth, NFCT_Q_DUMP, &family); nfct_close(cth); if (dump_xml_header_done == 0) { printf("\n"); fflush(stdout); } break; case CT_CREATE: if ((options & CT_OPT_ORIG) && !(options & CT_OPT_REPL)) nfct_setobjopt(tmpl.ct, NFCT_SOPT_SETUP_REPLY); else if (!(options & CT_OPT_ORIG) && (options & CT_OPT_REPL)) nfct_setobjopt(tmpl.ct, NFCT_SOPT_SETUP_ORIGINAL); if (options & CT_OPT_MARK) nfct_set_attr_u32(tmpl.ct, ATTR_MARK, tmpl.mark.value); cth = nfct_open(CONNTRACK, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfct_query(cth, NFCT_Q_CREATE, tmpl.ct); if (res != -1) counter++; nfct_close(cth); break; case EXP_CREATE: nfexp_set_attr(tmpl.exp, ATTR_EXP_MASTER, tmpl.ct); nfexp_set_attr(tmpl.exp, ATTR_EXP_EXPECTED, tmpl.exptuple); nfexp_set_attr(tmpl.exp, ATTR_EXP_MASK, tmpl.mask); cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfexp_query(cth, NFCT_Q_CREATE, tmpl.exp); nfct_close(cth); break; case CT_UPDATE: cth = nfct_open(CONNTRACK, 0); /* internal handler for delete_cb, otherwise we hit EILSEQ */ ith = nfct_open(CONNTRACK, 0); if (!cth || !ith) exit_error(OTHER_PROBLEM, "Can't open handler"); nfct_callback_register(cth, NFCT_T_ALL, update_cb, tmpl.ct); res = nfct_query(cth, NFCT_Q_DUMP, &family); nfct_close(ith); nfct_close(cth); break; case CT_DELETE: cth = nfct_open(CONNTRACK, 0); ith = nfct_open(CONNTRACK, 0); if (!cth || !ith) exit_error(OTHER_PROBLEM, "Can't open handler"); nfct_callback_register(cth, NFCT_T_ALL, delete_cb, tmpl.ct); filter_dump = nfct_filter_dump_create(); if (filter_dump == NULL) exit_error(OTHER_PROBLEM, "OOM"); nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK, &tmpl.filter_mark_kernel); nfct_filter_dump_set_attr_u8(filter_dump, NFCT_FILTER_DUMP_L3NUM, family); res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump); nfct_filter_dump_destroy(filter_dump); nfct_close(ith); nfct_close(cth); break; case EXP_DELETE: nfexp_set_attr(tmpl.exp, ATTR_EXP_EXPECTED, tmpl.ct); cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfexp_query(cth, NFCT_Q_DESTROY, tmpl.exp); nfct_close(cth); break; case CT_GET: cth = nfct_open(CONNTRACK, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); nfct_callback_register(cth, NFCT_T_ALL, dump_cb, tmpl.ct); res = nfct_query(cth, NFCT_Q_GET, tmpl.ct); nfct_close(cth); break; case EXP_GET: nfexp_set_attr(tmpl.exp, ATTR_EXP_MASTER, tmpl.ct); cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); nfexp_callback_register(cth, NFCT_T_ALL, dump_exp_cb, NULL); res = nfexp_query(cth, NFCT_Q_GET, tmpl.exp); nfct_close(cth); break; case CT_FLUSH: cth = nfct_open(CONNTRACK, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfct_query(cth, NFCT_Q_FLUSH, &family); nfct_close(cth); fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION); fprintf(stderr,"connection tracking table has been emptied.\n"); break; case EXP_FLUSH: cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfexp_query(cth, NFCT_Q_FLUSH, &family); nfct_close(cth); fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION); fprintf(stderr,"expectation table has been emptied.\n"); break; case CT_EVENT: if (options & CT_OPT_EVENT_MASK) { unsigned int nl_events = 0; if (event_mask & CT_EVENT_F_NEW) nl_events |= NF_NETLINK_CONNTRACK_NEW; if (event_mask & CT_EVENT_F_UPD) nl_events |= NF_NETLINK_CONNTRACK_UPDATE; if (event_mask & CT_EVENT_F_DEL) nl_events |= NF_NETLINK_CONNTRACK_DESTROY; cth = nfct_open(CONNTRACK, nl_events); } else { cth = nfct_open(CONNTRACK, NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); } if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); if (options & CT_OPT_BUFFERSIZE) { size_t ret; ret = nfnl_rcvbufsiz(nfct_nfnlh(cth), socketbuffersize); fprintf(stderr, "NOTICE: Netlink socket buffer size " "has been set to %zu bytes.\n", ret); } signal(SIGINT, event_sighandler); signal(SIGTERM, event_sighandler); nfct_callback_register(cth, NFCT_T_ALL, event_cb, tmpl.ct); res = nfct_catch(cth); if (res == -1) { if (errno == ENOBUFS) { fprintf(stderr, "WARNING: We have hit ENOBUFS! We " "are losing events.\nThis message " "means that the current netlink " "socket buffer size is too small.\n" "Please, check --buffer-size in " "conntrack(8) manpage.\n"); } } nfct_close(cth); break; case EXP_EVENT: if (options & CT_OPT_EVENT_MASK) { unsigned int nl_events = 0; if (event_mask & CT_EVENT_F_NEW) nl_events |= NF_NETLINK_CONNTRACK_EXP_NEW; if (event_mask & CT_EVENT_F_UPD) nl_events |= NF_NETLINK_CONNTRACK_EXP_UPDATE; if (event_mask & CT_EVENT_F_DEL) nl_events |= NF_NETLINK_CONNTRACK_EXP_DESTROY; cth = nfct_open(CONNTRACK, nl_events); } else { cth = nfct_open(EXPECT, NF_NETLINK_CONNTRACK_EXP_NEW | NF_NETLINK_CONNTRACK_EXP_UPDATE | NF_NETLINK_CONNTRACK_EXP_DESTROY); } if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); signal(SIGINT, exp_event_sighandler); signal(SIGTERM, exp_event_sighandler); nfexp_callback_register(cth, NFCT_T_ALL, event_exp_cb, NULL); res = nfexp_catch(cth); nfct_close(cth); break; case CT_COUNT: /* If we fail with netlink, fall back to /proc to ensure * backward compatibility. */ if (nfct_mnl_socket_open() < 0) goto try_proc_count; res = nfct_mnl_get(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_STATS, nfct_global_stats_cb); nfct_mnl_socket_close(); /* don't look at /proc, we got the information via ctnetlink */ if (res >= 0) break; try_proc_count: { #define NF_CONNTRACK_COUNT_PROC "/proc/sys/net/netfilter/nf_conntrack_count" FILE *fd; int count; fd = fopen(NF_CONNTRACK_COUNT_PROC, "r"); if (fd == NULL) { exit_error(OTHER_PROBLEM, "Can't open %s", NF_CONNTRACK_COUNT_PROC); } if (fscanf(fd, "%d", &count) != 1) { exit_error(OTHER_PROBLEM, "Can't read %s", NF_CONNTRACK_COUNT_PROC); } fclose(fd); printf("%d\n", count); break; } case EXP_COUNT: cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); nfexp_callback_register(cth, NFCT_T_ALL, count_exp_cb, NULL); res = nfexp_query(cth, NFCT_Q_DUMP, &family); nfct_close(cth); printf("%d\n", counter); break; case CT_STATS: /* If we fail with netlink, fall back to /proc to ensure * backward compatibility. */ if (nfct_mnl_socket_open() < 0) goto try_proc; res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_STATS_CPU, nfct_stats_cb); nfct_mnl_socket_close(); /* don't look at /proc, we got the information via ctnetlink */ if (res >= 0) break; goto try_proc; case EXP_STATS: /* If we fail with netlink, fall back to /proc to ensure * backward compatibility. */ if (nfct_mnl_socket_open() < 0) goto try_proc; res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK_EXP, IPCTNL_MSG_EXP_GET_STATS_CPU, nfexp_stats_cb); nfct_mnl_socket_close(); /* don't look at /proc, we got the information via ctnetlink */ if (res >= 0) break; try_proc: if (display_proc_conntrack_stats() < 0) exit_error(OTHER_PROBLEM, "Can't open /proc interface"); break; case CT_VERSION: printf("%s v%s (conntrack-tools)\n", PROGNAME, VERSION); break; case CT_HELP: usage(argv[0]); if (options & CT_OPT_PROTO) extension_help(h, protonum); break; default: usage(argv[0]); break; } if (res < 0) exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, command)); free_tmpl_objects(); free_options(); if (command && exit_msg[cmd][0]) { fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION); fprintf(stderr, exit_msg[cmd], counter); if (counter == 0 && !(command & (CT_LIST | EXP_LIST))) return EXIT_FAILURE; } return EXIT_SUCCESS; }