From d44489648c1a56d543a84bbebe455227bb25cf34 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 28 May 2012 14:06:48 +0200 Subject: conntrackd: fix compilation in src/parse.c Making all in src make[1]: Entering directory `/home/oden/RPM/BUILD/conntrack-tools-1.2.0/src' CC parse.o parse.c: In function 'msg2ct': parse.c:258:34: error: 'NULL' undeclared (first use in this function) parse.c:258:34: note: each undeclared identifier is reported only once for each function it appears in parse.c: In function 'msg2exp': parse.c:438:16: error: 'NULL' undeclared (first use in this function) Reported-by: Oden Eriksson Signed-off-by: Pablo Neira Ayuso --- src/parse.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/parse.c b/src/parse.c index 732bc44..1b83f81 100644 --- a/src/parse.c +++ b/src/parse.c @@ -19,6 +19,7 @@ #include "network.h" +#include #include #ifndef ssizeof -- cgit v1.2.3 From 8648ae6d08bb84030c2c3519454532f6e04e31d9 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 5 Jul 2012 00:17:48 +0200 Subject: conntrackd: add bugtrap notice in case of flush while commit in progress Flushing the external cache, ie. conntrackd -f, while commit is in progress is not allowed anymore, ie. conntrackd -c. Note that conntrackd -c is synchronous. Thus, it returns control to the caller once the commit has finished. Signed-off-by: Pablo Neira Ayuso --- src/sync-mode.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/sync-mode.c b/src/sync-mode.c index 10fdb9e..6f8eb04 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -608,6 +608,12 @@ static int local_handler_sync(int fd, int type, void *data) } break; case CT_FLUSH_CACHE: + /* if we're still committing, abort this command */ + if (STATE_SYNC(commit).clientfd != -1) { + dlog(LOG_ERR, "ignoring flush command, " + "commit still in progress"); + break; + } /* inmediate flush, remove pending flush scheduled if any */ del_alarm(&STATE_SYNC(reset_cache_alarm)); dlog(LOG_NOTICE, "flushing caches"); @@ -621,6 +627,12 @@ static int local_handler_sync(int fd, int type, void *data) STATE(mode)->internal->ct.flush(); break; case CT_FLUSH_EXT_CACHE: + /* if we're still committing, abort this command */ + if (STATE_SYNC(commit).clientfd != -1) { + dlog(LOG_ERR, "ignoring flush command, " + "commit still in progress"); + break; + } dlog(LOG_NOTICE, "flushing external cache"); STATE_SYNC(external)->ct.flush(); break; @@ -687,6 +699,12 @@ static int local_handler_sync(int fd, int type, void *data) local_commit(fd); break; case ALL_FLUSH_CACHE: + /* if we're still committing, abort this command */ + if (STATE_SYNC(commit).clientfd != -1) { + dlog(LOG_ERR, "ignoring flush command, " + "commit still in progress"); + break; + } dlog(LOG_NOTICE, "flushing caches"); STATE(mode)->internal->ct.flush(); STATE_SYNC(external)->ct.flush(); -- cgit v1.2.3 From 7eb63b5872f07903d952aa5cfd6ad0e7647a066a Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 5 Jul 2012 00:42:38 +0200 Subject: conntrackd: fix commit operation, needs to be synchronous While adding the expectation support for conntrackd, I accidentally broke synchrony in 'conntrackd -c' command. Basically, conntrackd -c should not return control to the shell until the cache has been committed. Signed-off-by: Pablo Neira Ayuso --- src/sync-mode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/sync-mode.c b/src/sync-mode.c index 6f8eb04..7fb3eba 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -696,7 +696,7 @@ static int local_handler_sync(int fd, int type, void *data) dlog(LOG_NOTICE, "committing expectation cache"); STATE_SYNC(commit).rq[0].cb = STATE_SYNC(external)->exp.commit; STATE_SYNC(commit).rq[1].cb = NULL; - local_commit(fd); + ret = local_commit(fd); break; case ALL_FLUSH_CACHE: /* if we're still committing, abort this command */ @@ -722,7 +722,7 @@ static int local_handler_sync(int fd, int type, void *data) } else { STATE_SYNC(commit).rq[1].cb = NULL; } - local_commit(fd); + ret = local_commit(fd); break; case EXP_DUMP_INT_XML: if (fork_process_new(CTD_PROC_ANY, 0, NULL, NULL) == 0) { -- cgit v1.2.3 From 3cd88a6d9c66360dad983578259ab92ba083fca8 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 30 Jul 2012 02:22:58 +0200 Subject: conntrackd: implement selective flushing for `-t' and `-F' commands This patch changes the current behaviour of `-t' and `-F' commands, that results in flushing the kernel conntrack table. With this patch, the entries that match the Filter clauses in conntrackd.conf are ignored. This fixes the situation in which some local ssh connection to the firewall is lost during the failover (since `-t' is invoked from the primary-backup.sh script). Note that the Filter clause tells what entries have to be ignored, ie. the entries that do not need to be replicated. It makes sense not to flush entries that are not replicated (usually traffic to the local firewall). Reported-by: Gaurav Sinha Signed-off-by: Pablo Neira Ayuso --- include/netlink.h | 2 +- src/internal_bypass.c | 2 +- src/netlink.c | 37 +++++++++++++++++++++++++++++++++++-- src/run.c | 2 +- src/sync-mode.c | 2 +- 5 files changed, 39 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/include/netlink.h b/include/netlink.h index 3bde30c..9a33083 100644 --- a/include/netlink.h +++ b/include/netlink.h @@ -12,7 +12,7 @@ struct nlif_handle *nl_init_interface_handler(void); int nl_send_resync(struct nfct_handle *h); void nl_resize_socket_buffer(struct nfct_handle *h); int nl_dump_conntrack_table(struct nfct_handle *h); -int nl_flush_conntrack_table(struct nfct_handle *h); +int nl_flush_conntrack_table_selective(void); int nl_get_conntrack(struct nfct_handle *h, const struct nf_conntrack *ct); int nl_create_conntrack(struct nfct_handle *h, const struct nf_conntrack *ct, int timeout); int nl_update_conntrack(struct nfct_handle *h, const struct nf_conntrack *ct, int timeout); diff --git a/src/internal_bypass.c b/src/internal_bypass.c index 5c83c21..1194339 100644 --- a/src/internal_bypass.c +++ b/src/internal_bypass.c @@ -67,7 +67,7 @@ static void internal_bypass_ct_dump(int fd, int type) static void internal_bypass_ct_flush(void) { - nl_flush_conntrack_table(STATE(flush)); + nl_flush_conntrack_table_selective(); } struct { diff --git a/src/netlink.c b/src/netlink.c index fe979e3..bd38d99 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -151,9 +151,42 @@ int nl_dump_conntrack_table(struct nfct_handle *h) return nfct_query(h, NFCT_Q_DUMP, &CONFIG(family)); } -int nl_flush_conntrack_table(struct nfct_handle *h) +static int +nl_flush_selective_cb(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, void *data) { - return nfct_query(h, NFCT_Q_FLUSH, &CONFIG(family)); + /* don't delete this conntrack, it's in the ignore filter */ + if (ct_filter_conntrack(ct, 1)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + nl_destroy_conntrack(STATE(flush), ct); + break; + default: + STATE(stats).nl_dump_unknown_type++; + break; + } + return NFCT_CB_CONTINUE; +} + +int nl_flush_conntrack_table_selective(void) +{ + struct nfct_handle *h; + int ret; + + h = nfct_open(CONNTRACK, 0); + if (h == NULL) { + dlog(LOG_ERR, "cannot open handle"); + return -1; + } + nfct_callback_register(h, NFCT_T_ALL, nl_flush_selective_cb, NULL); + + ret = nfct_query(h, NFCT_Q_DUMP, &CONFIG(family)); + + nfct_close(h); + + return ret; } int nl_send_resync(struct nfct_handle *h) diff --git a/src/run.c b/src/run.c index 26c1783..421ff41 100644 --- a/src/run.c +++ b/src/run.c @@ -196,7 +196,7 @@ static void local_flush_master(void) * meanwhile the parent process handles events. */ if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, NULL, NULL) == 0) { - nl_flush_conntrack_table(STATE(flush)); + nl_flush_conntrack_table_selective(); exit(EXIT_SUCCESS); } } diff --git a/src/sync-mode.c b/src/sync-mode.c index 7fb3eba..17f866a 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -304,7 +304,7 @@ static void do_reset_cache_alarm(struct alarm_block *a, void *data) * meanwhile the parent process handles events. */ if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, NULL, NULL) == 0) { - nl_flush_conntrack_table(STATE(flush)); + nl_flush_conntrack_table_selective(); exit(EXIT_SUCCESS); } /* this is not required if events don't get lost */ -- cgit v1.2.3 From 9aecd75541aab51f5add2884df5105bda87fc05a Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 27 Jun 2012 16:25:05 +0200 Subject: conntrack: add support for stats dumping via ctnetlink Since Linux kernel >= 3.6.x, we can dump the conntrack statistics via ctnetlink instead of using the /proc interface: conntrack -S cpu=0 searched=9177 found=387086 new=250451 invalid=1 ignore=4 delete=254093 delete_list=5467 insert=1825 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0 cpu=1 searched=390 found=37493 new=1531 invalid=0 ignore=0 delete=345 delete_list=345 insert=1531 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0 cpu=2 searched=333 found=68061 new=1895 invalid=0 ignore=1 delete=607 delete_list=607 insert=1896 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0 cpu=3 searched=71 found=13364 new=1254 invalid=0 ignore=0 delete=75 delete_list=75 insert=1254 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0 conntrack -S exp cpu=0 expect_new=9177 expect_create=387284 expect_delete=251141 cpu=1 expect_new=390 expect_create=37496 expect_delete=1531 cpu=2 expect_new=333 expect_create=68117 expect_delete=1895 cpu=3 expect_new=71 expect_create=13366 expect_delete=1255 Note that the output is not backward-compatible, but we fail back to previous output in case that ctnetlink stats dumping is not available. Signed-off-by: Pablo Neira Ayuso --- include/conntrack.h | 2 +- src/Makefile.am | 2 +- src/conntrack.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 215 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/include/conntrack.h b/include/conntrack.h index 3882de7..fd6126b 100644 --- a/include/conntrack.h +++ b/include/conntrack.h @@ -9,7 +9,7 @@ #include -#define NUMBER_OF_CMD 18 +#define NUMBER_OF_CMD 19 #define NUMBER_OF_OPT 24 struct ctproto_handler { diff --git a/src/Makefile.am b/src/Makefile.am index 5dbdef3..93c035c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,7 +7,7 @@ CLEANFILES = read_config_yy.c read_config_lex.c sbin_PROGRAMS = conntrack conntrackd nfct conntrack_SOURCES = conntrack.c -conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} +conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} ${LIBMNL_LIBS} nfct_SOURCES = nfct.c \ nfct-extensions/timeout.c diff --git a/src/conntrack.c b/src/conntrack.c index 0920bc5..6d0f301 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -34,6 +34,8 @@ * Ported to the new libnetfilter_conntrack API * 2008-04-13 Pablo Neira Ayuso : * Way more flexible update and delete operations + * + * Part of this code has been funded by Sophos Astaro */ #include "conntrack.h" @@ -57,6 +59,7 @@ #include #include #include +#include #include struct u32_mask { @@ -157,8 +160,11 @@ enum ct_command { EXP_COUNT_BIT = 16, EXP_COUNT = (1 << EXP_COUNT_BIT), - X_STATS_BIT = 17, - X_STATS = (1 << X_STATS_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 */ @@ -352,7 +358,8 @@ static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = /*EXP_EVENT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0}, /*CT_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, /*EXP_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, -/*X_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}, +/*CT_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +/*EXP_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, }; static const int cmd2type[][2] = { @@ -365,6 +372,7 @@ static const int cmd2type[][2] = { ['V'] = { CT_VERSION, CT_VERSION }, ['h'] = { CT_HELP, CT_HELP }, ['C'] = { CT_COUNT, EXP_COUNT }, + ['S'] = { CT_STATS, EXP_STATS }, }; static const int opt2type[] = { @@ -1462,6 +1470,171 @@ out_err: return ret; } +static struct nfct_mnl_socket { + struct mnl_socket *mnl; + uint32_t portid; +} sock; + +static int nfct_mnl_socket_open(void) +{ + sock.mnl = mnl_socket_open(NETLINK_NETFILTER); + if (sock.mnl == NULL) { + perror("mnl_socket_open"); + return -1; + } + if (mnl_socket_bind(sock.mnl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + return -1; + } + sock.portid = mnl_socket_get_portid(sock.mnl); + + return 0; +} + +static struct nlmsghdr * +nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type) +{ + struct nlmsghdr *nlh; + struct nfgenmsg *nfh; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (subsys << 8) | type; + nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP; + nlh->nlmsg_seq = time(NULL); + + nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); + nfh->nfgen_family = AF_INET; + nfh->version = NFNETLINK_V0; + nfh->res_id = 0; + + return nlh; +} + +static void nfct_mnl_socket_close(void) +{ + mnl_socket_close(sock.mnl); +} + +static int +nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int res; + + nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type); + + res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len); + if (res < 0) + return res; + + res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); + while (res > 0) { + res = mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid, + cb, NULL); + if (res <= MNL_CB_STOP) + break; + + res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); + } + + return res; +} + +static int nfct_stats_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTA_STATS_MAX) < 0) + return MNL_CB_OK; + + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { + perror("mnl_attr_validate"); + return MNL_CB_ERROR; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int nfct_stats_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[CTA_STATS_MAX+1] = {}; + struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); + const char *attr2name[CTA_STATS_MAX+1] = { + [CTA_STATS_SEARCHED] = "searched", + [CTA_STATS_FOUND] = "found", + [CTA_STATS_NEW] = "new", + [CTA_STATS_INVALID] = "invalid", + [CTA_STATS_IGNORE] = "ignore", + [CTA_STATS_DELETE] = "delete", + [CTA_STATS_DELETE_LIST] = "delete_list", + [CTA_STATS_INSERT] = "insert", + [CTA_STATS_INSERT_FAILED] = "insert_failed", + [CTA_STATS_DROP] = "drop", + [CTA_STATS_EARLY_DROP] = "early_drop", + [CTA_STATS_ERROR] = "error", + [CTA_STATS_SEARCH_RESTART] = "search_restart", + }; + int i; + + mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_attr_cb, tb); + + printf("cpu=%-4u\t", ntohs(nfg->res_id)); + + for (i=0; ires_id)); + + for (i=0; i= 0) + break; + + goto try_proc; + + case EXP_STATS: + /* If we fail with netlink, fall back to /proc to ensure + * backward compatibility. + */ + if (nfct_mnl_socket_open() < 0) + goto try_proc; + + res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK_EXP, + IPCTNL_MSG_EXP_GET_STATS_CPU, + nfexp_stats_cb); + + nfct_mnl_socket_close(); + + /* don't look at /proc, we got the information via ctnetlink */ + if (res >= 0) + break; +try_proc: if (display_proc_conntrack_stats() < 0) exit_error(OTHER_PROBLEM, "Can't open /proc interface"); break; -- cgit v1.2.3 From ec4c0ff33746fb2c663194f27dd41deee83f870f Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 27 Jun 2012 16:47:56 +0200 Subject: conntrack: -C uses ctnetlink instead of /proc/sys/net/netfilter/nf_conntrack_count Signed-off-by: Pablo Neira Ayuso --- src/conntrack.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/conntrack.c b/src/conntrack.c index 6d0f301..7451a80 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -1541,6 +1541,26 @@ nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb) return res; } +static int +nfct_mnl_get(uint16_t subsys, uint16_t type, mnl_cb_t cb) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int res; + + nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type); + + res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len); + if (res < 0) + return res; + + res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf)); + if (res < 0) + return res; + + return mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid, cb, NULL); +} + static int nfct_stats_attr_cb(const struct nlattr *attr, void *data) { const struct nlattr **tb = data; @@ -1635,6 +1655,37 @@ static int nfexp_stats_cb(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } +static int nfct_stats_global_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTA_STATS_GLOBAL_MAX) < 0) + return MNL_CB_OK; + + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { + perror("mnl_attr_validate"); + return MNL_CB_ERROR; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int nfct_global_stats_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[CTA_STATS_GLOBAL_MAX+1] = {}; + struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); + + mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_global_attr_cb, tb); + + if (tb[CTA_STATS_GLOBAL_ENTRIES]) { + printf("%d\n", + ntohl(mnl_attr_get_u32(tb[CTA_STATS_GLOBAL_ENTRIES]))); + } + return MNL_CB_OK; +} + static struct ctproto_handler *h; int main(int argc, char *argv[]) @@ -2136,7 +2187,25 @@ int main(int argc, char *argv[]) res = nfexp_catch(cth); nfct_close(cth); break; - case CT_COUNT: { + case CT_COUNT: + /* If we fail with netlink, fall back to /proc to ensure + * backward compatibility. + */ + if (nfct_mnl_socket_open() < 0) + goto try_proc_count; + + res = nfct_mnl_get(NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_CT_GET_STATS, + nfct_global_stats_cb); + + nfct_mnl_socket_close(); + + /* don't look at /proc, we got the information via ctnetlink */ + if (res >= 0) + break; + +try_proc_count: + { #define NF_CONNTRACK_COUNT_PROC "/proc/sys/net/netfilter/nf_conntrack_count" FILE *fd; int count; -- cgit v1.2.3 From c712ccebc993cad3f73000bbe9e4788ebeb95ca2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 24 Apr 2012 10:55:33 +0200 Subject: conntrackd: generalize file descriptor infrastructure This patch generalizes the select-based file descriptor infrastructure by allowing you to register file descriptors and its callbacks. Instead of hardcoding the descriptors that needs to be checked. Now, struct fds_item contains a callback and pointer to data that is passed to it: struct fds_item { struct list_head head; int fd; + void (*cb)(void *data); + void *data; }; Then, we check which ones are active in the select_main_step() function: list_for_each_entry(cur, &STATE(fds)->list, head) { if (FD_ISSET(cur->fd, &readfds)) cur->cb(cur->data); } And it invoked the corresponding callback. I had to slightly modify the channel infrastructure to fit it into the changes. This modularity is required for the upcoming cthelper support. Signed-off-by: Pablo Neira Ayuso --- include/channel.h | 11 ++- include/conntrackd.h | 3 +- include/fds.h | 4 +- src/channel.c | 5 + src/channel_tcp.c | 1 + src/fds.c | 61 ++++++++++++- src/main.c | 2 +- src/multichannel.c | 7 +- src/run.c | 251 +++++++++++++++++++-------------------------------- src/stats-mode.c | 1 - src/sync-mode.c | 146 +++++++++++++++--------------- src/tcp.c | 1 - 12 files changed, 249 insertions(+), 244 deletions(-) (limited to 'src') diff --git a/include/channel.h b/include/channel.h index 9b5fad8..46a354f 100644 --- a/include/channel.h +++ b/include/channel.h @@ -35,7 +35,8 @@ struct tcp_channel { #define CHANNEL_F_BUFFERED (1 << 1) #define CHANNEL_F_STREAM (1 << 2) #define CHANNEL_F_ERRORS (1 << 3) -#define CHANNEL_F_MAX (1 << 4) +#define CHANNEL_F_ACCEPT (1 << 4) +#define CHANNEL_F_MAX (1 << 5) union channel_type_conf { struct mcast_conf mcast; @@ -52,8 +53,12 @@ struct channel_conf { struct nlif_handle; +#define CHANNEL_T_DATAGRAM 0 +#define CHANNEL_T_STREAM 1 + struct channel_ops { int headersiz; + int type; void * (*open)(void *conf); void (*close)(void *channel); int (*send)(void *channel, const void *data, int len); @@ -97,6 +102,8 @@ void channel_stats(struct channel *c, int fd); void channel_stats_extended(struct channel *c, int active, struct nlif_handle *h, int fd); +int channel_type(struct channel *c); + #define MULTICHANNEL_MAX 4 struct multichannel { @@ -119,6 +126,6 @@ void multichannel_stats_extended(struct multichannel *m, int multichannel_get_ifindex(struct multichannel *m, int i); int multichannel_get_current_ifindex(struct multichannel *m); void multichannel_set_current_channel(struct multichannel *m, int i); -void multichannel_change_current_channel(struct multichannel *m, int i); +void multichannel_change_current_channel(struct multichannel *m, struct channel *c); #endif /* _CHANNEL_H_ */ diff --git a/include/conntrackd.h b/include/conntrackd.h index 9359dfa..0e203e7 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -264,7 +264,6 @@ extern struct ct_general_state st; struct ct_mode { struct internal_handler *internal; int (*init)(void); - void (*run)(fd_set *readfds); int (*local)(int fd, int type, void *data); void (*kill)(void); }; @@ -278,7 +277,7 @@ extern struct ct_mode stats_mode; /* These live in run.c */ void killer(int foo); int init(void); -void run(void); +void select_main_loop(void); /* from read_config_yy.c */ int diff --git a/include/fds.h b/include/fds.h index f3728d7..ed0c8be 100644 --- a/include/fds.h +++ b/include/fds.h @@ -12,11 +12,13 @@ struct fds { struct fds_item { struct list_head head; int fd; + void (*cb)(void *data); + void *data; }; struct fds *create_fds(void); void destroy_fds(struct fds *); -int register_fd(int fd, struct fds *fds); +int register_fd(int fd, void (*cb)(void *data), void *data, struct fds *fds); int unregister_fd(int fd, struct fds *fds); #endif diff --git a/src/channel.c b/src/channel.c index 818bb01..8b7c319 100644 --- a/src/channel.c +++ b/src/channel.c @@ -310,3 +310,8 @@ int channel_accept(struct channel *c) { return c->ops->accept(c); } + +int channel_type(struct channel *c) +{ + return c->ops->type; +} diff --git a/src/channel_tcp.c b/src/channel_tcp.c index f132840..a84603c 100644 --- a/src/channel_tcp.c +++ b/src/channel_tcp.c @@ -137,6 +137,7 @@ channel_tcp_accept(struct channel *c) struct channel_ops channel_tcp = { .headersiz = 40, /* IP header (20 bytes) + TCP header 20 (bytes) */ + .type = CHANNEL_T_STREAM, .open = channel_tcp_open, .close = channel_tcp_close, .send = channel_tcp_send, diff --git a/src/fds.c b/src/fds.c index 347eee1..0b95437 100644 --- a/src/fds.c +++ b/src/fds.c @@ -1,5 +1,5 @@ /* - * (C) 2006-2008 by Pablo Neira Ayuso + * (C) 2006-2012 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 @@ -14,9 +14,16 @@ * 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. + * + * Part of this code has been sponsored by Vyatta Inc. */ #include #include +#include +#include + +#include "conntrackd.h" +#include "date.h" #include "fds.h" struct fds *create_fds(void) @@ -44,7 +51,7 @@ void destroy_fds(struct fds *fds) free(fds); } -int register_fd(int fd, struct fds *fds) +int register_fd(int fd, void (*cb)(void *data), void *data, struct fds *fds) { struct fds_item *item; @@ -58,7 +65,10 @@ int register_fd(int fd, struct fds *fds) return -1; item->fd = fd; - list_add(&item->head, &fds->list); + item->cb = cb; + item->data = data; + /* Order matters: the descriptors are served in FIFO basis. */ + list_add_tail(&item->head, &fds->list); return 0; } @@ -92,3 +102,48 @@ int unregister_fd(int fd, struct fds *fds) return 0; } +static void select_main_step(struct timeval *next_alarm) +{ + int ret; + fd_set readfds = STATE(fds)->readfds; + struct fds_item *cur, *tmp; + + ret = select(STATE(fds)->maxfd + 1, &readfds, NULL, NULL, next_alarm); + if (ret == -1) { + /* interrupted syscall, retry */ + if (errno == EINTR) + return; + + STATE(stats).select_failed++; + return; + } + + /* signals are racy */ + sigprocmask(SIG_BLOCK, &STATE(block), NULL); + + list_for_each_entry_safe(cur, tmp, &STATE(fds)->list, head) { + if (FD_ISSET(cur->fd, &readfds)) + cur->cb(cur->data); + } + + sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); +} + +void __attribute__((noreturn)) select_main_loop(void) +{ + struct timeval next_alarm; + struct timeval *next = NULL; + + while(1) { + do_gettimeofday(); + + sigprocmask(SIG_BLOCK, &STATE(block), NULL); + if (next != NULL && !timerisset(next)) + next = do_alarm_run(&next_alarm); + else + next = get_next_alarm_run(&next_alarm); + sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); + + select_main_step(next); + } +} diff --git a/src/main.c b/src/main.c index f7803fd..26f6c14 100644 --- a/src/main.c +++ b/src/main.c @@ -406,6 +406,6 @@ int main(int argc, char *argv[]) /* * run main process */ - run(); + select_main_loop(); return 0; } diff --git a/src/multichannel.c b/src/multichannel.c index de69d5c..952b567 100644 --- a/src/multichannel.c +++ b/src/multichannel.c @@ -109,8 +109,9 @@ void multichannel_set_current_channel(struct multichannel *m, int i) m->current = m->channel[i]; } -void multichannel_change_current_channel(struct multichannel *m, int i) +void +multichannel_change_current_channel(struct multichannel *m, struct channel *c) { - if (m->current != m->channel[i]) - m->current = m->channel[i]; + if (m->current != c) + m->current = c; } diff --git a/src/run.c b/src/run.c index 421ff41..f94e853 100644 --- a/src/run.c +++ b/src/run.c @@ -1,5 +1,5 @@ /* - * (C) 2006-2011 by Pablo Neira Ayuso + * (C) 2006-2012 by Pablo Neira Ayuso * (C) 2011 by Vyatta Inc. * * This program is free software; you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * - * Description: run and init functions + * Part of this code has been sponsored by Vyatta Inc. */ #include "conntrackd.h" @@ -460,6 +460,87 @@ static int exp_get_handler(enum nf_conntrack_msg_type type, return NFCT_CB_CONTINUE; } +/* order received via UNIX socket */ +static void local_cb(void *data) +{ + do_local_server_step(&STATE(local), NULL, local_handler); +} + +/* we have received an event from ctnetlink */ +static void event_cb(void *data) +{ + int ret; + + ret = nfct_catch(STATE(event)); + /* reset event iteration limit counter */ + STATE(event_iterations_limit) = CONFIG(event_iterations_limit); + if (ret == -1) { + switch(errno) { + case ENOBUFS: + /* We have hit ENOBUFS, it's likely that we are + * losing events. Two possible situations may + * trigger this error: + * + * 1) The netlink receiver buffer is too small: + * increasing the netlink buffer size should + * be enough. However, some event messages + * got lost. We have to resync ourselves + * with the kernel table conntrack table to + * resolve the inconsistency. + * + * 2) The receiver is too slow to process the + * netlink messages so that the queue gets + * full quickly. This generally happens + * if the system is under heavy workload + * (busy CPU). In this case, increasing the + * size of the netlink receiver buffer + * would not help anymore since we would + * be delaying the overrun. Moreover, we + * should avoid resynchronizations. We + * should do our best here and keep + * replicating as much states as possible. + * If workload lowers at some point, + * we resync ourselves. + */ + nl_resize_socket_buffer(STATE(event)); + if (CONFIG(nl_overrun_resync) > 0 && + STATE(mode)->internal->flags & INTERNAL_F_RESYNC) { + add_alarm(&STATE(resync_alarm), + CONFIG(nl_overrun_resync),0); + } + STATE(stats).nl_catch_event_failed++; + STATE(stats).nl_overrun++; + break; + case ENOENT: + /* + * We received a message from another + * netfilter subsystem that we are not + * interested in. Just ignore it. + */ + break; + case EAGAIN: + /* No more events to receive, try later. */ + break; + default: + STATE(stats).nl_catch_event_failed++; + break; + } + } +} + +/* we previously requested a resync due to buffer overrun. */ +static void resync_cb(void *data) +{ + nfct_catch(STATE(resync)); + if (STATE(mode)->internal->ct.purge) + STATE(mode)->internal->ct.purge(); +} + +static void poll_cb(void *data) +{ + nfct_catch(STATE(resync)); +} + int init(void) { @@ -493,7 +574,7 @@ init(void) dlog(LOG_ERR, "can't open unix socket!"); return -1; } - register_fd(STATE(local).fd, STATE(fds)); + register_fd(STATE(local).fd, local_cb, NULL, STATE(fds)); /* resynchronize (like 'dump' socket) but it also purges old entries */ STATE(resync) = nfct_open(CONFIG(netlink).subsys_id, 0); @@ -507,7 +588,13 @@ init(void) NFCT_T_ALL, STATE(mode)->internal->ct.resync, NULL); - register_fd(nfct_fd(STATE(resync)), STATE(fds)); + if (CONFIG(flags) & CTD_POLL) { + register_fd(nfct_fd(STATE(resync)), poll_cb, + NULL, STATE(fds)); + } else { + register_fd(nfct_fd(STATE(resync)), resync_cb, + NULL, STATE(fds)); + } fcntl(nfct_fd(STATE(resync)), F_SETFL, O_NONBLOCK); if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { @@ -590,7 +677,7 @@ init(void) nfexp_callback_register2(STATE(event), NFCT_T_ALL, exp_event_handler, NULL); } - register_fd(nfct_fd(STATE(event)), STATE(fds)); + register_fd(nfct_fd(STATE(event)), event_cb, NULL, STATE(fds)); } /* Signals handling */ @@ -618,157 +705,3 @@ init(void) return 0; } - -static void run_events(struct timeval *next_alarm) -{ - int ret; - fd_set readfds = STATE(fds)->readfds; - - ret = select(STATE(fds)->maxfd + 1, &readfds, NULL, NULL, next_alarm); - if (ret == -1) { - /* interrupted syscall, retry */ - if (errno == EINTR) - return; - - STATE(stats).select_failed++; - return; - } - - /* signals are racy */ - sigprocmask(SIG_BLOCK, &STATE(block), NULL); - - /* order received via UNIX socket */ - if (FD_ISSET(STATE(local).fd, &readfds)) - do_local_server_step(&STATE(local), NULL, local_handler); - - /* we have receive an event from ctnetlink */ - if (FD_ISSET(nfct_fd(STATE(event)), &readfds)) { - ret = nfct_catch(STATE(event)); - /* reset event iteration limit counter */ - STATE(event_iterations_limit) = CONFIG(event_iterations_limit); - if (ret == -1) { - switch(errno) { - case ENOBUFS: - /* We have hit ENOBUFS, it's likely that we are - * losing events. Two possible situations may - * trigger this error: - * - * 1) The netlink receiver buffer is too small: - * increasing the netlink buffer size should - * be enough. However, some event messages - * got lost. We have to resync ourselves - * with the kernel table conntrack table to - * resolve the inconsistency. - * - * 2) The receiver is too slow to process the - * netlink messages so that the queue gets - * full quickly. This generally happens - * if the system is under heavy workload - * (busy CPU). In this case, increasing the - * size of the netlink receiver buffer - * would not help anymore since we would - * be delaying the overrun. Moreover, we - * should avoid resynchronizations. We - * should do our best here and keep - * replicating as much states as possible. - * If workload lowers at some point, - * we resync ourselves. - */ - nl_resize_socket_buffer(STATE(event)); - if (CONFIG(nl_overrun_resync) > 0 && - STATE(mode)->internal->flags & INTERNAL_F_RESYNC) { - add_alarm(&STATE(resync_alarm), - CONFIG(nl_overrun_resync),0); - } - STATE(stats).nl_catch_event_failed++; - STATE(stats).nl_overrun++; - break; - case ENOENT: - /* - * We received a message from another - * netfilter subsystem that we are not - * interested in. Just ignore it. - */ - break; - case EAGAIN: - /* No more events to receive, try later. */ - break; - default: - STATE(stats).nl_catch_event_failed++; - break; - } - } - } - /* we previously requested a resync due to buffer overrun. */ - if (FD_ISSET(nfct_fd(STATE(resync)), &readfds)) { - nfct_catch(STATE(resync)); - if (STATE(mode)->internal->ct.purge) - STATE(mode)->internal->ct.purge(); - } - - if (STATE(mode)->run) - STATE(mode)->run(&readfds); - - sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); -} - -static void run_polling(struct timeval *next_alarm) -{ - int ret; - fd_set readfds = STATE(fds)->readfds; - - ret = select(STATE(fds)->maxfd + 1, &readfds, NULL, NULL, next_alarm); - if (ret == -1) { - /* interrupted syscall, retry */ - if (errno == EINTR) - return; - - STATE(stats).select_failed++; - return; - } - - /* signals are racy */ - sigprocmask(SIG_BLOCK, &STATE(block), NULL); - - /* order received via UNIX socket */ - if (FD_ISSET(STATE(local).fd, &readfds)) - do_local_server_step(&STATE(local), NULL, local_handler); - - /* we requested a dump from the kernel via polling_alarm */ - if (FD_ISSET(nfct_fd(STATE(resync)), &readfds)) - nfct_catch(STATE(resync)); - - if (STATE(mode)->run) - STATE(mode)->run(&readfds); - - sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); -} - -static void __attribute__((noreturn)) -do_run(void (*run_step)(struct timeval *next_alarm)) -{ - struct timeval next_alarm; - struct timeval *next = NULL; - - while(1) { - do_gettimeofday(); - - sigprocmask(SIG_BLOCK, &STATE(block), NULL); - if (next != NULL && !timerisset(next)) - next = do_alarm_run(&next_alarm); - else - next = get_next_alarm_run(&next_alarm); - sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); - - run_step(next); - } -} - -void run(void) -{ - if (CONFIG(flags) & CTD_POLL) { - do_run(run_polling); - } else { - do_run(run_events); - } -} diff --git a/src/stats-mode.c b/src/stats-mode.c index b768033..6b7f08d 100644 --- a/src/stats-mode.c +++ b/src/stats-mode.c @@ -201,7 +201,6 @@ static struct internal_handler internal_cache_stats = { struct ct_mode stats_mode = { .init = init_stats, - .run = NULL, .local = local_handler_stats, .kill = kill_stats, .internal = &internal_cache_stats, diff --git a/src/sync-mode.c b/src/sync-mode.c index 17f866a..be6366d 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -1,5 +1,5 @@ /* - * (C) 2006-2011 by Pablo Neira Ayuso + * (C) 2006-2012 by Pablo Neira Ayuso * (C) 2011 by Vyatta Inc. * * This program is free software; you can redistribute it and/or modify @@ -78,7 +78,7 @@ static struct nf_expect *msg2exp_alloc(struct nethdr *net, size_t remain) } static void -do_channel_handler_step(int i, struct nethdr *net, size_t remain) +do_channel_handler_step(struct channel *c, struct nethdr *net, size_t remain) { struct nf_conntrack *ct = NULL; struct nf_expect *exp = NULL; @@ -91,10 +91,10 @@ do_channel_handler_step(int i, struct nethdr *net, size_t remain) switch (STATE_SYNC(sync)->recv(net)) { case MSG_DATA: - multichannel_change_current_channel(STATE_SYNC(channel), i); + multichannel_change_current_channel(STATE_SYNC(channel), c); break; case MSG_CTL: - multichannel_change_current_channel(STATE_SYNC(channel), i); + multichannel_change_current_channel(STATE_SYNC(channel), c); return; case MSG_BAD: STATE_SYNC(error).msg_rcv_malformed++; @@ -175,7 +175,7 @@ static int channel_stream(struct channel *m, const char *ptr, ssize_t remain) } /* handler for messages received */ -static int channel_handler_routine(struct channel *m, int i) +static int channel_handler_routine(struct channel *m) { ssize_t numbytes; ssize_t remain, pending = cur - __net; @@ -242,7 +242,7 @@ static int channel_handler_routine(struct channel *m, int i) HDR_NETWORK2HOST(net); - do_channel_handler_step(i, net, remain); + do_channel_handler_step(m, net, remain); ptr += net->len; remain -= net->len; } @@ -250,12 +250,13 @@ static int channel_handler_routine(struct channel *m, int i) } /* handler for messages received */ -static void channel_handler(struct channel *m, int i) +static void channel_handler(void *data) { + struct channel *c = data; int k; for (k=0; kinternal->ct.flush(); } +static void commit_cb(void *data) +{ + int ret; + + read_evfd(STATE_SYNC(commit).evfd); + + ret = STATE_SYNC(commit).rq[0].cb(STATE_SYNC(commit).h, 0); + if (ret == 0) { + /* we still have things in the callback queue. */ + if (STATE_SYNC(commit).rq[1].cb) { + int fd = STATE_SYNC(commit).clientfd; + + STATE_SYNC(commit).rq[0].cb = + STATE_SYNC(commit).rq[1].cb; + + STATE_SYNC(commit).rq[1].cb = NULL; + + STATE_SYNC(commit).clientfd = -1; + STATE_SYNC(commit).rq[0].cb(STATE_SYNC(commit).h, fd); + } else { + /* Close the client socket now, we're done. */ + close(STATE_SYNC(commit).clientfd); + STATE_SYNC(commit).clientfd = -1; + } + } +} + +static void channel_accept_cb(void *data) +{ + struct channel *c = data; + int fd; + + fd = channel_accept(data); + if (fd < 0) + return; + + register_fd(fd, channel_handler, c, STATE(fds)); +} + +static void tx_queue_cb(void *data) +{ + STATE_SYNC(sync)->xmit(); + + /* flush pending messages */ + multichannel_send_flush(STATE_SYNC(channel)); +} + static int init_sync(void) { int i; @@ -370,8 +418,19 @@ static int init_sync(void) for (i=0; ichannel_num; i++) { int fd = channel_get_fd(STATE_SYNC(channel)->channel[i]); fcntl(fd, F_SETFL, O_NONBLOCK); - if (register_fd(fd, STATE(fds)) == -1) - return -1; + + switch(channel_type(STATE_SYNC(channel)->channel[i])) { + case CHANNEL_T_STREAM: + register_fd(fd, channel_accept_cb, + STATE_SYNC(channel)->channel[i], + STATE(fds)); + break; + case CHANNEL_T_DATAGRAM: + register_fd(fd, channel_handler, + STATE_SYNC(channel)->channel[i], + STATE(fds)); + break; + } } STATE_SYNC(interface) = nl_init_interface_handler(); @@ -379,7 +438,8 @@ static int init_sync(void) dlog(LOG_ERR, "can't open interface watcher"); return -1; } - if (register_fd(nlif_fd(STATE_SYNC(interface)), STATE(fds)) == -1) + if (register_fd(nlif_fd(STATE_SYNC(interface)), + interface_handler, NULL, STATE(fds)) == -1) return -1; STATE_SYNC(tx_queue) = queue_create("txqueue", INT_MAX, QUEUE_F_EVFD); @@ -387,8 +447,8 @@ static int init_sync(void) dlog(LOG_ERR, "cannot create tx queue"); return -1; } - if (register_fd(queue_get_eventfd(STATE_SYNC(tx_queue)), - STATE(fds)) == -1) + if (register_fd(queue_get_eventfd(STATE_SYNC(tx_queue)), + tx_queue_cb, NULL, STATE(fds)) == -1) return -1; STATE_SYNC(commit).h = nfct_open(CONFIG(netlink).subsys_id, 0); @@ -404,7 +464,7 @@ static int init_sync(void) return -1; } if (register_fd(get_read_evfd(STATE_SYNC(commit).evfd), - STATE(fds)) == -1) { + commit_cb, NULL, STATE(fds)) == -1) { return -1; } STATE_SYNC(commit).clientfd = -1; @@ -417,61 +477,6 @@ static int init_sync(void) return 0; } -static void channel_check(struct channel *c, int i, fd_set *readfds) -{ - /* In case that this channel is connection-oriented. */ - if (channel_accept_isset(c, readfds)) - channel_accept(c); - - /* For data handling. */ - if (channel_isset(c, readfds)) - channel_handler(c, i); -} - -static void run_sync(fd_set *readfds) -{ - int i; - - for (i=0; ichannel_num; i++) - channel_check(STATE_SYNC(channel)->channel[i], i, readfds); - - if (FD_ISSET(queue_get_eventfd(STATE_SYNC(tx_queue)), readfds)) - STATE_SYNC(sync)->xmit(); - - if (FD_ISSET(nlif_fd(STATE_SYNC(interface)), readfds)) - interface_handler(); - - if (FD_ISSET(get_read_evfd(STATE_SYNC(commit).evfd), readfds)) { - int ret; - - read_evfd(STATE_SYNC(commit).evfd); - - ret = STATE_SYNC(commit).rq[0].cb(STATE_SYNC(commit).h, 0); - if (ret == 0) { - /* we still have things in the callback queue. */ - if (STATE_SYNC(commit).rq[1].cb) { - int fd = STATE_SYNC(commit).clientfd; - - STATE_SYNC(commit).rq[0].cb = - STATE_SYNC(commit).rq[1].cb; - - STATE_SYNC(commit).rq[1].cb = NULL; - - STATE_SYNC(commit).clientfd = -1; - STATE_SYNC(commit).rq[0].cb( - STATE_SYNC(commit).h, fd); - } else { - /* Close the client socket now, we're done. */ - close(STATE_SYNC(commit).clientfd); - STATE_SYNC(commit).clientfd = -1; - } - } - } - - /* flush pending messages */ - multichannel_send_flush(STATE_SYNC(channel)); -} - static void kill_sync(void) { STATE(mode)->internal->close(); @@ -747,7 +752,6 @@ static int local_handler_sync(int fd, int type, void *data) struct ct_mode sync_mode = { .init = init_sync, - .run = run_sync, .local = local_handler_sync, .kill = kill_sync, /* the internal handler is set in run-time. */ diff --git a/src/tcp.c b/src/tcp.c index f6b05ef..af27c46 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -264,7 +264,6 @@ int tcp_accept(struct tcp_sock *m) m->client_fd = ret; m->state = TCP_SERVER_CONNECTED; - register_fd(m->client_fd, STATE(fds)); } return m->client_fd; } -- cgit v1.2.3 From 5a0d0ecf30fb1686cfb10aaa852fee9c8ed4360a Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 24 Apr 2012 11:56:00 +0200 Subject: conntrackd: move ctnetlink code to ctnl.c (removed from run.c) This patch moves the specific ctnetlink code to ctnl.c to prepare the introduction of the cthelper infrastructure. Signed-off-by: Pablo Neira Ayuso --- include/conntrackd.h | 7 +- src/Makefile.am | 1 + src/ctnl.c | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/run.c | 477 ++-------------------------------------------- src/sync-mode.c | 3 - 5 files changed, 543 insertions(+), 467 deletions(-) create mode 100644 src/ctnl.c (limited to 'src') diff --git a/include/conntrackd.h b/include/conntrackd.h index 0e203e7..ec720ec 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -268,7 +268,12 @@ struct ct_mode { void (*kill)(void); }; -/* conntrackd modes */ +/* basic ctnl functions */ +void ctnl_kill(void); +int ctnl_local(int fd, int type, void *data); +int ctnl_init(void); + +/* conntrackd ctnl modes */ extern struct ct_mode sync_mode; extern struct ct_mode stats_mode; diff --git a/src/Makefile.am b/src/Makefile.am index 93c035c..fa4ab75 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,7 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ filter.c fds.c event.c process.c origin.c date.c \ cache.c cache-ct.c cache-exp.c \ cache_timer.c \ + ctnl.c \ sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \ traffic_stats.c stats-mode.c \ network.c cidr.c \ diff --git a/src/ctnl.c b/src/ctnl.c new file mode 100644 index 0000000..107cd5d --- /dev/null +++ b/src/ctnl.c @@ -0,0 +1,522 @@ +/* + * (C) 2006-2012 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. + * + * Part of this code has been sponsored by Vyatta Inc. + */ + +#include "conntrackd.h" +#include "netlink.h" +#include "filter.h" +#include "log.h" +#include "alarm.h" +#include "fds.h" +#include "traffic_stats.h" +#include "process.h" +#include "origin.h" +#include "date.h" +#include "internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +void ctnl_kill(void) +{ + if (!(CONFIG(flags) & CTD_POLL)) + nfct_close(STATE(event)); + + nfct_close(STATE(resync)); + nfct_close(STATE(get)); + origin_unregister(STATE(flush)); + nfct_close(STATE(flush)); + + if (STATE(us_filter)) + ct_filter_destroy(STATE(us_filter)); + STATE(mode)->kill(); + + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + nfct_close(STATE(dump)); + } +} + +static void local_flush_master(void) +{ + STATE(stats).nl_kernel_table_flush++; + dlog(LOG_NOTICE, "flushing kernel conntrack table"); + + /* fork a child process that performs the flush operation, + * meanwhile the parent process handles events. */ + if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, + NULL, NULL) == 0) { + nl_flush_conntrack_table_selective(); + exit(EXIT_SUCCESS); + } +} + +static void local_resync_master(void) +{ + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + STATE(stats).nl_kernel_table_resync++; + dlog(LOG_NOTICE, "resync with master conntrack table"); + nl_dump_conntrack_table(STATE(dump)); + } else { + dlog(LOG_NOTICE, "resync is unsupported in this mode"); + } +} + +static void local_exp_flush_master(void) +{ + if (!(CONFIG(flags) & CTD_EXPECT)) + return; + + STATE(stats).nl_kernel_table_flush++; + dlog(LOG_NOTICE, "flushing kernel expect table"); + + /* fork a child process that performs the flush operation, + * meanwhile the parent process handles events. */ + if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, + NULL, NULL) == 0) { + nl_flush_expect_table(STATE(flush)); + exit(EXIT_SUCCESS); + } +} + +static void local_exp_resync_master(void) +{ + if (!(CONFIG(flags) & CTD_EXPECT)) + return; + + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + STATE(stats).nl_kernel_table_resync++; + dlog(LOG_NOTICE, "resync with master expect table"); + nl_dump_expect_table(STATE(dump)); + } else { + dlog(LOG_NOTICE, "resync is unsupported in this mode"); + } +} + +int ctnl_local(int fd, int type, void *data) +{ + int ret = LOCAL_RET_OK; + + switch(type) { + case CT_FLUSH_MASTER: + local_flush_master(); + break; + case CT_RESYNC_MASTER: + local_resync_master(); + break; + case EXP_FLUSH_MASTER: + local_exp_flush_master(); + break; + case EXP_RESYNC_MASTER: + local_exp_resync_master(); + break; + case ALL_FLUSH_MASTER: + local_flush_master(); + local_exp_flush_master(); + break; + case ALL_RESYNC_MASTER: + local_resync_master(); + local_exp_resync_master(); + break; + } + + ret = STATE(mode)->local(fd, type, data); + if (ret == LOCAL_RET_ERROR) { + STATE(stats).local_unknown_request++; + return LOCAL_RET_ERROR; + } + return ret; +} + +static void do_overrun_resync_alarm(struct alarm_block *a, void *data) +{ + nl_send_resync(STATE(resync)); + STATE(stats).nl_kernel_table_resync++; +} + +static void do_polling_alarm(struct alarm_block *a, void *data) +{ + if (STATE(mode)->internal->ct.purge) + STATE(mode)->internal->ct.purge(); + + if (STATE(mode)->internal->exp.purge) + STATE(mode)->internal->exp.purge(); + + nl_send_resync(STATE(resync)); + nl_send_expect_resync(STATE(resync)); + add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); +} + +static int event_handler(const struct nlmsghdr *nlh, + enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + int origin_type; + + STATE(stats).nl_events_received++; + + /* skip user-space filtering if already do it in the kernel */ + if (ct_filter_conntrack(ct, !CONFIG(filter_from_kernelspace))) { + STATE(stats).nl_events_filtered++; + goto out; + } + + origin_type = origin_find(nlh); + + switch(type) { + case NFCT_T_NEW: + STATE(mode)->internal->ct.new(ct, origin_type); + break; + case NFCT_T_UPDATE: + STATE(mode)->internal->ct.upd(ct, origin_type); + break; + case NFCT_T_DESTROY: + if (STATE(mode)->internal->ct.del(ct, origin_type)) + update_traffic_stats(ct); + break; + default: + STATE(stats).nl_events_unknown_type++; + break; + } + +out: + /* we reset the iteration limiter in the main select loop. */ + if (STATE(event_iterations_limit)-- <= 0) + return NFCT_CB_STOP; + else + return NFCT_CB_CONTINUE; +} + +static int exp_event_handler(const struct nlmsghdr *nlh, + enum nf_conntrack_msg_type type, + struct nf_expect *exp, + void *data) +{ + int origin_type; + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); + + STATE(stats).nl_events_received++; + + if (!exp_filter_find(STATE(exp_filter), exp)) { + STATE(stats).nl_events_filtered++; + goto out; + } + if (ct_filter_conntrack(master, 1)) + return NFCT_CB_CONTINUE; + + origin_type = origin_find(nlh); + + switch(type) { + case NFCT_T_NEW: + STATE(mode)->internal->exp.new(exp, origin_type); + break; + case NFCT_T_UPDATE: + STATE(mode)->internal->exp.upd(exp, origin_type); + break; + case NFCT_T_DESTROY: + STATE(mode)->internal->exp.del(exp, origin_type); + break; + default: + STATE(stats).nl_events_unknown_type++; + break; + } + +out: + /* we reset the iteration limiter in the main select loop. */ + if (STATE(event_iterations_limit)-- <= 0) + return NFCT_CB_STOP; + else + return NFCT_CB_CONTINUE; +} + +static int dump_handler(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + if (ct_filter_conntrack(ct, 1)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + STATE(mode)->internal->ct.populate(ct); + break; + default: + STATE(stats).nl_dump_unknown_type++; + break; + } + return NFCT_CB_CONTINUE; +} + +static int exp_dump_handler(enum nf_conntrack_msg_type type, + struct nf_expect *exp, void *data) +{ + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); + + if (!exp_filter_find(STATE(exp_filter), exp)) + return NFCT_CB_CONTINUE; + + if (ct_filter_conntrack(master, 1)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + STATE(mode)->internal->exp.populate(exp); + break; + default: + STATE(stats).nl_dump_unknown_type++; + break; + } + return NFCT_CB_CONTINUE; +} + +static int get_handler(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + if (ct_filter_conntrack(ct, 1)) + return NFCT_CB_CONTINUE; + + STATE(get_retval) = 1; + return NFCT_CB_CONTINUE; +} + +static int exp_get_handler(enum nf_conntrack_msg_type type, + struct nf_expect *exp, void *data) +{ + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); + + if (!exp_filter_find(STATE(exp_filter), exp)) + return NFCT_CB_CONTINUE; + + if (ct_filter_conntrack(master, 1)) + return NFCT_CB_CONTINUE; + + STATE(get_retval) = 1; + return NFCT_CB_CONTINUE; +} + +/* we have received an event from ctnetlink */ +static void event_cb(void *data) +{ + int ret; + + ret = nfct_catch(STATE(event)); + /* reset event iteration limit counter */ + STATE(event_iterations_limit) = CONFIG(event_iterations_limit); + if (ret == -1) { + switch(errno) { + case ENOBUFS: + /* We have hit ENOBUFS, it's likely that we are + * losing events. Two possible situations may + * trigger this error: + * + * 1) The netlink receiver buffer is too small: + * increasing the netlink buffer size should + * be enough. However, some event messages + * got lost. We have to resync ourselves + * with the kernel table conntrack table to + * resolve the inconsistency. + * + * 2) The receiver is too slow to process the + * netlink messages so that the queue gets + * full quickly. This generally happens + * if the system is under heavy workload + * (busy CPU). In this case, increasing the + * size of the netlink receiver buffer + * would not help anymore since we would + * be delaying the overrun. Moreover, we + * should avoid resynchronizations. We + * should do our best here and keep + * replicating as much states as possible. + * If workload lowers at some point, + * we resync ourselves. + */ + nl_resize_socket_buffer(STATE(event)); + if (CONFIG(nl_overrun_resync) > 0 && + STATE(mode)->internal->flags & INTERNAL_F_RESYNC) { + add_alarm(&STATE(resync_alarm), + CONFIG(nl_overrun_resync),0); + } + STATE(stats).nl_catch_event_failed++; + STATE(stats).nl_overrun++; + break; + case ENOENT: + /* + * We received a message from another + * netfilter subsystem that we are not + * interested in. Just ignore it. + */ + break; + case EAGAIN: + /* No more events to receive, try later. */ + break; + default: + STATE(stats).nl_catch_event_failed++; + break; + } + } +} + +/* we previously requested a resync due to buffer overrun. */ +static void resync_cb(void *data) +{ + nfct_catch(STATE(resync)); + if (STATE(mode)->internal->ct.purge) + STATE(mode)->internal->ct.purge(); +} + +static void poll_cb(void *data) +{ + nfct_catch(STATE(resync)); +} + +int ctnl_init(void) +{ + if (CONFIG(flags) & CTD_STATS_MODE) + STATE(mode) = &stats_mode; + else if (CONFIG(flags) & CTD_SYNC_MODE) + STATE(mode) = &sync_mode; + else { + fprintf(stderr, "WARNING: No running mode specified. " + "Defaulting to statistics mode.\n"); + CONFIG(flags) |= CTD_STATS_MODE; + STATE(mode) = &stats_mode; + } + + /* Initialization */ + if (STATE(mode)->init() == -1) { + dlog(LOG_ERR, "initialization failed"); + return -1; + } + + /* resynchronize (like 'dump' socket) but it also purges old entries */ + STATE(resync) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(resync)== NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register(STATE(resync), + NFCT_T_ALL, + STATE(mode)->internal->ct.resync, + NULL); + if (CONFIG(flags) & CTD_POLL) { + register_fd(nfct_fd(STATE(resync)), poll_cb, + NULL, STATE(fds)); + } else { + register_fd(nfct_fd(STATE(resync)), resync_cb, + NULL, STATE(fds)); + } + fcntl(nfct_fd(STATE(resync)), F_SETFL, O_NONBLOCK); + + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + STATE(dump) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(dump) == NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register(STATE(dump), NFCT_T_ALL, + dump_handler, NULL); + + if (CONFIG(flags) & CTD_EXPECT) { + nfexp_callback_register(STATE(dump), NFCT_T_ALL, + exp_dump_handler, NULL); + } + + if (nl_dump_conntrack_table(STATE(dump)) == -1) { + dlog(LOG_ERR, "can't get kernel conntrack table"); + return -1; + } + + if (CONFIG(flags) & CTD_EXPECT) { + if (nl_dump_expect_table(STATE(dump)) == -1) { + dlog(LOG_ERR, "can't get kernel " + "expect table"); + return -1; + } + } + } + + STATE(get) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(get) == NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register(STATE(get), NFCT_T_ALL, get_handler, NULL); + + if (CONFIG(flags) & CTD_EXPECT) { + nfexp_callback_register(STATE(get), NFCT_T_ALL, + exp_get_handler, NULL); + } + + STATE(flush) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(flush) == NULL) { + dlog(LOG_ERR, "cannot open flusher handler"); + return -1; + } + /* register this handler as the origin of a flush operation */ + origin_register(STATE(flush), CTD_ORIGIN_FLUSH); + + if (CONFIG(flags) & CTD_POLL) { + init_alarm(&STATE(polling_alarm), NULL, do_polling_alarm); + add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); + dlog(LOG_NOTICE, "running in polling mode"); + } else { + init_alarm(&STATE(resync_alarm), NULL, do_overrun_resync_alarm); + /* + * The last nfct handler that we register is the event handler. + * The reason to do this is that we may receive events while + * populating the internal cache. Thus, we hit ENOBUFS + * prematurely. However, if we open the event handler before + * populating the internal cache, we may still lose events + * that have occured during the population. + */ + STATE(event) = nl_init_event_handler(); + if (STATE(event) == NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register2(STATE(event), NFCT_T_ALL, + event_handler, NULL); + + if (CONFIG(flags) & CTD_EXPECT) { + nfexp_callback_register2(STATE(event), NFCT_T_ALL, + exp_event_handler, NULL); + } + register_fd(nfct_fd(STATE(event)), event_cb, NULL, STATE(fds)); + } + + return 0; +} diff --git a/src/run.c b/src/run.c index f94e853..852bec6 100644 --- a/src/run.c +++ b/src/run.c @@ -45,24 +45,12 @@ void killer(int foo) /* no signals while handling signals */ sigprocmask(SIG_BLOCK, &STATE(block), NULL); - if (!(CONFIG(flags) & CTD_POLL)) - nfct_close(STATE(event)); - - nfct_close(STATE(resync)); - nfct_close(STATE(get)); - origin_unregister(STATE(flush)); - nfct_close(STATE(flush)); - - if (STATE(us_filter)) - ct_filter_destroy(STATE(us_filter)); local_server_destroy(&STATE(local)); - STATE(mode)->kill(); - if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { - nfct_close(STATE(dump)); - } - destroy_fds(STATE(fds)); + if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) + ctnl_kill(); + destroy_fds(STATE(fds)); unlink(CONFIG(lockfile)); dlog(LOG_NOTICE, "---- shutdown received ----"); close_log(); @@ -187,62 +175,6 @@ static void dump_stats_runtime(int fd) send(fd, buf, size, 0); } -static void local_flush_master(void) -{ - STATE(stats).nl_kernel_table_flush++; - dlog(LOG_NOTICE, "flushing kernel conntrack table"); - - /* fork a child process that performs the flush operation, - * meanwhile the parent process handles events. */ - if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, - NULL, NULL) == 0) { - nl_flush_conntrack_table_selective(); - exit(EXIT_SUCCESS); - } -} - -static void local_resync_master(void) -{ - if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { - STATE(stats).nl_kernel_table_resync++; - dlog(LOG_NOTICE, "resync with master conntrack table"); - nl_dump_conntrack_table(STATE(dump)); - } else { - dlog(LOG_NOTICE, "resync is unsupported in this mode"); - } -} - -static void local_exp_flush_master(void) -{ - if (!(CONFIG(flags) & CTD_EXPECT)) - return; - - STATE(stats).nl_kernel_table_flush++; - dlog(LOG_NOTICE, "flushing kernel expect table"); - - /* fork a child process that performs the flush operation, - * meanwhile the parent process handles events. */ - if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, - NULL, NULL) == 0) { - nl_flush_expect_table(STATE(flush)); - exit(EXIT_SUCCESS); - } -} - -static void local_exp_resync_master(void) -{ - if (!(CONFIG(flags) & CTD_EXPECT)) - return; - - if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { - STATE(stats).nl_kernel_table_resync++; - dlog(LOG_NOTICE, "resync with master expect table"); - nl_dump_expect_table(STATE(dump)); - } else { - dlog(LOG_NOTICE, "resync is unsupported in this mode"); - } -} - static int local_handler(int fd, void *data) { int ret = LOCAL_RET_OK; @@ -253,26 +185,9 @@ static int local_handler(int fd, void *data) return LOCAL_RET_OK; } switch(type) { - case CT_FLUSH_MASTER: - local_flush_master(); - break; - case CT_RESYNC_MASTER: - local_resync_master(); - break; - case EXP_FLUSH_MASTER: - local_exp_flush_master(); - break; - case EXP_RESYNC_MASTER: - local_exp_resync_master(); - break; - case ALL_FLUSH_MASTER: - local_flush_master(); - local_exp_flush_master(); - break; - case ALL_RESYNC_MASTER: - local_resync_master(); - local_exp_resync_master(); - break; + case KILL: + killer(0); + break; case STATS_RUNTIME: dump_stats_runtime(fd); break; @@ -281,183 +196,10 @@ static int local_handler(int fd, void *data) break; } - ret = STATE(mode)->local(fd, type, data); - if (ret == LOCAL_RET_ERROR) { - STATE(stats).local_unknown_request++; - return LOCAL_RET_ERROR; - } - return ret; -} - -static void do_overrun_resync_alarm(struct alarm_block *a, void *data) -{ - nl_send_resync(STATE(resync)); - STATE(stats).nl_kernel_table_resync++; -} - -static void do_polling_alarm(struct alarm_block *a, void *data) -{ - if (STATE(mode)->internal->ct.purge) - STATE(mode)->internal->ct.purge(); - - if (STATE(mode)->internal->exp.purge) - STATE(mode)->internal->exp.purge(); - - nl_send_resync(STATE(resync)); - nl_send_expect_resync(STATE(resync)); - add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); -} - -static int event_handler(const struct nlmsghdr *nlh, - enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data) -{ - int origin_type; - - STATE(stats).nl_events_received++; - - /* skip user-space filtering if already do it in the kernel */ - if (ct_filter_conntrack(ct, !CONFIG(filter_from_kernelspace))) { - STATE(stats).nl_events_filtered++; - goto out; - } - - origin_type = origin_find(nlh); - - switch(type) { - case NFCT_T_NEW: - STATE(mode)->internal->ct.new(ct, origin_type); - break; - case NFCT_T_UPDATE: - STATE(mode)->internal->ct.upd(ct, origin_type); - break; - case NFCT_T_DESTROY: - if (STATE(mode)->internal->ct.del(ct, origin_type)) - update_traffic_stats(ct); - break; - default: - STATE(stats).nl_events_unknown_type++; - break; - } - -out: - /* we reset the iteration limiter in the main select loop. */ - if (STATE(event_iterations_limit)-- <= 0) - return NFCT_CB_STOP; - else - return NFCT_CB_CONTINUE; -} - -static int exp_event_handler(const struct nlmsghdr *nlh, - enum nf_conntrack_msg_type type, - struct nf_expect *exp, - void *data) -{ - int origin_type; - const struct nf_conntrack *master = - nfexp_get_attr(exp, ATTR_EXP_MASTER); - - STATE(stats).nl_events_received++; - - if (!exp_filter_find(STATE(exp_filter), exp)) { - STATE(stats).nl_events_filtered++; - goto out; - } - if (ct_filter_conntrack(master, 1)) - return NFCT_CB_CONTINUE; - - origin_type = origin_find(nlh); - - switch(type) { - case NFCT_T_NEW: - STATE(mode)->internal->exp.new(exp, origin_type); - break; - case NFCT_T_UPDATE: - STATE(mode)->internal->exp.upd(exp, origin_type); - break; - case NFCT_T_DESTROY: - STATE(mode)->internal->exp.del(exp, origin_type); - break; - default: - STATE(stats).nl_events_unknown_type++; - break; - } - -out: - /* we reset the iteration limiter in the main select loop. */ - if (STATE(event_iterations_limit)-- <= 0) - return NFCT_CB_STOP; - else - return NFCT_CB_CONTINUE; -} + if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) + return ctnl_local(fd, type, data); -static int dump_handler(enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data) -{ - if (ct_filter_conntrack(ct, 1)) - return NFCT_CB_CONTINUE; - - switch(type) { - case NFCT_T_UPDATE: - STATE(mode)->internal->ct.populate(ct); - break; - default: - STATE(stats).nl_dump_unknown_type++; - break; - } - return NFCT_CB_CONTINUE; -} - -static int exp_dump_handler(enum nf_conntrack_msg_type type, - struct nf_expect *exp, void *data) -{ - const struct nf_conntrack *master = - nfexp_get_attr(exp, ATTR_EXP_MASTER); - - if (!exp_filter_find(STATE(exp_filter), exp)) - return NFCT_CB_CONTINUE; - - if (ct_filter_conntrack(master, 1)) - return NFCT_CB_CONTINUE; - - switch(type) { - case NFCT_T_UPDATE: - STATE(mode)->internal->exp.populate(exp); - break; - default: - STATE(stats).nl_dump_unknown_type++; - break; - } - return NFCT_CB_CONTINUE; -} - -static int get_handler(enum nf_conntrack_msg_type type, - struct nf_conntrack *ct, - void *data) -{ - if (ct_filter_conntrack(ct, 1)) - return NFCT_CB_CONTINUE; - - STATE(get_retval) = 1; - return NFCT_CB_CONTINUE; -} - -static int exp_get_handler(enum nf_conntrack_msg_type type, - struct nf_expect *exp, void *data) -{ - const struct nf_conntrack *master = - nfexp_get_attr(exp, ATTR_EXP_MASTER); - - if (!exp_filter_find(STATE(exp_filter), exp)) - return NFCT_CB_CONTINUE; - - if (ct_filter_conntrack(master, 1)) - return NFCT_CB_CONTINUE; - - STATE(get_retval) = 1; - return NFCT_CB_CONTINUE; + return ret; } /* order received via UNIX socket */ @@ -466,109 +208,17 @@ static void local_cb(void *data) do_local_server_step(&STATE(local), NULL, local_handler); } -/* we have received an event from ctnetlink */ -static void event_cb(void *data) -{ - int ret; - - ret = nfct_catch(STATE(event)); - /* reset event iteration limit counter */ - STATE(event_iterations_limit) = CONFIG(event_iterations_limit); - if (ret == -1) { - switch(errno) { - case ENOBUFS: - /* We have hit ENOBUFS, it's likely that we are - * losing events. Two possible situations may - * trigger this error: - * - * 1) The netlink receiver buffer is too small: - * increasing the netlink buffer size should - * be enough. However, some event messages - * got lost. We have to resync ourselves - * with the kernel table conntrack table to - * resolve the inconsistency. - * - * 2) The receiver is too slow to process the - * netlink messages so that the queue gets - * full quickly. This generally happens - * if the system is under heavy workload - * (busy CPU). In this case, increasing the - * size of the netlink receiver buffer - * would not help anymore since we would - * be delaying the overrun. Moreover, we - * should avoid resynchronizations. We - * should do our best here and keep - * replicating as much states as possible. - * If workload lowers at some point, - * we resync ourselves. - */ - nl_resize_socket_buffer(STATE(event)); - if (CONFIG(nl_overrun_resync) > 0 && - STATE(mode)->internal->flags & INTERNAL_F_RESYNC) { - add_alarm(&STATE(resync_alarm), - CONFIG(nl_overrun_resync),0); - } - STATE(stats).nl_catch_event_failed++; - STATE(stats).nl_overrun++; - break; - case ENOENT: - /* - * We received a message from another - * netfilter subsystem that we are not - * interested in. Just ignore it. - */ - break; - case EAGAIN: - /* No more events to receive, try later. */ - break; - default: - STATE(stats).nl_catch_event_failed++; - break; - } - } -} - -/* we previously requested a resync due to buffer overrun. */ -static void resync_cb(void *data) -{ - nfct_catch(STATE(resync)); - if (STATE(mode)->internal->ct.purge) - STATE(mode)->internal->ct.purge(); -} - -static void poll_cb(void *data) -{ - nfct_catch(STATE(resync)); -} - int init(void) { do_gettimeofday(); - if (CONFIG(flags) & CTD_STATS_MODE) - STATE(mode) = &stats_mode; - else if (CONFIG(flags) & CTD_SYNC_MODE) - STATE(mode) = &sync_mode; - else { - fprintf(stderr, "WARNING: No running mode specified. " - "Defaulting to statistics mode.\n"); - CONFIG(flags) |= CTD_STATS_MODE; - STATE(mode) = &stats_mode; - } - STATE(fds) = create_fds(); if (STATE(fds) == NULL) { dlog(LOG_ERR, "can't create file descriptor pool"); return -1; } - /* Initialization */ - if (STATE(mode)->init() == -1) { - dlog(LOG_ERR, "initialization failed"); - return -1; - } - /* local UNIX socket */ if (local_server_create(&STATE(local), &CONFIG(local)) == -1) { dlog(LOG_ERR, "can't open unix socket!"); @@ -576,110 +226,6 @@ init(void) } register_fd(STATE(local).fd, local_cb, NULL, STATE(fds)); - /* resynchronize (like 'dump' socket) but it also purges old entries */ - STATE(resync) = nfct_open(CONFIG(netlink).subsys_id, 0); - if (STATE(resync)== NULL) { - dlog(LOG_ERR, "can't open netlink handler: %s", - strerror(errno)); - dlog(LOG_ERR, "no ctnetlink kernel support?"); - return -1; - } - nfct_callback_register(STATE(resync), - NFCT_T_ALL, - STATE(mode)->internal->ct.resync, - NULL); - if (CONFIG(flags) & CTD_POLL) { - register_fd(nfct_fd(STATE(resync)), poll_cb, - NULL, STATE(fds)); - } else { - register_fd(nfct_fd(STATE(resync)), resync_cb, - NULL, STATE(fds)); - } - fcntl(nfct_fd(STATE(resync)), F_SETFL, O_NONBLOCK); - - if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { - STATE(dump) = nfct_open(CONFIG(netlink).subsys_id, 0); - if (STATE(dump) == NULL) { - dlog(LOG_ERR, "can't open netlink handler: %s", - strerror(errno)); - dlog(LOG_ERR, "no ctnetlink kernel support?"); - return -1; - } - nfct_callback_register(STATE(dump), NFCT_T_ALL, - dump_handler, NULL); - - if (CONFIG(flags) & CTD_EXPECT) { - nfexp_callback_register(STATE(dump), NFCT_T_ALL, - exp_dump_handler, NULL); - } - - if (nl_dump_conntrack_table(STATE(dump)) == -1) { - dlog(LOG_ERR, "can't get kernel conntrack table"); - return -1; - } - - if (CONFIG(flags) & CTD_EXPECT) { - if (nl_dump_expect_table(STATE(dump)) == -1) { - dlog(LOG_ERR, "can't get kernel " - "expect table"); - return -1; - } - } - } - - STATE(get) = nfct_open(CONFIG(netlink).subsys_id, 0); - if (STATE(get) == NULL) { - dlog(LOG_ERR, "can't open netlink handler: %s", - strerror(errno)); - dlog(LOG_ERR, "no ctnetlink kernel support?"); - return -1; - } - nfct_callback_register(STATE(get), NFCT_T_ALL, get_handler, NULL); - - if (CONFIG(flags) & CTD_EXPECT) { - nfexp_callback_register(STATE(get), NFCT_T_ALL, - exp_get_handler, NULL); - } - - STATE(flush) = nfct_open(CONFIG(netlink).subsys_id, 0); - if (STATE(flush) == NULL) { - dlog(LOG_ERR, "cannot open flusher handler"); - return -1; - } - /* register this handler as the origin of a flush operation */ - origin_register(STATE(flush), CTD_ORIGIN_FLUSH); - - if (CONFIG(flags) & CTD_POLL) { - init_alarm(&STATE(polling_alarm), NULL, do_polling_alarm); - add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); - dlog(LOG_NOTICE, "running in polling mode"); - } else { - init_alarm(&STATE(resync_alarm), NULL, do_overrun_resync_alarm); - /* - * The last nfct handler that we register is the event handler. - * The reason to do this is that we may receive events while - * populating the internal cache. Thus, we hit ENOBUFS - * prematurely. However, if we open the event handler before - * populating the internal cache, we may still lose events - * that have occured during the population. - */ - STATE(event) = nl_init_event_handler(); - if (STATE(event) == NULL) { - dlog(LOG_ERR, "can't open netlink handler: %s", - strerror(errno)); - dlog(LOG_ERR, "no ctnetlink kernel support?"); - return -1; - } - nfct_callback_register2(STATE(event), NFCT_T_ALL, - event_handler, NULL); - - if (CONFIG(flags) & CTD_EXPECT) { - nfexp_callback_register2(STATE(event), NFCT_T_ALL, - exp_event_handler, NULL); - } - register_fd(nfct_fd(STATE(event)), event_cb, NULL, STATE(fds)); - } - /* Signals handling */ sigemptyset(&STATE(block)); sigaddset(&STATE(block), SIGTERM); @@ -699,6 +245,11 @@ init(void) if (signal(SIGCHLD, child) == SIG_ERR) return -1; + /* Initialization */ + if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) + if (ctnl_init() < 0) + return -1; + time(&STATE(stats).daemon_start_time); dlog(LOG_NOTICE, "initialization completed"); diff --git a/src/sync-mode.c b/src/sync-mode.c index be6366d..e69ecfe 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -641,9 +641,6 @@ static int local_handler_sync(int fd, int type, void *data) dlog(LOG_NOTICE, "flushing external cache"); STATE_SYNC(external)->ct.flush(); break; - case KILL: - killer(0); - break; case STATS: STATE(mode)->internal->ct.stats(fd); STATE_SYNC(external)->ct.stats(fd); -- cgit v1.2.3 From 5e8f64f46cb1dd71b0a94cb7dad87da00b8c5e32 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 15 May 2012 01:51:29 +0200 Subject: conntrackd: add cthelper infrastructure (+ example FTP helper) This patch adds the user-space helper infrastructure. It also contains the implementation of the FTP helper in user-space. There's one example file that you can use to configure conntrackd as user-space connection tracking helper under: doc/helper/conntrackd.conf Signed-off-by: Pablo Neira Ayuso --- configure.ac | 9 +- doc/helper/conntrackd.conf | 82 ++++ include/Makefile.am | 3 +- include/conntrackd.h | 21 +- include/helper.h | 104 +++++ include/linux/netfilter/Makefile.am | 2 +- include/linux/netfilter/nfnetlink_cthelper.h | 55 +++ include/linux/netfilter/nfnetlink_queue.h | 99 +++++ include/myct.h | 43 ++ include/nfct.h | 10 + include/stack.h | 28 ++ src/Makefile.am | 24 +- src/cthelper.c | 521 ++++++++++++++++++++++ src/expect.c | 214 +++++++++ src/helpers.c | 76 ++++ src/helpers/Makefile.am | 9 + src/helpers/ftp.c | 599 ++++++++++++++++++++++++++ src/main.c | 3 +- src/nfct-extensions/helper.c | 619 +++++++++++++++++++++++++++ src/nfct.c | 6 + src/read_config_lex.l | 5 + src/read_config_yy.y | 200 +++++++++ src/run.c | 11 + src/stack.c | 56 +++ src/utils.c | 243 +++++++++++ 25 files changed, 3030 insertions(+), 12 deletions(-) create mode 100644 doc/helper/conntrackd.conf create mode 100644 include/helper.h create mode 100644 include/linux/netfilter/nfnetlink_cthelper.h create mode 100644 include/linux/netfilter/nfnetlink_queue.h create mode 100644 include/myct.h create mode 100644 include/stack.h create mode 100644 src/cthelper.c create mode 100644 src/expect.c create mode 100644 src/helpers.c create mode 100644 src/helpers/Makefile.am create mode 100644 src/helpers/ftp.c create mode 100644 src/nfct-extensions/helper.c create mode 100644 src/stack.c create mode 100644 src/utils.c (limited to 'src') diff --git a/configure.ac b/configure.ac index 8cd5626..5d1860d 100644 --- a/configure.ac +++ b/configure.ac @@ -9,6 +9,9 @@ AM_INIT_AUTOMAKE([-Wall foreign subdir-objects dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_SEARCH_LIBS([dlopen], [dl], [libdl_LIBS="$LIBS"; LIBS=""]) +AC_SUBST([libdl_LIBS]) + AC_PROG_CC AC_DISABLE_STATIC AM_PROG_LIBTOOL @@ -52,9 +55,11 @@ else fi PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.0]) -PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.0]) +PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.3]) PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.1]) PKG_CHECK_MODULES([LIBNETFILTER_CTTIMEOUT], [libnetfilter_cttimeout >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_CTHELPER], [libnetfilter_cthelper >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_QUEUE], [libnetfilter_queue >= 1.0.0]) AC_CHECK_HEADERS([linux/capability.h],, [AC_MSG_ERROR([Cannot find linux/capabibility.h])]) @@ -114,5 +119,5 @@ dnl debug/src/Makefile dnl extensions/Makefile dnl src/Makefile]) -AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile src/helpers/Makefile]) AC_OUTPUT diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf new file mode 100644 index 0000000..711b309 --- /dev/null +++ b/doc/helper/conntrackd.conf @@ -0,0 +1,82 @@ +# +# Helper settings +# + +Helper { + # Before this, you have to make sure you have registered the `ftp' + # user-space helper stub via: + # + # nfct helper add ftp inet tcp + # + Type ftp inet tcp { + # + # Set NFQUEUE number you want to use to receive traffic from + # the kernel. + # + QueueNum 0 + # + # Set the Expectation policy for this helper. + # + Policy ftp { + # + # Maximum number of simultaneous expectations + # + ExpectMax 1 + # + # Maximum living time for one expectation (in seconds). + # + ExpectTimeout 300 + } + } +} + +# +# General settings +# +General { + # + # Set the nice value of the daemon, this value goes from -20 + # (most favorable scheduling) to 19 (least favorable). Using a + # very low value reduces the chances to lose state-change events. + # Default is 0 but this example file sets it to most favourable + # scheduling as this is generally a good idea. See man nice(1) for + # more information. + # + Nice -20 + + # + # Select a different scheduler for the daemon, you can select between + # RR and FIFO and the process priority (minimum is 0, maximum is 99). + # See man sched_setscheduler(2) for more information. Using a RT + # scheduler reduces the chances to overrun the Netlink buffer. + # + # Scheduler { + # Type FIFO + # Priority 99 + # } + + # + # Logfile: on (/var/log/conntrackd.log), off, or a filename + # Default: off + # + LogFile on + + # + # Syslog: on, off or a facility name (daemon (default) or local0..7) + # Default: off + # + #Syslog on + + # + # Lockfile + # + LockFile /var/lock/conntrack.lock + + # + # Unix socket configuration + # + UNIX { + Path /var/run/conntrackd.ctl + Backlog 20 + } +} diff --git a/include/Makefile.am b/include/Makefile.am index 138005d..6bd0f7f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -5,5 +5,6 @@ noinst_HEADERS = alarm.h jhash.h cache.h linux_list.h linux_rbtree.h \ debug.h log.h hash.h mcast.h conntrack.h \ network.h filter.h queue.h vector.h cidr.h \ traffic_stats.h netlink.h fds.h event.h bitops.h channel.h \ - process.h origin.h internal.h external.h date.h nfct.h + process.h origin.h internal.h external.h date.h nfct.h \ + helper.h myct.h stack.h diff --git a/include/conntrackd.h b/include/conntrackd.h index ec720ec..19e613c 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -69,6 +69,7 @@ #define CTD_SYNC_NOTRACK (1UL << 4) #define CTD_POLL (1UL << 5) #define CTD_EXPECT (1UL << 6) +#define CTD_HELPER (1UL << 7) /* FILENAME_MAX is 4096 on my system, perhaps too much? */ #ifndef FILENAME_MAXLEN @@ -134,6 +135,9 @@ struct ct_conf { int syslog_facility; size_t buffer_size; } stats; + struct { + struct list_head list; + } cthelper; }; #define STATE(x) st.x @@ -252,13 +256,21 @@ struct ct_stats_state { struct cache *cache; /* internal events cache (netlink) */ }; -union ct_state { +#define STATE_CTH(x) state.cthelper->x + +struct ct_helper_state { + struct mnl_socket *nl; + uint32_t portid; +}; + +struct ct_state { struct ct_sync_state *sync; struct ct_stats_state *stats; + struct ct_helper_state *cthelper; }; extern struct ct_conf conf; -extern union ct_state state; +extern struct ct_state state; extern struct ct_general_state st; struct ct_mode { @@ -273,6 +285,11 @@ void ctnl_kill(void); int ctnl_local(int fd, int type, void *data); int ctnl_init(void); +/* basic cthelper functions */ +void cthelper_kill(void); +int cthelper_local(int fd, int type, void *data); +int cthelper_init(void); + /* conntrackd ctnl modes */ extern struct ct_mode sync_mode; extern struct ct_mode stats_mode; diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..02ff3df --- /dev/null +++ b/include/helper.h @@ -0,0 +1,104 @@ +#ifndef _CTD_HELPER_H_ +#define _CTD_HELPER_H_ + +#include +#include "linux_list.h" +#include "myct.h" + +#include + +struct pkt_buff; + +#define CTD_HELPER_NAME_LEN 16 +#define CTD_HELPER_POLICY_MAX 4 + +struct ctd_helper_policy { + char name[CTD_HELPER_NAME_LEN]; + uint32_t expect_timeout; + uint32_t expect_max; +}; + +struct ctd_helper { + struct list_head head; + char name[CTD_HELPER_NAME_LEN]; + uint8_t l4proto; + int (*cb)(struct pkt_buff *pkt, + uint32_t protoff, + struct myct *ct, + u_int32_t ctinfo); + + struct ctd_helper_policy policy[CTD_HELPER_POLICY_MAX]; + + int priv_data_len; +}; + +struct ctd_helper_instance { + struct list_head head; + uint32_t queue_num; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + struct ctd_helper_policy policy[CTD_HELPER_POLICY_MAX]; +}; + +extern int cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, uint32_t class, union nfct_attr_grp_addr *saddr, union nfct_attr_grp_addr *daddr, uint8_t l4proto, uint16_t *sport, uint16_t *dport, uint32_t flags); +extern int cthelper_add_expect(struct nf_expect *exp); +extern int cthelper_del_expect(struct nf_expect *exp); + +extern void cthelper_get_addr_src(struct nf_conntrack *ct, int dir, union nfct_attr_grp_addr *addr); +extern void cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, union nfct_attr_grp_addr *addr); + +extern int in4_pton(const char *src, int srclen, uint8_t *dst, int delim, const char **end); +extern int in6_pton(const char *src, int srclen, uint8_t *dst, int delim, const char **end); + +extern void helper_register(struct ctd_helper *helper); +struct ctd_helper *helper_find(const char *libdir_path, const char *name, uint8_t l4proto, int flags); + +#define min_t(type, x, y) ({ \ + type __min1 = (x); \ + type __min2 = (y); \ + __min1 < __min2 ? __min1: __min2; }) + +#define max_t(type, x, y) ({ \ + type __max1 = (x); \ + type __max2 = (y); \ + __max1 > __max2 ? __max1: __max2; }) + +#define ARRAY_SIZE MNL_ARRAY_SIZE + +enum ip_conntrack_dir { + IP_CT_DIR_ORIGINAL, + IP_CT_DIR_REPLY, + IP_CT_DIR_MAX +}; + +/* Connection state tracking for netfilter. This is separated from, + but required by, the NAT layer; it can also be used by an iptables + extension. */ +enum ip_conntrack_info { + /* Part of an established connection (either direction). */ + IP_CT_ESTABLISHED, + + /* Like NEW, but related to an existing connection, or ICMP error + (in either direction). */ + IP_CT_RELATED, + + /* Started a new connection to track (only + IP_CT_DIR_ORIGINAL); may be a retransmission. */ + IP_CT_NEW, + + /* >= this indicates reply direction */ + IP_CT_IS_REPLY, + + IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY, + IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY, + IP_CT_NEW_REPLY = IP_CT_NEW + IP_CT_IS_REPLY, + /* Number of distinct IP_CT types (no NEW in reply dirn). */ + IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1 +}; + +#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL) + +#define pr_debug printf + +#endif diff --git a/include/linux/netfilter/Makefile.am b/include/linux/netfilter/Makefile.am index 84315e3..6574060 100644 --- a/include/linux/netfilter/Makefile.am +++ b/include/linux/netfilter/Makefile.am @@ -1 +1 @@ -noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h +noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h nfnetlink_queue.h nfnetlink_cthelper.h diff --git a/include/linux/netfilter/nfnetlink_cthelper.h b/include/linux/netfilter/nfnetlink_cthelper.h new file mode 100644 index 0000000..33659f6 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_cthelper.h @@ -0,0 +1,55 @@ +#ifndef _NFNL_CTHELPER_H_ +#define _NFNL_CTHELPER_H_ + +#define NFCT_HELPER_STATUS_DISABLED 0 +#define NFCT_HELPER_STATUS_ENABLED 1 + +enum nfnl_acct_msg_types { + NFNL_MSG_CTHELPER_NEW, + NFNL_MSG_CTHELPER_GET, + NFNL_MSG_CTHELPER_DEL, + NFNL_MSG_CTHELPER_MAX +}; + +enum nfnl_cthelper_type { + NFCTH_UNSPEC, + NFCTH_NAME, + NFCTH_TUPLE, + NFCTH_QUEUE_NUM, + NFCTH_POLICY, + NFCTH_PRIV_DATA_LEN, + NFCTH_STATUS, + __NFCTH_MAX +}; +#define NFCTH_MAX (__NFCTH_MAX - 1) + +enum nfnl_cthelper_policy_type { + NFCTH_POLICY_SET_UNSPEC, + NFCTH_POLICY_SET_NUM, + NFCTH_POLICY_SET, + NFCTH_POLICY_SET1 = NFCTH_POLICY_SET, + NFCTH_POLICY_SET2, + NFCTH_POLICY_SET3, + NFCTH_POLICY_SET4, + __NFCTH_POLICY_SET_MAX +}; +#define NFCTH_POLICY_SET_MAX (__NFCTH_POLICY_SET_MAX - 1) + +enum nfnl_cthelper_pol_type { + NFCTH_POLICY_UNSPEC, + NFCTH_POLICY_NAME, + NFCTH_POLICY_EXPECT_MAX, + NFCTH_POLICY_EXPECT_TIMEOUT, + __NFCTH_POLICY_MAX +}; +#define NFCTH_POLICY_MAX (__NFCTH_POLICY_MAX - 1) + +enum nfnl_cthelper_tuple_type { + NFCTH_TUPLE_UNSPEC, + NFCTH_TUPLE_L3PROTONUM, + NFCTH_TUPLE_L4PROTONUM, + __NFCTH_TUPLE_MAX, +}; +#define NFCTH_TUPLE_MAX (__NFCTH_TUPLE_MAX - 1) + +#endif /* _NFNL_CTHELPER_H */ diff --git a/include/linux/netfilter/nfnetlink_queue.h b/include/linux/netfilter/nfnetlink_queue.h new file mode 100644 index 0000000..e0d8fd8 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_queue.h @@ -0,0 +1,99 @@ +#ifndef _NFNETLINK_QUEUE_H +#define _NFNETLINK_QUEUE_H + +#include +#include + +enum nfqnl_msg_types { + NFQNL_MSG_PACKET, /* packet from kernel to userspace */ + NFQNL_MSG_VERDICT, /* verdict from userspace to kernel */ + NFQNL_MSG_CONFIG, /* connect to a particular queue */ + NFQNL_MSG_VERDICT_BATCH, /* batchv from userspace to kernel */ + + NFQNL_MSG_MAX +}; + +struct nfqnl_msg_packet_hdr { + __be32 packet_id; /* unique ID of packet in queue */ + __be16 hw_protocol; /* hw protocol (network order) */ + __u8 hook; /* netfilter hook */ +} __attribute__ ((packed)); + +struct nfqnl_msg_packet_hw { + __be16 hw_addrlen; + __u16 _pad; + __u8 hw_addr[8]; +}; + +struct nfqnl_msg_packet_timestamp { + __aligned_be64 sec; + __aligned_be64 usec; +}; + +enum nfqnl_attr_type { + NFQA_UNSPEC, + NFQA_PACKET_HDR, + NFQA_VERDICT_HDR, /* nfqnl_msg_verdict_hrd */ + NFQA_MARK, /* __u32 nfmark */ + NFQA_TIMESTAMP, /* nfqnl_msg_packet_timestamp */ + NFQA_IFINDEX_INDEV, /* __u32 ifindex */ + NFQA_IFINDEX_OUTDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSINDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSOUTDEV, /* __u32 ifindex */ + NFQA_HWADDR, /* nfqnl_msg_packet_hw */ + NFQA_PAYLOAD, /* opaque data payload */ + NFQA_CT, /* nf_conntrack_netlink.h */ + NFQA_CT_INFO, /* enum ip_conntrack_info */ + + __NFQA_MAX +}; +#define NFQA_MAX (__NFQA_MAX - 1) + +struct nfqnl_msg_verdict_hdr { + __be32 verdict; + __be32 id; +}; + + +enum nfqnl_msg_config_cmds { + NFQNL_CFG_CMD_NONE, + NFQNL_CFG_CMD_BIND, + NFQNL_CFG_CMD_UNBIND, + NFQNL_CFG_CMD_PF_BIND, + NFQNL_CFG_CMD_PF_UNBIND, +}; + +struct nfqnl_msg_config_cmd { + __u8 command; /* nfqnl_msg_config_cmds */ + __u8 _pad; + __be16 pf; /* AF_xxx for PF_[UN]BIND */ +}; + +enum nfqnl_config_mode { + NFQNL_COPY_NONE, + NFQNL_COPY_META, + NFQNL_COPY_PACKET, +}; + +struct nfqnl_msg_config_params { + __be32 copy_range; + __u8 copy_mode; /* enum nfqnl_config_mode */ +} __attribute__ ((packed)); + + +enum nfqnl_attr_config { + NFQA_CFG_UNSPEC, + NFQA_CFG_CMD, /* nfqnl_msg_config_cmd */ + NFQA_CFG_PARAMS, /* nfqnl_msg_config_params */ + NFQA_CFG_QUEUE_MAXLEN, /* __u32 */ + NFQA_CFG_MASK, /* identify which flags to change */ + NFQA_CFG_FLAGS, /* value of these flags (__u32) */ + __NFQA_CFG_MAX +}; +#define NFQA_CFG_MAX (__NFQA_CFG_MAX-1) + +/* Flags for NFQA_CFG_FLAGS */ +#define NFQA_CFG_F_FAIL_OPEN (1 << 0) +#define NFQA_CFG_F_CONNTRACK (1 << 1) + +#endif /* _NFNETLINK_QUEUE_H */ diff --git a/include/myct.h b/include/myct.h new file mode 100644 index 0000000..45d9f29 --- /dev/null +++ b/include/myct.h @@ -0,0 +1,43 @@ +#ifndef _MYCT_H_ +#define _MYCT_H_ + +#include "linux_list.h" + +#include + +struct nf_conntrack; + +enum { + MYCT_NONE = 0, + MYCT_ESTABLISHED = (1 << 0), +}; + +enum { + MYCT_DIR_ORIG = 0, + MYCT_DIR_REPL, + MYCT_DIR_MAX, +}; + +union myct_proto { + uint16_t port; + uint16_t all; +}; + +struct myct_man { + union nfct_attr_grp_addr u3; + union myct_proto u; + uint16_t l3num; + uint8_t protonum; +}; + +struct myct_tuple { + struct myct_man src; + struct myct_man dst; +}; + +struct myct { + struct nf_conntrack *ct; + void *priv_data; +}; + +#endif diff --git a/include/nfct.h b/include/nfct.h index d6271cf..5548b03 100644 --- a/include/nfct.h +++ b/include/nfct.h @@ -4,6 +4,7 @@ enum { NFCT_SUBSYS_NONE = 0, NFCT_SUBSYS_TIMEOUT, + NFCT_SUBSYS_HELPER, NFCT_SUBSYS_VERSION, NFCT_SUBSYS_HELP, }; @@ -15,6 +16,7 @@ enum { NFCT_CMD_DELETE, NFCT_CMD_GET, NFCT_CMD_FLUSH, + NFCT_CMD_DISABLE, }; void nfct_perror(const char *msg); @@ -26,4 +28,12 @@ int nfct_cmd_timeout_delete(int argc, char *argv[]); int nfct_cmd_timeout_get(int argc, char *argv[]); int nfct_cmd_timeout_flush(int argc, char *argv[]); +int nfct_cmd_helper_parse_params(int argc, char *argv[]); +int nfct_cmd_helper_list(int argc, char *argv[]); +int nfct_cmd_helper_add(int argc, char *argv[]); +int nfct_cmd_helper_delete(int argc, char *argv[]); +int nfct_cmd_helper_get(int argc, char *argv[]); +int nfct_cmd_helper_flush(int argc, char *argv[]); +int nfct_cmd_helper_disable(int argc, char *argv[]); + #endif diff --git a/include/stack.h b/include/stack.h new file mode 100644 index 0000000..512a30f --- /dev/null +++ b/include/stack.h @@ -0,0 +1,28 @@ +#ifndef _STACK_H_ +#define _STACK_H_ + +#include "linux_list.h" + +struct stack { + struct list_head list; + int items; +}; + +static inline void stack_init(struct stack *s) +{ + INIT_LIST_HEAD(&s->list); +} + +struct stack_item { + struct list_head head; + int type; + int data_len; + char data[0]; +}; + +struct stack_item *stack_item_alloc(int type, size_t data_len); +void stack_item_free(struct stack_item *e); +void stack_item_push(struct stack *s, struct stack_item *e); +struct stack_item *stack_item_pop(struct stack *s, int type); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index fa4ab75..d8074d2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,7 @@ include $(top_srcdir)/Make_global.am +SUBDIRS = helpers + AM_YFLAGS = -d CLEANFILES = read_config_yy.c read_config_lex.c @@ -10,17 +12,24 @@ conntrack_SOURCES = conntrack.c conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} ${LIBMNL_LIBS} nfct_SOURCES = nfct.c \ - nfct-extensions/timeout.c + helpers.c \ + nfct-extensions/timeout.c \ + nfct-extensions/helper.c + nfct_LDADD = ${LIBMNL_LIBS} \ ${LIBNETFILTER_CONNTRACK_LIBS} \ - ${LIBNETFILTER_CTTIMEOUT_LIBS} + ${LIBNETFILTER_CTTIMEOUT_LIBS} \ + ${LIBNETFILTER_CTHELPER_LIBS} \ + ${libdl_LIBS} + +nfct_LDFLAGS = -export-dynamic conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ local.c log.c mcast.c udp.c netlink.c vector.c \ filter.c fds.c event.c process.c origin.c date.c \ cache.c cache-ct.c cache-exp.c \ cache_timer.c \ - ctnl.c \ + ctnl.c cthelper.c \ sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \ traffic_stats.c stats-mode.c \ network.c cidr.c \ @@ -29,11 +38,16 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ tcp.c channel_tcp.c \ external_cache.c external_inject.c \ internal_cache.c internal_bypass.c \ - read_config_yy.y read_config_lex.l + read_config_yy.y read_config_lex.l \ + stack.c helpers.c utils.c expect.c # yacc and lex generate dirty code read_config_yy.o read_config_lex.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls -conntrackd_LDADD = ${LIBNETFILTER_CONNTRACK_LIBS} +conntrackd_LDADD = ${LIBMNL_LIBS} ${LIBNETFILTER_CONNTRACK_LIBS} \ + ${LIBNETFILTER_QUEUE_LIBS} ${LIBNETFILTER_CTHELPER_LIBS} \ + ${libdl_LIBS} + +conntrackd_LDFLAGS = -export-dynamic EXTRA_DIST = read_config_yy.h diff --git a/src/cthelper.c b/src/cthelper.c new file mode 100644 index 0000000..c119869 --- /dev/null +++ b/src/cthelper.c @@ -0,0 +1,521 @@ +/* + * (C) 2006-2012 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. + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "conntrackd.h" +#include "log.h" +#include "fds.h" +#include "helper.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef __aligned_be64 +#define __aligned_be64 unsigned long long __attribute__((aligned(8))) +#endif + +#include + +#include +#include +#include +#include +#include +#include + +void cthelper_kill(void) +{ + mnl_socket_close(STATE_CTH(nl)); + free(state.cthelper); +} + +int cthelper_local(int fd, int type, void *data) +{ + /* No services to obtain information on helpers yet, sorry */ + return LOCAL_RET_OK; +} + +static struct nlmsghdr * +nfq_hdr_put(char *buf, int type, uint32_t queue_num) +{ + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | type; + nlh->nlmsg_flags = NLM_F_REQUEST; + + struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_UNSPEC; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(queue_num); + + return nlh; +} + +static int +pkt_get(void *pkt, uint32_t pktlen, uint16_t proto, uint32_t *protoff) +{ + switch(proto) { + case ETHERTYPE_IP: { + struct iphdr *ip = (struct iphdr *) pkt; + + /* No room for IPv4 header. */ + if (pktlen < sizeof(struct iphdr)) { + dlog(LOG_ERR, "no room for IPv4 header"); + return -1; + } + + /* this is not IPv4, skip. */ + if (ip->version != 4) { + dlog(LOG_ERR, "not IPv4, skipping"); + return -1; + } + + *protoff = 4 * ip->ihl; + + switch (ip->protocol) { + case IPPROTO_TCP: { + struct tcphdr *tcph = + (struct tcphdr *) ((char *)pkt + *protoff); + + /* No room for IPv4 header plus TCP header. */ + if (pktlen < *protoff + sizeof(struct tcphdr) + || pktlen < *protoff + tcph->doff * 4) { + dlog(LOG_ERR, "no room for IPv4 + TCP header, skip"); + return -1; + } + return 0; + } + case IPPROTO_UDP: + /* No room for IPv4 header plus UDP header. */ + if (pktlen < *protoff + sizeof(struct udphdr)) { + dlog(LOG_ERR, "no room for IPv4 + UDP header, skip"); + return -1; + } + return 0; + default: + dlog(LOG_ERR, "not TCP/UDP, skipping"); + return -1; + } + break; + } + case ETHERTYPE_IPV6: + dlog(LOG_ERR, "no IPv6 support sorry"); + return 0; + default: + /* Unknown layer 3 protocol. */ + dlog(LOG_ERR, "unknown layer 3 protocol (%d), skipping", proto); + return -1; + } + return 0; +} + +static int +pkt_verdict_issue(struct ctd_helper_instance *cur, struct myct *myct, + uint16_t queue_num, uint32_t id, uint32_t verdict, + struct pkt_buff *pktb) +{ + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlattr *nest; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num); + + /* save private data and send it back to kernel-space. */ + nfct_set_attr_l(myct->ct, ATTR_HELPER_INFO, myct->priv_data, + cur->helper->priv_data_len); + + nfq_nlmsg_verdict_put(nlh, id, verdict); + if (pktb_mangled(pktb)) + nfq_nlmsg_verdict_put_pkt(nlh, pktb_data(pktb), pktb_len(pktb)); + + nest = mnl_attr_nest_start(nlh, NFQA_CT); + if (nest == NULL) + return -1; + + nfct_nlmsg_build(nlh, myct->ct); + mnl_attr_nest_end(nlh, nest); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static int +pkt_verdict_error(uint16_t queue_num, uint32_t id) +{ + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num); + nfq_nlmsg_verdict_put(nlh, id, NF_ACCEPT); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); + return -1; + } + return 0; +} + +static struct ctd_helper_instance * +helper_run(struct pkt_buff *pktb, uint32_t protoff, struct myct *myct, + uint32_t ctinfo, uint32_t queue_num, int *verdict) +{ + struct ctd_helper_instance *cur, *helper = NULL; + + list_for_each_entry(cur, &CONFIG(cthelper).list, head) { + if (cur->queue_num == queue_num) { + const void *priv_data; + + /* retrieve helper private data. */ + priv_data = nfct_get_attr(myct->ct, ATTR_HELPER_INFO); + if (priv_data != NULL) { + myct->priv_data = + calloc(1, cur->helper->priv_data_len); + + if (myct->priv_data == NULL) + continue; + + memcpy(myct->priv_data, priv_data, + cur->helper->priv_data_len); + } + + *verdict = cur->helper->cb(pktb, protoff, myct, ctinfo); + helper = cur; + break; + } + } + return helper; +} + +static int nfq_queue_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfqnl_msg_packet_hdr *ph = NULL; + struct nlattr *attr[NFQA_MAX+1] = {}; + struct nfgenmsg *nfg; + uint8_t *pkt; + uint16_t l3num; + uint32_t id, ctinfo, queue_num = 0, protoff = 0, pktlen; + struct nf_conntrack *ct = NULL; + struct myct *myct; + struct ctd_helper_instance *helper; + struct pkt_buff *pktb; + int verdict = NF_ACCEPT; + + if (nfq_nlmsg_parse(nlh, attr) < 0) { + dlog(LOG_ERR, "problems parsing message from kernel"); + return MNL_CB_ERROR; + } + + ph = (struct nfqnl_msg_packet_hdr *) + mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); + if (ph == NULL) { + dlog(LOG_ERR, "problems retrieving metaheader"); + return MNL_CB_ERROR; + } + + id = ntohl(ph->packet_id); + + if (!attr[NFQA_PAYLOAD]) { + dlog(LOG_ERR, "packet with no payload"); + goto err; + } + if (!attr[NFQA_CT] || !attr[NFQA_CT_INFO]) { + dlog(LOG_ERR, "no CT attached to this packet"); + goto err; + } + + pkt = mnl_attr_get_payload(attr[NFQA_PAYLOAD]); + pktlen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]); + + nfg = mnl_nlmsg_get_payload(nlh); + l3num = nfg->nfgen_family; + queue_num = ntohs(nfg->res_id); + + if (pkt_get(pkt, pktlen, ntohs(ph->hw_protocol), &protoff)) + goto err; + + ct = nfct_new(); + if (ct == NULL) + goto err; + + if (nfct_payload_parse(mnl_attr_get_payload(attr[NFQA_CT]), + mnl_attr_get_payload_len(attr[NFQA_CT]), + l3num, ct) < 0) { + dlog(LOG_ERR, "cannot convert message to CT"); + goto err; + } + + myct = calloc(1, sizeof(struct myct)); + if (myct == NULL) + goto err; + + myct->ct = ct; + ctinfo = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO])); + + /* XXX: 256 bytes enough for possible NAT mangling in helpers? */ + pktb = pktb_alloc(AF_INET, pkt, pktlen, 256); + if (pktb == NULL) + goto err; + + /* Misconfiguration: if no helper found, accept the packet. */ + helper = helper_run(pktb, protoff, myct, ctinfo, queue_num, &verdict); + if (!helper) + goto err_pktb; + + if (pkt_verdict_issue(helper, myct, queue_num, id, verdict, pktb) < 0) + goto err_pktb; + + if (ct != NULL) + nfct_destroy(ct); + if (myct && myct->priv_data != NULL) + free(myct->priv_data); + if (myct != NULL) + free(myct); + + return MNL_CB_OK; +err_pktb: + pktb_free(pktb); +err: + /* In case of error, we don't want to disrupt traffic. We accept all. + * This is connection tracking after all. The policy is not to drop + * packet unless we enter some inconsistent state. + */ + pkt_verdict_error(queue_num, id); + + if (ct != NULL) + nfct_destroy(ct); + + return MNL_CB_OK; +} + +static void nfq_cb(void *data) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret; + + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + if (ret == -1) { + dlog(LOG_ERR, "failed to receive message: %s", strerror(errno)); + return; + } + + ret = mnl_cb_run(buf, ret, 0, STATE_CTH(portid), nfq_queue_cb, NULL); + if (ret < 0){ + dlog(LOG_ERR, "failed to process message"); + return; + } +} + +static int cthelper_setup(struct ctd_helper_instance *cur) +{ + struct nfct_helper *t; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t seq; + int j, ret; + + t = nfct_helper_alloc(); + if (t == NULL) { + dlog(LOG_ERR, "cannot allocate object for helper"); + return -1; + } + + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, cur->helper->name); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_QUEUE_NUM, cur->queue_num); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, cur->l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, cur->l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, + NFCT_HELPER_STATUS_ENABLED); + + dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d", + cur->helper->name, cur->queue_num); + + for (j=0; jhelper->policy[j].name[0]) + break; + + p = nfct_helper_policy_alloc(); + if (p == NULL) { + dlog(LOG_ERR, "cannot allocate object for helper"); + return -1; + } + /* FIXME: get existing policy values from the kernel first. */ + nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, + cur->helper->policy[j].name); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, + cur->helper->policy[j].expect_timeout); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, + cur->helper->policy[j].expect_max); + + dlog(LOG_NOTICE, "policy name=%s expect_timeout=%d expect_max=%d", + cur->helper->policy[j].name, + cur->helper->policy[j].expect_timeout, + cur->helper->policy[j].expect_max); + + nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); + } + + if (j == 0) { + dlog(LOG_ERR, "you have to define one policy for helper"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "sending cthelper configuration"); + return -1; + } + + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, STATE_CTH(portid), NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + } + if (ret == -1) { + dlog(LOG_ERR, "trying to configure cthelper `%s': %s", + cur->helper->name, strerror(errno)); + return -1; + } + + return 0; +} + +static int cthelper_nfqueue_setup(struct ctd_helper_instance *cur) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_BIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send bind command"); + return -1; + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num); + nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); + mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_CONNTRACK)); + mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(0xffffffff)); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send configuration"); + return -1; + } + + return 0; +} + +static int cthelper_configure(struct ctd_helper_instance *cur) +{ + /* First, configure cthelper. */ + if (cthelper_setup(cur) < 0) + return -1; + + /* Now, we are ready to configure nfqueue attached to this helper. */ + if (cthelper_nfqueue_setup(cur) < 0) + return -1; + + dlog(LOG_NOTICE, "helper `%s' configured successfully", + cur->helper->name); + + return 0; +} + +static int nfq_configure(void) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_UNBIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send pf unbind command"); + return -1; + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_BIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send pf bind command"); + return -1; + } + + return 0; +} + +int cthelper_init(void) +{ + struct ctd_helper_instance *cur; + int ret; + + state.cthelper = calloc(1, sizeof(struct ct_helper_state)); + if (state.cthelper == NULL) { + dlog(LOG_ERR, "can't allocate memory for cthelper struct"); + return -1; + } + + STATE_CTH(nl) = mnl_socket_open(NETLINK_NETFILTER); + if (STATE_CTH(nl) == NULL) { + dlog(LOG_ERR, "cannot open nfq socket"); + return -1; + } + fcntl(mnl_socket_get_fd(STATE_CTH(nl)), F_SETFL, O_NONBLOCK); + + if (mnl_socket_bind(STATE_CTH(nl), 0, MNL_SOCKET_AUTOPID) < 0) { + dlog(LOG_ERR, "cannot bind nfq socket"); + return -1; + } + STATE_CTH(portid) = mnl_socket_get_portid(STATE_CTH(nl)); + + if (nfq_configure()) + return -1; + + list_for_each_entry(cur, &CONFIG(cthelper).list, head) { + ret = cthelper_configure(cur); + if (ret < 0) + return ret; + } + + register_fd(mnl_socket_get_fd(STATE_CTH(nl)), nfq_cb, NULL, STATE(fds)); + + return 0; +} diff --git a/src/expect.c b/src/expect.c new file mode 100644 index 0000000..6069770 --- /dev/null +++ b/src/expect.c @@ -0,0 +1,214 @@ +/* + * (C) 2012 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 version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int +cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, + uint32_t class, + union nfct_attr_grp_addr *saddr, + union nfct_attr_grp_addr *daddr, + uint8_t l4proto, uint16_t *sport, uint16_t *dport, + uint32_t flags) +{ + struct nf_conntrack *expected, *mask; + + expected = nfct_new(); + if (!expected) + return -1; + + mask = nfct_new(); + if (!mask) + return -1; + + if (saddr) { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } else { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000); + break; + case AF_INET6: + for (i=0; i<4; i++) + memset(addr, 0x00000000, sizeof(uint32_t)); + + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, addr); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } + + if (sport) { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff); + break; + default: + break; + } + } else { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000); + break; + default: + break; + } + } + + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + uint32_t addr[4] = {}; + int i; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip); + nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr(mask, ATTR_IPV6_DST, addr); + break; + default: + break; + } + + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff); + break; + default: + break; + } + + nfexp_set_attr(exp, ATTR_EXP_MASTER, master); + nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected); + nfexp_set_attr(exp, ATTR_EXP_MASK, mask); + nfexp_set_attr_u32(exp, ATTR_EXP_FLAGS, flags); + + nfct_destroy(expected); + nfct_destroy(mask); + + return 0; +} + +static int cthelper_expect_cmd(struct nf_expect *exp, int cmd) +{ + int ret; + struct nfct_handle *h; + + h = nfct_open(EXPECT, 0); + if (!h) + return -1; + + ret = nfexp_query(h, cmd, exp); + + nfct_close(h); + return ret; +} + +int cthelper_add_expect(struct nf_expect *exp) +{ + return cthelper_expect_cmd(exp, NFCT_Q_CREATE_UPDATE); +} + +int cthelper_del_expect(struct nf_expect *exp) +{ + return cthelper_expect_cmd(exp, NFCT_Q_DESTROY); +} + +void +cthelper_get_addr_src(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_SRC, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_SRC, addr); + break; + } +} + +void +cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_DST, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_DST, addr); + break; + } +} diff --git a/src/helpers.c b/src/helpers.c new file mode 100644 index 0000000..3e4e6c8 --- /dev/null +++ b/src/helpers.c @@ -0,0 +1,76 @@ +/* + * (C) 2012 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 version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(helper_list); + +void helper_register(struct ctd_helper *helper) +{ + list_add(&helper->head, &helper_list); +} + +static struct ctd_helper * +__helper_find(const char *helper_name, uint8_t l4proto) +{ + struct ctd_helper *cur, *helper = NULL; + + list_for_each_entry(cur, &helper_list, head) { + if (strncmp(cur->name, helper_name, CTD_HELPER_NAME_LEN) != 0) + continue; + + if (cur->l4proto != l4proto) + continue; + + helper = cur; + break; + } + return helper; +} + +struct ctd_helper * +helper_find(const char *libdir_path, + const char *helper_name, uint8_t l4proto, int flag) +{ + char path[PATH_MAX]; + struct ctd_helper *helper; + struct stat sb; + + helper = __helper_find(helper_name, l4proto); + if (helper != NULL) + return helper; + + snprintf(path, sizeof(path), "%s/ct_helper_%s.so", + libdir_path, helper_name); + + if (stat(path, &sb) != 0) { + if (errno == ENOENT) + return NULL; + fprintf(stderr, "%s: %s\n", path, + strerror(errno)); + return NULL; + } + + if (dlopen(path, flag) == NULL) { + fprintf(stderr, "%s: %s\n", path, dlerror()); + return NULL; + } + + return __helper_find(helper_name, l4proto); +} diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am new file mode 100644 index 0000000..2c9d63b --- /dev/null +++ b/src/helpers/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/Make_global.am + +pkglib_LTLIBRARIES = ct_helper_ftp.la + +ct_helper_ftp_la_SOURCES = ftp.c +ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + + diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c new file mode 100644 index 0000000..962020b --- /dev/null +++ b/src/helpers/ftp.c @@ -0,0 +1,599 @@ +/* + * (C) 2010-2012 by Pablo Neira Ayuso + * + * Based on: kernel-space FTP extension for connection tracking. + * + * This port has been sponsored by Vyatta Inc. + * + * Original copyright notice: + * + * (C) 1999-2001 Paul `Rusty' Russell + * (C) 2002-2004 Netfilter Core Team + * (C) 2003,2004 USAGI/WIDE Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include /* for isdigit */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +static bool loose; /* XXX: export this as config option. */ + +#define NUM_SEQ_TO_REMEMBER 2 + +/* This structure exists only once per master */ +struct ftp_info { + /* Valid seq positions for cmd matching after newline */ + uint32_t seq_aft_nl[MYCT_DIR_MAX][NUM_SEQ_TO_REMEMBER]; + /* 0 means seq_match_aft_nl not set */ + int seq_aft_nl_num[MYCT_DIR_MAX]; +}; + +enum nf_ct_ftp_type { + /* PORT command from client */ + NF_CT_FTP_PORT, + /* PASV response from server */ + NF_CT_FTP_PASV, + /* EPRT command from client */ + NF_CT_FTP_EPRT, + /* EPSV response from server */ + NF_CT_FTP_EPSV, +}; + +static int +get_ipv6_addr(const char *src, size_t dlen, struct in6_addr *dst, u_int8_t term) +{ + const char *end; + int ret = in6_pton(src, min_t(size_t, dlen, 0xffff), + (uint8_t *)dst, term, &end); + if (ret > 0) + return (int)(end - src); + return 0; +} + +static int try_number(const char *data, size_t dlen, uint32_t array[], + int array_size, char sep, char term) +{ + uint32_t len; + int i; + + memset(array, 0, sizeof(array[0])*array_size); + + /* Keep data pointing at next char. */ + for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { + if (*data >= '0' && *data <= '9') { + array[i] = array[i]*10 + *data - '0'; + } + else if (*data == sep) + i++; + else { + /* Unexpected character; true if it's the + terminator and we're finished. */ + if (*data == term && i == array_size - 1) + return len; + pr_debug("Char %u (got %u nums) `%u' unexpected\n", + len, i, *data); + return 0; + } + } + pr_debug("Failed to fill %u numbers separated by %c\n", + array_size, sep); + return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, int start, size_t dlen, char delim, + struct myct_man *cmd) +{ + uint16_t tmp_port = 0; + uint32_t i; + + for (i = start; i < dlen; i++) { + /* Finished? */ + if (data[i] == delim) { + if (tmp_port == 0) + break; + cmd->u.port = htons(tmp_port); + pr_debug("get_port: return %d\n", tmp_port); + return i + 1; + } + else if (data[i] >= '0' && data[i] <= '9') + tmp_port = tmp_port*10 + data[i] - '0'; + else { /* Some other crap */ + pr_debug("get_port: invalid char.\n"); + break; + } + } + return 0; +} + +/* Returns 0, or length of numbers: 192,168,1,1,5,6 */ +static int try_rfc959(const char *data, size_t dlen, struct myct_man *cmd, + uint16_t l3protonum, char term) +{ + int length; + uint32_t array[6]; + + length = try_number(data, dlen, array, 6, ',', term); + if (length == 0) + return 0; + + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | + (array[2] << 8) | array[3]); + cmd->u.port = htons((array[4] << 8) | array[5]); + return length; +} + +/* Returns 0, or length of numbers: |1|132.235.1.2|6275| or |2|3ffe::1|6275| */ +static int try_eprt(const char *data, size_t dlen, + struct myct_man *cmd, uint16_t l3protonum, char term) +{ + char delim; + int length; + + /* First character is delimiter, then "1" for IPv4 or "2" for IPv6, + then delimiter again. */ + if (dlen <= 3) { + pr_debug("EPRT: too short\n"); + return 0; + } + delim = data[0]; + if (isdigit(delim) || delim < 33 || delim > 126 || data[2] != delim) { + pr_debug("try_eprt: invalid delimitter.\n"); + return 0; + } + + if ((l3protonum == PF_INET && data[1] != '1') || + (l3protonum == PF_INET6 && data[1] != '2')) { + pr_debug("EPRT: invalid protocol number.\n"); + return 0; + } + + pr_debug("EPRT: Got %c%c%c\n", delim, data[1], delim); + if (data[1] == '1') { + uint32_t array[4]; + + /* Now we have IP address. */ + length = try_number(data + 3, dlen - 3, array, 4, '.', delim); + if (length != 0) + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) + | (array[2] << 8) | array[3]); + } else { + /* Now we have IPv6 address. */ + length = get_ipv6_addr(data + 3, dlen - 3, + (struct in6_addr *)cmd->u3.ip6, delim); + } + + if (length == 0) + return 0; + pr_debug("EPRT: Got IP address!\n"); + /* Start offset includes initial "|1|", and trailing delimiter */ + return get_port(data, 3 + length + 1, dlen, delim, cmd); +} + +/* Returns 0, or length of numbers: |||6446| */ +static int try_epsv_response(const char *data, size_t dlen, + struct myct_man *cmd, + uint16_t l3protonum, char term) +{ + char delim; + + /* Three delimiters. */ + if (dlen <= 3) return 0; + delim = data[0]; + if (isdigit(delim) || delim < 33 || delim > 126 || + data[1] != delim || data[2] != delim) + return 0; + + return get_port(data, 3, dlen, delim, cmd); +} + +static struct ftp_search { + const char *pattern; + size_t plen; + char skip; + char term; + enum nf_ct_ftp_type ftptype; + int (*getnum)(const char *, size_t, struct myct_man *, uint16_t, char); +} search[MYCT_DIR_MAX][2] = { + [MYCT_DIR_ORIG] = { + { + .pattern = "PORT", + .plen = sizeof("PORT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = NF_CT_FTP_PORT, + .getnum = try_rfc959, + }, + { + .pattern = "EPRT", + .plen = sizeof("EPRT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = NF_CT_FTP_EPRT, + .getnum = try_eprt, + }, + }, + [MYCT_DIR_REPL] = { + { + .pattern = "227 ", + .plen = sizeof("227 ") - 1, + .skip = '(', + .term = ')', + .ftptype = NF_CT_FTP_PASV, + .getnum = try_rfc959, + }, + { + .pattern = "229 ", + .plen = sizeof("229 ") - 1, + .skip = '(', + .term = ')', + .ftptype = NF_CT_FTP_EPSV, + .getnum = try_epsv_response, + }, + }, +}; + +static int ftp_find_pattern(struct pkt_buff *pkt, + unsigned int dataoff, unsigned int dlen, + const char *pattern, size_t plen, + char skip, char term, + unsigned int *matchoff, unsigned int *matchlen, + struct myct_man *cmd, + int (*getnum)(const char *, size_t, + struct myct_man *cmd, + uint16_t, char), + int dir) +{ + char *data = (char *)pktb_network_header(pkt) + dataoff; + int numlen; + uint32_t i; + + if (dlen == 0) + return 0; + + /* short packet, skip partial matching. */ + if (dlen <= plen) + return 0; + + if (strncmp(data, pattern, plen) != 0) + return 0; + + pr_debug("Pattern matches!\n"); + + /* Now we've found the constant string, try to skip + to the 'skip' character */ + for (i = plen; data[i] != skip; i++) + if (i == dlen - 1) return 0; + + /* Skip over the last character */ + i++; + + pr_debug("Skipped up to `%c'!\n", skip); + + numlen = getnum(data + i, dlen - i, cmd, PF_INET, term); + if (!numlen) + return 0; + + pr_debug("Match succeded!\n"); + return 1; +} + +/* Look up to see if we're just after a \n. */ +static int find_nl_seq(uint32_t seq, struct ftp_info *info, int dir) +{ + int i; + + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) + if (info->seq_aft_nl[dir][i] == seq) + return 1; + return 0; +} + +/* We don't update if it's older than what we have. */ +static void update_nl_seq(uint32_t nl_seq, struct ftp_info *info, int dir) +{ + int i, oldest; + + /* Look for oldest: if we find exact match, we're done. */ + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) { + if (info->seq_aft_nl[dir][i] == nl_seq) + return; + } + + if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) { + info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq; + } else { + if (before(info->seq_aft_nl[dir][0], info->seq_aft_nl[dir][1])) + oldest = 0; + else + oldest = 1; + + if (after(nl_seq, info->seq_aft_nl[dir][oldest])) + info->seq_aft_nl[dir][oldest] = nl_seq; + } +} + +static int nf_nat_ftp_fmt_cmd(enum nf_ct_ftp_type type, + char *buffer, size_t buflen, + uint32_t addr, uint16_t port) +{ + switch (type) { + case NF_CT_FTP_PORT: + case NF_CT_FTP_PASV: + return snprintf(buffer, buflen, "%u,%u,%u,%u,%u,%u", + ((unsigned char *)&addr)[0], + ((unsigned char *)&addr)[1], + ((unsigned char *)&addr)[2], + ((unsigned char *)&addr)[3], + port >> 8, + port & 0xFF); + case NF_CT_FTP_EPRT: + return snprintf(buffer, buflen, "|1|%pI4|%u|", &addr, port); + case NF_CT_FTP_EPSV: + return snprintf(buffer, buflen, "|||%u|", port); + } + + return 0; +} + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int nf_nat_ftp(struct pkt_buff *pkt, + int dir, + int ctinfo, + enum nf_ct_ftp_type type, + unsigned int matchoff, + unsigned int matchlen, + struct nf_conntrack *ct, + struct nf_expect *exp) +{ + union nfct_attr_grp_addr newip; + uint16_t port; + char buffer[sizeof("|1|255.255.255.255|65535|")]; + unsigned int buflen; + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port; + + pr_debug("FTP_NAT: type %i, off %u len %u\n", type, matchoff, matchlen); + + /* Connection will come from wherever this packet goes, hence !dir */ + cthelper_get_addr_dst(ct, !dir, &newip); + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + + if (port == 0) + return NF_DROP; + + buflen = nf_nat_ftp_fmt_cmd(type, buffer, sizeof(buffer), + newip.ip, port); + if (!buflen) + goto out; + + if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) + goto out; + + return NF_ACCEPT; + +out: + cthelper_del_expect(exp); + return NF_DROP; +} + +static int +ftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct tcphdr *th; + unsigned int dataoff; + unsigned int matchoff = 0, matchlen = 0; /* makes gcc happy. */ + unsigned int datalen; + unsigned int i; + int found = 0, ends_in_nl; + uint32_t seq; + int ret = NF_ACCEPT; + struct myct_man cmd; + union nfct_attr_grp_addr addr; + union nfct_attr_grp_addr daddr; + int dir = CTINFO2DIR(ctinfo); + struct ftp_info *ftp_info = myct->priv_data; + struct nf_expect *exp = NULL; + + memset(&cmd, 0, sizeof(struct myct_man)); + memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + + /* Until there's been traffic both ways, don't look in packets. */ + if (ctinfo != IP_CT_ESTABLISHED && + ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("ftp: Conntrackinfo = %u\n", ctinfo); + goto out; + } + + th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + + dataoff = protoff + th->doff * 4; + datalen = pktb_len(pkt) - dataoff; + + ends_in_nl = (pktb_network_header(pkt)[pktb_len(pkt) - 1] == '\n'); + seq = ntohl(th->seq) + datalen; + + /* Look up to see if we're just after a \n. */ + if (!find_nl_seq(ntohl(th->seq), ftp_info, dir)) { + /* Now if this ends in \n, update ftp info. */ + pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n", + ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)", + ftp_info->seq_aft_nl[dir][0], + ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)", + ftp_info->seq_aft_nl[dir][1]); + goto out_update_nl; + } + + /* Initialize IP/IPv6 addr to expected address (it's not mentioned + in EPSV responses) */ + cmd.l3num = nfct_get_attr_u16(myct->ct, ATTR_L3PROTO); + nfct_get_attr_grp(myct->ct, ATTR_GRP_ORIG_ADDR_SRC, &cmd.u3); + + for (i = 0; i < ARRAY_SIZE(search[dir]); i++) { + found = ftp_find_pattern(pkt, dataoff, datalen, + search[dir][i].pattern, + search[dir][i].plen, + search[dir][i].skip, + search[dir][i].term, + &matchoff, &matchlen, + &cmd, + search[dir][i].getnum, + dir); + if (found) break; + } + if (found == 0) /* No match */ + goto out_update_nl; + + pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n", + matchlen, pktb_network_header(pkt) + matchoff, + matchlen, ntohl(th->seq) + matchoff); + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_dst(myct->ct, !dir, &daddr); + + cthelper_get_addr_src(myct->ct, dir, &addr); + + /* Update the ftp info */ + if ((cmd.l3num == nfct_get_attr_u16(myct->ct, ATTR_L3PROTO)) && + memcmp(&cmd.u3, &addr, sizeof(addr)) != 0) { + /* Enrico Scholz's passive FTP to partially RNAT'd ftp + server: it really wants us to connect to a + different IP address. Simply don't record it for + NAT. */ + if (cmd.l3num == PF_INET) { + pr_debug("conntrack_ftp: NOT RECORDING: %pI4 != %pI4\n", + &cmd.u3.ip, &addr); + } else { + pr_debug("conntrack_ftp: NOT RECORDING: %pI6 != %pI6\n", + cmd.u3.ip6, &addr); + } + /* Thanks to Cristiano Lincoln Mattos + for reporting this potential + problem (DMZ machines opening holes to internal + networks, or the packet filter itself). */ + if (!loose) { + ret = NF_ACCEPT; + goto out; + } + memcpy(&daddr, &cmd.u3, sizeof(cmd.u3)); + } + + exp = nfexp_new(); + if (exp == NULL) + goto out_update_nl; + + cthelper_get_addr_src(myct->ct, !dir, &addr); + + if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, IPPROTO_TCP, + NULL, &cmd.u.port, 0)) { + pr_debug("conntrack_ftp: failed to init expectation\n"); + goto out_update_nl; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_ftp(pkt, dir, ctinfo, search[dir][i].ftptype, + matchoff, matchlen, myct->ct, exp); + goto out_update_nl; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("conntrack_ftp: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + goto out_update_nl; + } + +out_update_nl: + if (exp != NULL) + nfexp_destroy(exp); + + /* Now if this ends in \n, update ftp info. Seq may have been + * adjusted by NAT code. */ + if (ends_in_nl) + update_nl_seq(seq, ftp_info, dir); +out: + return ret; +} + +static struct ctd_helper ftp_helper = { + .name = "ftp", + .l4proto = IPPROTO_TCP, + .cb = ftp_helper_cb, + .priv_data_len = sizeof(struct ftp_info), + .policy = { + [0] = { + .name = "ftp", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) ftp_init(void); + +void ftp_init(void) +{ + helper_register(&ftp_helper); +} diff --git a/src/main.c b/src/main.c index 26f6c14..831a3c2 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include "conntrackd.h" #include "log.h" +#include "helper.h" #include #include @@ -31,7 +32,7 @@ #include struct ct_general_state st; -union ct_state state; +struct ct_state state; static const char usage_daemon_commands[] = "Daemon mode commands:\n" diff --git a/src/nfct-extensions/helper.c b/src/nfct-extensions/helper.c new file mode 100644 index 0000000..e8f85bb --- /dev/null +++ b/src/nfct-extensions/helper.c @@ -0,0 +1,619 @@ +/* + * (C) 2012 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 code has been sponsored by Vyatta Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nfct.h" +#include "helper.h" + +static void +nfct_cmd_helper_usage(char *argv[]) +{ + fprintf(stderr, "nfct v%s: Missing command\n" + "%s helper list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); +} + +int +nfct_cmd_helper_parse_params(int argc, char *argv[]) +{ + int cmd = NFCT_CMD_NONE, ret = 0; + + if (argc < 3) { + fprintf(stderr, "nfct v%s: Missing command\n" + "%s helper list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); + exit(EXIT_FAILURE); + } + if (strncmp(argv[2], "list", strlen(argv[2])) == 0) + cmd = NFCT_CMD_LIST; + else if (strncmp(argv[2], "add", strlen(argv[2])) == 0) + cmd = NFCT_CMD_ADD; + else if (strncmp(argv[2], "delete", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DELETE; + else if (strncmp(argv[2], "get", strlen(argv[2])) == 0) + cmd = NFCT_CMD_GET; + else if (strncmp(argv[2], "flush", strlen(argv[2])) == 0) + cmd = NFCT_CMD_FLUSH; + else if (strncmp(argv[2], "disable", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DISABLE; + else { + fprintf(stderr, "nfct v%s: Unknown command: %s\n", + VERSION, argv[2]); + nfct_cmd_helper_usage(argv); + exit(EXIT_FAILURE); + } + switch(cmd) { + case NFCT_CMD_LIST: + ret = nfct_cmd_helper_list(argc, argv); + break; + case NFCT_CMD_ADD: + ret = nfct_cmd_helper_add(argc, argv); + break; + case NFCT_CMD_DELETE: + ret = nfct_cmd_helper_delete(argc, argv); + break; + case NFCT_CMD_GET: + ret = nfct_cmd_helper_get(argc, argv); + break; + case NFCT_CMD_FLUSH: + ret = nfct_cmd_helper_flush(argc, argv); + break; + case NFCT_CMD_DISABLE: + ret = nfct_cmd_helper_disable(argc, argv); + break; + } + + return ret; +} + +static int nfct_helper_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfct_helper *t; + char buf[4096]; + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + goto err; + } + + if (nfct_helper_nlmsg_parse_payload(nlh, t) < 0) { + nfct_perror("nfct_helper_nlmsg_parse_payload"); + goto err_free; + } + + nfct_helper_snprintf(buf, sizeof(buf), t, 0, 0); + printf("%s\n", buf); + +err_free: + nfct_helper_free(t); +err: + return MNL_CB_OK; +} + +int nfct_cmd_helper_list(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + unsigned int seq, portid; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, + NLM_F_DUMP, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_add(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + int ret, j; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct helper add name " + "family protocol"); + return -1; + } + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + + /* XXX use prefix defined in configure.ac. */ + helper = helper_find("/usr/lib/conntrack-tools", + argv[3], l4proto, RTLD_LAZY); + if (helper == NULL) { + nfct_perror("that helper is not supported"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PRIV_DATA_LEN, + helper->priv_data_len); + + for (j=0; jpolicy[j].name[0]) + break; + + p = nfct_helper_policy_alloc(); + if (p == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, + helper->policy[j].name); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, + helper->policy[j].expect_timeout); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, + helper->policy[j].expect_max); + + nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_delete(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + int ret; + + if (argc < 4) { + nfct_perror("missing helper policy name"); + return -1; + } else if (argc > 6) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + + if (argc >= 5) { + uint16_t l3proto; + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + } + + if (argc == 6) { + uint8_t l4proto; + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, + NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_get(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + int ret; + + if (argc < 4) { + nfct_perror("missing helper policy name"); + return -1; + } else if (argc > 6) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + + if (argc >= 5) { + uint16_t l3proto; + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + } + + if (argc == 6) { + uint8_t l4proto; + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, + NLM_F_ACK, seq); + + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_flush(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, + NLM_F_ACK, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_disable(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + int ret; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct helper add name " + "family protocol"); + return -1; + } + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + + /* XXX use prefix defined in configure.ac. */ + helper = helper_find("/usr/lib/conntrack-tools", + argv[3], l4proto, RTLD_LAZY); + if (helper == NULL) { + nfct_perror("that helper is not supported"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, + NFCT_HELPER_STATUS_DISABLED); + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + diff --git a/src/nfct.c b/src/nfct.c index db629e7..b5c9654 100644 --- a/src/nfct.c +++ b/src/nfct.c @@ -56,6 +56,8 @@ int main(int argc, char *argv[]) } if (strncmp(argv[1], "timeout", strlen(argv[1])) == 0) { subsys = NFCT_SUBSYS_TIMEOUT; + } else if (strncmp(argv[1], "helper", strlen(argv[1])) == 0) { + subsys = NFCT_SUBSYS_HELPER; } else if (strncmp(argv[1], "version", strlen(argv[1])) == 0) subsys = NFCT_SUBSYS_VERSION; else if (strncmp(argv[1], "help", strlen(argv[1])) == 0) @@ -71,6 +73,9 @@ int main(int argc, char *argv[]) case NFCT_SUBSYS_TIMEOUT: ret = nfct_cmd_timeout_parse_params(argc, argv); break; + case NFCT_SUBSYS_HELPER: + ret = nfct_cmd_helper_parse_params(argc, argv); + break; case NFCT_SUBSYS_VERSION: ret = nfct_cmd_version(argc, argv); break; @@ -99,6 +104,7 @@ static const char help_msg[] = "nfct v%s: utility for the Netfilter's Connection Tracking System\n" "Usage: %s command [parameters]...\n\n" "Subsystem:\n" + " helper\t\tAllows to configure user-space helper\n" " timeout\t\tAllows definition of fine-grain timeout policies\n" " version\t\tDisplay version and disclaimer\n" " help\t\t\tDisplay this help message\n" diff --git a/src/read_config_lex.l b/src/read_config_lex.l index 01fe4fc..31fa32e 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -142,6 +142,11 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "TCPWindowTracking" { return T_TCP_WINDOW_TRACKING; } "ExpectationSync" { return T_EXPECT_SYNC; } "ErrorQueueLength" { return T_ERROR_QUEUE_LENGTH; } +"Helper" { return T_HELPER; } +"QueueNum" { return T_HELPER_QUEUE_NUM; } +"Policy" { return T_HELPER_POLICY; } +"ExpectMax" { return T_HELPER_EXPECT_MAX; } +"ExpectTimeout" { return T_HELPER_EXPECT_TIMEOUT; } {is_on} { return T_ON; } {is_off} { return T_OFF; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index b22784c..c9235d3 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -28,8 +28,11 @@ #include "conntrackd.h" #include "bitops.h" #include "cidr.h" +#include "helper.h" +#include "stack.h" #include #include +#include #include #include @@ -48,6 +51,15 @@ static void print_err(int err, const char *msg, ...); static void __kernel_filter_start(void); static void __kernel_filter_add_state(int value); static void __max_dedicated_links_reached(void); + +struct stack symbol_stack; + +enum { + SYMBOL_HELPER_QUEUE_NUM, + SYMBOL_HELPER_POLICY_EXPECT_ROOT, + SYMBOL_HELPER_EXPECT_POLICY_LEAF, +}; + %} %union { @@ -74,6 +86,8 @@ static void __max_dedicated_links_reached(void); %token T_SCHEDULER T_TYPE T_PRIO T_NETLINK_EVENTS_RELIABLE %token T_DISABLE_INTERNAL_CACHE T_DISABLE_EXTERNAL_CACHE T_ERROR_QUEUE_LENGTH %token T_OPTIONS T_TCP_WINDOW_TRACKING T_EXPECT_SYNC +%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_POLICY T_HELPER_EXPECT_MAX +%token T_HELPER_EXPECT_TIMEOUT %token T_IP T_PATH_VAL %token T_NUMBER @@ -96,6 +110,7 @@ line : ignore_protocol | general | sync | stats + | helper ; logfile_bool : T_LOG T_ON @@ -1561,6 +1576,186 @@ buffer_size: T_STAT_BUFFER_SIZE T_NUMBER print_err(CTD_CFG_WARN, "`LogFileBufferSize' is deprecated"); }; +helper: T_HELPER '{' helper_list '}' +{ + conf.flags |= CTD_HELPER; +}; + +helper_list: + | helper_list helper_line + ; + +helper_line: helper_type + ; + +helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list '}' +{ + struct ctd_helper_instance *helper_inst; + struct ctd_helper *helper; + struct stack_item *e; + uint16_t l3proto; + uint8_t l4proto; + + if (strcmp($3, "inet") == 0) + l3proto = AF_INET; + else if (strcmp($3, "inet6") == 0) + l3proto = AF_INET6; + else { + print_err(CTD_CFG_ERROR, "unknown layer 3 protocol"); + exit(EXIT_FAILURE); + } + + if (strcmp($4, "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp($4, "udp") == 0) + l4proto = IPPROTO_UDP; + else { + print_err(CTD_CFG_ERROR, "unknown layer 4 protocol"); + exit(EXIT_FAILURE); + } + + /* XXX use configure.ac definitions. */ + helper = helper_find("/usr/lib/conntrack-tools", $2, l4proto, RTLD_NOW); + if (helper == NULL) { + print_err(CTD_CFG_ERROR, "Unknown `%s' helper", $2); + exit(EXIT_FAILURE); + } + + helper_inst = calloc(1, sizeof(struct ctd_helper_instance)); + if (helper_inst == NULL) + break; + + helper_inst->l3proto = l3proto; + helper_inst->l4proto = l4proto; + helper_inst->helper = helper; + + while ((e = stack_item_pop(&symbol_stack, -1)) != NULL) { + + switch(e->type) { + case SYMBOL_HELPER_QUEUE_NUM: { + int *qnum = (int *) &e->data; + + helper_inst->queue_num = *qnum; + stack_item_free(e); + break; + } + case SYMBOL_HELPER_POLICY_EXPECT_ROOT: { + struct ctd_helper_policy *pol = + (struct ctd_helper_policy *) &e->data; + struct ctd_helper_policy *matching = NULL; + int i; + + for (i=0; ipolicy[i].name, + pol->name) != 0) + continue; + + matching = pol; + break; + } + if (matching == NULL) { + print_err(CTD_CFG_ERROR, + "Unknown policy `%s' in helper " + "configuration", pol->name); + exit(EXIT_FAILURE); + } + /* FIXME: First set default policy, then change only + * tuned fields, not everything. + */ + memcpy(&helper->policy[i], pol, + sizeof(struct ctd_helper_policy)); + + stack_item_free(e); + break; + } + default: + print_err(CTD_CFG_ERROR, + "Unexpected symbol parsing helper policy"); + exit(EXIT_FAILURE); + break; + } + } + list_add(&helper_inst->head, &CONFIG(cthelper).list); +}; + +helper_type_list: + | helper_type_list helper_type_line + ; + +helper_type_line: helper_type + ; + +helper_type: T_HELPER_QUEUE_NUM T_NUMBER +{ + int *qnum; + struct stack_item *e; + + e = stack_item_alloc(SYMBOL_HELPER_QUEUE_NUM, sizeof(int)); + qnum = (int *) e->data; + *qnum = $2; + stack_item_push(&symbol_stack, e); +}; + +helper_type: T_HELPER_POLICY T_STRING '{' helper_policy_list '}' +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + print_err(CTD_CFG_ERROR, + "Helper policy configuration empty, fix your " + "configuration file, please"); + exit(EXIT_FAILURE); + break; + } + + policy = (struct ctd_helper_policy *) &e->data; + strncpy(policy->name, $2, CTD_HELPER_NAME_LEN); + policy->name[CTD_HELPER_NAME_LEN-1] = '\0'; + /* Now object is complete. */ + e->type = SYMBOL_HELPER_POLICY_EXPECT_ROOT; + stack_item_push(&symbol_stack, e); +}; + +helper_policy_list: + | helper_policy_list helper_policy_line + ; + +helper_policy_line: helper_policy_expect_max + | helper_policy_expect_timeout + ; + +helper_policy_expect_max: T_HELPER_EXPECT_MAX T_NUMBER +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, + sizeof(struct ctd_helper_policy)); + } + policy = (struct ctd_helper_policy *) &e->data; + policy->expect_max = $2; + stack_item_push(&symbol_stack, e); +}; + +helper_policy_expect_timeout: T_HELPER_EXPECT_TIMEOUT T_NUMBER +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, + sizeof(struct ctd_helper_policy)); + } + policy = (struct ctd_helper_policy *) &e->data; + policy->expect_timeout = $2; + stack_item_push(&symbol_stack, e); +}; + %% int __attribute__((noreturn)) @@ -1640,6 +1835,11 @@ init_config(char *filename) CONFIG(stats).syslog_facility = -1; CONFIG(netlink).subsys_id = -1; + /* Initialize list of user-space helpers */ + INIT_LIST_HEAD(&CONFIG(cthelper).list); + + stack_init(&symbol_stack); + yyrestart(fp); yyparse(); fclose(fp); diff --git a/src/run.c b/src/run.c index 852bec6..3337694 100644 --- a/src/run.c +++ b/src/run.c @@ -50,6 +50,9 @@ void killer(int foo) if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) ctnl_kill(); + if (CONFIG(flags) & CTD_HELPER) + cthelper_kill(); + destroy_fds(STATE(fds)); unlink(CONFIG(lockfile)); dlog(LOG_NOTICE, "---- shutdown received ----"); @@ -199,6 +202,9 @@ static int local_handler(int fd, void *data) if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) return ctnl_local(fd, type, data); + if (CONFIG(flags) & CTD_HELPER) + return cthelper_local(fd, type, data); + return ret; } @@ -250,6 +256,11 @@ init(void) if (ctnl_init() < 0) return -1; + if (CONFIG(flags) & CTD_HELPER) { + if (cthelper_init() < 0) + return -1; + } + time(&STATE(stats).daemon_start_time); dlog(LOG_NOTICE, "initialization completed"); diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..104b7ba --- /dev/null +++ b/src/stack.c @@ -0,0 +1,56 @@ +/* + * (C) 2005-2012 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. + */ + +#include +#include +#include +#include + +#include "stack.h" + +struct stack_item * +stack_item_alloc(int type, size_t data_len) +{ + struct stack_item *e; + + e = calloc(1, sizeof(struct stack_item) + data_len); + if (e == NULL) + return NULL; + + e->data_len = data_len; + e->type = type; + + return e; +} + +void stack_item_free(struct stack_item *e) +{ + free(e); +} + +void stack_item_push(struct stack *s, struct stack_item *e) +{ + list_add(&e->head, &s->list); +} + +struct stack_item *stack_item_pop(struct stack *s, int type) +{ + struct stack_item *cur, *tmp, *found = NULL; + + list_for_each_entry_safe(cur, tmp, &s->list, head) { + if (cur->type != type && type != -1) + continue; + + list_del(&cur->head); + found = cur; + break; + } + + return found; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..fabec24 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,243 @@ +/* The following code has been extracted from the kenrel sources, if there is + * any problem, blame for mangling it. --pablo */ + +/* + * Generic address resultion entity + * + * Authors: + * net_random Alan Cox + * net_ratelimit Andi Kleen + * in{4,6}_pton YOSHIFUJI Hideaki, Copyright (C)2006 USAGI/WIDE Project + * + * Created by Alexey Kuznetsov + * + * 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. + */ + +/* + * lib/hexdump.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See README and COPYING for + * more details. + */ + +#include +#include +#include /* for memcpy */ + +#include "helper.h" + +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +#define IN6PTON_XDIGIT 0x00010000 +#define IN6PTON_DIGIT 0x00020000 +#define IN6PTON_COLON_MASK 0x00700000 +#define IN6PTON_COLON_1 0x00100000 /* single : requested */ +#define IN6PTON_COLON_2 0x00200000 /* second : requested */ +#define IN6PTON_COLON_1_2 0x00400000 /* :: requested */ +#define IN6PTON_DOT 0x00800000 /* . */ +#define IN6PTON_DELIM 0x10000000 +#define IN6PTON_NULL 0x20000000 /* first/tail */ +#define IN6PTON_UNKNOWN 0x40000000 + +static inline int xdigit2bin(char c, int delim) +{ + int val; + + if (c == delim || c == '\0') + return IN6PTON_DELIM; + if (c == ':') + return IN6PTON_COLON_MASK; + if (c == '.') + return IN6PTON_DOT; + + val = hex_to_bin(c); + if (val >= 0) + return val | IN6PTON_XDIGIT | (val < 10 ? IN6PTON_DIGIT : 0); + + if (delim == -1) + return IN6PTON_DELIM; + return IN6PTON_UNKNOWN; +} + +int in4_pton(const char *src, int srclen, + uint8_t *dst, + int delim, const char **end) +{ + const char *s; + uint8_t *d; + uint8_t dbuf[4]; + int ret = 0; + int i; + int w = 0; + + if (srclen < 0) + srclen = strlen(src); + s = src; + d = dbuf; + i = 0; + while(1) { + int c; + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & (IN6PTON_DIGIT | IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK))) { + goto out; + } + if (c & (IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (w == 0) + goto out; + *d++ = w & 0xff; + w = 0; + i++; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (i != 4) + goto out; + break; + } + goto cont; + } + w = (w * 10) + c; + if ((w & 0xffff) > 255) { + goto out; + } +cont: + if (i >= 4) + goto out; + s++; + srclen--; + } + ret = 1; + memcpy(dst, dbuf, sizeof(dbuf)); +out: + if (end) + *end = s; + return ret; +} + +int in6_pton(const char *src, int srclen, + uint8_t *dst, + int delim, const char **end) +{ + const char *s, *tok = NULL; + uint8_t *d, *dc = NULL; + uint8_t dbuf[16]; + int ret = 0; + int i; + int state = IN6PTON_COLON_1_2 | IN6PTON_XDIGIT | IN6PTON_NULL; + int w = 0; + + memset(dbuf, 0, sizeof(dbuf)); + + s = src; + d = dbuf; + if (srclen < 0) + srclen = strlen(src); + + while (1) { + int c; + + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & state)) + goto out; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + /* process one 16-bit word */ + if (!(state & IN6PTON_NULL)) { + *d++ = (w >> 8) & 0xff; + *d++ = w & 0xff; + } + w = 0; + if (c & IN6PTON_DELIM) { + /* We've processed last word */ + break; + } + /* + * COLON_1 => XDIGIT + * COLON_2 => XDIGIT|DELIM + * COLON_1_2 => COLON_2 + */ + switch (state & IN6PTON_COLON_MASK) { + case IN6PTON_COLON_2: + dc = d; + state = IN6PTON_XDIGIT | IN6PTON_DELIM; + if (dc - dbuf >= (int)sizeof(dbuf)) + state |= IN6PTON_NULL; + break; + case IN6PTON_COLON_1|IN6PTON_COLON_1_2: + state = IN6PTON_XDIGIT | IN6PTON_COLON_2; + break; + case IN6PTON_COLON_1: + state = IN6PTON_XDIGIT; + break; + case IN6PTON_COLON_1_2: + state = IN6PTON_COLON_2; + break; + default: + state = 0; + } + tok = s + 1; + goto cont; + } + + if (c & IN6PTON_DOT) { + ret = in4_pton(tok ? tok : s, srclen + (int)(s - tok), d, delim, &s); + if (ret > 0) { + d += 4; + break; + } + goto out; + } + + w = (w << 4) | (0xff & c); + state = IN6PTON_COLON_1 | IN6PTON_DELIM; + if (!(w & 0xf000)) { + state |= IN6PTON_XDIGIT; + } + if (!dc && d + 2 < dbuf + sizeof(dbuf)) { + state |= IN6PTON_COLON_1_2; + state &= ~IN6PTON_DELIM; + } + if (d + 2 >= dbuf + sizeof(dbuf)) { + state &= ~(IN6PTON_COLON_1|IN6PTON_COLON_1_2); + } +cont: + if ((dc && d + 4 < dbuf + sizeof(dbuf)) || + d + 4 == dbuf + sizeof(dbuf)) { + state |= IN6PTON_DOT; + } + if (d >= dbuf + sizeof(dbuf)) { + state &= ~(IN6PTON_XDIGIT|IN6PTON_COLON_MASK); + } + s++; + srclen--; + } + + i = 15; d--; + + if (dc) { + while(d >= dc) + dst[i--] = *d--; + while(i >= dc - dbuf) + dst[i--] = 0; + while(i >= 0) + dst[i--] = *d--; + } else + memcpy(dst, dbuf, sizeof(dbuf)); + + ret = 1; +out: + if (end) + *end = s; + return ret; +} -- cgit v1.2.3 From 969d93f14fffadb5cae67a7662484c1e064bbff1 Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 May 2012 14:31:35 +0200 Subject: conntrackd: RPC helper added to cthelper How to use this helper in a few steps: 1) You can enable this helper via: nfct helper add rpc inet tcp nfct helper add rpc inet udp 2) Configure /etc/conntrackd/conntrackd.conf and launch it. 3) You can test this helper locally with the following rule-set: iptables -A OUTPUT -t raw -p udp -m udp --dport 111 -j CT --helper rpc iptables -A OUTPUT -t raw -p tcp -m tcp --dport 111 -j CT --helper rpc iptables -A OUTPUT -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 111 -j ACCEPT iptables -A OUTPUT -p udp -m state --state NEW,ESTABLISHED -m udp --dport 111 -j ACCEPT iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -P OUTPUT DROP 4) Configure NFS and export some local directory. Then, mount it with version 3. mount.nfs -onfsvers=3 127.0.0.1:/srv/cvs /mnt/ You should see permanent expectations created for this. Signed-off-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 14 ++ src/helpers/Makefile.am | 7 +- src/helpers/rpc.c | 488 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 src/helpers/rpc.c (limited to 'src') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 711b309..2bf99fa 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -28,6 +28,20 @@ Helper { ExpectTimeout 300 } } + Type rpc inet tcp { + QueueNum 1 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } + Type rpc inet udp { + QueueNum 2 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index 2c9d63b..f441b29 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,9 +1,12 @@ include $(top_srcdir)/Make_global.am -pkglib_LTLIBRARIES = ct_helper_ftp.la +pkglib_LTLIBRARIES = ct_helper_ftp.la \ + ct_helper_rpc.la ct_helper_ftp_la_SOURCES = ftp.c ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) - +ct_helper_rpc_la_SOURCES = rpc.c +ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/rpc.c b/src/helpers/rpc.c new file mode 100644 index 0000000..82493c2 --- /dev/null +++ b/src/helpers/rpc.c @@ -0,0 +1,488 @@ +/* + * (C) 2012 by Jozsef Kadlecsik + * (C) 2012 by Pablo Neira Ayuso + * + * Based on: RPC extension for conntrack. + * + * This port has been sponsored by Vyatta Inc. + * + * Original copyright notice: + * + * (C) 2000 by Marcelo Barbosa Lima + * (C) 2001 by Rusty Russell + * (C) 2002,2003 by Ian (Larry) Latter + * (C) 2004,2005 by David Stes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* RFC 1050: RPC: Remote Procedure Call Protocol Specification Version 2 */ +/* RFC 1014: XDR: External Data Representation Standard */ +#define SUPPORTED_RPC_VERSION 2 + +struct rpc_info { + /* XID */ + uint32_t xid; + /* program */ + uint32_t pm_prog; + /* program version */ + uint32_t pm_vers; + /* transport protocol: TCP|UDP */ + uint32_t pm_prot; +}; + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_rpc(struct pkt_buff *pkt, int dir, struct nf_expect *exp, + uint8_t proto, uint32_t *port_ptr) +{ + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port, port; + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, proto); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + nfct_destroy(nat_tuple); + + if (port == 0) + return NF_DROP; + + *port_ptr = htonl(port); + + return NF_ACCEPT; +} + +#define OFFSET(o, n) ((o) += n) +#define ROUNDUP(n) ((((n) + 3)/4)*4) + +static int +rpc_call(const uint32_t *data, uint32_t offset, uint32_t datalen, + struct rpc_info *rpc_info) +{ + uint32_t p, r; + + /* RPC CALL message body */ + + /* call_body { + * rpcvers + * prog + * vers + * proc + * opaque_auth cred + * opaque_auth verf + * pmap + * } + * + * opaque_auth { + * flavour + * opaque[len] <= MAX_AUTH_BYTES + * } + */ + if (datalen < OFFSET(offset, 4*4 + 2*2*4)) { + pr_debug("RPC CALL: too short packet: %u < %u\n", + datalen, offset); + return -1; + } + /* Check rpcversion */ + p = IXDR_GET_INT32(data); + if (p != SUPPORTED_RPC_VERSION) { + pr_debug("RPC CALL: wrong rpcvers %u != %u\n", + p, SUPPORTED_RPC_VERSION); + return -1; + } + /* Skip non-portmap requests */ + p = IXDR_GET_INT32(data); + if (p != PMAPPROG) { + pr_debug("RPC CALL: not portmap %u != %lu\n", + p, PMAPPROG); + return -1; + } + /* Check portmap version */ + p = IXDR_GET_INT32(data); + if (p != PMAPVERS) { + pr_debug("RPC CALL: wrong portmap version %u != %lu\n", + p, PMAPVERS); + return -1; + } + /* Skip non PMAPPROC_GETPORT requests */ + p = IXDR_GET_INT32(data); + if (p != PMAPPROC_GETPORT) { + pr_debug("RPC CALL: not PMAPPROC_GETPORT %u != %lu\n", + p, PMAPPROC_GETPORT); + return -1; + } + /* Check and skip credentials */ + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC CALL: cred: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC CALL: invalid sized cred %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + if (datalen < OFFSET(offset, r)) { + pr_debug("RPC CALL: too short to carry cred: %u < %u, %u\n", + datalen, offset, r); + return -1; + } + data += r/4; + /* Check and skip verifier */ + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC CALL: verf: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC CALL: invalid sized verf %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + if (datalen < OFFSET(offset, r)) { + pr_debug("RPC CALL: too short to carry verf: %u < %u, %u\n", + datalen, offset, r); + return -1; + } + data += r/4; + /* pmap { + * prog + * vers + * prot + * port + * } + */ + /* Check CALL size */ + if (datalen != offset + 4*4) { + pr_debug("RPC CALL: invalid size to carry pmap: %u != %u\n", + datalen, offset + 4*4); + return -1; + } + rpc_info->pm_prog = IXDR_GET_INT32(data); + rpc_info->pm_vers = IXDR_GET_INT32(data); + rpc_info->pm_prot = IXDR_GET_INT32(data); + /* Check supported protocols */ + if (!(rpc_info->pm_prot == IPPROTO_TCP + || rpc_info->pm_prot == IPPROTO_UDP)) { + pr_debug("RPC CALL: unsupported protocol %u", + rpc_info->pm_prot); + return -1; + } + p = IXDR_GET_INT32(data); + /* Check port: must be zero */ + if (p != 0) { + pr_debug("RPC CALL: port is nonzero %u\n", + ntohl(p)); + return -1; + } + pr_debug("RPC CALL: processed: xid %u, prog %u, vers %u, prot %u\n", + rpc_info->xid, rpc_info->pm_prog, + rpc_info->pm_vers, rpc_info->pm_prot); + + return 0; +} + +static int +rpc_reply(uint32_t *data, uint32_t offset, uint32_t datalen, + struct rpc_info *rpc_info, uint32_t **port_ptr) +{ + uint16_t port; + uint32_t p, r; + + /* RPC REPLY message body */ + + /* reply_body { + * reply_stat + * xdr_union { + * accepted_reply + * rejected_reply + * } + * } + * accepted_reply { + * opaque_auth verf + * accept_stat + * xdr_union { + * port + * struct mismatch_info + * } + * } + */ + + /* Check size: reply status */ + if (datalen < OFFSET(offset, 4)) { + pr_debug("RPC REPL: too short, missing rp_stat: %u < %u\n", + datalen, offset); + return -1; + } + p = IXDR_GET_INT32(data); + /* Check accepted request */ + if (p != MSG_ACCEPTED) { + pr_debug("RPC REPL: not accepted %u != %u\n", + p, MSG_ACCEPTED); + return -1; + } + /* Check and skip verifier */ + if (datalen < OFFSET(offset, 2*4)) { + pr_debug("RPC REPL: too short, missing verf: %u < %u\n", + datalen, offset); + return -1; + } + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC REPL: verf: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC REPL: invalid sized verf %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + /* verifier + ac_stat + port */ + if (datalen != OFFSET(offset, r) + 2*4) { + pr_debug("RPC REPL: invalid size to carry verf and " + "success: %u != %u\n", + datalen, offset + 2*4); + return -1; + } + data += r/4; + /* Check success */ + p = IXDR_GET_INT32(data); + if (p != SUCCESS) { + pr_debug("RPC REPL: not success %u != %u\n", + p, SUCCESS); + return -1; + } + /* Get port */ + *port_ptr = data; + port = IXDR_GET_INT32(data); /* -Wunused-but-set-parameter */ + if (port == 0) { + pr_debug("RPC REPL: port is zero\n"); + return -1; + } + pr_debug("RPC REPL: processed: xid %u, prog %u, vers %u, " + "prot %u, port %u\n", + rpc_info->xid, rpc_info->pm_prog, rpc_info->pm_vers, + rpc_info->pm_prot, port); + return 0; +} + +static int +rpc_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + int dir = CTINFO2DIR(ctinfo); + unsigned int offset = protoff, datalen; + uint32_t *data, *port_ptr = NULL, xid; + uint16_t port; + uint8_t proto = nfct_get_attr_u8(myct->ct, ATTR_L4PROTO); + enum msg_type rm_dir; + struct rpc_info *rpc_info = myct->priv_data; + union nfct_attr_grp_addr addr, daddr; + struct nf_expect *exp = NULL; + int ret = NF_ACCEPT; + + /* Until there's been traffic both ways, don't look into TCP packets. */ + if (proto == IPPROTO_TCP + && ctinfo != IP_CT_ESTABLISHED + && ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("TCP RPC: Conntrackinfo = %u\n", ctinfo); + return ret; + } + if (proto == IPPROTO_TCP) { + struct tcphdr *th = + (struct tcphdr *) (pktb_network_header(pkt) + protoff); + offset += th->doff * 4; + } else { + offset += sizeof(struct udphdr); + } + /* Skip broken headers */ + if (offset % 4) { + pr_debug("RPC: broken header: offset %u%%4 != 0\n", offset); + return ret; + } + + /* Take into Record Fragment header */ + if (proto == IPPROTO_TCP) + offset += 4; + + datalen = pktb_len(pkt); + data = (uint32_t *)(pktb_network_header(pkt) + offset); + + /* rpc_msg { + * xid + * direction + * xdr_union { + * call_body + * reply_body + * } + * } + */ + + /* Check minimal msg size: xid + direction */ + if (datalen < OFFSET(offset, 2*4)) { + pr_debug("RPC: too short packet: %u < %u\n", + datalen, offset); + return ret; + } + xid = IXDR_GET_INT32(data); + rm_dir = IXDR_GET_INT32(data); + + /* Check direction */ + if (!((rm_dir == CALL && dir == MYCT_DIR_ORIG) + || (rm_dir == REPLY && dir == MYCT_DIR_REPL))) { + pr_debug("RPC: rm_dir != dir %u != %u\n", rm_dir, dir); + goto out; + } + + if (rm_dir == CALL) { + if (rpc_call(data, offset, datalen, rpc_info) < 0) + goto out; + + rpc_info->xid = xid; + + return ret; + } else { + /* Check XID */ + if (xid != rpc_info->xid) { + pr_debug("RPC REPL: XID does not match: %u != %u\n", + xid, rpc_info->xid); + goto out; + } + if (rpc_reply(data, offset, datalen, rpc_info, &port_ptr) < 0) + goto out; + + port = IXDR_GET_INT32(port_ptr); + port = htons(port); + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_dst(myct->ct, !dir, &daddr); + cthelper_get_addr_src(myct->ct, !dir, &addr); + + exp = nfexp_new(); + if (exp == NULL) + goto out; + + if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, + rpc_info->pm_prot, + NULL, &port, NF_CT_EXPECT_PERMANENT)) { + pr_debug("RPC: failed to init expectation\n"); + goto out_exp; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_rpc(pkt, dir, exp, rpc_info->pm_prot, + port_ptr); + goto out_exp; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("RPC: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + } + } + +out_exp: + nfexp_destroy(exp); +out: + rpc_info->xid = 0; + return ret; +} + +static struct ctd_helper rpc_helper_tcp = { + .name = "rpc", + .l4proto = IPPROTO_TCP, + .cb = rpc_helper_cb, + .priv_data_len = sizeof(struct rpc_info), + .policy = { + { + .name = "rpc", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +static struct ctd_helper rpc_helper_udp = { + .name = "rpc", + .l4proto = IPPROTO_UDP, + .cb = rpc_helper_cb, + .priv_data_len = sizeof(struct rpc_info), + .policy = { + { + .name = "rpc", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) rpc_init(void); + +void rpc_init(void) +{ + helper_register(&rpc_helper_tcp); + helper_register(&rpc_helper_udp); +} -- cgit v1.2.3 From 40f4330e6b50ed2b198549b1006c6fcb349f5a3b Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 May 2012 14:43:20 +0200 Subject: conntrackd: TNS helper added to cthelper Signed-off-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 7 + src/helpers/Makefile.am | 7 +- src/helpers/tns.c | 407 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 src/helpers/tns.c (limited to 'src') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 2bf99fa..80f1f92 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -42,6 +42,13 @@ Helper { ExpectTimeout 300 } } + Type tns inet tcp { + QueueNum 3 + Policy tns { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index f441b29..589b4f3 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,7 +1,8 @@ include $(top_srcdir)/Make_global.am pkglib_LTLIBRARIES = ct_helper_ftp.la \ - ct_helper_rpc.la + ct_helper_rpc.la \ + ct_helper_tns.la ct_helper_ftp_la_SOURCES = ftp.c ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) @@ -10,3 +11,7 @@ ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) ct_helper_rpc_la_SOURCES = rpc.c ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_tns_la_SOURCES = tns.c +ct_helper_tns_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/tns.c b/src/helpers/tns.c new file mode 100644 index 0000000..5833fea --- /dev/null +++ b/src/helpers/tns.c @@ -0,0 +1,407 @@ +/* + * (C) 2012 by Jozsef Kadlecsik + * (C) 2012 by Pablo Neira Ayuso + * + * Sponsored by Vyatta Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include /* for isdigit */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* TNS SQL*Net Version 2 */ +enum tns_types { + TNS_TYPE_CONNECT = 1, + TNS_TYPE_ACCEPT = 2, + TNS_TYPE_ACK = 3, + TNS_TYPE_REFUSE = 4, + TNS_TYPE_REDIRECT = 5, + TNS_TYPE_DATA = 6, + TNS_TYPE_NULL = 7, + TNS_TYPE_ABORT = 9, + TNS_TYPE_RESEND = 11, + TNS_TYPE_MARKER = 12, + TNS_TYPE_ATTENTION = 13, + TNS_TYPE_CONTROL = 14, + TNS_TYPE_MAX = 19, +}; + +struct tns_header { + uint16_t len; + uint16_t csum; + uint8_t type; + uint8_t reserved; + uint16_t header_csum; +}; + +struct tns_redirect { + uint16_t data_len; +}; + +struct tns_info { + /* Scan next DATA|REDIRECT packet */ + bool parse; +}; + +static int try_number(const char *data, size_t dlen, uint32_t array[], + int array_size, char sep, char term) +{ + uint32_t len; + int i; + + memset(array, 0, sizeof(array[0])*array_size); + + /* Keep data pointing at next char. */ + for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { + if (*data >= '0' && *data <= '9') { + array[i] = array[i]*10 + *data - '0'; + } + else if (*data == sep) + i++; + else { + /* Skip spaces. */ + if (*data == ' ') + continue; + /* Unexpected character; true if it's the + terminator and we're finished. */ + if (*data == term && i == array_size - 1) + return len; + pr_debug("Char %u (got %u nums) `%c' unexpected\n", + len, i, *data); + return 0; + } + } + pr_debug("Failed to fill %u numbers separated by %c\n", + array_size, sep); + return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, size_t dlen, char delim, + struct myct_man *cmd) +{ + uint16_t tmp_port = 0; + uint32_t i; + + for (i = 0; i < dlen; i++) { + /* Finished? */ + if (data[i] == delim) { + if (tmp_port == 0) + break; + cmd->u.port = htons(tmp_port); + pr_debug("get_port: return %d\n", tmp_port); + return i + 1; + } + else if (data[i] >= '0' && data[i] <= '9') + tmp_port = tmp_port*10 + data[i] - '0'; + else if (data[i] == ' ') /* Skip spaces */ + continue; + else { /* Some other crap */ + pr_debug("get_port: invalid char `%c'\n", data[i]); + break; + } + } + return 0; +} + +/* (ADDRESS=(PROTOCOL=tcp)(DEV=x)(HOST=a.b.c.d)(PORT=a)) */ +/* FIXME: handle hostnames */ + +/* Returns 0, or length of port number */ +static unsigned int +find_pattern(struct pkt_buff *pkt, unsigned int dataoff, size_t dlen, + struct myct_man *cmd, unsigned int *numoff) +{ + const char *data = (const char *)pktb_network_header(pkt) + dataoff + + sizeof(struct tns_header); + int length, offset, ret; + uint32_t array[4]; + const char *p, *start; + + p = strstr(data, "("); + if (!p) + return 0; + + p = strstr(p+1, "HOST="); + if (!p) { + pr_debug("HOST= not found\n"); + return 0; + } + + start = p + strlen("HOST="); + offset = (int)(p - data) + strlen("HOST="); + *numoff = offset + sizeof(struct tns_header); + data += offset; + + length = try_number(data, dlen - offset, array, 4, '.', ')'); + if (length == 0) + return 0; + + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | + (array[2] << 8) | array[3]); + + p = strstr(data+length, "("); + if (!p) + return 0; + + p = strstr(p, "PORT="); + if (!p) { + pr_debug("PORT= not found\n"); + return 0; + } + + p += strlen("PORT="); + ret = get_port(p, dlen - offset - length, ')', cmd); + if (ret == 0) + return 0; + + p += ret; + return (int)(p - start); +} + +static inline uint16_t +nton(uint16_t len, unsigned int matchoff, unsigned int matchlen) +{ + uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen; + + return htons(l); +} + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp, + struct nf_conntrack *ct, int dir, + unsigned int matchoff, unsigned int matchlen) +{ + union nfct_attr_grp_addr newip; + char buffer[sizeof("255.255.255.255)(PORT=65535)")]; + unsigned int buflen; + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port, port; + + /* Connection will come from wherever this packet goes, hence !dir */ + cthelper_get_addr_dst(ct, !dir, &newip); + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + nfct_destroy(nat_tuple); + + if (port == 0) + return NF_DROP; + + buflen = snprintf(buffer, sizeof(buffer), + "%u.%u.%u.%u)(PORT=%u)", + ((unsigned char *)&newip.ip)[0], + ((unsigned char *)&newip.ip)[1], + ((unsigned char *)&newip.ip)[2], + ((unsigned char *)&newip.ip)[3], port); + if (!buflen) + goto out; + + if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) + goto out; + + if (buflen != matchlen) { + /* FIXME: recalculate checksum */ + tns->csum = 0; + tns->header_csum = 0; + + tns->len = nton(tns->len, matchlen, buflen); + if (tns->type == TNS_TYPE_REDIRECT) { + struct tns_redirect *r; + + r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header)); + + r->data_len = nton(r->data_len, matchlen, buflen); + } + } + + return NF_ACCEPT; + +out: + cthelper_del_expect(exp); + return NF_DROP; +} + +static int +tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct tcphdr *th; + struct tns_header *tns; + int dir = CTINFO2DIR(ctinfo); + unsigned int dataoff, datalen, numoff = 0, numlen; + struct tns_info *tns_info = myct->priv_data; + union nfct_attr_grp_addr addr; + struct nf_expect *exp = NULL; + struct myct_man cmd; + int ret = NF_ACCEPT; + + memset(&cmd, 0, sizeof(struct myct_man)); + memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + + /* Until there's been traffic both ways, don't look into TCP packets. */ + if (ctinfo != IP_CT_ESTABLISHED + && ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("TNS: Conntrackinfo = %u\n", ctinfo); + goto out; + } + /* Parse server direction only */ + if (dir != MYCT_DIR_REPL) { + pr_debug("TNS: skip client direction\n"); + goto out; + } + + th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + + dataoff = protoff + th->doff * 4; + datalen = pktb_len(pkt); + + if (datalen < sizeof(struct tns_header)) { + pr_debug("TNS: skip packet with short header\n"); + goto out; + } + + tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff); + + if (tns->type == TNS_TYPE_REDIRECT) { + struct tns_redirect *redirect; + + dataoff += sizeof(struct tns_header); + datalen -= sizeof(struct tns_header); + redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff); + tns_info->parse = false; + + if (ntohs(redirect->data_len) == 0) { + tns_info->parse = true; + goto out; + } + goto parse; + } + + /* Parse only the very next DATA packet */ + if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) { + tns_info->parse = false; + goto out; + } +parse: + numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff); + tns_info->parse = false; + if (!numlen) + goto out; + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_src(myct->ct, !dir, &addr); + + exp = nfexp_new(); + if (exp == NULL) + goto out; + + if (cthelper_expect_init(exp, myct->ct, 0, + &addr, &cmd.u3, + IPPROTO_TCP, + NULL, &cmd.u.port, 0)) { + pr_debug("TNS: failed to init expectation\n"); + goto out_exp; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. + */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, + numoff + sizeof(struct tns_header), numlen); + goto out_exp; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("TNS: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + goto out_exp; + } + goto out; + +out_exp: + nfexp_destroy(exp); +out: + return ret; +} + +static struct ctd_helper tns_helper = { + .name = "tns", + .l4proto = IPPROTO_TCP, + .cb = tns_helper_cb, + .priv_data_len = sizeof(struct tns_info), + .policy = { + [0] = { + .name = "tns", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) tns_init(void); + +void tns_init(void) +{ + helper_register(&tns_helper); +} -- cgit v1.2.3 From f9ad41077c473884f33c6677f81119614d5a8eb2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 1 Aug 2012 19:36:01 +0200 Subject: nfct: helper: use CONNTRACKD_LIB_DIR variable set during configuration Instead of hardcoded path to /usr/lib/conntrack-tools/ which might not be true if options like --prefix with different location is passed to conntrack. Signed-off-by: Pablo Neira Ayuso --- configure.ac | 5 +++++ src/nfct-extensions/helper.c | 8 ++------ 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/configure.ac b/configure.ac index 5d1860d..27ad01b 100644 --- a/configure.ac +++ b/configure.ac @@ -119,5 +119,10 @@ dnl debug/src/Makefile dnl extensions/Makefile dnl src/Makefile]) +if test ! -z "$libdir"; then + MODULE_DIR="\\\"$libdir/conntrack-tools/\\\"" + CFLAGS="$CFLAGS -DCONNTRACKD_LIB_DIR=$MODULE_DIR" +fi + AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile src/helpers/Makefile]) AC_OUTPUT diff --git a/src/nfct-extensions/helper.c b/src/nfct-extensions/helper.c index e8f85bb..f91fc41 100644 --- a/src/nfct-extensions/helper.c +++ b/src/nfct-extensions/helper.c @@ -202,9 +202,7 @@ int nfct_cmd_helper_add(int argc, char *argv[]) return -1; } - /* XXX use prefix defined in configure.ac. */ - helper = helper_find("/usr/lib/conntrack-tools", - argv[3], l4proto, RTLD_LAZY); + helper = helper_find(CONNTRACKD_LIB_DIR, argv[3], l4proto, RTLD_LAZY); if (helper == NULL) { nfct_perror("that helper is not supported"); return -1; @@ -558,9 +556,7 @@ int nfct_cmd_helper_disable(int argc, char *argv[]) return -1; } - /* XXX use prefix defined in configure.ac. */ - helper = helper_find("/usr/lib/conntrack-tools", - argv[3], l4proto, RTLD_LAZY); + helper = helper_find(CONNTRACKD_LIB_DIR, argv[3], l4proto, RTLD_LAZY); if (helper == NULL) { nfct_perror("that helper is not supported"); return -1; -- cgit v1.2.3 From 18bfa4bfb3bb875c36756272bb653c33e338c776 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Thu, 2 Aug 2012 05:33:56 +0000 Subject: conntrackd: don't resync expectations if such sync has been disabled conntrackd was segfaulting with `ExpectationSync` set to `Off` and PollSecs (polling mode) in use. Signed-off-by: Vincent Bernat Signed-off-by: Pablo Neira Ayuso --- src/ctnl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ctnl.c b/src/ctnl.c index 107cd5d..bb54727 100644 --- a/src/ctnl.c +++ b/src/ctnl.c @@ -164,7 +164,9 @@ static void do_polling_alarm(struct alarm_block *a, void *data) STATE(mode)->internal->exp.purge(); nl_send_resync(STATE(resync)); - nl_send_expect_resync(STATE(resync)); + if (CONFIG(flags) & CTD_EXPECT) + nl_send_expect_resync(STATE(resync)); + add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); } -- cgit v1.2.3 From 137b0a2ac9568638be078126ca92ac62ca51e1f4 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 21 Aug 2012 13:46:02 +0200 Subject: cthelper: ftp: fix EPRT case for IPv4 %pI4 also exists in the Linux kernel. It would be good to have some generic functions to convert binary data to address string. Later. Signed-off-by: Pablo Neira Ayuso --- src/helpers/ftp.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c index 962020b..2c8dcd6 100644 --- a/src/helpers/ftp.c +++ b/src/helpers/ftp.c @@ -346,7 +346,12 @@ static int nf_nat_ftp_fmt_cmd(enum nf_ct_ftp_type type, port >> 8, port & 0xFF); case NF_CT_FTP_EPRT: - return snprintf(buffer, buflen, "|1|%pI4|%u|", &addr, port); + return snprintf(buffer, buflen, "|1|%u.%u.%u.%u|%u|", + ((unsigned char *)&addr)[0], + ((unsigned char *)&addr)[1], + ((unsigned char *)&addr)[2], + ((unsigned char *)&addr)[3], + port); case NF_CT_FTP_EPSV: return snprintf(buffer, buflen, "|||%u|", port); } -- cgit v1.2.3 From 46faeab56cf4117f41cb6f1f1c40a9c18a81372f Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 8 Sep 2012 21:39:21 +0200 Subject: conntrackd: parse: fix wrong maximum length for ATTR_EXP_FN It was set to NFCT_HELPER_NAME_MAX (16 bytes), but we have function names that are larger, eg. nf-nat-follow-master which is 18 bytes long. This leads to hitting malformed message while synchronizing expectations. I'll add some new constant to libnetfilter_conntrack instead of hardcoding this, later. Reported-by: Gaurav Sinha Signed-off-by: Pablo Neira Ayuso --- src/parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/parse.c b/src/parse.c index 1b83f81..8ce4495 100644 --- a/src/parse.c +++ b/src/parse.c @@ -397,7 +397,7 @@ static struct exp_parser { [NTA_EXP_FN] = { .parse = exp_parse_str, .exp_attr = ATTR_EXP_FN, - .max_size = NFCT_HELPER_NAME_MAX, + .max_size = 32, /* XXX: artificial limit */ }, }; -- cgit v1.2.3 From febb3cceac1889fb6558b8ef40ac733072fdcd47 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 10 Sep 2012 13:17:24 +0200 Subject: conntrackd: cthelper: add QueueLen option This patch adds the QueueLen option, that allows you to increase the maximum number of packets waiting in the nfnetlink_queue to receive a verdict from userspace. Rising the default value (1024) is useful to avoid hitting the following error message: "nf_queue: full at X entries, dropping packets(s)". Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 13 +++++++++++++ include/helper.h | 1 + src/cthelper.c | 6 ++++-- src/read_config_lex.l | 1 + src/read_config_yy.y | 23 +++++++++++++++++++++-- 5 files changed, 40 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 80f1f92..56f5162 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -14,6 +14,16 @@ Helper { # the kernel. # QueueNum 0 + + # + # Maximum number of packets waiting in the queue to receive + # a verdict from user-space. Default is 1024. + # + # Rise value if you hit the following error message: + # "nf_queue: full at X entries, dropping packets(s)" + # + QueueLen 10240 + # # Set the Expectation policy for this helper. # @@ -30,6 +40,7 @@ Helper { } Type rpc inet tcp { QueueNum 1 + QueueLen 10240 Policy rpc { ExpectMax 1 ExpectTimeout 300 @@ -37,6 +48,7 @@ Helper { } Type rpc inet udp { QueueNum 2 + QueueLen 10240 Policy rpc { ExpectMax 1 ExpectTimeout 300 @@ -44,6 +56,7 @@ Helper { } Type tns inet tcp { QueueNum 3 + QueueLen 10240 Policy tns { ExpectMax 1 ExpectTimeout 300 diff --git a/include/helper.h b/include/helper.h index 329fd2d..9d96fb7 100644 --- a/include/helper.h +++ b/include/helper.h @@ -35,6 +35,7 @@ struct ctd_helper { struct ctd_helper_instance { struct list_head head; uint32_t queue_num; + uint32_t queue_len; uint16_t l3proto; uint8_t l4proto; struct ctd_helper *helper; diff --git a/src/cthelper.c b/src/cthelper.c index c119869..307be96 100644 --- a/src/cthelper.c +++ b/src/cthelper.c @@ -353,8 +353,9 @@ static int cthelper_setup(struct ctd_helper_instance *cur) nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, NFCT_HELPER_STATUS_ENABLED); - dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d", - cur->helper->name, cur->queue_num); + dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d and " + "queuelen=%d", cur->helper->name, cur->queue_num, + cur->queue_len); for (j=0; jqueue_len)); if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { dlog(LOG_ERR, "failed to send configuration"); diff --git a/src/read_config_lex.l b/src/read_config_lex.l index 31fa32e..bec2d81 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -144,6 +144,7 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "ErrorQueueLength" { return T_ERROR_QUEUE_LENGTH; } "Helper" { return T_HELPER; } "QueueNum" { return T_HELPER_QUEUE_NUM; } +"QueueLen" { return T_HELPER_QUEUE_LEN; } "Policy" { return T_HELPER_POLICY; } "ExpectMax" { return T_HELPER_EXPECT_MAX; } "ExpectTimeout" { return T_HELPER_EXPECT_TIMEOUT; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index c9235d3..72a9654 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -56,6 +56,7 @@ struct stack symbol_stack; enum { SYMBOL_HELPER_QUEUE_NUM, + SYMBOL_HELPER_QUEUE_LEN, SYMBOL_HELPER_POLICY_EXPECT_ROOT, SYMBOL_HELPER_EXPECT_POLICY_LEAF, }; @@ -86,8 +87,8 @@ enum { %token T_SCHEDULER T_TYPE T_PRIO T_NETLINK_EVENTS_RELIABLE %token T_DISABLE_INTERNAL_CACHE T_DISABLE_EXTERNAL_CACHE T_ERROR_QUEUE_LENGTH %token T_OPTIONS T_TCP_WINDOW_TRACKING T_EXPECT_SYNC -%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_POLICY T_HELPER_EXPECT_MAX -%token T_HELPER_EXPECT_TIMEOUT +%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_QUEUE_LEN T_HELPER_POLICY +%token T_HELPER_EXPECT_TIMEOUT T_HELPER_EXPECT_MAX %token T_IP T_PATH_VAL %token T_NUMBER @@ -1639,6 +1640,13 @@ helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list '}' stack_item_free(e); break; } + case SYMBOL_HELPER_QUEUE_LEN: { + int *qlen = (int *) &e->data; + + helper_inst->queue_len = *qlen; + stack_item_free(e); + break; + } case SYMBOL_HELPER_POLICY_EXPECT_ROOT: { struct ctd_helper_policy *pol = (struct ctd_helper_policy *) &e->data; @@ -1696,6 +1704,17 @@ helper_type: T_HELPER_QUEUE_NUM T_NUMBER stack_item_push(&symbol_stack, e); }; +helper_type: T_HELPER_QUEUE_LEN T_NUMBER +{ + int *qlen; + struct stack_item *e; + + e = stack_item_alloc(SYMBOL_HELPER_QUEUE_LEN, sizeof(int)); + qlen = (int *) e->data; + *qlen = $2; + stack_item_push(&symbol_stack, e); +}; + helper_type: T_HELPER_POLICY T_STRING '{' helper_policy_list '}' { struct stack_item *e; -- cgit v1.2.3 From a95338a1715b025bf1b39136ba10de5907c1080b Mon Sep 17 00:00:00 2001 From: Nicolas Dichtel Date: Fri, 21 Sep 2012 03:55:39 +0000 Subject: build: fix libraries dependencies in Makefiles Several includes are missing when netfilter libs are not in the standard path. Signed-off-by: Nicolas Dichtel Signed-off-by: Pablo Neira Ayuso --- Make_global.am | 4 +++- src/Makefile.am | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/Make_global.am b/Make_global.am index 7b5342d..23c7dd0 100644 --- a/Make_global.am +++ b/Make_global.am @@ -4,4 +4,6 @@ AM_CFLAGS = -std=gnu99 -W -Wall \ -Wmissing-prototypes -Wwrite-strings -Wcast-qual -Wfloat-equal -Wshadow -Wpointer-arith -Wbad-function-cast -Wsign-compare -Waggregate-return -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wstrict-prototypes -Wundef \ -Wno-unused-parameter ${LIBNFNETLINK_CFLAGS} ${LIBMNL_CFLAGS} \ ${LIBNETFILTER_CONNTRACK_CFLAGS} \ - ${LIBNETFILTER_CTTIMEOUT_CFLAGS} + ${LIBNETFILTER_CTTIMEOUT_CFLAGS} \ + ${LIBNETFILTER_QUEUE_CFLAGS} \ + ${LIBNETFILTER_CTHELPER_CFLAGS} diff --git a/src/Makefile.am b/src/Makefile.am index d8074d2..ec03e46 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,7 +9,7 @@ CLEANFILES = read_config_yy.c read_config_lex.c sbin_PROGRAMS = conntrack conntrackd nfct conntrack_SOURCES = conntrack.c -conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} ${LIBMNL_LIBS} +conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} ${LIBMNL_LIBS} ${LIBNFNETLINK_LIBS} nfct_SOURCES = nfct.c \ helpers.c \ @@ -46,7 +46,7 @@ read_config_yy.o read_config_lex.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-mi conntrackd_LDADD = ${LIBMNL_LIBS} ${LIBNETFILTER_CONNTRACK_LIBS} \ ${LIBNETFILTER_QUEUE_LIBS} ${LIBNETFILTER_CTHELPER_LIBS} \ - ${libdl_LIBS} + ${libdl_LIBS} ${LIBNFNETLINK_LIBS} conntrackd_LDFLAGS = -export-dynamic -- cgit v1.2.3 From 3f845636159298fb18b6d6c455066d0344a61bee Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 3 Oct 2012 22:19:25 +0200 Subject: conntrackd: fix crash if ExpectationSync is enabled on old Linux kernels ExpectationSync requires Linux kernel >= 3.5 to work sanely, document this. Still, we don't want to crash if someone enables expectation sync with old Linux kernels (like 2.6.32). Reported-by: James Gutholm Tested-by: James Gutholm Signed-off-by: Pablo Neira Ayuso --- doc/manual/conntrack-tools.tmpl | 7 +++++++ doc/sync/alarm/conntrackd.conf | 3 ++- doc/sync/ftfw/conntrackd.conf | 3 ++- doc/sync/notrack/conntrackd.conf | 3 ++- src/build.c | 3 ++- src/filter.c | 12 +++++++++++- 6 files changed, 26 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index 47e6f84..63a53e4 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -660,6 +660,13 @@ Sync { Synchronization of expectations + Check your Linux kernel version first + + The synchronization of expectations require a Linux kernel >= 3.5 + to work appropriately. + + + The connection tracking system provides helpers that allows you to filter multi-flow application protocols like FTP, H.323 and SIP among many others. These protocols usually split the control and data traffic in diff --git a/doc/sync/alarm/conntrackd.conf b/doc/sync/alarm/conntrackd.conf index b9520fb..0223745 100644 --- a/doc/sync/alarm/conntrackd.conf +++ b/doc/sync/alarm/conntrackd.conf @@ -194,7 +194,8 @@ Sync { # Set this option on if you want to enable the synchronization # of expectations. You have to specify the list of helpers that - # you want to enable. Default is off. + # you want to enable. Default is off. This feature requires + # a Linux kernel >= 3.5. # # ExpectationSync { # ftp diff --git a/doc/sync/ftfw/conntrackd.conf b/doc/sync/ftfw/conntrackd.conf index 53a7d0f..65e7b77 100644 --- a/doc/sync/ftfw/conntrackd.conf +++ b/doc/sync/ftfw/conntrackd.conf @@ -217,7 +217,8 @@ Sync { # Set this option on if you want to enable the synchronization # of expectations. You have to specify the list of helpers that - # you want to enable. Default is off. + # you want to enable. Default is off. This feature requires + # a Linux kernel >= 3.5. # # ExpectationSync { # ftp diff --git a/doc/sync/notrack/conntrackd.conf b/doc/sync/notrack/conntrackd.conf index 11f022e..3d036fb 100644 --- a/doc/sync/notrack/conntrackd.conf +++ b/doc/sync/notrack/conntrackd.conf @@ -256,7 +256,8 @@ Sync { # Set this option on if you want to enable the synchronization # of expectations. You have to specify the list of helpers that - # you want to enable. Default is off. + # you want to enable. Default is off. This feature requires + # a Linux kernel >= 3.5. # # ExpectationSync { # ftp diff --git a/src/build.c b/src/build.c index 7d4ef12..e15eb4f 100644 --- a/src/build.c +++ b/src/build.c @@ -356,7 +356,8 @@ void exp2msg(const struct nf_expect *exp, struct nethdr *n) exp_build_u32(exp, ATTR_EXP_NAT_DIR, n, NTA_EXP_NAT_DIR); } - exp_build_str(exp, ATTR_EXP_HELPER_NAME, n, NTA_EXP_HELPER_NAME); + if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME)) + exp_build_str(exp, ATTR_EXP_HELPER_NAME, n, NTA_EXP_HELPER_NAME); if (nfexp_attr_is_set(exp, ATTR_EXP_FN)) exp_build_str(exp, ATTR_EXP_FN, n, NTA_EXP_FN); } diff --git a/src/filter.c b/src/filter.c index 39dd4ca..02a8078 100644 --- a/src/filter.c +++ b/src/filter.c @@ -473,7 +473,17 @@ int exp_filter_find(struct exp_filter *f, const struct nf_expect *exp) return 1; list_for_each_entry(item, &f->list, head) { - const char *name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME); + const char *name; + + if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME)) + name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME); + else { + /* No helper name, this is likely to be a kernel older + * which does not include the helper name, just skip + * this so we don't crash. + */ + return 0; + } /* we allow partial matching to support things like sip-PORT. */ if (strncasecmp(item->helper_name, name, -- cgit v1.2.3