| /* |
| * (C) 2005-2012 by Pablo Neira Ayuso <pablo@netfilter.org> |
| * (C) 2012 by Intra2net AG <http://www.intra2net.com> |
| * |
| * 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 <jmiguel at talika.eii.us.es> |
| * for introducing me to advanced firewalling stuff. |
| * |
| * --pablo 13/04/2005 |
| * |
| * 2005-04-16 Harald Welte <laforge@netfilter.org>: |
| * Add support for conntrack accounting and conntrack mark |
| * 2005-06-23 Harald Welte <laforge@netfilter.org>: |
| * Add support for expect creation |
| * 2005-09-24 Harald Welte <laforge@netfilter.org>: |
| * Remove remaints of "-A" |
| * 2007-04-22 Pablo Neira Ayuso <pablo@netfilter.org>: |
| * Ported to the new libnetfilter_conntrack API |
| * 2008-04-13 Pablo Neira Ayuso <pablo@netfilter.org>: |
| * Way more flexible update and delete operations |
| * |
| * Part of this code has been funded by Sophos Astaro <http://www.sophos.com> |
| */ |
| |
| #include "conntrack.h" |
| |
| #include <stdio.h> |
| #include <getopt.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <netinet/in.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #ifdef HAVE_ARPA_INET_H |
| #include <arpa/inet.h> |
| #endif |
| #include <signal.h> |
| #include <string.h> |
| #include <netdb.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <libmnl/libmnl.h> |
| #include <libnetfilter_conntrack/libnetfilter_conntrack.h> |
| |
| 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; |
| |
| /* 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'] = '}', |
| ['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 }, |
| ['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, |
| ['{'] = 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<<i)) |
| break; |
| |
| return i; |
| } |
| |
| static const char *get_long_opt(int opt) |
| { |
| struct option o; |
| int i; |
| |
| for (i = 0;; i++) { |
| o = opts[i]; |
| if (o.name == NULL) |
| break; |
| if (o.val == opt) |
| return o.name; |
| } |
| return "unknown"; |
| } |
| |
| int generic_opt_check(int local_options, int num_opts, |
| char *optset, const char *optflg[], |
| unsigned int *coupled_flags, int coupled_flags_size, |
| int *partial) |
| { |
| int i, matching = -1, special_case = 0; |
| |
| for (i = 0; i < num_opts; i++) { |
| if (!(local_options & (1<<i))) { |
| if (optset[i] == 1) |
| exit_error(PARAMETER_PROBLEM, |
| "You need to supply the " |
| "`--%s' option for this " |
| "command", optflg[i]); |
| } else { |
| if (optset[i] == 0) |
| exit_error(PARAMETER_PROBLEM, "Illegal " |
| "option `--%s' with this " |
| "command", optflg[i]); |
| } |
| if (optset[i] == 3) |
| special_case = 1; |
| } |
| |
| /* no weird flags combinations, leave */ |
| if (!special_case || coupled_flags == NULL) |
| return 1; |
| |
| *partial = -1; |
| for (i=0; i<coupled_flags_size; i++) { |
| /* we look for an exact matching to ensure this is correct */ |
| if ((local_options & coupled_flags[i]) == coupled_flags[i]) { |
| matching = i; |
| break; |
| } |
| /* ... otherwise look for the first partial matching */ |
| if ((local_options & coupled_flags[i]) && *partial < 0) { |
| *partial = i; |
| } |
| } |
| |
| /* we found an exact matching, game over */ |
| if (matching >= 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), |
| }; |
| |
| 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", "labels", }, 6, |
| { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS, _O_CL }, |
| }, |
| }; |
| |
| 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; |
| #ifdef HAVE_INET_PTON_IPV6 |
| else if (inet_pton(AF_INET6, cp, &parse->addr6) > 0) |
| return AF_INET6; |
| #endif |
| 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 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, NULL) == 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", 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("</conntrack>\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("</expect>\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 (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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| "<conntrack>\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); |
| |
| 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 (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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| "<conntrack>\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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| "<expect>\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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| "<expect>\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 && i<CT_STATS_ENTRIES_MAX; i++) { |
| strncpy(output[i], token, CT_STATS_STRING_MAX); |
| output[i][CT_STATS_STRING_MAX-1]='\0'; |
| token = strtok(NULL, " "); |
| } |
| max = i; |
| |
| if (fgets(buf, sizeof(buf), fd) == NULL) { |
| ret = -1; |
| goto out_err; |
| } |
| |
| nl = strchr(buf, '\n'); |
| while (nl != NULL) { |
| *nl = '\0'; |
| nl = strchr(buf, '\n'); |
| } |
| token = strtok(buf, " "); |
| for (i=0; token != NULL && i<CT_STATS_ENTRIES_MAX; i++) { |
| value[i] = (unsigned int) strtol(token, (char**) NULL, 16); |
| token = strtok(NULL, " "); |
| } |
| |
| for (i=0; i<max; i++) |
| printf("%-10s\t\t%-8u\n", output[i], value[i]); |
| |
| out_err: |
| fclose(fd); |
| return ret; |
| } |
| |
| static struct nfct_mnl_socket { |
| struct mnl_socket *mnl; |
| uint32_t portid; |
| } sock; |
| |
| static int nfct_mnl_socket_open(void) |
| { |
| sock.mnl = mnl_socket_open(NETLINK_NETFILTER); |
| if (sock.mnl == NULL) { |
| perror("mnl_socket_open"); |
| return -1; |
| } |
| if (mnl_socket_bind(sock.mnl, 0, MNL_SOCKET_AUTOPID) < 0) { |
| perror("mnl_socket_bind"); |
| return -1; |
| } |
| sock.portid = mnl_socket_get_portid(sock.mnl); |
| |
| return 0; |
| } |
| |
| static struct nlmsghdr * |
| nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type, |
| uint8_t family) |
| { |
| struct nlmsghdr *nlh; |
| struct nfgenmsg *nfh; |
| |
| nlh = mnl_nlmsg_put_header(buf); |
| nlh->nlmsg_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; i<CTA_STATS_MAX+1; i++) { |
| if (tb[i]) { |
| printf("%s=%u ", |
| attr2name[i], ntohl(mnl_attr_get_u32(tb[i]))); |
| } |
| } |
| printf("\n"); |
| return MNL_CB_OK; |
| } |
| |
| static int nfexp_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_EXP_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 nfexp_stats_cb(const struct nlmsghdr *nlh, void *data) |
| { |
| struct nlattr *tb[CTA_STATS_EXP_MAX+1] = {}; |
| struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); |
| const char *attr2name[CTA_STATS_EXP_MAX+1] = { |
| [CTA_STATS_EXP_NEW] = "expect_new", |
| [CTA_STATS_EXP_CREATE] = "expect_create", |
| [CTA_STATS_EXP_DELETE] = "expect_delete", |
| }; |
| int i; |
| |
| mnl_attr_parse(nlh, sizeof(*nfg), nfexp_stats_attr_cb, tb); |
| |
| printf("cpu=%-4u\t", ntohs(nfg->res_id)); |
| |
| for (i=0; i<CTA_STATS_EXP_MAX+1; i++) { |
| if (tb[i]) { |
| printf("%s=%u ", |
| attr2name[i], ntohl(mnl_attr_get_u32(tb[i]))); |
| } |
| } |
| printf("\n"); |
| return MNL_CB_OK; |
| } |
| |
| static int nfct_stats_global_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_GLOBAL_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_global_stats_cb(const struct nlmsghdr *nlh, void *data) |
| { |
| struct nlattr *tb[CTA_STATS_GLOBAL_MAX+1] = {}; |
| struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); |
| |
| mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_global_attr_cb, tb); |
| |
| if (tb[CTA_STATS_GLOBAL_ENTRIES]) { |
| printf("%d\n", |
| ntohl(mnl_attr_get_u32(tb[CTA_STATS_GLOBAL_ENTRIES]))); |
| } |
| return MNL_CB_OK; |
| } |
| |
| static int mnl_nfct_dump_cb(const struct nlmsghdr *nlh, void *data) |
| { |
| struct nf_conntrack *ct; |
| char buf[4096]; |
| |
| ct = nfct_new(); |
| if (ct == NULL) |
| return MNL_CB_OK; |
| |
| nfct_nlmsg_parse(nlh, ct); |
| |
| nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, NFCT_O_DEFAULT, 0); |
| printf("%s\n", buf); |
| |
| nfct_destroy(ct); |
| |
| counter++; |
| |
| return MNL_CB_OK; |
| } |
| |
| static struct ctproto_handler *h; |
| |
| static void labelmap_init(void) |
| { |
| if (labelmap) |
| return; |
| labelmap = nfct_labelmap_new(NULL); |
| if (!labelmap) |
| perror("nfct_labelmap_new"); |
| } |
| |
| static void |
| nfct_network_attr_prepare(const int family, enum ct_direction dir) |
| { |
| const union ct_address *address, *netmask; |
| enum nf_conntrack_attr attr; |
| int i; |
| struct ct_network *net = &dir2network[dir]; |
| |
| attr = famdir2attr[family == AF_INET6][dir]; |
| |
| address = nfct_get_attr(tmpl.ct, attr); |
| netmask = nfct_get_attr(tmpl.mask, attr); |
| |
| switch(family) { |
| case AF_INET: |
| net->network.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_opt(int opt, struct nf_conntrack *ct, union ct_address *ad, |
| int l3protonum) |
| { |
| options |= opt2type[opt]; |
| 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; |
| } |
| nfct_set_attr_u8(ct, opt2attr[opt], l3protonum); |
| } |
| |
| static void |
| nfct_parse_addr_from_opt(int opt, struct nf_conntrack *ct, |
| struct nf_conntrack *ctmask, |
| union ct_address *ad, int *family) |
| { |
| int l3protonum, mask, maskopt; |
| |
| l3protonum = parse_addr(optarg, ad, &mask); |
| if (l3protonum == AF_UNSPEC) { |
| exit_error(PARAMETER_PROBLEM, |
| "Invalid IP address `%s'", optarg); |
| } |
| set_family(family, l3protonum); |
| maskopt = opt2maskopt[opt]; |
| if (!maskopt && mask != -1) { |
| 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 (!maskopt || mask == -1 || 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); |
| } |
| |
| 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, tmpl.ct, tmpl.mask, |
| &ad, &family); |
| break; |
| case '[': |
| case ']': |
| nfct_parse_addr_from_opt(c, tmpl.exptuple, tmpl.mask, |
| &ad, &family); |
| break; |
| case '{': |
| case '}': |
| nfct_parse_addr_from_opt(c, 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': { |
| char *tmp = NULL; |
| |
| options |= opt2type[c]; |
| |
| tmp = get_optional_arg(argc, argv); |
| if (tmp == NULL) |
| continue; |
| |
| set_family(&family, AF_INET); |
| nat_parse(tmp, tmpl.ct, opt2type[c]); |
| 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; |
| 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) |
| 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) |
| 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"); |
| |
| 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("</conntrack>\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("</expect>\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"); |
| |
| 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); |
| } |
| |
| nfct_filter_init(family); |
| |
| 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, 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) |
| 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) |
| 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; |
| } |