/* * (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 #include #include #ifdef HAVE_ARPA_INET_H #include #endif #include #include #include #include #include #include #include #include static struct nfct_mnl_socket { struct mnl_socket *mnl; uint32_t portid; } sock; 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; bool filter_mark_kernel_set; /* Allows filtering by ctlabels */ struct nfct_bitmask *label; /* Allows setting/removing specific ctlabels */ struct nfct_bitmask *label_modify; } 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); if (tmpl.label) nfct_bitmask_destroy(tmpl.label); if (tmpl.label_modify) nfct_bitmask_destroy(tmpl.label_modify); } 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), CT_OPT_LABEL_BIT = 24, CT_OPT_LABEL = (1 << CT_OPT_LABEL_BIT), CT_OPT_ADD_LABEL_BIT = 25, CT_OPT_ADD_LABEL = (1 << CT_OPT_ADD_LABEL_BIT), CT_OPT_DEL_LABEL_BIT = 26, CT_OPT_DEL_LABEL = (1 << CT_OPT_DEL_LABEL_BIT), CT_OPT_ORIG_ZONE_BIT = 27, CT_OPT_ORIG_ZONE = (1 << CT_OPT_ORIG_ZONE_BIT), CT_OPT_REPL_ZONE_BIT = 28, CT_OPT_REPL_ZONE = (1 << CT_OPT_REPL_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 | CT_OPT_LABEL | \ CT_OPT_ORIG_ZONE | CT_OPT_REPL_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", [CT_OPT_LABEL_BIT] = "label", [CT_OPT_ADD_LABEL_BIT] = "label-add", [CT_OPT_DEL_LABEL_BIT] = "label-del", [CT_OPT_ORIG_ZONE_BIT] = "orig-zone", [CT_OPT_REPL_ZONE_BIT] = "reply-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'}, {"label", 1, 0, 'l'}, {"label-add", 1, 0, '<'}, {"label-del", 2, 0, '>'}, {"orig-zone", 1, 0, '('}, {"reply-zone", 1, 0, ')'}, {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:l:<:>::(:):"; /* 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 l < > ( ) */ /*CT_LIST*/ {2,2,2,2,2,0,2,2,0,0,0,2,2,0,2,0,2,2,2,2,2,0,2,2,2,0,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,0,2,0,2,2}, /*CT_UPDATE*/ {2,2,2,2,2,2,2,0,0,0,0,2,2,0,2,2,2,2,2,2,0,0,0,0,2,2,2,0,0}, /*CT_DELETE*/ {2,2,2,2,2,2,2,0,0,0,0,2,2,0,2,2,2,2,2,2,0,0,0,2,2,0,0,2,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,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,0,0,0,0,0}, /*CT_EVENT*/ {2,2,2,2,2,0,0,0,2,0,0,2,2,0,2,0,0,2,2,2,2,2,2,2,2,0,0,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,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,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,0,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,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,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,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,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,0,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,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,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,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,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, ['l'] = CT_OPT_LABEL, ['<'] = CT_OPT_ADD_LABEL, ['>'] = CT_OPT_DEL_LABEL, ['('] = CT_OPT_ORIG_ZONE, [')'] = CT_OPT_REPL_ZONE, }; static const int opt2maskopt[] = { ['s'] = '{', ['d'] = '}', ['g'] = 0, ['j'] = 0, ['n'] = 0, ['r'] = 0, /* no netmask */ ['q'] = 0, /* support yet */ ['{'] = 0, ['}'] = 0, ['['] = '{', [']'] = '}', }; static const int opt2family_attr[][2] = { ['s'] = { ATTR_ORIG_IPV4_SRC, ATTR_ORIG_IPV6_SRC }, ['d'] = { ATTR_ORIG_IPV4_DST, ATTR_ORIG_IPV6_DST }, ['g'] = { ATTR_DNAT_IPV4, ATTR_DNAT_IPV6 }, ['n'] = { ATTR_SNAT_IPV4, ATTR_SNAT_IPV6 }, ['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, ['g'] = ATTR_ORIG_L3PROTO, ['n'] = ATTR_ORIG_L3PROTO, ['r'] = ATTR_REPL_L3PROTO, ['q'] = ATTR_REPL_L3PROTO, ['{'] = ATTR_ORIG_L3PROTO, ['}'] = ATTR_ORIG_L3PROTO, ['['] = ATTR_ORIG_L3PROTO, [']'] = ATTR_ORIG_L3PROTO, ['m'] = ATTR_MARK, ['c'] = ATTR_SECMARK, ['i'] = ATTR_ID, ['w'] = ATTR_ZONE, ['l'] = ATTR_CONNLABELS, ['<'] = ATTR_CONNLABELS, ['>'] = ATTR_CONNLABELS, ['('] = ATTR_ORIG_ZONE, [')'] = ATTR_REPL_ZONE, }; enum ct_direction { DIR_SRC = 0, DIR_DST = 1, }; union ct_address { uint32_t v4; uint32_t v6[4]; }; static struct ct_network { union ct_address netmask; union ct_address network; } dir2network[2]; static const int famdir2attr[2][2] = { { ATTR_ORIG_IPV4_SRC, ATTR_ORIG_IPV4_DST }, { ATTR_ORIG_IPV6_SRC, ATTR_ORIG_IPV6_DST } }; 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, dying, unconfirmed\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" " -l, --label label[,...]\t\tconntrack labels\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" ; static const char usage_update_parameters[] = "Updating parameters and options:\n" " --label-add label\tAdd label\n" " --label-del label\tDelete label\n"; static const char usage_parameters[] = "Common parameters and options:\n" " -s, --src, --orig-src ip\t\tSource address from original direction\n" " -d, --dst, --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" " --orig-zone value\t\tSet zone for original direction\n" " --reply-zone value\t\tSet zone for reply direction\n" " -b, --buffer-size\t\tNetlink socket buffer size\n" " --mask-src ip\t\t\tSource mask address\n" " --mask-dst ip\t\t\tDestination mask address\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; static struct nfct_labelmap *labelmap; static int filter_family; 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 (strcasecmp(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), _O_CL = (1 << 5), _O_US = (1 << 6), }; 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[7]; size_t size; unsigned int value[8]; } parse_array[PARSE_MAX] = { { {"ASSURED", "SEEN_REPLY", "UNSET", "FIXED_TIMEOUT", "EXPECTED", "OFFLOAD"}, 6, { IPS_ASSURED, IPS_SEEN_REPLY, 0, IPS_FIXED_TIMEOUT, IPS_EXPECTED, IPS_OFFLOAD} }, { {"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", "labels", "userspace" }, 7, { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS, _O_CL, _O_US }, }, }; 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 int get_label(char *name) { int bit = nfct_labelmap_get_bit(labelmap, name); if (bit < 0) exit_error(PARAMETER_PROBLEM, "unknown label '%s'", name); return bit; } static void set_label(struct nfct_bitmask *b, char *name) { int bit = get_label(name); nfct_bitmask_set_bit(b, bit); } static unsigned int set_max_label(char *name, unsigned int current_max) { int bit = get_label(name); if ((unsigned int) bit > current_max) return (unsigned int) bit; return current_max; } static unsigned int parse_label_get_max(char *arg) { unsigned int max = 0; char *parse; while ((parse = strchr(arg, ',')) != NULL) { parse[0] = '\0'; max = set_max_label(arg, max); arg = &parse[1]; } max = set_max_label(arg, max); return max; } static void parse_label(struct nfct_bitmask *b, char *arg) { char * parse; while ((parse = strchr(arg, ',')) != NULL) { parse[0] = '\0'; set_label(b, arg); arg = &parse[1]; } set_label(b, arg); } static void add_command(unsigned int *cmd, const int newcmd) { if (*cmd) exit_error(PARAMETER_PROBLEM, "Invalid commands combination"); *cmd |= newcmd; } static char *get_optional_arg(int argc, char *argv[]) { char *arg = 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 arg; else if (optind < argc && argv[optind][0] != '-' && argv[optind][0] != '!') arg = argv[optind++]; return arg; } enum { CT_TABLE_CONNTRACK, CT_TABLE_EXPECT, CT_TABLE_DYING, CT_TABLE_UNCONFIRMED, }; static unsigned int check_type(int argc, char *argv[]) { const char *table = get_optional_arg(argc, argv); /* default to conntrack subsystem if nothing has been specified. */ if (table == NULL) return CT_TABLE_CONNTRACK; if (strncmp("expect", table, strlen(table)) == 0) return CT_TABLE_EXPECT; else if (strncmp("conntrack", table, strlen(table)) == 0) return CT_TABLE_CONNTRACK; else if (strncmp("dying", table, strlen(table)) == 0) return CT_TABLE_DYING; else if (strncmp("unconfirmed", table, strlen(table)) == 0) return CT_TABLE_UNCONFIRMED; 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; else if (inet_pton(AF_INET6, cp, &parse->addr6) > 0) return AF_INET6; return AF_UNSPEC; } static int parse_addr(const char *cp, union ct_address *address, int *mask) { char buf[INET6_ADDRSTRLEN]; struct addr_parse parse; char *slash, *end; int family; strncpy((char *) &buf, cp, INET6_ADDRSTRLEN); buf[INET6_ADDRSTRLEN - 1] = '\0'; if (mask != NULL) { slash = strchr(buf, '/'); if (slash != NULL) { *mask = strtol(slash + 1, &end, 10); if (*mask < 0 || end != slash + strlen(slash)) *mask = -2; /* invalid netmask */ slash[0] = '\0'; } else { *mask = -1; /* no netmask */ } } family = parse_inetaddr(buf, &parse); switch (family) { case AF_INET: address->v4 = parse.addr.s_addr; if (mask != NULL && *mask > 32) *mask = -2; /* invalid netmask */ break; case AF_INET6: memcpy(address->v6, &parse.addr6, sizeof(parse.addr6)); if (mask != NULL && *mask > 128) *mask = -2; /* invalid netmask */ break; } return family; } static bool valid_port(char *cursor) { const char *str = cursor; /* Missing port number */ if (!*str) return false; /* Must be entirely digits - no spaces or +/- */ while (*cursor) { if (!isdigit(*cursor)) return false; else ++cursor; } /* Must be in range */ errno = 0; long port = strtol(str, NULL, 10); if ((errno == ERANGE && (port == LONG_MAX || port == LONG_MIN)) || (errno != 0 && port == 0) || (port > USHRT_MAX)) return false; return true; } static void split_address_and_port(const char *arg, char **address, char **port_str) { char *cursor = strchr(arg, '['); if (cursor) { /* IPv6 address with port*/ char *start = cursor + 1; cursor = strchr(start, ']'); if (start == cursor) { exit_error(PARAMETER_PROBLEM, "No IPv6 address specified"); } else if (!cursor) { exit_error(PARAMETER_PROBLEM, "No closing ']' around IPv6 address"); } size_t len = cursor - start; cursor = strchr(cursor, ':'); if (cursor) { /* Copy address only if there is a port */ *address = strndup(start, len); } } else { cursor = strchr(arg, ':'); if (cursor && !strchr(cursor + 1, ':')) { /* IPv4 address with port */ *address = strndup(arg, cursor - arg); } else { /* v6 address */ cursor = NULL; } } if (cursor) { /* Parse port entry */ cursor++; if (strlen(cursor) == 0) { exit_error(PARAMETER_PROBLEM, "No port specified after `:'"); } if (!valid_port(cursor)) { exit_error(PARAMETER_PROBLEM, "Invalid port `%s'", cursor); } *port_str = strdup(cursor); } else { /* No port colon or more than one colon (ipv6) * assume arg is straight IP address and no port */ *address = strdup(arg); } } 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", usage_update_parameters); fprintf(stdout, "\n%s\n", usage_parameters); } static unsigned int output_mask; static int filter_label(const struct nf_conntrack *ct) { if (tmpl.label == NULL) return 0; const struct nfct_bitmask *ctb = nfct_get_attr(ct, ATTR_CONNLABELS); if (ctb == NULL) return 1; for (unsigned int i = 0; i <= nfct_bitmask_maxbit(tmpl.label); i++) { if (nfct_bitmask_test_bit(tmpl.label, i) && !nfct_bitmask_test_bit(ctb, i)) return 1; } return 0; } 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 nfct_ip6_net_cmp(const union ct_address *addr, const struct ct_network *net) { int i; for (i=0;i<4;i++) if ((addr->v6[i] & net->netmask.v6[i]) != net->network.v6[i]) return 1; return 0; } static int nfct_ip_net_cmp(int family, const union ct_address *addr, const struct ct_network *net) { switch(family) { case AF_INET: return (addr->v4 & net->netmask.v4) != net->network.v4; case AF_INET6: return nfct_ip6_net_cmp(addr, net); default: return 0; } } static int nfct_filter_network_direction(const struct nf_conntrack *ct, enum ct_direction dir) { const int family = filter_family; const union ct_address *address; enum nf_conntrack_attr attr; struct ct_network *net = &dir2network[dir]; if (nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO) != family) return 1; attr = famdir2attr[family == AF_INET6][dir]; address = nfct_get_attr(ct, attr); return nfct_ip_net_cmp(family, address, net); } static int filter_network(const struct nf_conntrack *ct) { if (options & CT_OPT_MASK_SRC) { if (nfct_filter_network_direction(ct, DIR_SRC)) return 1; } if (options & CT_OPT_MASK_DST) { if (nfct_filter_network_direction(ct, DIR_DST)) return 1; } return 0; } static int nfct_filter(struct nf_conntrack *obj, struct nf_conntrack *ct) { if (filter_nat(obj, ct) || filter_mark(ct) || filter_label(ct) || filter_network(ct)) return 1; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return 1; 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); mnl_socket_close(sock.mnl); 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(const struct nlmsghdr *nlh, void *data) { unsigned int op_type = NFCT_O_DEFAULT; struct nf_conntrack *obj = data; enum nf_conntrack_msg_type type; unsigned int op_flags = 0; struct nf_conntrack *ct; bool userspace = false; char buf[1024]; switch(nlh->nlmsg_type & 0xff) { case IPCTNL_MSG_CT_NEW: if (nlh->nlmsg_flags & NLM_F_CREATE) type = NFCT_T_NEW; else type = NFCT_T_UPDATE; break; case IPCTNL_MSG_CT_DELETE: type = NFCT_T_DESTROY; break; default: /* Unknown event type. */ type = 0; break; } ct = nfct_new(); if (!ct) goto out; if (nfct_nlmsg_parse(nlh, ct) < 0) goto out; if (nfct_filter(obj, ct)) goto out; 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_labels(buf, sizeof(buf), ct, type, op_type, op_flags, labelmap); if (output_mask & _O_US) { if (nlh->nlmsg_pid) userspace = true; else userspace = false; } printf("%s%s\n", buf, userspace ? " [USERSPACE]" : ""); fflush(stdout); counter++; out: nfct_destroy(ct); return MNL_CB_OK; } 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 (nfct_filter(obj, ct)) 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_labels(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags, labelmap); 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 (nfct_filter(obj, ct)) 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_labels(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags, labelmap); 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 struct nfct_bitmask *xnfct_bitmask_clone(const struct nfct_bitmask *a) { struct nfct_bitmask *b = nfct_bitmask_clone(a); if (!b) exit_error(OTHER_PROBLEM, "out of memory"); return b; } static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct) { struct nfct_bitmask *ctb, *newmask; unsigned int i; if ((options & (CT_OPT_ADD_LABEL|CT_OPT_DEL_LABEL)) == 0) return; nfct_copy_attr(tmp, ct, ATTR_CONNLABELS); ctb = (void *) nfct_get_attr(tmp, ATTR_CONNLABELS); if (options & CT_OPT_ADD_LABEL) { if (ctb == NULL) { nfct_set_attr(tmp, ATTR_CONNLABELS, xnfct_bitmask_clone(tmpl.label_modify)); return; } /* If we send a bitmask shorter than the kernel sent to us, the bits we * omit will be cleared (as "padding"). So we always have to send the * same sized bitmask as we received. * * Mask has to have the same size as the labels, otherwise it will not * be encoded by libnetfilter_conntrack, as different sizes are not * accepted by the kernel. */ newmask = nfct_bitmask_new(nfct_bitmask_maxbit(ctb)); for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) { if (nfct_bitmask_test_bit(tmpl.label_modify, i)) { nfct_bitmask_set_bit(ctb, i); nfct_bitmask_set_bit(newmask, i); } else if (nfct_bitmask_test_bit(ctb, i)) { /* Kernel only retains old bit values that are sent as * zeroes in BOTH labels and mask. */ nfct_bitmask_unset_bit(ctb, i); } } nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask); } else if (ctb != NULL) { /* CT_OPT_DEL_LABEL */ if (tmpl.label_modify == NULL) { newmask = nfct_bitmask_new(0); if (newmask) nfct_set_attr(tmp, ATTR_CONNLABELS, newmask); return; } for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) { if (nfct_bitmask_test_bit(tmpl.label_modify, i)) nfct_bitmask_unset_bit(ctb, i); } newmask = xnfct_bitmask_clone(tmpl.label_modify); nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask); } } 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) || filter_label(ct) || filter_network(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); copy_label(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) fprintf(stderr, "Operation failed: %s\n", 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; int cpu; 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 = family; 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, uint8_t family) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; int res; nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, family); 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, uint8_t family) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; int res; nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, family); 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; inetwork.v4 = address->v4 & netmask->v4; break; case AF_INET6: for (i=0;i<4;i++) net->network.v6[i] = address->v6[i] & netmask->v6[i]; break; } memcpy(&net->netmask, netmask, sizeof(union ct_address)); /* avoid exact source matching */ nfct_attr_unset(tmpl.ct, attr); } static void nfct_filter_init(const int family) { filter_family = family; if (options & CT_OPT_MASK_SRC) { if (!(options & CT_OPT_ORIG_SRC)) exit_error(PARAMETER_PROBLEM, "Can't use --mask-src without --src"); nfct_network_attr_prepare(family, DIR_SRC); } if (options & CT_OPT_MASK_DST) { if (!(options & CT_OPT_ORIG_DST)) exit_error(PARAMETER_PROBLEM, "Can't use --mask-dst without --dst"); nfct_network_attr_prepare(family, DIR_DST); } } static void merge_bitmasks(struct nfct_bitmask **current, struct nfct_bitmask *src) { unsigned int i; if (*current == NULL) { *current = src; return; } /* "current" must be the larger bitmask object */ if (nfct_bitmask_maxbit(src) > nfct_bitmask_maxbit(*current)) { struct nfct_bitmask *tmp = *current; *current = src; src = tmp; } for (i = 0; i <= nfct_bitmask_maxbit(src); i++) { if (nfct_bitmask_test_bit(src, i)) nfct_bitmask_set_bit(*current, i); } nfct_bitmask_destroy(src); } static void nfct_build_netmask(uint32_t *dst, int b, int n) { int i; for (i = 0; i < n; i++) { if (b >= 32) { dst[i] = 0xffffffff; b -= 32; } else if (b > 0) { dst[i] = (1 << b) - 1; b = 0; } else { dst[i] = 0; } } } static void nfct_set_addr_only(const int opt, struct nf_conntrack *ct, union ct_address *ad, const int l3protonum) { switch (l3protonum) { case AF_INET: nfct_set_attr_u32(ct, opt2family_attr[opt][0], ad->v4); break; case AF_INET6: nfct_set_attr(ct, opt2family_attr[opt][1], &ad->v6); break; } } static void nfct_set_addr_opt(const int opt, struct nf_conntrack *ct, union ct_address *ad, const int l3protonum) { options |= opt2type[opt]; nfct_set_addr_only(opt, ct, ad, l3protonum); nfct_set_attr_u8(ct, opt2attr[opt], l3protonum); } static void nfct_parse_addr_from_opt(const int opt, const char *arg, struct nf_conntrack *ct, struct nf_conntrack *ctmask, union ct_address *ad, int *family) { int mask, maskopt; const int l3protonum = parse_addr(arg, ad, &mask); if (l3protonum == AF_UNSPEC) { exit_error(PARAMETER_PROBLEM, "Invalid IP address `%s'", arg); } set_family(family, l3protonum); maskopt = opt2maskopt[opt]; if (mask != -1 && !maskopt) { exit_error(PARAMETER_PROBLEM, "CIDR notation unavailable" " for `--%s'", get_long_opt(opt)); } else if (mask == -2) { exit_error(PARAMETER_PROBLEM, "Invalid netmask"); } nfct_set_addr_opt(opt, ct, ad, l3protonum); /* bail if we don't have a netmask to set*/ if (mask == -1 || !maskopt || ctmask == NULL) return; switch(l3protonum) { case AF_INET: if (mask == 32) return; nfct_build_netmask(&ad->v4, mask, 1); break; case AF_INET6: if (mask == 128) return; nfct_build_netmask((uint32_t *) &ad->v6, mask, 4); break; } nfct_set_addr_opt(maskopt, ctmask, ad, l3protonum); } static void nfct_set_nat_details(const int opt, struct nf_conntrack *ct, union ct_address *ad, const char *port_str, const int family) { const int type = opt2type[opt]; nfct_set_addr_only(opt, ct, ad, family); if (port_str && type == CT_OPT_SRC_NAT) { nfct_set_attr_u16(ct, ATTR_SNAT_PORT, ntohs((uint16_t)atoi(port_str))); } else if (port_str && type == CT_OPT_DST_NAT) { nfct_set_attr_u16(ct, ATTR_DNAT_PORT, ntohs((uint16_t)atoi(port_str))); } } int main(int argc, char *argv[]) { int c, cmd; unsigned int type = 0, event_mask = 0, l4flags = 0, status = 0; int res = 0, partial; size_t socketbuffersize = 0; int family = AF_UNSPEC; int protonum = 0; union ct_address ad; unsigned int command = 0; /* we release these objects in the exit_error() path. */ if (!alloc_tmpl_objects()) exit_error(OTHER_PROBLEM, "out of memory"); register_tcp(); register_udp(); register_udplite(); register_sctp(); register_dccp(); register_icmp(); register_icmpv6(); register_gre(); register_unknown(); /* disable explicit missing arguments error output from getopt_long */ opterr = 0; while ((c = getopt_long(argc, argv, getopt_str, opts, NULL)) != -1) { switch(c) { /* commands */ case 'L': type = check_type(argc, argv); /* Special case: dumping dying and unconfirmed list * are handled like normal conntrack dumps. */ if (type == CT_TABLE_DYING || type == CT_TABLE_UNCONFIRMED) add_command(&command, cmd2type[c][0]); else add_command(&command, cmd2type[c][type]); break; case 'I': case 'D': case 'G': case 'F': case 'E': case 'V': case 'h': case 'C': case 'S': type = check_type(argc, argv); if (type == CT_TABLE_DYING || type == CT_TABLE_UNCONFIRMED) { exit_error(PARAMETER_PROBLEM, "Can't do that command with " "tables `dying' and `unconfirmed'"); } add_command(&command, cmd2type[c][type]); break; case 'U': type = check_type(argc, argv); if (type == CT_TABLE_DYING || type == CT_TABLE_UNCONFIRMED) { exit_error(PARAMETER_PROBLEM, "Can't do that command with " "tables `dying' and `unconfirmed'"); } else if (type == CT_TABLE_CONNTRACK) add_command(&command, CT_UPDATE); else exit_error(PARAMETER_PROBLEM, "Can't update expectations"); break; /* options */ case 's': case 'd': case 'r': case 'q': nfct_parse_addr_from_opt(c, optarg, tmpl.ct, tmpl.mask, &ad, &family); break; case '[': case ']': nfct_parse_addr_from_opt(c, optarg, tmpl.exptuple, tmpl.mask, &ad, &family); break; case '{': case '}': nfct_parse_addr_from_opt(c, optarg, tmpl.mask, NULL, &ad, &family); break; case 'p': options |= CT_OPT_PROTO; h = findproto(optarg, &protonum); if (!h) exit_error(PARAMETER_PROBLEM, "`%s' unsupported protocol", optarg); opts = merge_options(opts, h->opts, &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); if (output_mask & _O_CL) labelmap_init(); break; case 'z': options |= CT_OPT_ZERO; break; case 'n': case 'g': case 'j': options |= opt2type[c]; char *optional_arg = get_optional_arg(argc, argv); if (optional_arg) { char *port_str = NULL; char *nat_address = NULL; split_address_and_port(optional_arg, &nat_address, &port_str); nfct_parse_addr_from_opt(c, nat_address, tmpl.ct, NULL, &ad, &family); if (c == 'j') { /* Set details on both src and dst * with any-nat */ nfct_set_nat_details('g', tmpl.ct, &ad, port_str, family); nfct_set_nat_details('n', tmpl.ct, &ad, port_str, family); } else { nfct_set_nat_details(c, tmpl.ct, &ad, port_str, family); } } break; case 'w': case '(': case ')': 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; tmpl.filter_mark_kernel_set = true; break; case 'l': case '<': case '>': options |= opt2type[c]; labelmap_init(); if ((options & (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL)) == (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL)) exit_error(OTHER_PROBLEM, "cannot use --label-add and " "--label-del at the same time"); if (c == '>') { /* DELETE */ char *tmp = get_optional_arg(argc, argv); if (tmp == NULL) /* delete all labels */ break; optarg = tmp; } char *optarg2 = strdup(optarg); unsigned int max = parse_label_get_max(optarg); struct nfct_bitmask * b = nfct_bitmask_new(max); if (!b) exit_error(OTHER_PROBLEM, "out of memory"); parse_label(b, optarg2); /* join "-l foo -l bar" into single bitmask object */ if (c == 'l') { merge_bitmasks(&tmpl.label, b); } else { merge_bitmasks(&tmpl.label_modify, b); } free(optarg2); 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 ':': exit_error(PARAMETER_PROBLEM, "option `%s' requires an " "argument", argv[optind-1]); case '?': 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: if (type == CT_TABLE_DYING) { if (nfct_mnl_socket_open(0) < 0) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_DYING, mnl_nfct_dump_cb, family); nfct_mnl_socket_close(); break; } else if (type == CT_TABLE_UNCONFIRMED) { if (nfct_mnl_socket_open(0) < 0) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_UNCONFIRMED, mnl_nfct_dump_cb, family); nfct_mnl_socket_close(); break; } 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_filter_init(family); 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"); if (tmpl.filter_mark_kernel_set) { 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); if (options & CT_OPT_ADD_LABEL) nfct_set_attr(tmpl.ct, ATTR_CONNLABELS, xnfct_bitmask_clone(tmpl.label_modify)); 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_filter_init(family); 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_filter_init(family); 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"); if (tmpl.filter_mark_kernel_set) { 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; res = nfct_mnl_socket_open(nl_events); } else { res = nfct_mnl_socket_open(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); } if (res < 0) exit_error(OTHER_PROBLEM, "Can't open netlink socket"); if (options & CT_OPT_BUFFERSIZE) { socklen_t socklen = sizeof(socketbuffersize); res = setsockopt(mnl_socket_get_fd(sock.mnl), SOL_SOCKET, SO_RCVBUFFORCE, &socketbuffersize, sizeof(socketbuffersize)); if (res < 0) { setsockopt(mnl_socket_get_fd(sock.mnl), SOL_SOCKET, SO_RCVBUF, &socketbuffersize, socketbuffersize); } getsockopt(mnl_socket_get_fd(sock.mnl), SOL_SOCKET, SO_RCVBUF, &socketbuffersize, &socklen); fprintf(stderr, "NOTICE: Netlink socket buffer size " "has been set to %zu bytes.\n", socketbuffersize); } nfct_filter_init(family); signal(SIGINT, event_sighandler); signal(SIGTERM, event_sighandler); while (1) { char buf[MNL_SOCKET_BUFFER_SIZE]; res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); if (res < 0) { 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"); continue; } exit_error(OTHER_PROBLEM, "failed to received netlink event: %s", strerror(errno)); break; } res = mnl_cb_run(buf, res, 0, 0, event_cb, tmpl.ct); } mnl_socket_close(sock.mnl); 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) < 0) goto try_proc_count; res = nfct_mnl_get(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_STATS, nfct_global_stats_cb, AF_UNSPEC); 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) < 0) goto try_proc; res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_STATS_CPU, nfct_stats_cb, AF_UNSPEC); 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) < 0) goto try_proc; res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK_EXP, IPCTNL_MSG_EXP_GET_STATS_CPU, nfexp_stats_cb, AF_UNSPEC); 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 (labelmap) nfct_labelmap_destroy(labelmap); 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; }