/* * (C) 2005-2008 by Pablo Neira Ayuso * * 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 */ #include "conntrack.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ARPA_INET_H #include #endif #include #include #include #include #include #include static const char *optflags[NUMBER_OF_OPT] = { "src","dst","reply-src","reply-dst","protonum","timeout","status","zero", "event-mask","tuple-src","tuple-dst","mask-src","mask-dst","nat-range","mark", "id","family","src-nat","dst-nat","output","secmark","buffersize"}; 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'}, {0, 0, 0, 0} }; #define OPTION_OFFSET 256 static struct nfct_handle *cth, *ith; static struct option *opts = original_opts; static unsigned int global_option_offset = 0; /* 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*/ /*CT_LIST*/ {2,2,2,2,2,0,2,2,0,0,0,0,0,0,2,0,2,2,2,2,2,0}, /*CT_CREATE*/ {3,3,3,3,1,1,2,0,0,0,0,0,0,2,2,0,0,2,2,0,0,0}, /*CT_UPDATE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0}, /*CT_DELETE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0}, /*CT_GET*/ {3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,2,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}, /*CT_EVENT*/ {2,2,2,2,2,0,0,0,2,0,0,0,0,0,2,0,0,2,2,2,2,2}, /*VERSION*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*HELP*/ {0,0,0,0,2,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,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}, /*EXP_DELETE*/{1,1,2,2,1,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}, /*EXP_FLUSH*/ {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,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}, /*EXP_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*X_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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; #define CT_COMPARISON (CT_OPT_PROTO | CT_OPT_ORIG | CT_OPT_REPL | CT_OPT_MARK |\ CT_OPT_SECMARK | CT_OPT_STATUS | CT_OPT_ID) void register_proto(struct ctproto_handler *h) { if (strcmp(h->version, VERSION) != 0) { fprintf(stderr, "plugin `%s': version %s (I'm %s)\n", h->name, h->version, VERSION); exit(1); } list_add(&h->head, &proto_list); } extern struct ctproto_handler ct_proto_unknown; static struct ctproto_handler *findproto(char *name, int *pnum) { struct ctproto_handler *cur; struct protoent *pent; int protonum; /* is it in the list of supported protocol? */ list_for_each_entry(cur, &proto_list, head) { if (strcmp(cur->name, name) == 0) { *pnum = cur->protonum; return cur; } } /* using the protocol name for an unsupported protocol? */ if ((pent = getprotobyname(name))) { *pnum = pent->p_proto; return &ct_proto_unknown; } /* using a protocol number? */ protonum = atoi(name); if (protonum > 0 && protonum <= IPPROTO_MAX) { /* try lookup by number, perhaps this protocol is supported */ list_for_each_entry(cur, &proto_list, head) { if (cur->protonum == protonum) { *pnum = protonum; return cur; } } *pnum = protonum; return &ct_proto_unknown; } return NULL; } static void extension_help(struct ctproto_handler *h, int protonum) { const char *name; if (h == &ct_proto_unknown) { struct protoent *pent; pent = getprotobynumber(protonum); if (!pent) name = h->name; else name = pent->p_name; } else { name = h->name; } fprintf(stdout, "Proto `%s' help:\n", name); h->help(); } static void __attribute__((noreturn)) exit_tryhelp(int status) { fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", PROGNAME, PROGNAME); exit(status); } static void free_options(void) { if (opts != original_opts) { free(opts); opts = original_opts; global_option_offset = 0; } } void __attribute__((noreturn)) exit_error(enum exittype status, const char *msg, ...) { va_list args; free_options(); va_start(args, msg); fprintf(stderr,"%s v%s (conntrack-tools): ", PROGNAME, VERSION); vfprintf(stderr, msg, args); fprintf(stderr, "\n"); va_end(args); if (status == PARAMETER_PROBLEM) exit_tryhelp(status); 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 action command) { unsigned int i; struct table_struct { enum action 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); } #define PARSE_STATUS 0 #define PARSE_EVENT 1 #define PARSE_OUTPUT 2 #define PARSE_MAX 3 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, {~0U, NF_NETLINK_CONNTRACK_NEW, NF_NETLINK_CONNTRACK_UPDATE, NF_NETLINK_CONNTRACK_DESTROY} }, { {"xml", "extended", "timestamp", "id" }, 4, { _O_XML, _O_EXT, _O_TMS, _O_ID }, }, }; 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 add_command(unsigned int *cmd, const int newcmd) { if (*cmd) exit_error(PARAMETER_PROBLEM, "Invalid commands combination"); *cmd |= newcmd; } static unsigned int check_type(int argc, char *argv[]) { char *table = NULL; /* Nasty bug or feature in getopt_long ? * It seems that it behaves badly with optional arguments. * Fortunately, I just stole the fix from iptables ;) */ if (optarg) return 0; else if (optind < argc && argv[optind][0] != '-' && argv[optind][0] != '!') table = argv[optind++]; if (!table) return 0; if (strncmp("expect", table, strlen(table)) == 0) return 1; else if (strncmp("conntrack", table, strlen(table)) == 0) return 0; else exit_error(PARAMETER_PROBLEM, "unknown type `%s'", table); return 0; } static void set_family(int *family, int new) { if (*family == AF_UNSPEC) *family = new; else if (*family != new) exit_error(PARAMETER_PROBLEM, "mismatched address family"); } struct addr_parse { struct in_addr addr; struct in6_addr addr6; unsigned int family; }; static int parse_inetaddr(const char *cp, struct addr_parse *parse) { if (inet_aton(cp, &parse->addr)) return AF_INET; #ifdef HAVE_INET_PTON_IPV6 else if (inet_pton(AF_INET6, cp, &parse->addr6) > 0) return AF_INET6; #endif exit_error(PARAMETER_PROBLEM, "Invalid IP address `%s'", cp); } union ct_address { uint32_t v4; uint32_t v6[4]; }; static int parse_addr(const char *cp, union ct_address *address) { struct addr_parse parse; int ret; if ((ret = parse_inetaddr(cp, &parse)) == AF_INET) address->v4 = parse.addr.s_addr; else if (ret == AF_INET6) memcpy(address->v6, &parse.addr6, sizeof(parse.addr6)); return ret; } /* Shamelessly stolen from libipt_DNAT ;). Ranges expected in network order. */ static void nat_parse(char *arg, int portok, struct nf_conntrack *obj, int type) { char *colon, *error; union ct_address parse; colon = strchr(arg, ':'); if (colon) { uint16_t port; if (!portok) exit_error(PARAMETER_PROBLEM, "Need TCP or UDP with port specification"); port = (uint16_t)atoi(colon+1); if (port == 0) 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(obj, ATTR_SNAT_PORT, port); else if (type == CT_OPT_DST_NAT) nfct_set_attr_u16(obj, ATTR_DNAT_PORT, port); } if (parse_addr(arg, &parse) != AF_INET) return; if (type == CT_OPT_SRC_NAT) nfct_set_attr_u32(obj, ATTR_SNAT_IPV4, parse.v4); else if (type == CT_OPT_DST_NAT) nfct_set_attr_u32(obj, ATTR_DNAT_IPV4, parse.v4); } static const char usage_commands[] = "Commands:\n" " -L [table] [options]\t\tList conntrack or expectation table\n" " -G [table] parameters\t\tGet conntrack or expectation\n" " -D [table] parameters\t\tDelete conntrack or expectation\n" " -I [table] parameters\t\tCreate a conntrack or expectation\n" " -U [table] parameters\t\tUpdate a conntrack\n" " -E [table] [options]\t\tShow events\n" " -F [table]\t\t\tFlush table\n" " -C [table]\t\t\tShow counter\n" " -S\t\t\t\tShow statistics\n"; static const char usage_tables[] = "Tables: conntrack, expect\n"; static const char usage_conntrack_parameters[] = "Conntrack parameters and options:\n" " -n, --src-nat ip\t\t\tsource NAT ip\n" " -g, --dst-nat ip\t\t\tdestination NAT ip\n" " -m, --mark mark\t\t\tSet mark\n" " -c, --secmark secmark\t\t\tSet selinux secmark\n" " -e, --event-mask eventmask\t\tEvent mask, eg. NEW,DESTROY\n" " -z, --zero \t\t\t\tZero counters while listing\n" " -o, --output type[,...]\t\tOutput format, eg. xml\n"; static const char usage_expectation_parameters[] = "Expectation parameters and options:\n" " --tuple-src ip\tSource address in expect tuple\n" " --tuple-dst ip\tDestination address in expect tuple\n" " --mask-src ip\t\tSource mask address\n" " --mask-dst ip\t\tDestination mask address\n"; static const char usage_parameters[] = "Common parameters and options:\n" " -s, --orig-src ip\t\tSource address from original direction\n" " -d, --orig-dst ip\t\tDestination address from original direction\n" " -r, --reply-src ip\t\tSource addres from reply direction\n" " -q, --reply-dst ip\t\tDestination address from reply direction\n" " -p, --protonum proto\t\tLayer 4 Protocol, eg. 'tcp'\n" " -f, --family proto\t\tLayer 3 Protocol, eg. 'ipv6'\n" " -t, --timeout timeout\t\tSet timeout\n" " -u, --status status\t\tSet status, eg. ASSURED\n" " -b, --buffer-size\t\tNetlink socket buffer size\n" ; static void usage(char *prog) { fprintf(stdout, "Command line interface for the connection " "tracking system. Version %s\n", VERSION); fprintf(stdout, "Usage: %s [commands] [options]\n", prog); fprintf(stdout, "\n%s", usage_commands); fprintf(stdout, "\n%s", usage_tables); fprintf(stdout, "\n%s", usage_conntrack_parameters); fprintf(stdout, "\n%s", usage_expectation_parameters); fprintf(stdout, "\n%s\n", usage_parameters); } static unsigned int output_mask; static int filter_nat(const struct nf_conntrack *obj, const struct nf_conntrack *ct) { uint32_t ip; if (options & CT_OPT_SRC_NAT) { if (!nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT)) return 1; if (nfct_attr_is_set(obj, ATTR_SNAT_IPV4)) { ip = nfct_get_attr_u32(obj, ATTR_SNAT_IPV4); if (ip != nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)) return 1; } } if (options & CT_OPT_DST_NAT) { if (!nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT)) return 1; if (nfct_attr_is_set(obj, ATTR_DNAT_IPV4)) { ip = nfct_get_attr_u32(obj, ATTR_DNAT_IPV4); if (ip != nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC)) 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); nfct_close(cth); exit(0); } static int event_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { char buf[1024]; struct nf_conntrack *obj = data; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return NFCT_CB_CONTINUE; if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { dump_xml_header_done = 0; printf("\n" "\n"); } } if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_TMS) { if (!(output_mask & _O_XML)) { struct timeval tv; gettimeofday(&tv, NULL); printf("[%-8ld.%-6ld]\t", tv.tv_sec, tv.tv_usec); } else op_flags |= NFCT_OF_TIME; } if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, type, op_type, op_flags); printf("%s\n", buf); fflush(stdout); counter++; return NFCT_CB_CONTINUE; } static int dump_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { char buf[1024]; struct nf_conntrack *obj = data; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return NFCT_CB_CONTINUE; if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { dump_xml_header_done = 0; printf("\n" "\n"); } } if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); counter++; return NFCT_CB_CONTINUE; } static int delete_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { int res; char buf[1024]; struct nf_conntrack *obj = data; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return NFCT_CB_CONTINUE; res = nfct_query(ith, NFCT_Q_DESTROY, ct); if (res < 0) exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_DELETE)); if (output_mask & _O_XML) op_type = NFCT_O_XML; if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); counter++; return NFCT_CB_CONTINUE; } static int print_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { char buf[1024]; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; if (output_mask & _O_XML) op_type = NFCT_O_XML; if (output_mask & _O_EXT) op_flags = NFCT_OF_SHOW_LAYER3; if (output_mask & _O_ID) op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); printf("%s\n", buf); return NFCT_CB_CONTINUE; } static int update_cb(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data) { int res; struct nf_conntrack *obj = data; char __tmp[nfct_maxsize()]; struct nf_conntrack *tmp = (struct nf_conntrack *) (void *)__tmp; memset(tmp, 0, sizeof(__tmp)); if (filter_nat(obj, ct)) return NFCT_CB_CONTINUE; if (nfct_attr_is_set(obj, ATTR_ID) && nfct_attr_is_set(ct, ATTR_ID) && nfct_get_attr_u32(obj, ATTR_ID) != nfct_get_attr_u32(ct, ATTR_ID)) return NFCT_CB_CONTINUE; if (options & CT_OPT_TUPLE_ORIG && !nfct_cmp(obj, ct, NFCT_CMP_ORIG)) return NFCT_CB_CONTINUE; if (options & CT_OPT_TUPLE_REPL && !nfct_cmp(obj, ct, NFCT_CMP_REPL)) return NFCT_CB_CONTINUE; nfct_copy(tmp, ct, NFCT_CP_ORIG); nfct_copy(tmp, obj, NFCT_CP_META); res = nfct_query(ith, NFCT_Q_UPDATE, tmp); if (res < 0) exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_UPDATE)); nfct_callback_register(ith, NFCT_T_ALL, print_cb, NULL); res = nfct_query(ith, NFCT_Q_GET, tmp); if (res < 0) { /* 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_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]; nfexp_snprintf(buf,sizeof(buf), exp, NFCT_T_UNKNOWN, NFCT_O_DEFAULT, 0); printf("%s\n", buf); 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'; nl = strchr(buf, '\n'); } token = strtok(buf, " "); for (i=0; token != NULL && iopts, &h->option_offset); if (opts == NULL) exit_error(OTHER_PROBLEM, "out of memory"); nfct_set_attr_u8(obj, ATTR_L4PROTO, protonum); break; case 't': options |= CT_OPT_TIMEOUT; nfct_set_attr_u32(obj, ATTR_TIMEOUT, atol(optarg)); nfexp_set_attr_u32(exp, ATTR_EXP_TIMEOUT, atol(optarg)); break; case 'u': options |= CT_OPT_STATUS; parse_parameter(optarg, &status, PARSE_STATUS); nfct_set_attr_u32(obj, ATTR_STATUS, status); break; case 'e': options |= CT_OPT_EVENT_MASK; parse_parameter(optarg, &event_mask, PARSE_EVENT); break; case 'o': options |= CT_OPT_OUTPUT; parse_parameter(optarg, &output_mask, PARSE_OUTPUT); break; case 'z': options |= CT_OPT_ZERO; break; case 'n': case 'g': { char *tmp = NULL; options |= opt2type[c]; if (optarg) continue; else if (optind < argc && argv[optind][0] != '-' && argv[optind][0] != '!') tmp = argv[optind++]; if (tmp == NULL) continue; set_family(&family, AF_INET); nat_parse(tmp, 1, obj, opt2type[c]); break; } case 'i': case 'm': case 'c': options |= opt2type[c]; nfct_set_attr_u32(obj, opt2attr[c], strtoul(optarg, NULL, 0)); break; case 'a': fprintf(stderr, "WARNING: ignoring -%c, " "deprecated option.\n", c); break; case 'f': options |= CT_OPT_FAMILY; if (strncmp(optarg, "ipv4", strlen("ipv4")) == 0) set_family(&family, AF_INET); else if (strncmp(optarg, "ipv6", strlen("ipv6")) == 0) set_family(&family, AF_INET6); else exit_error(PARAMETER_PROBLEM, "`%s' unsupported protocol", optarg); break; case 'b': socketbuffersize = atol(optarg); options |= CT_OPT_BUFFERSIZE; break; case '?': if (optopt) exit_error(PARAMETER_PROBLEM, "option `%s' requires an " "argument", argv[optind-1]); else exit_error(PARAMETER_PROBLEM, "unknown option `%s'", argv[optind-1]); break; default: if (h && h->parse_opts &&!h->parse_opts(c - h->option_offset, obj, exptuple, mask, &l4flags)) exit_error(PARAMETER_PROBLEM, "parse error"); break; } } /* default family */ if (family == AF_UNSPEC) family = AF_INET; 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, obj); switch(command) { case CT_LIST: cth = nfct_open(CONNTRACK, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); if (options & CT_COMPARISON && options & CT_OPT_ZERO) exit_error(PARAMETER_PROBLEM, "Can't use -z with " "filtering parameters"); nfct_callback_register(cth, NFCT_T_ALL, dump_cb, obj); if (options & CT_OPT_ZERO) res = nfct_query(cth, NFCT_Q_DUMP_RESET, &family); else res = nfct_query(cth, NFCT_Q_DUMP, &family); 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); break; case CT_CREATE: if ((options & CT_OPT_ORIG) && !(options & CT_OPT_REPL)) nfct_setobjopt(obj, NFCT_SOPT_SETUP_REPLY); else if (!(options & CT_OPT_ORIG) && (options & CT_OPT_REPL)) nfct_setobjopt(obj, NFCT_SOPT_SETUP_ORIGINAL); cth = nfct_open(CONNTRACK, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfct_query(cth, NFCT_Q_CREATE, obj); if (res != -1) counter++; nfct_close(cth); break; case EXP_CREATE: nfexp_set_attr(exp, ATTR_EXP_MASTER, obj); nfexp_set_attr(exp, ATTR_EXP_EXPECTED, exptuple); nfexp_set_attr(exp, ATTR_EXP_MASK, mask); cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfexp_query(cth, NFCT_Q_CREATE, exp); nfct_close(cth); break; case CT_UPDATE: cth = nfct_open(CONNTRACK, 0); /* internal handler for delete_cb, otherwise we hit EILSEQ */ ith = nfct_open(CONNTRACK, 0); if (!cth || !ith) exit_error(OTHER_PROBLEM, "Can't open handler"); nfct_callback_register(cth, NFCT_T_ALL, update_cb, obj); res = nfct_query(cth, NFCT_Q_DUMP, &family); nfct_close(ith); nfct_close(cth); break; case CT_DELETE: cth = nfct_open(CONNTRACK, 0); ith = nfct_open(CONNTRACK, 0); if (!cth || !ith) exit_error(OTHER_PROBLEM, "Can't open handler"); nfct_callback_register(cth, NFCT_T_ALL, delete_cb, obj); res = nfct_query(cth, NFCT_Q_DUMP, &family); nfct_close(ith); nfct_close(cth); break; case EXP_DELETE: nfexp_set_attr(exp, ATTR_EXP_EXPECTED, obj); cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); res = nfexp_query(cth, NFCT_Q_DESTROY, 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, obj); res = nfct_query(cth, NFCT_Q_GET, obj); nfct_close(cth); break; case EXP_GET: nfexp_set_attr(exp, ATTR_EXP_MASTER, obj); 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, 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); break; case CT_EVENT: if (options & CT_OPT_EVENT_MASK) cth = nfct_open(CONNTRACK, event_mask); else cth = nfct_open(CONNTRACK, NFCT_ALL_CT_GROUPS); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); if (options & CT_OPT_BUFFERSIZE) { size_t ret; ret = nfnl_rcvbufsiz(nfct_nfnlh(cth), socketbuffersize); fprintf(stderr, "NOTICE: Netlink socket buffer size " "has been set to %zu bytes.\n", ret); } signal(SIGINT, event_sighandler); signal(SIGTERM, event_sighandler); nfct_callback_register(cth, NFCT_T_ALL, event_cb, obj); 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: cth = nfct_open(EXPECT, NF_NETLINK_CONNTRACK_EXP_NEW); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); signal(SIGINT, event_sighandler); signal(SIGTERM, event_sighandler); nfexp_callback_register(cth, NFCT_T_ALL, dump_exp_cb, NULL); res = nfexp_catch(cth); nfct_close(cth); break; case CT_COUNT: { #define NF_CONNTRACK_COUNT_PROC "/proc/sys/net/netfilter/nf_conntrack_count" int fd, count; char buf[strlen("2147483647")]; /* INT_MAX */ fd = open(NF_CONNTRACK_COUNT_PROC, O_RDONLY); if (fd == -1) { exit_error(OTHER_PROBLEM, "Can't open %s", NF_CONNTRACK_COUNT_PROC); } if (read(fd, buf, sizeof(buf)) == -1) { exit_error(OTHER_PROBLEM, "Can't read %s", NF_CONNTRACK_COUNT_PROC); } close(fd); count = atoi(buf); 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 X_STATS: 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_options(); if (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; }