diff options
Diffstat (limited to 'src/conntrack.c')
-rw-r--r-- | src/conntrack.c | 1889 |
1 files changed, 1361 insertions, 528 deletions
diff --git a/src/conntrack.c b/src/conntrack.c index fb4e5be..0d71352 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -41,6 +41,7 @@ #include "conntrack.h" #include <stdio.h> +#include <assert.h> #include <getopt.h> #include <stdlib.h> #include <ctype.h> @@ -53,6 +54,8 @@ #include <sys/socket.h> #include <sys/time.h> #include <time.h> +#include <inttypes.h> +#include <dirent.h> #ifdef HAVE_ARPA_INET_H #include <arpa/inet.h> #endif @@ -65,10 +68,14 @@ #include <linux/netfilter/nf_conntrack_common.h> #include <libnetfilter_conntrack/libnetfilter_conntrack.h> -static struct nfct_mnl_socket { +struct nfct_mnl_socket { struct mnl_socket *mnl; uint32_t portid; -} sock; +}; + +static struct nfct_mnl_socket _sock; +static struct nfct_mnl_socket _modifier_sock; +static struct nfct_mnl_socket _event_sock; struct u32_mask { uint32_t value; @@ -76,7 +83,7 @@ struct u32_mask { }; /* These are the template objects that are used to send commands. */ -static struct { +struct ct_tmpl { struct nf_conntrack *ct; struct nf_expect *exp; /* Expectations require the expectation tuple and the mask. */ @@ -89,104 +96,65 @@ static struct { struct nfct_filter_dump_mark filter_mark_kernel; bool filter_mark_kernel_set; + /* Allow to filter by status from kernel-space. */ + struct nfct_filter_dump_mark filter_status_kernel; + bool filter_status_kernel_set; + /* Allows filtering by ctlabels */ struct nfct_bitmask *label; /* Allows setting/removing specific ctlabels */ struct nfct_bitmask *label_modify; -} tmpl; +}; -static int alloc_tmpl_objects(void) +static struct ct_tmpl *cur_tmpl; + +struct ct_cmd { + struct list_head list; + unsigned int command; + unsigned int cmd; + unsigned int type; + unsigned int event_mask; + int options; + int family; + int protonum; + size_t socketbuffersize; + struct ct_tmpl tmpl; +}; + +static int alloc_tmpl_objects(struct ct_tmpl *tmpl) { - tmpl.ct = nfct_new(); - tmpl.exptuple = nfct_new(); - tmpl.mask = nfct_new(); - tmpl.exp = nfexp_new(); + tmpl->ct = nfct_new(); + tmpl->exptuple = nfct_new(); + tmpl->mask = nfct_new(); + tmpl->exp = nfexp_new(); - memset(&tmpl.mark, 0, sizeof(tmpl.mark)); + memset(&tmpl->mark, 0, sizeof(tmpl->mark)); - return tmpl.ct != NULL && tmpl.exptuple != NULL && - tmpl.mask != NULL && tmpl.exp != NULL; + cur_tmpl = tmpl; + + return tmpl->ct != NULL && tmpl->exptuple != NULL && + tmpl->mask != NULL && tmpl->exp != NULL; } -static void free_tmpl_objects(void) +static void free_tmpl_objects(struct ct_tmpl *tmpl) { - 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); + if (!tmpl) + return; + 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), @@ -325,6 +293,7 @@ static const char *optflags[NUMBER_OF_OPT] = { static struct option original_opts[] = { {"dump", 2, 0, 'L'}, {"create", 2, 0, 'I'}, + {"add", 2, 0, 'A'}, {"delete", 2, 0, 'D'}, {"update", 2, 0, 'U'}, {"get", 2, 0, 'G'}, @@ -368,7 +337,7 @@ static struct option original_opts[] = { {0, 0, 0, 0} }; -static const char *getopt_str = ":L::I::U::D::G::E::F::hVs:d:r:q:" +static const char *getopt_str = ":L::I::U::D::G::E::F::A::hVs:d:r:q:" "p:t:u:e:a:z[:]:{:}:m:i:f:o:n::" "g::c:b:C::Sj::w:l:<:>::(:):"; @@ -385,26 +354,27 @@ static const char *getopt_str = ":L::I::U::D::G::E::F::hVs:d:r:q:" 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}, + /* s d r q p t u z e [ ] { } a m i f n g o c b j w l < > ( ) */ + [CT_LIST_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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,0,0,0}, + [CT_EVENT_BIT] = {2,2,2,2,2,0,0,0,2,0,0,2,2,0,2,0,2,2,2,2,2,2,2,2,2,0,0,2,2}, + [CT_VERSION_BIT]= {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_HELP_BIT] = {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_BIT] = {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_BIT]= {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_BIT]= {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_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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_BIT] = {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_ADD_BIT] = {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}, }; static const int cmd2type[][2] = { @@ -418,6 +388,8 @@ static const int cmd2type[][2] = { ['h'] = { CT_HELP, CT_HELP }, ['C'] = { CT_COUNT, EXP_COUNT }, ['S'] = { CT_STATS, EXP_STATS }, + ['U'] = { CT_UPDATE, 0 }, + ['A'] = { CT_ADD, 0 }, }; static const int opt2type[] = { @@ -521,6 +493,7 @@ static char exit_msg[NUMBER_OF_CMD][64] = { [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", + [CT_ADD_BIT] = "%d flow entries have been added.\n", }; static const char usage_commands[] = @@ -565,7 +538,7 @@ 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" + " -r, --reply-src ip\t\tSource address 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" @@ -581,7 +554,7 @@ static const char usage_parameters[] = #define OPTION_OFFSET 256 -static struct nfct_handle *cth, *ith; +static struct nfct_handle *cth; static struct option *opts = original_opts; static unsigned int global_option_offset = 0; @@ -593,7 +566,6 @@ static unsigned int addr_valid_flags[ADDR_VALID_FLAGS_MAX] = { static LIST_HEAD(proto_list); -static unsigned int options; static struct nfct_labelmap *labelmap; static int filter_family; @@ -607,8 +579,276 @@ void register_proto(struct ctproto_handler *h) list_add(&h->head, &proto_list); } +#define BUFFER_SIZE(ret, size, len, offset) do { \ + if ((int)ret > (int)len) \ + ret = len; \ + size += ret; \ + offset += ret; \ + len -= ret; \ +} while(0) + +static int ct_snprintf_u32_bitmap(char *buf, size_t size, + const struct nf_conntrack *ct, + const struct ct_print_opts *attr) +{ + unsigned int offset = 0, ret, len = size; + bool found = false; + uint32_t val; + int i; + + val = nfct_get_attr_u32(ct, attr->type); + + for (i = 0; i < attr->val_mapping_count; i++) { + if (!(val & (1 << i))) + continue; + if (!attr->val_mapping[i]) + continue; + + ret = snprintf(buf + offset, len, "%s,", attr->val_mapping[i]); + BUFFER_SIZE(ret, size, len, offset); + found = true; + } + + if (found) { + offset--; + ret = snprintf(buf + offset, len, " "); + BUFFER_SIZE(ret, size, len, offset); + } + + return offset; +} + +static int ct_snprintf_attr(char *buf, size_t size, + const struct nf_conntrack *ct, + const struct ct_print_opts *attr) +{ + char ipstr[INET6_ADDRSTRLEN] = {}; + unsigned int offset = 0; + int type = attr->type; + int len = size, ret; + + ret = snprintf(buf, len, "%s ", attr->name); + BUFFER_SIZE(ret, size, len, offset); + + switch (attr->datatype) { + case CT_ATTR_TYPE_U8: + if (attr->val_mapping) + ret = snprintf(buf + offset, len, "%s ", + attr->val_mapping[nfct_get_attr_u8(ct, type)]); + else + ret = snprintf(buf + offset, len, "%u ", + nfct_get_attr_u8(ct, type)); + break; + case CT_ATTR_TYPE_BE16: + ret = snprintf(buf + offset, len, "%u ", + ntohs(nfct_get_attr_u16(ct, type))); + break; + case CT_ATTR_TYPE_U16: + ret = snprintf(buf + offset, len, "%u ", + nfct_get_attr_u16(ct, type)); + break; + case CT_ATTR_TYPE_BE32: + ret = snprintf(buf + offset, len, "%u ", + ntohl(nfct_get_attr_u32(ct, type))); + break; + case CT_ATTR_TYPE_U32: + ret = snprintf(buf + offset, len, "%u ", + nfct_get_attr_u32(ct, type)); + break; + case CT_ATTR_TYPE_U64: + ret = snprintf(buf + offset, len, "%lu ", + nfct_get_attr_u64(ct, type)); + break; + case CT_ATTR_TYPE_IPV4: + inet_ntop(AF_INET, nfct_get_attr(ct, type), + ipstr, sizeof(ipstr)); + ret = snprintf(buf + offset, len, "%s ", ipstr); + break; + case CT_ATTR_TYPE_IPV6: + inet_ntop(AF_INET6, nfct_get_attr(ct, type), + ipstr, sizeof(ipstr)); + ret = snprintf(buf + offset, len, "%s ", ipstr); + break; + case CT_ATTR_TYPE_U32_BITMAP: + ret = ct_snprintf_u32_bitmap(buf + offset, len, ct, attr); + if (ret == 0 && type == ATTR_STATUS) + ret = snprintf(buf + offset, len, "UNSET "); + break; + default: + /* Unsupported datatype, should not ever happen */ + ret = 0; + break; + } + BUFFER_SIZE(ret, size, len, offset); + + return offset; +} + +int ct_snprintf_opts(char *buf, unsigned int len, const struct nf_conntrack *ct, + const struct ct_print_opts *attrs) +{ + unsigned int size = 0, offset = 0, ret; + int i; + + for (i = 0; attrs[i].name; ++i) { + if (!nfct_attr_is_set(ct, attrs[i].type)) + continue; + + ret = ct_snprintf_attr(buf + offset, len, ct, &attrs[i]); + BUFFER_SIZE(ret, size, len, offset); + } + + return offset; +} + +static const struct ct_print_opts attrs_ip_map[] = { + [ATTR_ORIG_IPV4_SRC] = { "-s", ATTR_ORIG_IPV4_SRC, CT_ATTR_TYPE_IPV4, 0, 0 }, + [ATTR_ORIG_IPV4_DST] = { "-d", ATTR_ORIG_IPV4_DST, CT_ATTR_TYPE_IPV4, 0, 0 }, + [ATTR_DNAT_IPV4] = { "-g", ATTR_REPL_IPV4_SRC, CT_ATTR_TYPE_IPV4, 0, 0 }, + [ATTR_SNAT_IPV4] = { "-n", ATTR_REPL_IPV4_DST, CT_ATTR_TYPE_IPV4, 0, 0 }, + [ATTR_REPL_IPV4_SRC] = { "-r", ATTR_REPL_IPV4_SRC, CT_ATTR_TYPE_IPV4, 0, 0 }, + [ATTR_REPL_IPV4_DST] = { "-q", ATTR_REPL_IPV4_DST, CT_ATTR_TYPE_IPV4, 0, 0 }, + [ATTR_ORIG_IPV6_SRC] = { "-s", ATTR_ORIG_IPV6_SRC, CT_ATTR_TYPE_IPV6, 0, 0 }, + [ATTR_ORIG_IPV6_DST] = { "-d", ATTR_ORIG_IPV6_DST, CT_ATTR_TYPE_IPV6, 0, 0 }, + [ATTR_DNAT_IPV6] = { "-g", ATTR_REPL_IPV6_SRC, CT_ATTR_TYPE_IPV6, 0, 0 }, + [ATTR_SNAT_IPV6] = { "-n", ATTR_REPL_IPV6_DST, CT_ATTR_TYPE_IPV6, 0, 0 }, + [ATTR_REPL_IPV6_SRC] = { "-r", ATTR_REPL_IPV6_SRC, CT_ATTR_TYPE_IPV6, 0, 0 }, + [ATTR_REPL_IPV6_DST] = { "-q", ATTR_REPL_IPV6_DST, CT_ATTR_TYPE_IPV6, 0, 0 }, +}; + +static const char *conntrack_status_map[] = { + [IPS_SEEN_REPLY_BIT] = "SEEN_REPLY", + [IPS_ASSURED_BIT] = "ASSURED", + [IPS_FIXED_TIMEOUT_BIT] = "FIXED_TIMEOUT", + [IPS_EXPECTED_BIT] = "EXPECTED" +}; + +static struct ct_print_opts attrs_generic[] = { + { "-t", ATTR_TIMEOUT, CT_ATTR_TYPE_U32, 0, 0 }, + { "-u", ATTR_STATUS, CT_ATTR_TYPE_U32_BITMAP, + sizeof(conntrack_status_map)/sizeof(conntrack_status_map[0]), + conntrack_status_map }, + { "-c", ATTR_SECMARK, CT_ATTR_TYPE_U32, 0, 0 }, + { "-w", ATTR_ZONE, CT_ATTR_TYPE_U16, 0, 0 }, + { "--orig-zone", ATTR_ORIG_ZONE, CT_ATTR_TYPE_U16, 0, 0 }, + { "--reply-zone", ATTR_REPL_ZONE, CT_ATTR_TYPE_U16, 0, 0 }, + {}, +}; + +static int ct_save_snprintf(char *buf, size_t len, + const struct nf_conntrack *ct, + struct nfct_labelmap *map, + enum nf_conntrack_msg_type type) +{ + struct ct_print_opts tuple_attr_print[5] = {}; + unsigned int size = 0, offset = 0; + struct ctproto_handler *cur; + uint8_t l3proto, l4proto; + int tuple_attrs[4] = {}; + bool l4proto_set; + unsigned i; + int ret; + + switch (type) { + case NFCT_T_NEW: + ret = snprintf(buf + offset, len, "-A "); + BUFFER_SIZE(ret, size, len, offset); + break; + case NFCT_T_UPDATE: + ret = snprintf(buf + offset, len, "-U "); + BUFFER_SIZE(ret, size, len, offset); + break; + case NFCT_T_DESTROY: + ret = snprintf(buf + offset, len, "-D "); + BUFFER_SIZE(ret, size, len, offset); + break; + default: + break; + } + + ret = ct_snprintf_opts(buf + offset, len, ct, attrs_generic); + BUFFER_SIZE(ret, size, len, offset); + + l3proto = nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO); + if (!l3proto) + l3proto = nfct_get_attr_u8(ct, ATTR_REPL_L3PROTO); + switch (l3proto) { + case AF_INET: + tuple_attrs[0] = ATTR_ORIG_IPV4_SRC; + tuple_attrs[1] = ATTR_ORIG_IPV4_DST; + tuple_attrs[2] = nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT) ? + ATTR_DNAT_IPV4 : ATTR_REPL_IPV4_SRC; + tuple_attrs[3] = nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT) ? + ATTR_SNAT_IPV4 : ATTR_REPL_IPV4_DST; + break; + case AF_INET6: + tuple_attrs[0] = ATTR_ORIG_IPV6_SRC; + tuple_attrs[1] = ATTR_ORIG_IPV6_DST; + tuple_attrs[2] = nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT) ? + ATTR_DNAT_IPV6 : ATTR_REPL_IPV6_SRC; + tuple_attrs[3] = nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT) ? + ATTR_SNAT_IPV6 : ATTR_REPL_IPV6_DST; + break; + default: + fprintf(stderr, "WARNING: unsupported l3proto %d, skipping.\n", + l3proto); + return 0; + } + + for (i = 0; i < sizeof(tuple_attrs) / sizeof(tuple_attrs[0]); i++) { + memcpy(&tuple_attr_print[i], &attrs_ip_map[tuple_attrs[i]], + sizeof(tuple_attr_print[0])); + } + + ret = ct_snprintf_opts(buf + offset, len, ct, tuple_attr_print); + BUFFER_SIZE(ret, size, len, offset); + + l4proto = nfct_get_attr_u8(ct, ATTR_L4PROTO); + + l4proto_set = false; + /* is it in the list of supported protocol? */ + list_for_each_entry(cur, &proto_list, head) { + if (cur->protonum != l4proto) + continue; + + ret = snprintf(buf + offset, len, "-p %s ", cur->name); + BUFFER_SIZE(ret, size, len, offset); + + ret = ct_snprintf_opts(buf + offset, len, ct, cur->print_opts); + BUFFER_SIZE(ret, size, len, offset); + + l4proto_set = true; + break; + } + + if (!l4proto_set) { + ret = snprintf(buf + offset, len, "-p %d ", l4proto); + BUFFER_SIZE(ret, size, len, offset); + } + + /* skip trailing space, if any */ + for (; size && buf[size-1] == ' '; --size) + buf[size-1] = '\0'; + + return size; +} + extern struct ctproto_handler ct_proto_unknown; +static int parse_proto_num(const char *str) +{ + unsigned long val; + char *endptr; + + val = strtoul(str, &endptr, 0); + if (val > IPPROTO_RAW || + endptr == str || + *endptr != '\0') + return -1; + + return val; +} + static struct ctproto_handler *findproto(char *name, int *pnum) { struct ctproto_handler *cur; @@ -628,8 +868,8 @@ static struct ctproto_handler *findproto(char *name, int *pnum) return &ct_proto_unknown; } /* using a protocol number? */ - protonum = atoi(name); - if (protonum >= 0 && protonum <= IPPROTO_MAX) { + protonum = parse_proto_num(name); + if (protonum >= 0) { /* try lookup by number, perhaps this protocol is supported */ list_for_each_entry(cur, &proto_list, head) { if (cur->protonum == protonum) { @@ -696,7 +936,7 @@ exit_error(enum exittype status, const char *msg, ...) if (status == PARAMETER_PROBLEM) exit_tryhelp(status); /* release template objects that were allocated in the setup stage. */ - free_tmpl_objects(); + free_tmpl_objects(cur_tmpl); exit(status); } @@ -818,11 +1058,11 @@ err2str(int err, enum ct_command command) { { 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, + { CT_CREATE|CT_GET|CT_DELETE|CT_ADD, ENOENT, "such conntrack doesn't exist" }, - { CT_CREATE|CT_GET, ENOMEM, "not enough memory" }, + { CT_CREATE|CT_GET|CT_ADD, ENOMEM, "not enough memory" }, { CT_GET, EAFNOSUPPORT, "protocol not supported" }, - { CT_CREATE, ETIME, "conntrack has expired" }, + { CT_CREATE|CT_ADD, 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 " @@ -855,7 +1095,7 @@ enum { _O_ID = (1 << 3), _O_KTMS = (1 << 4), _O_CL = (1 << 5), - _O_US = (1 << 6), + _O_SAVE = (1 << 6), }; enum { @@ -866,7 +1106,7 @@ enum { }; static struct parse_parameter { - const char *parameter[7]; + const char *parameter[8]; size_t size; unsigned int value[8]; } parse_array[PARSE_MAX] = { @@ -874,13 +1114,13 @@ static struct parse_parameter { { IPS_ASSURED, IPS_SEEN_REPLY, 0, IPS_FIXED_TIMEOUT, IPS_EXPECTED, IPS_OFFLOAD, IPS_HW_OFFLOAD} }, { {"ALL", "NEW", "UPDATES", "DESTROY"}, 4, { CT_EVENT_F_ALL, CT_EVENT_F_NEW, CT_EVENT_F_UPD, CT_EVENT_F_DEL } }, - { {"xml", "extended", "timestamp", "id", "ktimestamp", "labels", "userspace" }, 7, - { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS, _O_CL, _O_US }, + { {"xml", "extended", "timestamp", "id", "ktimestamp", "labels", "userspace", "save"}, 8, + { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS, _O_CL, 0, _O_SAVE }, }, }; static int -do_parse_parameter(const char *str, size_t str_length, unsigned int *value, +do_parse_parameter(const char *str, size_t str_length, unsigned int *value, int parse_type) { size_t i; @@ -905,7 +1145,7 @@ do_parse_parameter(const char *str, size_t str_length, unsigned int *value, ret = 1; break; } - + return ret; } @@ -927,6 +1167,53 @@ parse_parameter(const char *arg, unsigned int *status, int parse_type) } static void +parse_parameter_mask(const char *arg, unsigned int *status, unsigned int *mask, int parse_type) +{ + static const char unreplied[] = "UNREPLIED"; + unsigned int *value; + const char *comma; + bool negated; + + while ((comma = strchr(arg, ',')) != NULL) { + if (comma == arg) + exit_error(PARAMETER_PROBLEM,"Bad parameter `%s'", arg); + + negated = *arg == '!'; + if (negated) + arg++; + if (comma == arg) + exit_error(PARAMETER_PROBLEM,"Bad parameter `%s'", arg); + + value = negated ? mask : status; + + if (!negated && strncmp(arg, unreplied, strlen(unreplied)) == 0) { + *mask |= IPS_SEEN_REPLY; + arg = comma+1; + continue; + } + + if (!do_parse_parameter(arg, comma-arg, value, parse_type)) + exit_error(PARAMETER_PROBLEM,"Bad parameter `%s'", arg); + arg = comma+1; + } + + negated = *arg == '!'; + if (negated) + arg++; + value = negated ? mask : status; + + if (!negated && strncmp(arg, unreplied, strlen(unreplied)) == 0) { + *mask |= IPS_SEEN_REPLY; + return; + } + + if (strlen(arg) == 0 + || !do_parse_parameter(arg, strlen(arg), + value, parse_type)) + exit_error(PARAMETER_PROBLEM, "Bad parameter `%s'", arg); +} + +static void parse_u32_mask(const char *arg, struct u32_mask *m) { char *end; @@ -1190,8 +1477,7 @@ split_address_and_port(const char *arg, char **address, char **port_str) } } -static void -usage(char *prog) +static void usage(const char *prog) { fprintf(stdout, "Command line interface for the connection " "tracking system. Version %s\n", VERSION); @@ -1208,17 +1494,17 @@ usage(char *prog) static unsigned int output_mask; static int -filter_label(const struct nf_conntrack *ct) +filter_label(const struct nf_conntrack *ct, const struct ct_tmpl *tmpl) { - if (tmpl.label == NULL) + 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) && + 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; } @@ -1226,25 +1512,26 @@ filter_label(const struct nf_conntrack *ct) return 0; } -static int -filter_mark(const struct nf_conntrack *ct) +static int filter_mark(const struct ct_cmd *cmd, const struct nf_conntrack *ct) { - if ((options & CT_OPT_MARK) && - !mark_cmp(&tmpl.mark, ct)) + const struct ct_tmpl *tmpl = &cmd->tmpl; + + if ((cmd->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) +static int filter_nat(const struct ct_cmd *cmd, 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 check_srcnat = cmd->options & CT_OPT_SRC_NAT ? 1 : 0; + int check_dstnat = cmd->options & CT_OPT_DST_NAT ? 1 : 0; + struct nf_conntrack *obj = cmd->tmpl.ct; int has_srcnat = 0, has_dstnat = 0; uint32_t ip; uint16_t port; - if (options & CT_OPT_ANY_NAT) + if (cmd->options & CT_OPT_ANY_NAT) check_srcnat = check_dstnat = 1; if (check_srcnat) { @@ -1307,13 +1594,14 @@ filter_nat(const struct nf_conntrack *obj, const struct nf_conntrack *ct) nfct_getobjopt(ct, NFCT_GOPT_IS_DPAT))) has_dstnat = 1; } - if (options & CT_OPT_ANY_NAT) + if (cmd->options & CT_OPT_ANY_NAT) return !(has_srcnat || has_dstnat); - else if ((options & CT_OPT_SRC_NAT) && (options & CT_OPT_DST_NAT)) + else if ((cmd->options & CT_OPT_SRC_NAT) && + (cmd->options & CT_OPT_DST_NAT)) return !(has_srcnat && has_dstnat); - else if (options & CT_OPT_SRC_NAT) + else if (cmd->options & CT_OPT_SRC_NAT) return !has_srcnat; - else if (options & CT_OPT_DST_NAT) + else if (cmd->options & CT_OPT_DST_NAT) return !has_dstnat; return 0; @@ -1361,14 +1649,14 @@ nfct_filter_network_direction(const struct nf_conntrack *ct, enum ct_direction d } static int -filter_network(const struct nf_conntrack *ct) +filter_network(const struct ct_cmd *cmd, const struct nf_conntrack *ct) { - if (options & CT_OPT_MASK_SRC) { + if (cmd->options & CT_OPT_MASK_SRC) { if (nfct_filter_network_direction(ct, DIR_SRC)) return 1; } - if (options & CT_OPT_MASK_DST) { + if (cmd->options & CT_OPT_MASK_DST) { if (nfct_filter_network_direction(ct, DIR_DST)) return 1; } @@ -1376,15 +1664,18 @@ filter_network(const struct nf_conntrack *ct) } static int -nfct_filter(struct nf_conntrack *obj, struct nf_conntrack *ct) +nfct_filter(struct ct_cmd *cmd, struct nf_conntrack *ct, + const struct ct_tmpl *tmpl) { - if (filter_nat(obj, ct) || - filter_mark(ct) || - filter_label(ct) || - filter_network(ct)) + struct nf_conntrack *obj = cmd->tmpl.ct; + + if (filter_nat(cmd, ct) || + filter_mark(cmd, ct) || + filter_label(ct, tmpl) || + filter_network(cmd, ct)) return 1; - if (options & CT_COMPARISON && + if (cmd->options & CT_COMPARISON && !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) return 1; @@ -1404,7 +1695,7 @@ event_sighandler(int s) fprintf(stderr, "%s v%s (conntrack-tools): ", PROGNAME, VERSION); fprintf(stderr, "%d flow events have been shown.\n", counter); - mnl_socket_close(sock.mnl); + mnl_socket_close(_sock.mnl); exit(0); } @@ -1422,14 +1713,177 @@ exp_event_sighandler(int s) exit(0); } +static char *pid2name(pid_t pid) +{ + char procname[256], *prog; + FILE *fp; + int ret; + + ret = snprintf(procname, sizeof(procname), "/proc/%lu/stat", (unsigned long)pid); + if (ret < 0 || ret > (int)sizeof(procname)) + return NULL; + + fp = fopen(procname, "r"); + if (!fp) + return NULL; + + ret = fscanf(fp, "%*u (%m[^)]", &prog); + + fclose(fp); + + if (ret == 1) + return prog; + + return NULL; +} + +static char *portid2name(pid_t pid, uint32_t portid, unsigned long inode) +{ + const struct dirent *ent; + char procname[256]; + DIR *dir; + int ret; + + ret = snprintf(procname, sizeof(procname), "/proc/%lu/fd/", (unsigned long)pid); + if (ret < 0 || ret >= (int)sizeof(procname)) + return NULL; + + dir = opendir(procname); + if (!dir) + return NULL; + + for (;;) { + unsigned long ino; + char tmp[128]; + ssize_t rl; + + ent = readdir(dir); + if (!ent) + break; + + if (ent->d_type != DT_LNK) + continue; + + ret = snprintf(procname, sizeof(procname), "/proc/%d/fd/%s", + pid, ent->d_name); + if (ret < 0 || ret >= (int)sizeof(procname)) + continue; + + rl = readlink(procname, tmp, sizeof(tmp)); + if (rl <= 0 || rl >= (ssize_t)sizeof(tmp)) + continue; + + tmp[rl] = 0; + + ret = sscanf(tmp, "socket:[%lu]", &ino); + if (ret == 1 && ino == inode) { + closedir(dir); + return pid2name(pid); + } + } + + closedir(dir); + return NULL; +} + +static char *name_by_portid(uint32_t portid, unsigned long inode) +{ + const struct dirent *ent; + char *prog; + DIR *dir; + + /* Many netlink users use their process ID to allocate the first port id. */ + prog = portid2name(portid, portid, inode); + if (prog) + return prog; + + /* no luck, search harder. */ + dir = opendir("/proc"); + if (!dir) + return NULL; + + for (;;) { + unsigned long pid; + char *end; + + ent = readdir(dir); + if (!ent) + break; + + if (ent->d_type != DT_DIR) + continue; + + pid = strtoul(ent->d_name, &end, 10); + if (pid <= 1 || *end) + continue; + + if (pid == portid) /* already tried */ + continue; + + prog = portid2name(pid, portid, inode); + if (prog) + break; + } + + closedir(dir); + return prog; +} + +static char *get_progname(uint32_t portid) +{ + FILE *fp = fopen("/proc/net/netlink", "r"); + uint32_t portid_check; + unsigned long inode; + int ret, prot; + + if (!fp) + return NULL; + + for (;;) { + char line[256]; + + if (!fgets(line, sizeof(line), fp)) + break; + + ret = sscanf(line, "%*x %d %u %*x %*d %*d %*x %*d %*u %lu\n", + &prot, &portid_check, &inode); + + if (ret == EOF) + break; + + if (ret == 3 && portid_check == portid && prot == NETLINK_NETFILTER) { + static uint32_t last_portid; + static uint32_t last_inode; + static char *last_program; + char *prog; + + fclose(fp); + + if (last_portid == portid && last_inode == inode) + return last_program; + + prog = name_by_portid(portid, inode); + + free(last_program); + last_program = prog; + last_portid = portid; + last_inode = inode; + return prog; + } + } + + fclose(fp); + return NULL; +} + static int event_cb(const struct nlmsghdr *nlh, void *data) { + struct nfgenmsg *nfh = mnl_nlmsg_get_payload(nlh); unsigned int op_type = NFCT_O_DEFAULT; - struct nf_conntrack *obj = data; enum nf_conntrack_msg_type type; + struct ct_cmd *cmd = data; unsigned int op_flags = 0; struct nf_conntrack *ct; - bool userspace = false; char buf[1024]; switch(nlh->nlmsg_type & 0xff) { @@ -1455,9 +1909,16 @@ static int event_cb(const struct nlmsghdr *nlh, void *data) if (nfct_nlmsg_parse(nlh, ct) < 0) goto out; - if (nfct_filter(obj, ct)) + if ((filter_family != AF_UNSPEC && + filter_family != nfh->nfgen_family) || + nfct_filter(cmd, ct, cur_tmpl)) goto out; + if (output_mask & _O_SAVE) { + ct_save_snprintf(buf, sizeof(buf), ct, labelmap, type); + goto done; + } + if (output_mask & _O_XML) { op_type = NFCT_O_XML; if (dump_xml_header_done) { @@ -1482,15 +1943,17 @@ static int event_cb(const struct nlmsghdr *nlh, void *data) op_flags |= NFCT_OF_ID; nfct_snprintf_labels(buf, sizeof(buf), ct, type, op_type, op_flags, labelmap); +done: + if (nlh->nlmsg_pid && !(output_mask & _O_XML)) { + char *prog = get_progname(nlh->nlmsg_pid); - if (output_mask & _O_US) { - if (nlh->nlmsg_pid) - userspace = true; + if (prog) + printf("%s [USERSPACE] portid=%u progname=%s\n", buf, nlh->nlmsg_pid, prog); else - userspace = false; + printf("%s [USERSPACE] portid=%u\n", buf, nlh->nlmsg_pid); + } else { + puts(buf); } - - printf("%s%s\n", buf, userspace ? " [USERSPACE]" : ""); fflush(stdout); counter++; @@ -1499,59 +1962,46 @@ out: return MNL_CB_OK; } -static int dump_cb(enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data) +static int nfct_mnl_request(struct nfct_mnl_socket *sock, uint16_t subsys, + int family, uint16_t type, uint16_t flags, + mnl_cb_t cb, const struct nf_conntrack *ct, + const struct ct_cmd *cmd); + +static int mnl_nfct_delete_cb(const struct nlmsghdr *nlh, void *data) { - char buf[1024]; - struct nf_conntrack *obj = data; + struct nfct_mnl_socket *modifier_sock = &_modifier_sock; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; + struct ct_cmd *cmd = data; + struct nf_conntrack *ct; + char buf[1024]; + int res; - 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; -} + ct = nfct_new(); + if (ct == NULL) + return MNL_CB_OK; -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; + nfct_nlmsg_parse(nlh, ct); - if (nfct_filter(obj, ct)) - return NFCT_CB_CONTINUE; + if (nfct_filter(cmd, ct, cur_tmpl)) + goto destroy_ok; - res = nfct_query(ith, NFCT_Q_DESTROY, ct); - if (res < 0) + res = nfct_mnl_request(modifier_sock, NFNL_SUBSYS_CTNETLINK, + nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO), + IPCTNL_MSG_CT_DELETE, NLM_F_ACK, NULL, ct, NULL); + if (res < 0) { + /* the entry has vanish in middle of the delete */ + if (errno == ENOENT) + goto done; exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_DELETE)); + } + + if (output_mask & _O_SAVE) { + ct_save_snprintf(buf, sizeof(buf), ct, labelmap, NFCT_T_DESTROY); + goto done; + } if (output_mask & _O_XML) op_type = NFCT_O_XML; @@ -1561,20 +2011,33 @@ static int delete_cb(enum nf_conntrack_msg_type type, op_flags |= NFCT_OF_ID; nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags); +done: printf("%s\n", buf); counter++; - return NFCT_CB_CONTINUE; +destroy_ok: + nfct_destroy(ct); + return MNL_CB_OK; } -static int print_cb(enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data) +static int mnl_nfct_print_cb(const struct nlmsghdr *nlh, void *data) { - char buf[1024]; unsigned int op_type = NFCT_O_DEFAULT; unsigned int op_flags = 0; + struct nf_conntrack *ct; + char buf[1024]; + + ct = nfct_new(); + if (ct == NULL) + return MNL_CB_OK; + + nfct_nlmsg_parse(nlh, ct); + + if (output_mask & _O_SAVE) { + ct_save_snprintf(buf, sizeof(buf), ct, labelmap, NFCT_T_NEW); + goto done; + } if (output_mask & _O_XML) op_type = NFCT_O_XML; @@ -1584,25 +2047,29 @@ static int print_cb(enum nf_conntrack_msg_type type, op_flags |= NFCT_OF_ID; nfct_snprintf_labels(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags, labelmap); +done: printf("%s\n", buf); - return NFCT_CB_CONTINUE; + nfct_destroy(ct); + + return MNL_CB_OK; } -static void copy_mark(struct nf_conntrack *tmp, +static void copy_mark(const struct ct_cmd *cmd, struct nf_conntrack *tmp, const struct nf_conntrack *ct, const struct u32_mask *m) { - if (options & CT_OPT_MARK) { + if (cmd->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) +static void copy_status(const struct ct_cmd *cmd, struct nf_conntrack *tmp, + const struct nf_conntrack *ct) { - if (options & CT_OPT_STATUS) { + if (cmd->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); @@ -1618,21 +2085,23 @@ static struct nfct_bitmask *xnfct_bitmask_clone(const struct nfct_bitmask *a) return b; } -static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct) +static void copy_label(const struct ct_cmd *cmd, struct nf_conntrack *tmp, + const struct nf_conntrack *ct, + const struct ct_tmpl *tmpl) { struct nfct_bitmask *ctb, *newmask; unsigned int i; - if ((options & (CT_OPT_ADD_LABEL|CT_OPT_DEL_LABEL)) == 0) + if ((cmd->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 (cmd->options & CT_OPT_ADD_LABEL) { if (ctb == NULL) { nfct_set_attr(tmp, ATTR_CONNLABELS, - xnfct_bitmask_clone(tmpl.label_modify)); + xnfct_bitmask_clone(tmpl->label_modify)); return; } /* If we send a bitmask shorter than the kernel sent to us, the bits we @@ -1646,7 +2115,7 @@ static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct) 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)) { + 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)) { @@ -1659,7 +2128,7 @@ static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct) nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask); } else if (ctb != NULL) { /* CT_OPT_DEL_LABEL */ - if (tmpl.label_modify == NULL) { + if (tmpl->label_modify == NULL) { newmask = nfct_bitmask_new(0); if (newmask) nfct_set_attr(tmp, ATTR_CONNLABELS, newmask); @@ -1667,35 +2136,43 @@ static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct) } for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) { - if (nfct_bitmask_test_bit(tmpl.label_modify, i)) + if (nfct_bitmask_test_bit(tmpl->label_modify, i)) nfct_bitmask_unset_bit(ctb, i); } - newmask = xnfct_bitmask_clone(tmpl.label_modify); + 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) +static int mnl_nfct_update_cb(const struct nlmsghdr *nlh, void *data) { + struct ct_cmd *cmd = data; + struct nfct_mnl_socket *modifier_sock = &_modifier_sock; + struct nf_conntrack *ct, *obj = cmd->tmpl.ct, *tmp = NULL; int res; - struct nf_conntrack *obj = data, *tmp; - if (filter_nat(obj, ct) || - filter_label(ct) || - filter_network(ct)) - return NFCT_CB_CONTINUE; + ct = nfct_new(); + if (ct == NULL) + return MNL_CB_OK; + + nfct_nlmsg_parse(nlh, ct); + + if (filter_nat(cmd, ct) || + filter_label(ct, cur_tmpl) || + filter_network(cmd, ct)) + goto destroy_ok; 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; + goto destroy_ok; - 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; + if (cmd->options & CT_OPT_TUPLE_ORIG && + !nfct_cmp(obj, ct, NFCT_CMP_ORIG)) + goto destroy_ok; + if (cmd->options & CT_OPT_TUPLE_REPL && + !nfct_cmp(obj, ct, NFCT_CMP_REPL)) + goto destroy_ok; tmp = nfct_new(); if (tmp == NULL) @@ -1703,41 +2180,52 @@ static int update_cb(enum nf_conntrack_msg_type type, 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); + copy_mark(cmd, tmp, ct, &cur_tmpl->mark); + copy_status(cmd, tmp, ct); + copy_label(cmd, tmp, ct, cur_tmpl); /* 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; - } + if (nfct_cmp(tmp, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) + goto destroy_ok; - res = nfct_query(ith, NFCT_Q_UPDATE, tmp); - if (res < 0) - fprintf(stderr, - "Operation failed: %s\n", + res = nfct_mnl_request(modifier_sock, NFNL_SUBSYS_CTNETLINK, + nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO), + IPCTNL_MSG_CT_NEW, NLM_F_ACK, NULL, tmp, NULL); + if (res < 0) { + /* the entry has vanish in middle of the update */ + if (errno == ENOENT) + goto destroy_ok; + else if (cmd->options & (CT_OPT_ADD_LABEL | CT_OPT_DEL_LABEL) && + !nfct_attr_is_set(ct, ATTR_CONNLABELS) && + errno == ENOSPC) + goto destroy_ok; + + 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); + res = nfct_mnl_request(modifier_sock, NFNL_SUBSYS_CTNETLINK, + nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO), + IPCTNL_MSG_CT_GET, 0, + mnl_nfct_print_cb, tmp, NULL); 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; - } + if (errno == ENOENT) + goto destroy_ok; exit_error(OTHER_PROBLEM, "Operation failed: %s", err2str(errno, CT_UPDATE)); } - nfct_destroy(tmp); - nfct_callback_unregister(ith); counter++; - return NFCT_CB_CONTINUE; +destroy_ok: + if (tmp) + nfct_destroy(tmp); + nfct_destroy(ct); + + return MNL_CB_OK; } static int dump_exp_cb(enum nf_conntrack_msg_type type, @@ -1882,32 +2370,33 @@ out_err: return ret; } -static int nfct_mnl_socket_open(unsigned int events) +static int nfct_mnl_socket_open(struct nfct_mnl_socket *socket, + unsigned int events) { - sock.mnl = mnl_socket_open(NETLINK_NETFILTER); - if (sock.mnl == NULL) { + socket->mnl = mnl_socket_open(NETLINK_NETFILTER); + if (socket->mnl == NULL) { perror("mnl_socket_open"); return -1; } - if (mnl_socket_bind(sock.mnl, events, MNL_SOCKET_AUTOPID) < 0) { + if (mnl_socket_bind(socket->mnl, events, MNL_SOCKET_AUTOPID) < 0) { perror("mnl_socket_bind"); return -1; } - sock.portid = mnl_socket_get_portid(sock.mnl); + socket->portid = mnl_socket_get_portid(socket->mnl); return 0; } static struct nlmsghdr * nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type, - uint8_t family) + uint16_t flags, 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_flags = NLM_F_REQUEST | flags; nlh->nlmsg_seq = time(NULL); nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); @@ -1918,63 +2407,114 @@ nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type, return nlh; } -static void nfct_mnl_socket_close(void) +static void nfct_mnl_socket_close(const struct nfct_mnl_socket *sock) { - mnl_socket_close(sock.mnl); + mnl_socket_close(sock->mnl); } -static int -nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb, uint8_t family) +static int nfct_mnl_socket_check_open(struct nfct_mnl_socket *socket, + unsigned int events) +{ + if (socket->mnl != NULL) + return 0; + + return nfct_mnl_socket_open(socket, events); +} + +static void nfct_mnl_socket_check_close(struct nfct_mnl_socket *sock) +{ + if (sock->mnl) { + nfct_mnl_socket_close(sock); + memset(sock, 0, sizeof(*sock)); + } +} + +static int __nfct_mnl_dump(struct nfct_mnl_socket *sock, + const struct nlmsghdr *nlh, mnl_cb_t cb, void *data) { 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); + res = mnl_socket_sendto(sock->mnl, nlh, nlh->nlmsg_len); if (res < 0) return res; - res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); + 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); + res = mnl_cb_run(buf, res, nlh->nlmsg_seq, sock->portid, + cb, data); if (res <= MNL_CB_STOP) break; - res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); + 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) +nfct_mnl_dump(struct nfct_mnl_socket *sock, uint16_t subsys, uint16_t type, + mnl_cb_t cb, struct ct_cmd *cmd, + const struct nfct_filter_dump *filter_dump) { + uint8_t family = cmd ? cmd->family : AF_UNSPEC; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; - int res; - nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, family); + nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, NLM_F_DUMP, family); - res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len); - if (res < 0) - return res; + if (filter_dump) + nfct_nlmsg_build_filter(nlh, filter_dump); - res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); - if (res < 0) - return res; + return __nfct_mnl_dump(sock, nlh, cb, cmd); +} + +static int nfct_mnl_talk(struct nfct_mnl_socket *sock, + const struct nlmsghdr *nlh, mnl_cb_t cb, + const struct ct_cmd *cmd) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret; + + ret = mnl_socket_sendto(sock->mnl, nlh, nlh->nlmsg_len); + if (ret < 0) + return ret; + + ret = mnl_socket_recvfrom(sock->mnl, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return mnl_cb_run(buf, ret, nlh->nlmsg_seq, sock->portid, cb, (void *)cmd); +} + +static int nfct_mnl_request(struct nfct_mnl_socket *sock, uint16_t subsys, + int family, uint16_t type, uint16_t flags, + mnl_cb_t cb, const struct nf_conntrack *ct, + const struct ct_cmd *cmd) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int err; - return mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid, cb, NULL); + nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, flags, family); + + if (ct) { + err = nfct_nlmsg_build(nlh, ct); + if (err < 0) + return err; + } + + return nfct_mnl_talk(sock, nlh, cb, cmd); } +#define UNKNOWN_STATS_NUM 4 + 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) + if (mnl_attr_type_valid(attr, CTA_STATS_MAX + UNKNOWN_STATS_NUM) < 0) return MNL_CB_OK; if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { @@ -1988,9 +2528,9 @@ static int nfct_stats_attr_cb(const struct nlattr *attr, void *data) static int nfct_stats_cb(const struct nlmsghdr *nlh, void *data) { - struct nlattr *tb[CTA_STATS_MAX+1] = {}; + struct nlattr *tb[CTA_STATS_MAX + UNKNOWN_STATS_NUM + 1] = {}; struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); - const char *attr2name[CTA_STATS_MAX+1] = { + const char *attr2name[CTA_STATS_MAX + UNKNOWN_STATS_NUM + 1] = { [CTA_STATS_SEARCHED] = "searched", [CTA_STATS_FOUND] = "found", [CTA_STATS_NEW] = "new", @@ -2004,6 +2544,16 @@ static int nfct_stats_cb(const struct nlmsghdr *nlh, void *data) [CTA_STATS_EARLY_DROP] = "early_drop", [CTA_STATS_ERROR] = "error", [CTA_STATS_SEARCH_RESTART] = "search_restart", + [CTA_STATS_CLASH_RESOLVE] = "clash_resolve", + [CTA_STATS_CHAIN_TOOLONG] = "chaintoolong", + + /* leave at end. Allows to show counters supported + * by newer kernel with older conntrack-tools release. + */ + [CTA_STATS_MAX + 1] = "unknown1", + [CTA_STATS_MAX + 2] = "unknown2", + [CTA_STATS_MAX + 3] = "unknown3", + [CTA_STATS_MAX + 4] = "unknown4", }; int i; @@ -2011,7 +2561,7 @@ static int nfct_stats_cb(const struct nlmsghdr *nlh, void *data) printf("cpu=%-4u\t", ntohs(nfg->res_id)); - for (i=0; i<CTA_STATS_MAX+1; i++) { + for (i=0; i <= CTA_STATS_MAX + UNKNOWN_STATS_NUM; i++) { if (tb[i]) { printf("%s=%u ", attr2name[i], ntohl(mnl_attr_get_u32(tb[i]))); @@ -2096,6 +2646,9 @@ static int nfct_global_stats_cb(const struct nlmsghdr *nlh, void *data) static int mnl_nfct_dump_cb(const struct nlmsghdr *nlh, void *data) { + unsigned int op_type = NFCT_O_DEFAULT; + unsigned int op_flags = 0; + struct ct_cmd *cmd = data; struct nf_conntrack *ct; char buf[4096]; @@ -2105,7 +2658,34 @@ static int mnl_nfct_dump_cb(const struct nlmsghdr *nlh, void *data) nfct_nlmsg_parse(nlh, ct); - nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, NFCT_O_DEFAULT, 0); + if (nfct_filter(cmd, ct, cur_tmpl)) { + nfct_destroy(ct); + return MNL_CB_OK; + } + + if (output_mask & _O_SAVE) { + ct_save_snprintf(buf, sizeof(buf), ct, labelmap, NFCT_T_NEW); + goto done; + } + + 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); +done: printf("%s\n", buf); nfct_destroy(ct); @@ -2127,7 +2707,8 @@ static void labelmap_init(void) } static void -nfct_network_attr_prepare(const int family, enum ct_direction dir) +nfct_network_attr_prepare(const int family, enum ct_direction dir, + const struct ct_tmpl *tmpl) { const union ct_address *address, *netmask; enum nf_conntrack_attr attr; @@ -2136,8 +2717,8 @@ nfct_network_attr_prepare(const int family, enum ct_direction dir) attr = famdir2attr[family == AF_INET6][dir]; - address = nfct_get_attr(tmpl.ct, attr); - netmask = nfct_get_attr(tmpl.mask, attr); + address = nfct_get_attr(tmpl->ct, attr); + netmask = nfct_get_attr(tmpl->mask, attr); switch(family) { case AF_INET: @@ -2152,25 +2733,29 @@ nfct_network_attr_prepare(const int family, enum ct_direction dir) memcpy(&net->netmask, netmask, sizeof(union ct_address)); /* avoid exact source matching */ - nfct_attr_unset(tmpl.ct, attr); + nfct_attr_unset(tmpl->ct, attr); } -static void -nfct_filter_init(const int family) +static void nfct_filter_init(const struct ct_cmd *cmd) { + const struct ct_tmpl *tmpl = &cmd->tmpl; + int family = cmd->family; + filter_family = family; - if (options & CT_OPT_MASK_SRC) { - if (!(options & CT_OPT_ORIG_SRC)) + if (cmd->options & CT_OPT_MASK_SRC) { + assert(family != AF_UNSPEC); + if (!(cmd->options & CT_OPT_ORIG_SRC)) exit_error(PARAMETER_PROBLEM, "Can't use --mask-src without --src"); - nfct_network_attr_prepare(family, DIR_SRC); + nfct_network_attr_prepare(family, DIR_SRC, tmpl); } - if (options & CT_OPT_MASK_DST) { - if (!(options & CT_OPT_ORIG_DST)) + if (cmd->options & CT_OPT_MASK_DST) { + assert(family != AF_UNSPEC); + if (!(cmd->options & CT_OPT_ORIG_DST)) exit_error(PARAMETER_PROBLEM, "Can't use --mask-dst without --dst"); - nfct_network_attr_prepare(family, DIR_DST); + nfct_network_attr_prepare(family, DIR_DST, tmpl); } } @@ -2238,9 +2823,9 @@ nfct_set_addr_only(const int opt, struct nf_conntrack *ct, union ct_address *ad, static void nfct_set_addr_opt(const int opt, struct nf_conntrack *ct, union ct_address *ad, - const int l3protonum) + const int l3protonum, unsigned int *options) { - options |= opt2type[opt]; + *options |= opt2type[opt]; nfct_set_addr_only(opt, ct, ad, l3protonum); nfct_set_attr_u8(ct, opt2attr[opt], l3protonum); } @@ -2249,7 +2834,8 @@ static void nfct_parse_addr_from_opt(const int opt, const char *arg, struct nf_conntrack *ct, struct nf_conntrack *ctmask, - union ct_address *ad, int *family) + union ct_address *ad, int *family, + unsigned int *options) { int mask, maskopt; @@ -2269,7 +2855,7 @@ nfct_parse_addr_from_opt(const int opt, const char *arg, "Invalid netmask"); } - nfct_set_addr_opt(opt, ct, ad, l3protonum); + nfct_set_addr_opt(opt, ct, ad, l3protonum, options); /* bail if we don't have a netmask to set*/ if (mask == -1 || !maskopt || ctmask == NULL) @@ -2288,7 +2874,7 @@ nfct_parse_addr_from_opt(const int opt, const char *arg, break; } - nfct_set_addr_opt(maskopt, ctmask, ad, l3protonum); + nfct_set_addr_opt(maskopt, ctmask, ad, l3protonum, options); } static void @@ -2309,33 +2895,41 @@ nfct_set_nat_details(const int opt, struct nf_conntrack *ct, } -int main(int argc, char *argv[]) +static int print_stats(const struct ct_cmd *cmd) +{ + if (cmd->command && exit_msg[cmd->cmd][0]) { + fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION); + fprintf(stderr, exit_msg[cmd->cmd], counter); + if (counter == 0 && + !(cmd->command & (CT_LIST | EXP_LIST))) + return -1; + } + + return 0; +} + +static void do_parse(struct ct_cmd *ct_cmd, int argc, char *argv[]) { - int c, cmd; unsigned int type = 0, event_mask = 0, l4flags = 0, status = 0; - int res = 0, partial; + int protonum = 0, family = AF_UNSPEC; size_t socketbuffersize = 0; - int family = AF_UNSPEC; - int protonum = 0; - union ct_address ad; unsigned int command = 0; + unsigned int options = 0; + struct ct_tmpl *tmpl; + int res = 0, partial; + union ct_address ad; + int c, cmd; /* we release these objects in the exit_error() path. */ - if (!alloc_tmpl_objects()) + if (!alloc_tmpl_objects(&ct_cmd->tmpl)) 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(); + tmpl = &ct_cmd->tmpl; /* disable explicit missing arguments error output from getopt_long */ opterr = 0; + /* reset optind, for the case do_parse is called multiple times */ + optind = 0; while ((c = getopt_long(argc, argv, getopt_str, opts, NULL)) != -1) { switch(c) { @@ -2360,45 +2954,44 @@ int main(int argc, char *argv[]) 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': + case 'A': 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 + } + if (cmd2type[c][type]) + add_command(&command, cmd2type[c][type]); + else { exit_error(PARAMETER_PROBLEM, - "Can't update expectations"); + "Can't do --%s on %s", + get_long_opt(c), + type == CT_TABLE_CONNTRACK ? + "conntrack" : "expectations"); + } break; /* options */ case 's': case 'd': case 'r': case 'q': - nfct_parse_addr_from_opt(c, optarg, tmpl.ct, - tmpl.mask, &ad, &family); + nfct_parse_addr_from_opt(c, optarg, tmpl->ct, + tmpl->mask, &ad, &family, + &options); break; case '[': case ']': - nfct_parse_addr_from_opt(c, optarg, tmpl.exptuple, - tmpl.mask, &ad, &family); + nfct_parse_addr_from_opt(c, optarg, tmpl->exptuple, + tmpl->mask, &ad, &family, + &options); break; case '{': case '}': - nfct_parse_addr_from_opt(c, optarg, tmpl.mask, - NULL, &ad, &family); + nfct_parse_addr_from_opt(c, optarg, tmpl->mask, + NULL, &ad, &family, &options); break; case 'p': options |= CT_OPT_PROTO; @@ -2412,18 +3005,25 @@ int main(int argc, char *argv[]) if (opts == NULL) exit_error(OTHER_PROBLEM, "out of memory"); - nfct_set_attr_u8(tmpl.ct, ATTR_L4PROTO, protonum); + 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, + 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); + parse_parameter_mask(optarg, &status, + &tmpl->filter_status_kernel.mask, + PARSE_STATUS); + nfct_set_attr_u32(tmpl->ct, ATTR_STATUS, status); + if (tmpl->filter_status_kernel.mask == 0) + tmpl->filter_status_kernel.mask = status; + + tmpl->filter_status_kernel.val = status; + tmpl->filter_status_kernel_set = true; break; case 'e': options |= CT_OPT_EVENT_MASK; @@ -2434,6 +3034,10 @@ int main(int argc, char *argv[]) parse_parameter(optarg, &output_mask, PARSE_OUTPUT); if (output_mask & _O_CL) labelmap_init(); + if ((output_mask & _O_SAVE) && + (output_mask & (_O_EXT |_O_TMS |_O_ID | _O_KTMS | _O_CL | _O_XML))) + exit_error(OTHER_PROBLEM, + "cannot combine save output with any other output type, use -o save only"); break; case 'z': options |= CT_OPT_ZERO; @@ -2452,43 +3056,46 @@ int main(int argc, char *argv[]) &nat_address, &port_str); nfct_parse_addr_from_opt(c, nat_address, - tmpl.ct, NULL, - &ad, &family); + tmpl->ct, NULL, + &ad, &family, + &options); if (c == 'j') { /* Set details on both src and dst * with any-nat */ - nfct_set_nat_details('g', tmpl.ct, &ad, + nfct_set_nat_details('g', tmpl->ct, &ad, port_str, family); - nfct_set_nat_details('n', tmpl.ct, &ad, + nfct_set_nat_details('n', tmpl->ct, &ad, port_str, family); } else { - nfct_set_nat_details(c, tmpl.ct, &ad, + nfct_set_nat_details(c, tmpl->ct, &ad, port_str, family); } + free(port_str); + free(nat_address); } break; case 'w': case '(': case ')': options |= opt2type[c]; - nfct_set_attr_u16(tmpl.ct, + 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, + nfct_set_attr_u32(tmpl->ct, opt2attr[c], strtoul(optarg, NULL, 0)); break; case 'm': options |= opt2type[c]; - parse_u32_mask(optarg, &tmpl.mark); - tmpl.filter_mark_kernel.val = tmpl.mark.value; - tmpl.filter_mark_kernel.mask = tmpl.mark.mask; - tmpl.filter_mark_kernel_set = true; + parse_u32_mask(optarg, &tmpl->mark); + tmpl->filter_mark_kernel.val = tmpl->mark.value; + tmpl->filter_mark_kernel.mask = tmpl->mark.mask; + tmpl->filter_mark_kernel_set = true; break; case 'l': case '<': @@ -2519,9 +3126,9 @@ int main(int argc, char *argv[]) /* join "-l foo -l bar" into single bitmask object */ if (c == 'l') { - merge_bitmasks(&tmpl.label, b); + merge_bitmasks(&tmpl->label, b); } else { - merge_bitmasks(&tmpl.label_modify, b); + merge_bitmasks(&tmpl->label_modify, b); } free(optarg2); @@ -2554,18 +3161,31 @@ int main(int argc, char *argv[]) "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)) + 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; + /* default family only for the following commands */ + if (family == AF_UNSPEC) { + switch (command) { + case CT_LIST: + case CT_UPDATE: + case CT_DELETE: + case CT_GET: + case CT_FLUSH: + case CT_EVENT: + break; + default: + family = AF_INET; + break; + } + } + /* we cannot check this combination with generic_opt_check. */ if (options & CT_OPT_ANY_NAT && @@ -2593,65 +3213,76 @@ int main(int argc, char *argv[]) } } if (!(command & CT_HELP) && h && h->final_check) - h->final_check(l4flags, cmd, tmpl.ct); + h->final_check(l4flags, cmd, tmpl->ct); - switch(command) { + free_options(); + + ct_cmd->command = command; + ct_cmd->cmd = cmd; + ct_cmd->options = options; + ct_cmd->family = family; + ct_cmd->type = type; + ct_cmd->protonum = protonum; + ct_cmd->event_mask = event_mask; + ct_cmd->socketbuffersize = socketbuffersize; +} + +static int do_command_ct(const char *progname, struct ct_cmd *cmd, + struct nfct_mnl_socket *sock) +{ + struct nfct_mnl_socket *modifier_sock = &_modifier_sock; + struct nfct_mnl_socket *event_sock = &_event_sock; struct nfct_filter_dump *filter_dump; + uint16_t nl_flags = 0; + int res = 0; + switch(cmd->command) { case CT_LIST: - if (type == CT_TABLE_DYING) { - if (nfct_mnl_socket_open(0) < 0) - exit_error(OTHER_PROBLEM, "Can't open handler"); - - res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, + if (cmd->type == CT_TABLE_DYING) { + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_DYING, - mnl_nfct_dump_cb, family); - - nfct_mnl_socket_close(); + mnl_nfct_dump_cb, cmd, NULL); break; - } else if (type == CT_TABLE_UNCONFIRMED) { - if (nfct_mnl_socket_open(0) < 0) - exit_error(OTHER_PROBLEM, "Can't open handler"); - - res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, + } else if (cmd->type == CT_TABLE_UNCONFIRMED) { + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_UNCONFIRMED, - mnl_nfct_dump_cb, family); - - nfct_mnl_socket_close(); + mnl_nfct_dump_cb, cmd, NULL); break; } - cth = nfct_open(CONNTRACK, 0); - if (!cth) - exit_error(OTHER_PROBLEM, "Can't open handler"); - - if (options & CT_COMPARISON && - options & CT_OPT_ZERO) + if (cmd->options & CT_COMPARISON && + cmd->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); + nfct_filter_init(cmd); filter_dump = nfct_filter_dump_create(); if (filter_dump == NULL) exit_error(OTHER_PROBLEM, "OOM"); - if (tmpl.filter_mark_kernel_set) { + if (cmd->tmpl.filter_mark_kernel_set) { nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK, - &tmpl.filter_mark_kernel); + &cmd->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); + cmd->family); + if (cmd->tmpl.filter_status_kernel_set) { + nfct_filter_dump_set_attr(filter_dump, + NFCT_FILTER_DUMP_STATUS, + &cmd->tmpl.filter_status_kernel); + } + if (cmd->options & CT_OPT_ZERO) { + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_GET_CTRZERO, + mnl_nfct_dump_cb, cmd, filter_dump); + } else { + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_GET, + mnl_nfct_dump_cb, cmd, filter_dump); + } nfct_filter_dump_destroy(filter_dump); @@ -2659,17 +3290,14 @@ int main(int argc, char *argv[]) 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); + res = nfexp_query(cth, NFCT_Q_DUMP, &cmd->family); nfct_close(cth); if (dump_xml_header_done == 0) { @@ -2679,127 +3307,112 @@ int main(int argc, char *argv[]) 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) + case CT_ADD: + if ((cmd->options & CT_OPT_ORIG) && !(cmd->options & CT_OPT_REPL)) + nfct_setobjopt(cmd->tmpl.ct, NFCT_SOPT_SETUP_REPLY); + else if (!(cmd->options & CT_OPT_ORIG) && (cmd->options & CT_OPT_REPL)) + nfct_setobjopt(cmd->tmpl.ct, NFCT_SOPT_SETUP_ORIGINAL); + + if (cmd->options & CT_OPT_MARK) + nfct_set_attr_u32(cmd->tmpl.ct, ATTR_MARK, cmd->tmpl.mark.value); + + if (cmd->options & CT_OPT_ADD_LABEL) + nfct_set_attr(cmd->tmpl.ct, ATTR_CONNLABELS, + xnfct_bitmask_clone(cmd->tmpl.label_modify)); + + if (cmd->command == CT_CREATE) + nl_flags = NLM_F_EXCL; + + res = nfct_mnl_request(sock, NFNL_SUBSYS_CTNETLINK, cmd->family, + IPCTNL_MSG_CT_NEW, + NLM_F_CREATE | NLM_F_ACK | nl_flags, + NULL, cmd->tmpl.ct, NULL); + if (res >= 0) 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); + nfexp_set_attr(cmd->tmpl.exp, ATTR_EXP_MASTER, cmd->tmpl.ct); + nfexp_set_attr(cmd->tmpl.exp, ATTR_EXP_EXPECTED, cmd->tmpl.exptuple); + nfexp_set_attr(cmd->tmpl.exp, ATTR_EXP_MASK, cmd->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); + res = nfexp_query(cth, NFCT_Q_CREATE, cmd->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) + if (nfct_mnl_socket_check_open(modifier_sock, 0) < 0) 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); + nfct_filter_init(cmd); + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_GET, mnl_nfct_update_cb, + cmd, NULL); break; - + case CT_DELETE: - cth = nfct_open(CONNTRACK, 0); - ith = nfct_open(CONNTRACK, 0); - if (!cth || !ith) + if (nfct_mnl_socket_check_open(modifier_sock, 0) < 0) exit_error(OTHER_PROBLEM, "Can't open handler"); - nfct_filter_init(family); - - nfct_callback_register(cth, NFCT_T_ALL, delete_cb, tmpl.ct); + nfct_filter_init(cmd); filter_dump = nfct_filter_dump_create(); if (filter_dump == NULL) exit_error(OTHER_PROBLEM, "OOM"); - if (tmpl.filter_mark_kernel_set) { + if (cmd->tmpl.filter_mark_kernel_set) { nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK, - &tmpl.filter_mark_kernel); + &cmd->tmpl.filter_mark_kernel); } nfct_filter_dump_set_attr_u8(filter_dump, NFCT_FILTER_DUMP_L3NUM, - family); + cmd->family); - res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump); + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_GET, mnl_nfct_delete_cb, + cmd, 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); + nfexp_set_attr(cmd->tmpl.exp, ATTR_EXP_EXPECTED, cmd->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); + res = nfexp_query(cth, NFCT_Q_DESTROY, cmd->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); + res = nfct_mnl_request(sock, NFNL_SUBSYS_CTNETLINK, cmd->family, + IPCTNL_MSG_CT_GET, 0, + mnl_nfct_dump_cb, cmd->tmpl.ct, cmd); break; case EXP_GET: - nfexp_set_attr(tmpl.exp, ATTR_EXP_MASTER, tmpl.ct); + nfexp_set_attr(cmd->tmpl.exp, ATTR_EXP_MASTER, cmd->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); + res = nfexp_query(cth, NFCT_Q_GET, cmd->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); + res = nfct_mnl_request(sock, NFNL_SUBSYS_CTNETLINK, cmd->family, + IPCTNL_MSG_CT_DELETE, NLM_F_ACK, NULL, NULL, NULL); + fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION); fprintf(stderr,"connection tracking table has been emptied.\n"); break; @@ -2808,26 +3421,27 @@ int main(int argc, char *argv[]) cth = nfct_open(EXPECT, 0); if (!cth) exit_error(OTHER_PROBLEM, "Can't open handler"); - res = nfexp_query(cth, NFCT_Q_FLUSH, &family); + res = nfexp_query(cth, NFCT_Q_FLUSH, &cmd->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) { + if (cmd->options & CT_OPT_EVENT_MASK) { unsigned int nl_events = 0; - if (event_mask & CT_EVENT_F_NEW) + if (cmd->event_mask & CT_EVENT_F_NEW) nl_events |= NF_NETLINK_CONNTRACK_NEW; - if (event_mask & CT_EVENT_F_UPD) + if (cmd->event_mask & CT_EVENT_F_UPD) nl_events |= NF_NETLINK_CONNTRACK_UPDATE; - if (event_mask & CT_EVENT_F_DEL) + if (cmd->event_mask & CT_EVENT_F_DEL) nl_events |= NF_NETLINK_CONNTRACK_DESTROY; - res = nfct_mnl_socket_open(nl_events); + res = nfct_mnl_socket_open(event_sock, nl_events); } else { - res = nfct_mnl_socket_open(NF_NETLINK_CONNTRACK_NEW | + res = nfct_mnl_socket_open(event_sock, + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); } @@ -2835,27 +3449,29 @@ int main(int argc, char *argv[]) if (res < 0) exit_error(OTHER_PROBLEM, "Can't open netlink socket"); - if (options & CT_OPT_BUFFERSIZE) { + if (cmd->options & CT_OPT_BUFFERSIZE) { + size_t socketbuffersize = cmd->socketbuffersize; + socklen_t socklen = sizeof(socketbuffersize); - res = setsockopt(mnl_socket_get_fd(sock.mnl), + res = setsockopt(mnl_socket_get_fd(sock->mnl), SOL_SOCKET, SO_RCVBUFFORCE, &socketbuffersize, sizeof(socketbuffersize)); if (res < 0) { - setsockopt(mnl_socket_get_fd(sock.mnl), + setsockopt(mnl_socket_get_fd(sock->mnl), SOL_SOCKET, SO_RCVBUF, &socketbuffersize, - socketbuffersize); + sizeof(socketbuffersize)); } - getsockopt(mnl_socket_get_fd(sock.mnl), SOL_SOCKET, + getsockopt(mnl_socket_get_fd(sock->mnl), SOL_SOCKET, SO_RCVBUF, &socketbuffersize, &socklen); fprintf(stderr, "NOTICE: Netlink socket buffer size " "has been set to %zu bytes.\n", socketbuffersize); } - nfct_filter_init(family); + nfct_filter_init(cmd); signal(SIGINT, event_sighandler); signal(SIGTERM, event_sighandler); @@ -2863,7 +3479,7 @@ int main(int argc, char *argv[]) while (1) { char buf[MNL_SOCKET_BUFFER_SIZE]; - res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); + res = mnl_socket_recvfrom(event_sock->mnl, buf, sizeof(buf)); if (res < 0) { if (errno == ENOBUFS) { fprintf(stderr, @@ -2880,20 +3496,20 @@ int main(int argc, char *argv[]) strerror(errno)); break; } - res = mnl_cb_run(buf, res, 0, 0, event_cb, tmpl.ct); + mnl_cb_run(buf, res, 0, 0, event_cb, cmd); } - mnl_socket_close(sock.mnl); + mnl_socket_close(event_sock->mnl); break; case EXP_EVENT: - if (options & CT_OPT_EVENT_MASK) { + if (cmd->options & CT_OPT_EVENT_MASK) { unsigned int nl_events = 0; - if (event_mask & CT_EVENT_F_NEW) + if (cmd->event_mask & CT_EVENT_F_NEW) nl_events |= NF_NETLINK_CONNTRACK_EXP_NEW; - if (event_mask & CT_EVENT_F_UPD) + if (cmd->event_mask & CT_EVENT_F_UPD) nl_events |= NF_NETLINK_CONNTRACK_EXP_UPDATE; - if (event_mask & CT_EVENT_F_DEL) + if (cmd->event_mask & CT_EVENT_F_DEL) nl_events |= NF_NETLINK_CONNTRACK_EXP_DESTROY; cth = nfct_open(CONNTRACK, nl_events); @@ -2916,20 +3532,14 @@ int main(int argc, char *argv[]) /* If we fail with netlink, fall back to /proc to ensure * backward compatibility. */ - if (nfct_mnl_socket_open(0) < 0) - goto try_proc_count; - - res = nfct_mnl_get(NFNL_SUBSYS_CTNETLINK, - IPCTNL_MSG_CT_GET_STATS, - nfct_global_stats_cb, AF_UNSPEC); - - nfct_mnl_socket_close(); + res = nfct_mnl_request(sock, NFNL_SUBSYS_CTNETLINK, AF_UNSPEC, + IPCTNL_MSG_CT_GET_STATS, 0, + nfct_global_stats_cb, NULL, NULL); /* 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; @@ -2953,7 +3563,7 @@ try_proc_count: 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); + res = nfexp_query(cth, NFCT_Q_DUMP, &cmd->family); nfct_close(cth); printf("%d\n", counter); break; @@ -2961,14 +3571,9 @@ try_proc_count: /* If we fail with netlink, fall back to /proc to ensure * backward compatibility. */ - if (nfct_mnl_socket_open(0) < 0) - goto try_proc; - - res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK, + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_STATS_CPU, - nfct_stats_cb, AF_UNSPEC); - - nfct_mnl_socket_close(); + nfct_stats_cb, NULL, NULL); /* don't look at /proc, we got the information via ctnetlink */ if (res >= 0) @@ -2980,14 +3585,9 @@ try_proc_count: /* If we fail with netlink, fall back to /proc to ensure * backward compatibility. */ - if (nfct_mnl_socket_open(0) < 0) - goto try_proc; - - res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK_EXP, + res = nfct_mnl_dump(sock, NFNL_SUBSYS_CTNETLINK_EXP, IPCTNL_MSG_EXP_GET_STATS_CPU, - nfexp_stats_cb, AF_UNSPEC); - - nfct_mnl_socket_close(); + nfexp_stats_cb, NULL, NULL); /* don't look at /proc, we got the information via ctnetlink */ if (res >= 0) @@ -3000,30 +3600,263 @@ try_proc: 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); + usage(progname); + if (cmd->options & CT_OPT_PROTO) + extension_help(h, cmd->protonum); break; default: - usage(argv[0]); + usage(progname); break; } if (res < 0) exit_error(OTHER_PROBLEM, "Operation failed: %s", - err2str(errno, command)); + err2str(errno, cmd->command)); - free_tmpl_objects(); - free_options(); + free_tmpl_objects(&cmd->tmpl); 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; +} + +/* Taken from iptables/xshared.c: + * - add_argv() + * - add_param_to_argv() + * - free_argv() + */ +#define MAX_ARGC 255 +struct argv_store { + int argc; + char *argv[MAX_ARGC]; + int argvattr[MAX_ARGC]; +}; + +/* function adding one argument to store, updating argc + * returns if argument added, does not return otherwise */ +static void add_argv(struct argv_store *store, const char *what, int quoted) +{ + if (store->argc + 1 >= MAX_ARGC) + exit_error(PARAMETER_PROBLEM, "too many arguments"); + if (!what) + exit_error(PARAMETER_PROBLEM, "invalid NULL argument"); + + store->argv[store->argc] = strdup(what); + store->argvattr[store->argc] = quoted; + store->argv[++store->argc] = NULL; +} + +static void free_argv(struct argv_store *store) +{ + while (store->argc) { + store->argc--; + free(store->argv[store->argc]); + store->argvattr[store->argc] = 0; } +} - return EXIT_SUCCESS; +struct ct_param_buf { + char buffer[1024]; + int len; +}; + +static void add_param(struct ct_param_buf *param, const char *curchar) +{ + param->buffer[param->len++] = *curchar; + if (param->len >= (int)sizeof(param->buffer)) + exit_error(PARAMETER_PROBLEM, "Parameter too long!"); +} + +static void add_param_to_argv(struct argv_store *store, char *parsestart) +{ + int quote_open = 0, escaped = 0, quoted = 0; + struct ct_param_buf param = {}; + char *curchar; + + /* After fighting with strtok enough, here's now + * a 'real' parser. According to Rusty I'm now no + * longer a real hacker, but I can live with that */ + + for (curchar = parsestart; *curchar; curchar++) { + if (quote_open) { + if (escaped) { + add_param(¶m, curchar); + escaped = 0; + continue; + } else if (*curchar == '\\') { + escaped = 1; + continue; + } else if (*curchar == '"') { + quote_open = 0; + } else { + add_param(¶m, curchar); + continue; + } + } else { + if (*curchar == '"') { + quote_open = 1; + quoted = 1; + continue; + } + } + + switch (*curchar) { + case '"': + break; + case ' ': + case '\t': + case '\n': + if (!param.len) { + /* two spaces? */ + continue; + } + break; + default: + /* regular character, copy to buffer */ + add_param(¶m, curchar); + continue; + } + + param.buffer[param.len] = '\0'; + add_argv(store, param.buffer, quoted); + param.len = 0; + quoted = 0; + } + if (param.len) { + param.buffer[param.len] = '\0'; + add_argv(store, param.buffer, 0); + } +} + +static void ct_file_parse_line(struct list_head *cmd_list, + const char *progname, char *buffer) +{ + struct argv_store store = {}; + struct ct_cmd *ct_cmd; + + /* skip prepended tabs and spaces */ + for (; *buffer == ' ' || *buffer == '\t'; buffer++); + + if (buffer[0] == '\n' || + buffer[0] == '#') + return; + + add_argv(&store, progname, false); + add_param_to_argv(&store, buffer); + + ct_cmd = calloc(1, sizeof(*ct_cmd)); + if (!ct_cmd) + exit_error(OTHER_PROBLEM, "OOM"); + + do_parse(ct_cmd, store.argc, store.argv); + free_argv(&store); + + list_add_tail(&ct_cmd->list, cmd_list); +} + +static void ct_parse_file(struct list_head *cmd_list, const char *progname, + const char *file_name) +{ + char buffer[10240] = {}; + FILE *file; + + if (!strcmp(file_name, "-")) + file_name = "/dev/stdin"; + + file = fopen(file_name, "r"); + if (!file) + exit_error(PARAMETER_PROBLEM, + "Failed to open file %s for reading", file_name); + + while (fgets(buffer, sizeof(buffer), file)) + ct_file_parse_line(cmd_list, progname, buffer); + + fclose(file); +} + +static struct { + uint32_t command; + const char *text; +} ct_unsupp_cmd_parse_file[] = { + { CT_LIST, "-L" }, + { CT_GET, "-G" }, + { CT_EVENT, "-E" }, + { CT_VERSION, "-V" }, + { CT_HELP, "-h" }, + { EXP_LIST, "-L expect" }, + { EXP_CREATE, "-C expect" }, + { EXP_DELETE, "-D expect" }, + { EXP_GET, "-G expect" }, + { EXP_FLUSH, "-F expect" }, + { EXP_EVENT, "-E expect" }, + { CT_COUNT, "-C" }, + { EXP_COUNT, "-C expect" }, + { CT_STATS, "-S" }, + { EXP_STATS, "-S expect" }, + { 0, NULL }, +}; + +static const char *ct_unsupp_cmd_file(const struct ct_cmd *cmd) +{ + int i; + + for (i = 0; ct_unsupp_cmd_parse_file[i].text; i++) { + if (cmd->command == ct_unsupp_cmd_parse_file[i].command) + return ct_unsupp_cmd_parse_file[i].text; + } + + return "unknown"; +} + +int main(int argc, char *argv[]) +{ + struct nfct_mnl_socket *modifier_sock = &_modifier_sock; + struct nfct_mnl_socket *sock = &_sock; + struct ct_cmd *cmd, *next; + LIST_HEAD(cmd_list); + int res = 0; + + register_tcp(); + register_udp(); + register_udplite(); + register_sctp(); + register_dccp(); + register_icmp(); + register_icmpv6(); + register_gre(); + register_unknown(); + + if (nfct_mnl_socket_open(sock, 0) < 0) + exit_error(OTHER_PROBLEM, "Can't open handler"); + + if (argc > 2 && + (!strcmp(argv[1], "-R") || !strcmp(argv[1], "--load-file"))) { + ct_parse_file(&cmd_list, argv[0], argv[2]); + + list_for_each_entry(cmd, &cmd_list, list) { + if (!(cmd->command & + (CT_CREATE | CT_ADD | CT_UPDATE | CT_DELETE | CT_FLUSH))) + exit_error(PARAMETER_PROBLEM, + "Cannot use command `%s' with --load-file", + ct_unsupp_cmd_file(cmd)); + } + list_for_each_entry_safe(cmd, next, &cmd_list, list) { + res |= do_command_ct(argv[0], cmd, sock); + list_del(&cmd->list); + free(cmd); + } + } else { + cmd = calloc(1, sizeof(*cmd)); + if (!cmd) + exit_error(OTHER_PROBLEM, "OOM"); + + do_parse(cmd, argc, argv); + res = do_command_ct(argv[0], cmd, sock); + res |= print_stats(cmd); + free(cmd); + } + nfct_mnl_socket_close(sock); + nfct_mnl_socket_check_close(modifier_sock); + + return res < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } |