summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGaurav Sinha <gaurav.sinha@vyatta.com>2012-05-30 07:54:05 -0700
committerGaurav Sinha <gaurav.sinha@vyatta.com>2012-05-30 07:54:05 -0700
commita608049a22dc23676c85bbf443e45cbbf0e9b83c (patch)
tree1b82fa315337a8503390384c2684fdbb27b58294 /src
parent775fea07517af4b68cb2ce75e25ee5af09af0f05 (diff)
parent687fc04ea8de73eb1ec19d933c8d81f054c977dd (diff)
downloadconntrack-tools-a608049a22dc23676c85bbf443e45cbbf0e9b83c.tar.gz
conntrack-tools-a608049a22dc23676c85bbf443e45cbbf0e9b83c.zip
Merge branch 'cthelper9' of git://git.netfilter.org/conntrack-tools into user_space_helpers
Conflicts: .gitignore src/run.c
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am27
-rw-r--r--src/channel.c5
-rw-r--r--src/channel_tcp.c1
-rw-r--r--src/conntrack.c42
-rw-r--r--src/cthelper.c521
-rw-r--r--src/ctnl.c522
-rw-r--r--src/expect.c212
-rw-r--r--src/fds.c61
-rw-r--r--src/filter.c9
-rw-r--r--src/helpers.c76
-rw-r--r--src/helpers/Makefile.am17
-rw-r--r--src/helpers/ftp.c599
-rw-r--r--src/helpers/rpc.c488
-rw-r--r--src/helpers/tns.c396
-rw-r--r--src/main.c6
-rw-r--r--src/multichannel.c7
-rw-r--r--src/nfct-extensions/helper.c619
-rw-r--r--src/nfct-extensions/timeout.c486
-rw-r--r--src/nfct.c122
-rw-r--r--src/read_config_lex.l5
-rw-r--r--src/read_config_yy.y200
-rw-r--r--src/run.c564
-rw-r--r--src/stack.c56
-rw-r--r--src/stats-mode.c1
-rw-r--r--src/sync-mode.c149
-rw-r--r--src/tcp.c30
-rw-r--r--src/utils.c243
28 files changed, 4809 insertions, 656 deletions
diff --git a/src/.gitignore b/src/.gitignore
index 6e6763d..55a0d27 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1,5 +1,6 @@
/conntrack
/conntrackd
+/nfct
/read_config_lex.c
/read_config_yy.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 7d7b2ac..0b047e9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,19 +1,35 @@
include $(top_srcdir)/Make_global.am
+SUBDIRS = helpers
+
AM_YFLAGS = -d
CLEANFILES = read_config_yy.c read_config_lex.c
-sbin_PROGRAMS = conntrack conntrackd
+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}
+nfct_SOURCES = nfct.c \
+ helpers.c \
+ nfct-extensions/timeout.c \
+ nfct-extensions/helper.c
+
+nfct_LDADD = ${LIBMNL_LIBS} \
+ ${LIBNETFILTER_CONNTRACK_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 cthelper.c \
sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \
traffic_stats.c stats-mode.c \
network.c cidr.c \
@@ -22,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/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/conntrack.c b/src/conntrack.c
index 61d595b..07cc2f9 100644
--- a/src/conntrack.c
+++ b/src/conntrack.c
@@ -1,5 +1,6 @@
/*
- * (C) 2005-2008 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2005-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2012 by Intra2net AG <http://www.intra2net.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -72,6 +73,9 @@ static struct {
/* Allows filtering/setting specific bits in the ctmark */
struct u32_mask mark;
+
+ /* Allow to filter by mark from kernel-space. */
+ struct nfct_filter_dump_mark filter_mark_kernel;
} tmpl;
static int alloc_tmpl_objects(void)
@@ -1374,6 +1378,7 @@ static int event_exp_cb(enum nf_conntrack_msg_type type,
nfexp_snprintf(buf,sizeof(buf), exp, type, op_type, op_flags);
printf("%s\n", buf);
+ fflush(stdout);
counter++;
return NFCT_CB_CONTINUE;
@@ -1630,6 +1635,8 @@ int main(int argc, char *argv[])
case 'm':
options |= opt2type[c];
parse_u32_mask(optarg, &tmpl.mark);
+ tmpl.filter_mark_kernel.val = tmpl.mark.value;
+ tmpl.filter_mark_kernel.mask = tmpl.mark.mask;
break;
case 'a':
fprintf(stderr, "WARNING: ignoring -%c, "
@@ -1703,6 +1710,7 @@ int main(int argc, char *argv[])
h->final_check(l4flags, cmd, tmpl.ct);
switch(command) {
+ struct nfct_filter_dump *filter_dump;
case CT_LIST:
cth = nfct_open(CONNTRACK, 0);
@@ -1716,10 +1724,23 @@ int main(int argc, char *argv[])
nfct_callback_register(cth, NFCT_T_ALL, dump_cb, tmpl.ct);
+ filter_dump = nfct_filter_dump_create();
+ if (filter_dump == NULL)
+ exit_error(OTHER_PROBLEM, "OOM");
+
+ nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK,
+ &tmpl.filter_mark_kernel);
+ nfct_filter_dump_set_attr_u8(filter_dump,
+ NFCT_FILTER_DUMP_L3NUM,
+ family);
+
if (options & CT_OPT_ZERO)
- res = nfct_query(cth, NFCT_Q_DUMP_RESET, &family);
+ res = nfct_query(cth, NFCT_Q_DUMP_FILTER_RESET,
+ filter_dump);
else
- res = nfct_query(cth, NFCT_Q_DUMP, &family);
+ res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump);
+
+ nfct_filter_dump_destroy(filter_dump);
if (dump_xml_header_done == 0) {
printf("</conntrack>\n");
@@ -1798,7 +1819,20 @@ int main(int argc, char *argv[])
nfct_callback_register(cth, NFCT_T_ALL, delete_cb, tmpl.ct);
- res = nfct_query(cth, NFCT_Q_DUMP, &family);
+ filter_dump = nfct_filter_dump_create();
+ if (filter_dump == NULL)
+ exit_error(OTHER_PROBLEM, "OOM");
+
+ nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK,
+ &tmpl.filter_mark_kernel);
+ nfct_filter_dump_set_attr_u8(filter_dump,
+ NFCT_FILTER_DUMP_L3NUM,
+ family);
+
+ res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump);
+
+ nfct_filter_dump_destroy(filter_dump);
+
nfct_close(ith);
nfct_close(cth);
break;
diff --git a/src/cthelper.c b/src/cthelper.c
new file mode 100644
index 0000000..7624dc0
--- /dev/null
+++ b/src/cthelper.c
@@ -0,0 +1,521 @@
+/*
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#include "conntrackd.h"
+#include "log.h"
+#include "fds.h"
+#include "helper.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <net/ethernet.h>
+
+#ifndef __aligned_be64
+#define __aligned_be64 unsigned long long __attribute__((aligned(8)))
+#endif
+
+#include <linux/netfilter/nfnetlink_queue.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_cthelper/libnetfilter_cthelper.h>
+#include <linux/netfilter.h>
+#include <libnetfilter_queue/pktbuff.h>
+
+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_build_header(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 nlmsghdr *nlh;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+
+ nlh = nfq_build_header(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_build(nlh, id, verdict);
+
+ 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_build_header(buf, NFQNL_MSG_VERDICT, queue_num);
+ nfq_nlmsg_verdict_build(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(void *pkt, uint32_t pktlen, 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;
+ struct pkt_buff *pktb;
+
+ /* 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);
+ }
+
+ /* XXX: 256 bytes for extra allocation for all mangling
+ * we do in helpers.
+ */
+ pktb = pktb_alloc(AF_INET, pkt, pktlen, 256);
+ if (pktb == NULL)
+ break;
+
+ *verdict = cur->helper->cb(pktb, protoff, myct, ctinfo);
+
+ pktb_free(pktb);
+
+ 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;
+ 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]));
+
+ /* Misconfiguration: if no helper found, accept the packet. */
+ helper = helper_run(pkt, pktlen, protoff, myct, ctinfo, queue_num,
+ &verdict);
+ if (!helper)
+ goto err;
+
+ if (pkt_verdict_issue(helper, myct, queue_num, id, verdict) < 0)
+ goto err;
+
+ 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:
+ /* 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; j<CTD_HELPER_POLICY_MAX; j++) {
+ struct nfct_helper_policy *p;
+
+ if (!cur->helper->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_build_header(buf, NFQNL_MSG_CONFIG, cur->queue_num);
+ nfq_nlmsg_cfg_build_request(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_build_header(buf, NFQNL_MSG_CONFIG, cur->queue_num);
+ nfq_nlmsg_cfg_add_copy(nlh, NFQNL_COPY_PACKET, 0xffff);
+ mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQNL_F_CONNTRACK));
+
+ 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_build_header(buf, NFQNL_MSG_CONFIG, 0);
+ nfq_nlmsg_cfg_build_request(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_build_header(buf, NFQNL_MSG_CONFIG, 0);
+ nfq_nlmsg_cfg_build_request(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/ctnl.c b/src/ctnl.c
new file mode 100644
index 0000000..019c7e8
--- /dev/null
+++ b/src/ctnl.c
@@ -0,0 +1,522 @@
+/*
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#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 <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+
+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(STATE(flush));
+ 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/expect.c b/src/expect.c
new file mode 100644
index 0000000..eab9094
--- /dev/null
+++ b/src/expect.c
@@ -0,0 +1,212 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#include "helper.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+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)
+{
+ 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);
+
+ 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);
+}
+
+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/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 <pablo@netfilter.org>
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* 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. <http://www.vyatta.com>
*/
#include <stdlib.h>
#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#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/filter.c b/src/filter.c
index afefbfa..39dd4ca 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -1,6 +1,7 @@
/*
- * (C) 2006-2008 by Pablo Neira Ayuso <pablo@netfilter.org>
- *
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2011-2012 by Vyatta Inc <http://www.vyatta.com>
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -449,7 +450,7 @@ int exp_filter_add(struct exp_filter *f, const char *helper_name)
return -1;
list_for_each_entry(item, &f->list, head) {
- if (strncmp(item->helper_name, helper_name,
+ if (strncasecmp(item->helper_name, helper_name,
NFCT_HELPER_NAME_MAX) == 0) {
return -1;
}
@@ -475,7 +476,7 @@ int exp_filter_find(struct exp_filter *f, const struct nf_expect *exp)
const char *name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME);
/* we allow partial matching to support things like sip-PORT. */
- if (strncmp(item->helper_name, name,
+ if (strncasecmp(item->helper_name, name,
strlen(item->helper_name)) == 0) {
return 1;
}
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 <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#include "helper.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+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..589b4f3
--- /dev/null
+++ b/src/helpers/Makefile.am
@@ -0,0 +1,17 @@
+include $(top_srcdir)/Make_global.am
+
+pkglib_LTLIBRARIES = ct_helper_ftp.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)
+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/ftp.c b/src/helpers/ftp.c
new file mode 100644
index 0000000..c6ad7da
--- /dev/null
+++ b/src/helpers/ftp.c
@@ -0,0 +1,599 @@
+/*
+ * (C) 2010-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Based on: kernel-space FTP extension for connection tracking.
+ *
+ * This port has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ *
+ * Original copyright notice:
+ *
+ * (C) 1999-2001 Paul `Rusty' Russell
+ * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
+ * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org>
+ *
+ * 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 <ctype.h> /* for isdigit */
+#include <errno.h>
+
+#include <netinet/tcp.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+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(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
+ <lincoln@cesar.org.br> 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)) {
+ 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/helpers/rpc.c b/src/helpers/rpc.c
new file mode 100644
index 0000000..97c1b35
--- /dev/null
+++ b/src/helpers/rpc.c
@@ -0,0 +1,488 @@
+/*
+ * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Based on: RPC extension for conntrack.
+ *
+ * This port has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ *
+ * Original copyright notice:
+ *
+ * (C) 2000 by Marcelo Barbosa Lima <marcelo.lima@dcc.unicamp.br>
+ * (C) 2001 by Rusty Russell <rusty@rustcorp.com.au>
+ * (C) 2002,2003 by Ian (Larry) Latter <Ian.Latter@mq.edu.au>
+ * (C) 2004,2005 by David Stes <stes@pandora.be>
+ *
+ * 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 <errno.h>
+
+#include <rpc/rpc_msg.h>
+#include <rpc/pmap_prot.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+/* 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)) {
+ 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);
+}
diff --git a/src/helpers/tns.c b/src/helpers/tns.c
new file mode 100644
index 0000000..77f01d9
--- /dev/null
+++ b/src/helpers/tns.c
@@ -0,0 +1,396 @@
+/*
+ * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Sponsored by Vyatta Inc. <http://www.vyatta.com>
+ *
+ * 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 <ctype.h> /* for isdigit */
+#include <errno.h>
+
+#include <netinet/tcp.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+/* 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;
+ uint32_t array[4];
+ const char *p;
+
+ p = strstr(data, "(");
+ if (!p)
+ return 0;
+
+ p = strstr(p+1, "HOST=");
+ if (!p) {
+ pr_debug("HOST= not found\n");
+ return 0;
+ }
+
+ offset = (int)(p - data) + strlen("HOST=");
+ *numoff = offset;
+ 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=");
+ return get_port(p, dlen - offset - length, ')', cmd);
+}
+
+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),
+ "%pI4)(PORT=%u)", &newip.ip, port);
+ if (!buflen)
+ goto out;
+
+ if (!nfq_tcp_mangle(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)) {
+ 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, 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);
+}
diff --git a/src/main.c b/src/main.c
index 0850a29..831a3c2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -19,6 +19,7 @@
#include "conntrackd.h"
#include "log.h"
+#include "helper.h"
#include <sys/types.h>
#include <sys/stat.h>
@@ -31,7 +32,7 @@
#include <limits.h>
struct ct_general_state st;
-union ct_state state;
+struct ct_state state;
static const char usage_daemon_commands[] =
"Daemon mode commands:\n"
@@ -49,6 +50,7 @@ static const char usage_client_commands[] =
"dump statistics\n"
" -R [ct|expect], resync with kernel conntrack table\n"
" -n, request resync with other node (only FT-FW and NOTRACK modes)\n"
+ " -B, force a bulk send to other replica firewalls\n"
" -x, dump cache in XML format (requires -i or -e)\n"
" -t, reset the kernel timeout (see PurgeTimeout clause)\n"
" -v, display conntrackd version\n"
@@ -405,6 +407,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/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 <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/netfilter/nfnetlink_cthelper.h>
+#include <libnetfilter_cthelper/libnetfilter_cthelper.h>
+
+#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; j<CTD_HELPER_POLICY_MAX; j++) {
+ struct nfct_helper_policy *p;
+
+ if (!helper->policy[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-extensions/timeout.c b/src/nfct-extensions/timeout.c
new file mode 100644
index 0000000..5b32023
--- /dev/null
+++ b/src/nfct-extensions/timeout.c
@@ -0,0 +1,486 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/netfilter/nfnetlink_cttimeout.h>
+#include <libnetfilter_cttimeout/libnetfilter_cttimeout.h>
+
+#include "nfct.h"
+
+static void
+nfct_cmd_timeout_usage(char *argv[])
+{
+ fprintf(stderr, "nfct v%s: Missing command\n"
+ "%s timeout list|add|delete|get|flush "
+ "[parameters...]\n", VERSION, argv[0]);
+}
+
+int nfct_cmd_timeout_parse_params(int argc, char *argv[])
+{
+ int cmd = NFCT_CMD_NONE, ret;
+
+ if (argc < 3) {
+ nfct_cmd_timeout_usage(argv);
+ return -1;
+ }
+ 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 {
+ fprintf(stderr, "nfct v%s: Unknown command: %s\n",
+ VERSION, argv[2]);
+ nfct_cmd_timeout_usage(argv);
+ return -1;
+ }
+ switch(cmd) {
+ case NFCT_CMD_LIST:
+ ret = nfct_cmd_timeout_list(argc, argv);
+ break;
+ case NFCT_CMD_ADD:
+ ret = nfct_cmd_timeout_add(argc, argv);
+ break;
+ case NFCT_CMD_DELETE:
+ ret = nfct_cmd_timeout_delete(argc, argv);
+ break;
+ case NFCT_CMD_GET:
+ ret = nfct_cmd_timeout_get(argc, argv);
+ break;
+ case NFCT_CMD_FLUSH:
+ ret = nfct_cmd_timeout_flush(argc, argv);
+ break;
+ }
+
+ return ret;
+}
+
+static int nfct_timeout_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nfct_timeout *t;
+ char buf[4096];
+
+ t = nfct_timeout_alloc();
+ if (t == NULL) {
+ nfct_perror("OOM");
+ goto err;
+ }
+
+ if (nfct_timeout_nlmsg_parse_payload(nlh, t) < 0) {
+ nfct_perror("nfct_timeout_nlmsg_parse_payload");
+ goto err_free;
+ }
+
+ nfct_timeout_snprintf(buf, sizeof(buf), t, NFCT_TIMEOUT_O_DEFAULT, 0);
+ printf("%s\n", buf);
+
+err_free:
+ nfct_timeout_free(t);
+err:
+ return MNL_CB_OK;
+}
+
+int nfct_cmd_timeout_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_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_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_timeout_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;
+}
+
+static uint32_t nfct_timeout_attr_max[IPPROTO_MAX] = {
+ [IPPROTO_ICMP] = NFCT_TIMEOUT_ATTR_ICMP_MAX,
+ [IPPROTO_TCP] = NFCT_TIMEOUT_ATTR_TCP_MAX,
+ [IPPROTO_UDP] = NFCT_TIMEOUT_ATTR_UDP_MAX,
+ [IPPROTO_UDPLITE] = NFCT_TIMEOUT_ATTR_UDPLITE_MAX,
+ [IPPROTO_SCTP] = NFCT_TIMEOUT_ATTR_SCTP_MAX,
+ [IPPROTO_DCCP] = NFCT_TIMEOUT_ATTR_DCCP_MAX,
+ [IPPROTO_ICMPV6] = NFCT_TIMEOUT_ATTR_ICMPV6_MAX,
+ [IPPROTO_GRE] = NFCT_TIMEOUT_ATTR_GRE_MAX,
+ [IPPROTO_RAW] = NFCT_TIMEOUT_ATTR_GENERIC_MAX,
+};
+
+int nfct_cmd_timeout_add(int argc, char *argv[])
+{
+ struct mnl_socket *nl;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+ uint16_t l3proto;
+ uint8_t l4proto;
+ int ret, i;
+ unsigned int j;
+
+ if (argc < 6) {
+ nfct_perror("missing parameters\n"
+ "syntax: nfct timeout add name "
+ "family protocol state1 "
+ "timeout1 state2 timeout2...");
+ return -1;
+ }
+
+ t = nfct_timeout_alloc();
+ if (t == NULL) {
+ nfct_perror("OOM");
+ return -1;
+ }
+
+ nfct_timeout_attr_set(t, NFCT_TIMEOUT_ATTR_NAME, argv[3]);
+
+ 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_timeout_attr_set_u16(t, NFCT_TIMEOUT_ATTR_L3PROTO, l3proto);
+
+ if (strcmp(argv[5], "tcp") == 0)
+ l4proto = IPPROTO_TCP;
+ else if (strcmp(argv[5], "udp") == 0)
+ l4proto = IPPROTO_UDP;
+ else if (strcmp(argv[5], "udplite") == 0)
+ l4proto = IPPROTO_UDPLITE;
+ else if (strcmp(argv[5], "sctp") == 0)
+ l4proto = IPPROTO_SCTP;
+ else if (strcmp(argv[5], "dccp") == 0)
+ l4proto = IPPROTO_DCCP;
+ else if (strcmp(argv[5], "icmp") == 0)
+ l4proto = IPPROTO_ICMP;
+ else if (strcmp(argv[5], "icmpv6") == 0)
+ l4proto = IPPROTO_ICMPV6;
+ else if (strcmp(argv[5], "gre") == 0)
+ l4proto = IPPROTO_GRE;
+ else if (strcmp(argv[5], "generic") == 0)
+ l4proto = IPPROTO_RAW;
+ else {
+ nfct_perror("unknown layer 4 protocol");
+ return -1;
+ }
+ nfct_timeout_attr_set_u8(t, NFCT_TIMEOUT_ATTR_L4PROTO, l4proto);
+
+ for (i=6; i<argc; i+=2) {
+ int matching = -1;
+
+ for (j=0; j<nfct_timeout_attr_max[l4proto]; j++) {
+ const char *state_name;
+
+ state_name =
+ nfct_timeout_policy_attr_to_name(l4proto, j);
+ if (state_name == NULL) {
+ nfct_perror("state name is NULL");
+ return -1;
+ }
+ if (strcasecmp(argv[i], state_name) != 0)
+ continue;
+
+ matching = j;
+ break;
+ }
+ if (matching != -1) {
+ if (i+1 >= argc) {
+ nfct_perror("missing value for this timeout");
+ return -1;
+ }
+ nfct_timeout_policy_attr_set_u32(t, matching,
+ atoi(argv[i+1]));
+ matching = -1;
+ } else {
+ fprintf(stderr, "nfct v%s: Wrong state name: `%s' "
+ "for protocol `%s'\n",
+ VERSION, argv[i], argv[5]);
+ return -1;
+ }
+ }
+
+ seq = time(NULL);
+ nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_NEW,
+ NLM_F_CREATE | NLM_F_ACK, seq);
+ nfct_timeout_nlmsg_build_payload(nlh, t);
+
+ nfct_timeout_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_timeout_delete(int argc, char *argv[])
+{
+ struct mnl_socket *nl;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+ int ret;
+
+ if (argc < 4) {
+ nfct_perror("missing timeout policy name");
+ return -1;
+ } else if (argc > 4) {
+ nfct_perror("too many arguments");
+ return -1;
+ }
+
+ t = nfct_timeout_alloc();
+ if (t == NULL) {
+ nfct_perror("OOM");
+ return -1;
+ }
+
+ nfct_timeout_attr_set(t, NFCT_TIMEOUT_ATTR_NAME, argv[3]);
+
+ seq = time(NULL);
+ nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_DELETE,
+ NLM_F_ACK, seq);
+ nfct_timeout_nlmsg_build_payload(nlh, t);
+
+ nfct_timeout_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_timeout_get(int argc, char *argv[])
+{
+ struct mnl_socket *nl;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+ int ret;
+
+ if (argc < 4) {
+ nfct_perror("missing timeout policy name");
+ return -1;
+ } else if (argc > 4) {
+ nfct_perror("too many arguments");
+ return -1;
+ }
+
+ t = nfct_timeout_alloc();
+ if (t == NULL) {
+ nfct_perror("OOM");
+ return -1;
+ }
+ nfct_timeout_attr_set(t, NFCT_TIMEOUT_ATTR_NAME, argv[3]);
+
+ seq = time(NULL);
+ nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_GET,
+ NLM_F_ACK, seq);
+
+ nfct_timeout_nlmsg_build_payload(nlh, t);
+
+ nfct_timeout_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_timeout_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_timeout_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_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_DELETE,
+ 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;
+}
diff --git a/src/nfct.c b/src/nfct.c
new file mode 100644
index 0000000..b5c9654
--- /dev/null
+++ b/src/nfct.c
@@ -0,0 +1,122 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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. <http://www.vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/netfilter/nfnetlink_cttimeout.h>
+#include <libnetfilter_cttimeout/libnetfilter_cttimeout.h>
+
+#include "nfct.h"
+
+static int nfct_cmd_version(int argc, char *argv[]);
+static int nfct_cmd_help(int argc, char *argv[]);
+
+static void usage(char *argv[])
+{
+ fprintf(stderr, "Usage: %s subsystem command [parameters]...\n",
+ argv[0]);
+}
+
+void nfct_perror(const char *msg)
+{
+ if (errno == 0) {
+ fprintf(stderr, "nfct v%s: %s\n", VERSION, msg);
+ } else {
+ fprintf(stderr, "nfct v%s: %s: %s\n",
+ VERSION, msg, strerror(errno));
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int subsys = NFCT_SUBSYS_NONE, ret = 0;
+
+ if (argc < 2) {
+ usage(argv);
+ exit(EXIT_FAILURE);
+ }
+ 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)
+ subsys = NFCT_SUBSYS_HELP;
+ else {
+ fprintf(stderr, "nfct v%s: Unknown subsystem: %s\n",
+ VERSION, argv[1]);
+ usage(argv);
+ exit(EXIT_FAILURE);
+ }
+
+ switch(subsys) {
+ 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;
+ case NFCT_SUBSYS_HELP:
+ ret = nfct_cmd_help(argc, argv);
+ break;
+ }
+ return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+static const char version_msg[] =
+ "nfct v%s: utility for the Netfilter's Connection Tracking System\n"
+ "Copyright (C) 2012 Pablo Neira Ayuso <pablo@netfilter.org>\n"
+ "This program comes with ABSOLUTELY NO WARRANTY.\n"
+ "This is free software, and you are welcome to redistribute it under "
+ "certain \nconditions; see LICENSE file distributed in this package "
+ "for details.\n";
+
+static int nfct_cmd_version(int argc, char *argv[])
+{
+ printf(version_msg, VERSION);
+ return 0;
+}
+
+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"
+ "Commands:\n"
+ " list [reset]\t\tList the accounting object table (and reset)\n"
+ " add object-name\tAdd new accounting object to table\n"
+ " delete object-name\tDelete existing accounting object\n"
+ " get object-name\tGet existing accounting object\n"
+ " flush\t\t\tFlush accounting object table\n";
+
+static int nfct_cmd_help(int argc, char *argv[])
+{
+ printf(help_msg, VERSION, argv[0]);
+ return 0;
+}
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 d94bd85..623b79d 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 <syslog.h>
#include <sched.h>
+#include <dlfcn.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h>
@@ -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 <string> T_IP T_PATH_VAL
%token <val> T_NUMBER
@@ -96,6 +110,7 @@ line : ignore_protocol
| general
| sync
| stats
+ | helper
;
logfile_bool : T_LOG T_ON
@@ -1580,6 +1595,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; i<CTD_HELPER_POLICY_MAX; i++) {
+ if (strcmp(helper->policy[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))
@@ -1659,6 +1854,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 159ef30..3337694 100644
--- a/src/run.c
+++ b/src/run.c
@@ -1,5 +1,5 @@
/*
- * (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2011 by Vyatta Inc. <http://www.vyatta.com>
*
* 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. <http://www.vyatta.com>
*/
#include "conntrackd.h"
@@ -45,24 +45,15 @@ 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();
+ if (CONFIG(flags) & CTD_HELPER)
+ cthelper_kill();
+
+ destroy_fds(STATE(fds));
unlink(CONFIG(lockfile));
dlog(LOG_NOTICE, "---- shutdown received ----");
close_log();
@@ -187,62 +178,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(STATE(flush));
- 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 +188,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,182 +199,19 @@ 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:
- 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);
+ if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))
+ return ctnl_local(fd, type, data);
- STATE(stats).nl_events_received++;
+ if (CONFIG(flags) & CTD_HELPER)
+ return cthelper_local(fd, type, data);
- 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;
+ return ret;
}
-static int exp_get_handler(enum nf_conntrack_msg_type type,
- struct nf_expect *exp, void *data)
+/* order received via UNIX socket */
+static void local_cb(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;
+ do_local_server_step(&STATE(local), NULL, local_handler);
}
int
@@ -464,133 +219,18 @@ 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!");
return -1;
}
- register_fd(STATE(local).fd, 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);
- register_fd(nfct_fd(STATE(resync)), 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)), STATE(fds));
- }
+ register_fd(STATE(local).fd, local_cb, NULL, STATE(fds));
/* Signals handling */
sigemptyset(&STATE(block));
@@ -611,163 +251,19 @@ init(void)
if (signal(SIGCHLD, child) == SIG_ERR)
return -1;
- time(&STATE(stats).daemon_start_time);
-
- dlog(LOG_NOTICE, "initialization completed");
-
- 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;
+ /* Initialization */
+ if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))
+ if (ctnl_init() < 0)
+ return -1;
- STATE(stats).select_failed++;
- return;
+ if (CONFIG(flags) & CTD_HELPER) {
+ if (cthelper_init() < 0)
+ return -1;
}
- /* 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);
+ time(&STATE(stats).daemon_start_time);
- run_step(next);
- }
-}
+ dlog(LOG_NOTICE, "initialization completed");
-void run(void)
-{
- if (CONFIG(flags) & CTD_POLL) {
- do_run(run_polling);
- } else {
- do_run(run_events);
- }
+ return 0;
}
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 <pablo@netfilter.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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/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 10fdb9e..b5707d5 100644
--- a/src/sync-mode.c
+++ b/src/sync-mode.c
@@ -1,5 +1,5 @@
/*
- * (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2011 by Vyatta Inc. <http://www.vyatta.com>
*
* 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; k<CONFIG(event_iterations_limit); k++) {
- if (channel_handler_routine(m, i) == -1) {
+ if (channel_handler_routine(c) == -1) {
break;
}
}
@@ -284,7 +285,7 @@ static void interface_candidate(void)
dlog(LOG_ERR, "no dedicated links available!");
}
-static void interface_handler(void)
+static void interface_handler(void *data)
{
int idx = multichannel_get_current_ifindex(STATE_SYNC(channel));
unsigned int flags;
@@ -311,6 +312,53 @@ static void do_reset_cache_alarm(struct alarm_block *a, void *data)
STATE(mode)->internal->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; i<STATE_SYNC(channel)->channel_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; i<STATE_SYNC(channel)->channel_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();
@@ -624,9 +629,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);
@@ -729,7 +731,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 c551c54..af27c46 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -27,7 +27,7 @@
struct tcp_sock *tcp_server_create(struct tcp_conf *c)
{
- int yes = 1, ret;
+ int yes = 1;
struct tcp_sock *m;
socklen_t socklen = sizeof(int);
@@ -109,30 +109,7 @@ struct tcp_sock *tcp_server_create(struct tcp_conf *c)
return NULL;
}
- /* now we accept new connections ... */
- ret = accept(m->fd, NULL, NULL);
- if (ret == -1) {
- if (errno != EAGAIN) {
- /* unexpected error, give up. */
- close(m->fd);
- free(m);
- m = NULL;
- } else {
- /* still in progress ... we'll do it in tcp_recv() */
- m->state = TCP_SERVER_ACCEPTING;
- }
- } else {
- /* very unlikely at this stage. */
- if (fcntl(ret, F_SETFL, O_NONBLOCK) == -1) {
- /* unexpected error, give up. */
- close(m->fd);
- free(m);
- return NULL;
- }
- m->client_fd = ret;
- m->state = TCP_SERVER_CONNECTED;
- register_fd(m->client_fd, STATE(fds));
- }
+ m->state = TCP_SERVER_ACCEPTING;
return m;
}
@@ -287,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;
}
@@ -367,7 +343,6 @@ ssize_t tcp_recv(struct tcp_sock *m, void *data, int size)
close(m->client_fd);
m->client_fd = -1;
m->state = TCP_SERVER_ACCEPTING;
- tcp_accept(m);
} else if (errno != EAGAIN) {
m->stats.error++;
}
@@ -377,7 +352,6 @@ ssize_t tcp_recv(struct tcp_sock *m, void *data, int size)
close(m->client_fd);
m->client_fd = -1;
m->state = TCP_SERVER_ACCEPTING;
- tcp_accept(m);
}
if (ret >= 0) {
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 <kuznet@ms2.inr.ac.ru>
+ *
+ * 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 <stdint.h>
+#include <ctype.h>
+#include <string.h> /* 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;
+}