summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPablo Neira Ayuso <pablo@netfilter.org>2012-05-15 01:51:29 +0200
committerPablo Neira Ayuso <pablo@netfilter.org>2012-06-07 17:33:22 +0200
commita6cf1454b9a435d489ebdc0692058a3c27a59e30 (patch)
treeb950739aed892c232e47662f38793831a286ffec /src
parente47233151ca5098b268281329b119a398918d75f (diff)
downloadconntrack-tools-a6cf1454b9a435d489ebdc0692058a3c27a59e30.tar.gz
conntrack-tools-a6cf1454b9a435d489ebdc0692058a3c27a59e30.zip
conntrackd: add cthelper infrastructure (+ example FTP helper)
This patch adds the user-space helper infrastructure. It also contains the implementation of the FTP helper in user-space. There's one example file that you can use to configure conntrackd as user-space connection tracking helper under: doc/helper/conntrackd.conf Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am24
-rw-r--r--src/cthelper.c520
-rw-r--r--src/expect.c214
-rw-r--r--src/helpers.c76
-rw-r--r--src/helpers/Makefile.am9
-rw-r--r--src/helpers/ftp.c599
-rw-r--r--src/main.c3
-rw-r--r--src/nfct-extensions/helper.c619
-rw-r--r--src/nfct.c6
-rw-r--r--src/read_config_lex.l5
-rw-r--r--src/read_config_yy.y200
-rw-r--r--src/run.c11
-rw-r--r--src/stack.c56
-rw-r--r--src/utils.c243
14 files changed, 2579 insertions, 6 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index bbea176..0b047e9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,7 @@
include $(top_srcdir)/Make_global.am
+SUBDIRS = helpers
+
AM_YFLAGS = -d
CLEANFILES = read_config_yy.c read_config_lex.c
@@ -10,17 +12,24 @@ conntrack_SOURCES = conntrack.c
conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS}
nfct_SOURCES = nfct.c \
- nfct-extensions/timeout.c
+ helpers.c \
+ nfct-extensions/timeout.c \
+ nfct-extensions/helper.c
+
nfct_LDADD = ${LIBMNL_LIBS} \
${LIBNETFILTER_CONNTRACK_LIBS} \
- ${LIBNETFILTER_CTTIMEOUT_LIBS}
+ ${LIBNETFILTER_CTTIMEOUT_LIBS} \
+ ${LIBNETFILTER_CTHELPER_LIBS} \
+ ${libdl_LIBS}
+
+nfct_LDFLAGS = -export-dynamic
conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \
local.c log.c mcast.c udp.c netlink.c vector.c \
filter.c fds.c event.c process.c origin.c date.c \
cache.c cache-ct.c cache-exp.c \
cache_timer.c \
- ctnl.c \
+ ctnl.c cthelper.c \
sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \
traffic_stats.c stats-mode.c \
network.c cidr.c \
@@ -29,11 +38,16 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \
tcp.c channel_tcp.c \
external_cache.c external_inject.c \
internal_cache.c internal_bypass.c \
- read_config_yy.y read_config_lex.l
+ read_config_yy.y read_config_lex.l \
+ stack.c helpers.c utils.c expect.c
# yacc and lex generate dirty code
read_config_yy.o read_config_lex.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls
-conntrackd_LDADD = ${LIBNETFILTER_CONNTRACK_LIBS}
+conntrackd_LDADD = ${LIBMNL_LIBS} ${LIBNETFILTER_CONNTRACK_LIBS} \
+ ${LIBNETFILTER_QUEUE_LIBS} ${LIBNETFILTER_CTHELPER_LIBS} \
+ ${libdl_LIBS}
+
+conntrackd_LDFLAGS = -export-dynamic
EXTRA_DIST = read_config_yy.h
diff --git a/src/cthelper.c b/src/cthelper.c
new file mode 100644
index 0000000..e303558
--- /dev/null
+++ b/src/cthelper.c
@@ -0,0 +1,520 @@
+/*
+ * (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_queue/pktbuff.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_hdr_put(char *buf, int type, uint32_t queue_num)
+{
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | type;
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+
+ struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+ nfg->nfgen_family = AF_UNSPEC;
+ nfg->version = NFNETLINK_V0;
+ nfg->res_id = htons(queue_num);
+
+ return nlh;
+}
+
+static int
+pkt_get(void *pkt, uint32_t pktlen, uint16_t proto, uint32_t *protoff)
+{
+ switch(proto) {
+ case ETHERTYPE_IP: {
+ struct iphdr *ip = (struct iphdr *) pkt;
+
+ /* No room for IPv4 header. */
+ if (pktlen < sizeof(struct iphdr)) {
+ dlog(LOG_ERR, "no room for IPv4 header");
+ return -1;
+ }
+
+ /* this is not IPv4, skip. */
+ if (ip->version != 4) {
+ dlog(LOG_ERR, "not IPv4, skipping");
+ return -1;
+ }
+
+ *protoff = 4 * ip->ihl;
+
+ switch (ip->protocol) {
+ case IPPROTO_TCP: {
+ struct tcphdr *tcph =
+ (struct tcphdr *) ((char *)pkt + *protoff);
+
+ /* No room for IPv4 header plus TCP header. */
+ if (pktlen < *protoff + sizeof(struct tcphdr)
+ || pktlen < *protoff + tcph->doff * 4) {
+ dlog(LOG_ERR, "no room for IPv4 + TCP header, skip");
+ return -1;
+ }
+ return 0;
+ }
+ case IPPROTO_UDP:
+ /* No room for IPv4 header plus UDP header. */
+ if (pktlen < *protoff + sizeof(struct udphdr)) {
+ dlog(LOG_ERR, "no room for IPv4 + UDP header, skip");
+ return -1;
+ }
+ return 0;
+ default:
+ dlog(LOG_ERR, "not TCP/UDP, skipping");
+ return -1;
+ }
+ break;
+ }
+ case ETHERTYPE_IPV6:
+ dlog(LOG_ERR, "no IPv6 support sorry");
+ return 0;
+ default:
+ /* Unknown layer 3 protocol. */
+ dlog(LOG_ERR, "unknown layer 3 protocol (%d), skipping", proto);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+pkt_verdict_issue(struct ctd_helper_instance *cur, struct myct *myct,
+ uint16_t queue_num, uint32_t id, uint32_t verdict,
+ struct pkt_buff *pktb)
+{
+ struct nlmsghdr *nlh;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+
+ nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num);
+
+ /* save private data and send it back to kernel-space. */
+ nfct_set_attr_l(myct->ct, ATTR_HELPER_INFO, myct->priv_data,
+ cur->helper->priv_data_len);
+
+ nfq_nlmsg_verdict_put(nlh, id, verdict);
+ if (pktb_mangled(pktb))
+ nfq_nlmsg_verdict_put_pkt(nlh, pktb_data(pktb), pktb_len(pktb));
+
+ nest = mnl_attr_nest_start(nlh, NFQA_CT);
+ if (nest == NULL)
+ return -1;
+
+ nfct_nlmsg_build(nlh, myct->ct);
+ mnl_attr_nest_end(nlh, nest);
+
+ if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) {
+ dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+pkt_verdict_error(uint16_t queue_num, uint32_t id)
+{
+ struct nlmsghdr *nlh;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num);
+ nfq_nlmsg_verdict_put(nlh, id, NF_ACCEPT);
+
+ if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) {
+ dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static struct ctd_helper_instance *
+helper_run(struct pkt_buff *pktb, uint32_t protoff, struct myct *myct,
+ uint32_t ctinfo, uint32_t queue_num, int *verdict)
+{
+ struct ctd_helper_instance *cur, *helper = NULL;
+
+ list_for_each_entry(cur, &CONFIG(cthelper).list, head) {
+ if (cur->queue_num == queue_num) {
+ const void *priv_data;
+
+ /* retrieve helper private data. */
+ priv_data = nfct_get_attr(myct->ct, ATTR_HELPER_INFO);
+ if (priv_data != NULL) {
+ myct->priv_data =
+ calloc(1, cur->helper->priv_data_len);
+
+ if (myct->priv_data == NULL)
+ continue;
+
+ memcpy(myct->priv_data, priv_data,
+ cur->helper->priv_data_len);
+ }
+
+ *verdict = cur->helper->cb(pktb, protoff, myct, ctinfo);
+ helper = cur;
+ break;
+ }
+ }
+ return helper;
+}
+
+static int nfq_queue_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nfqnl_msg_packet_hdr *ph = NULL;
+ struct nlattr *attr[NFQA_MAX+1] = {};
+ struct nfgenmsg *nfg;
+ uint8_t *pkt;
+ uint16_t l3num;
+ uint32_t id, ctinfo, queue_num = 0, protoff = 0, pktlen;
+ struct nf_conntrack *ct = NULL;
+ struct myct *myct;
+ struct ctd_helper_instance *helper;
+ struct pkt_buff *pktb;
+ int verdict = NF_ACCEPT;
+
+ if (nfq_nlmsg_parse(nlh, attr) < 0) {
+ dlog(LOG_ERR, "problems parsing message from kernel");
+ return MNL_CB_ERROR;
+ }
+
+ ph = (struct nfqnl_msg_packet_hdr *)
+ mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
+ if (ph == NULL) {
+ dlog(LOG_ERR, "problems retrieving metaheader");
+ return MNL_CB_ERROR;
+ }
+
+ id = ntohl(ph->packet_id);
+
+ if (!attr[NFQA_PAYLOAD]) {
+ dlog(LOG_ERR, "packet with no payload");
+ goto err;
+ }
+ if (!attr[NFQA_CT] || !attr[NFQA_CT_INFO]) {
+ dlog(LOG_ERR, "no CT attached to this packet");
+ goto err;
+ }
+
+ pkt = mnl_attr_get_payload(attr[NFQA_PAYLOAD]);
+ pktlen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]);
+
+ nfg = mnl_nlmsg_get_payload(nlh);
+ l3num = nfg->nfgen_family;
+ queue_num = ntohs(nfg->res_id);
+
+ if (pkt_get(pkt, pktlen, ntohs(ph->hw_protocol), &protoff))
+ goto err;
+
+ ct = nfct_new();
+ if (ct == NULL)
+ goto err;
+
+ if (nfct_payload_parse(mnl_attr_get_payload(attr[NFQA_CT]),
+ mnl_attr_get_payload_len(attr[NFQA_CT]),
+ l3num, ct) < 0) {
+ dlog(LOG_ERR, "cannot convert message to CT");
+ goto err;
+ }
+
+ myct = calloc(1, sizeof(struct myct));
+ if (myct == NULL)
+ goto err;
+
+ myct->ct = ct;
+ ctinfo = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO]));
+
+ /* XXX: 256 bytes enough for possible NAT mangling in helpers? */
+ pktb = pktb_alloc(AF_INET, pkt, pktlen, 256);
+ if (pktb == NULL)
+ goto err;
+
+ /* Misconfiguration: if no helper found, accept the packet. */
+ helper = helper_run(pktb, protoff, myct, ctinfo, queue_num, &verdict);
+ if (!helper)
+ goto err_pktb;
+
+ if (pkt_verdict_issue(helper, myct, queue_num, id, verdict, pktb) < 0)
+ goto err_pktb;
+
+ if (ct != NULL)
+ nfct_destroy(ct);
+ if (myct && myct->priv_data != NULL)
+ free(myct->priv_data);
+ if (myct != NULL)
+ free(myct);
+
+ return MNL_CB_OK;
+err_pktb:
+ pktb_free(pktb);
+err:
+ /* In case of error, we don't want to disrupt traffic. We accept all.
+ * This is connection tracking after all. The policy is not to drop
+ * packet unless we enter some inconsistent state.
+ */
+ pkt_verdict_error(queue_num, id);
+
+ if (ct != NULL)
+ nfct_destroy(ct);
+
+ return MNL_CB_OK;
+}
+
+static void nfq_cb(void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ int ret;
+
+ ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf));
+ if (ret == -1) {
+ dlog(LOG_ERR, "failed to receive message: %s", strerror(errno));
+ return;
+ }
+
+ ret = mnl_cb_run(buf, ret, 0, STATE_CTH(portid), nfq_queue_cb, NULL);
+ if (ret < 0){
+ dlog(LOG_ERR, "failed to process message");
+ return;
+ }
+}
+
+static int cthelper_setup(struct ctd_helper_instance *cur)
+{
+ struct nfct_helper *t;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t seq;
+ int j, ret;
+
+ t = nfct_helper_alloc();
+ if (t == NULL) {
+ dlog(LOG_ERR, "cannot allocate object for helper");
+ return -1;
+ }
+
+ nfct_helper_attr_set(t, NFCTH_ATTR_NAME, cur->helper->name);
+ nfct_helper_attr_set_u32(t, NFCTH_ATTR_QUEUE_NUM, cur->queue_num);
+ nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, cur->l3proto);
+ nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, cur->l4proto);
+ nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS,
+ NFCT_HELPER_STATUS_ENABLED);
+
+ dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d",
+ cur->helper->name, cur->queue_num);
+
+ for (j=0; 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_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num);
+ nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_BIND);
+
+ if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) {
+ dlog(LOG_ERR, "failed to send bind command");
+ return -1;
+ }
+
+ nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num);
+ nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
+ mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(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_hdr_put(buf, NFQNL_MSG_CONFIG, 0);
+ nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_UNBIND);
+
+ if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) {
+ dlog(LOG_ERR, "failed to send pf unbind command");
+ return -1;
+ }
+
+ nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0);
+ nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_BIND);
+
+ if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) {
+ dlog(LOG_ERR, "failed to send pf bind command");
+ return -1;
+ }
+
+ return 0;
+}
+
+int cthelper_init(void)
+{
+ struct ctd_helper_instance *cur;
+ int ret;
+
+ state.cthelper = calloc(1, sizeof(struct ct_helper_state));
+ if (state.cthelper == NULL) {
+ dlog(LOG_ERR, "can't allocate memory for cthelper struct");
+ return -1;
+ }
+
+ STATE_CTH(nl) = mnl_socket_open(NETLINK_NETFILTER);
+ if (STATE_CTH(nl) == NULL) {
+ dlog(LOG_ERR, "cannot open nfq socket");
+ return -1;
+ }
+ fcntl(mnl_socket_get_fd(STATE_CTH(nl)), F_SETFL, O_NONBLOCK);
+
+ if (mnl_socket_bind(STATE_CTH(nl), 0, MNL_SOCKET_AUTOPID) < 0) {
+ dlog(LOG_ERR, "cannot bind nfq socket");
+ return -1;
+ }
+ STATE_CTH(portid) = mnl_socket_get_portid(STATE_CTH(nl));
+
+ if (nfq_configure())
+ return -1;
+
+ list_for_each_entry(cur, &CONFIG(cthelper).list, head) {
+ ret = cthelper_configure(cur);
+ if (ret < 0)
+ return ret;
+ }
+
+ register_fd(mnl_socket_get_fd(STATE_CTH(nl)), nfq_cb, NULL, STATE(fds));
+
+ return 0;
+}
diff --git a/src/expect.c b/src/expect.c
new file mode 100644
index 0000000..94c26bc
--- /dev/null
+++ b/src/expect.c
@@ -0,0 +1,214 @@
+/*
+ * (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,
+ uint32_t flags)
+{
+ struct nf_conntrack *expected, *mask;
+
+ expected = nfct_new();
+ if (!expected)
+ return -1;
+
+ mask = nfct_new();
+ if (!mask)
+ return -1;
+
+ if (saddr) {
+ switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) {
+ int i;
+ uint32_t addr[4] = {};
+
+ case AF_INET:
+ nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip);
+
+ nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff);
+ break;
+ case AF_INET6:
+ nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6);
+
+ for (i=0; i<4; i++)
+ memset(addr, 0xffffffff, sizeof(uint32_t));
+
+ nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(mask, ATTR_IPV6_SRC, addr);
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) {
+ int i;
+ uint32_t addr[4] = {};
+
+ case AF_INET:
+ nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000);
+
+ nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000);
+ break;
+ case AF_INET6:
+ for (i=0; i<4; i++)
+ memset(addr, 0x00000000, sizeof(uint32_t));
+
+ nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(expected, ATTR_IPV6_SRC, addr);
+
+ nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(mask, ATTR_IPV6_SRC, addr);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (sport) {
+ switch(l4proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto);
+ nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport);
+ nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto);
+ nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff);
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch(l4proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto);
+ nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000);
+ nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto);
+ nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000);
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) {
+ uint32_t addr[4] = {};
+ int i;
+
+ case AF_INET:
+ nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip);
+ nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff);
+ break;
+ case AF_INET6:
+ nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6);
+
+ for (i=0; i<4; i++)
+ memset(addr, 0xffffffff, sizeof(uint32_t));
+
+ nfct_set_attr(mask, ATTR_IPV6_DST, addr);
+ break;
+ default:
+ break;
+ }
+
+ switch(l4proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto);
+ nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport);
+ nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto);
+ nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff);
+ break;
+ default:
+ break;
+ }
+
+ nfexp_set_attr(exp, ATTR_EXP_MASTER, master);
+ nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected);
+ nfexp_set_attr(exp, ATTR_EXP_MASK, mask);
+ nfexp_set_attr_u32(exp, ATTR_EXP_FLAGS, flags);
+
+ nfct_destroy(expected);
+ nfct_destroy(mask);
+
+ return 0;
+}
+
+static int cthelper_expect_cmd(struct nf_expect *exp, int cmd)
+{
+ int ret;
+ struct nfct_handle *h;
+
+ h = nfct_open(EXPECT, 0);
+ if (!h)
+ return -1;
+
+ ret = nfexp_query(h, cmd, exp);
+
+ nfct_close(h);
+ return ret;
+}
+
+int cthelper_add_expect(struct nf_expect *exp)
+{
+ return cthelper_expect_cmd(exp, NFCT_Q_CREATE);
+}
+
+int cthelper_del_expect(struct nf_expect *exp)
+{
+ return cthelper_expect_cmd(exp, NFCT_Q_DESTROY);
+}
+
+void
+cthelper_get_addr_src(struct nf_conntrack *ct, int dir,
+ union nfct_attr_grp_addr *addr)
+{
+ switch (dir) {
+ case MYCT_DIR_ORIG:
+ nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_SRC, addr);
+ break;
+ case MYCT_DIR_REPL:
+ nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_SRC, addr);
+ break;
+ }
+}
+
+void
+cthelper_get_addr_dst(struct nf_conntrack *ct, int dir,
+ union nfct_attr_grp_addr *addr)
+{
+ switch (dir) {
+ case MYCT_DIR_ORIG:
+ nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_DST, addr);
+ break;
+ case MYCT_DIR_REPL:
+ nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_DST, addr);
+ break;
+ }
+}
diff --git a/src/helpers.c b/src/helpers.c
new file mode 100644
index 0000000..3e4e6c8
--- /dev/null
+++ b/src/helpers.c
@@ -0,0 +1,76 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <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..2c9d63b
--- /dev/null
+++ b/src/helpers/Makefile.am
@@ -0,0 +1,9 @@
+include $(top_srcdir)/Make_global.am
+
+pkglib_LTLIBRARIES = ct_helper_ftp.la
+
+ct_helper_ftp_la_SOURCES = ftp.c
+ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
+
+
diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c
new file mode 100644
index 0000000..962020b
--- /dev/null
+++ b/src/helpers/ftp.c
@@ -0,0 +1,599 @@
+/*
+ * (C) 2010-2012 by Pablo Neira Ayuso <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_ipv4(pkt, matchoff, matchlen, buffer, buflen))
+ goto out;
+
+ return NF_ACCEPT;
+
+out:
+ cthelper_del_expect(exp);
+ return NF_DROP;
+}
+
+static int
+ftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ struct tcphdr *th;
+ unsigned int dataoff;
+ unsigned int matchoff = 0, matchlen = 0; /* makes gcc happy. */
+ unsigned int datalen;
+ unsigned int i;
+ int found = 0, ends_in_nl;
+ uint32_t seq;
+ int ret = NF_ACCEPT;
+ struct myct_man cmd;
+ union nfct_attr_grp_addr addr;
+ union nfct_attr_grp_addr daddr;
+ int dir = CTINFO2DIR(ctinfo);
+ struct ftp_info *ftp_info = myct->priv_data;
+ struct nf_expect *exp = NULL;
+
+ memset(&cmd, 0, sizeof(struct myct_man));
+ memset(&addr, 0, sizeof(union nfct_attr_grp_addr));
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED &&
+ ctinfo != IP_CT_ESTABLISHED_REPLY) {
+ pr_debug("ftp: Conntrackinfo = %u\n", ctinfo);
+ goto out;
+ }
+
+ th = (struct tcphdr *) (pktb_network_header(pkt) + protoff);
+
+ dataoff = protoff + th->doff * 4;
+ datalen = pktb_len(pkt) - dataoff;
+
+ ends_in_nl = (pktb_network_header(pkt)[pktb_len(pkt) - 1] == '\n');
+ seq = ntohl(th->seq) + datalen;
+
+ /* Look up to see if we're just after a \n. */
+ if (!find_nl_seq(ntohl(th->seq), ftp_info, dir)) {
+ /* Now if this ends in \n, update ftp info. */
+ pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n",
+ ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)",
+ ftp_info->seq_aft_nl[dir][0],
+ ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)",
+ ftp_info->seq_aft_nl[dir][1]);
+ goto out_update_nl;
+ }
+
+ /* Initialize IP/IPv6 addr to expected address (it's not mentioned
+ in EPSV responses) */
+ cmd.l3num = nfct_get_attr_u16(myct->ct, ATTR_L3PROTO);
+ nfct_get_attr_grp(myct->ct, ATTR_GRP_ORIG_ADDR_SRC, &cmd.u3);
+
+ for (i = 0; i < ARRAY_SIZE(search[dir]); i++) {
+ found = ftp_find_pattern(pkt, dataoff, datalen,
+ search[dir][i].pattern,
+ search[dir][i].plen,
+ search[dir][i].skip,
+ search[dir][i].term,
+ &matchoff, &matchlen,
+ &cmd,
+ search[dir][i].getnum,
+ dir);
+ if (found) break;
+ }
+ if (found == 0) /* No match */
+ goto out_update_nl;
+
+ pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n",
+ matchlen, pktb_network_header(pkt) + matchoff,
+ matchlen, ntohl(th->seq) + matchoff);
+
+ /* We refer to the reverse direction ("!dir") tuples here,
+ * because we're expecting something in the other direction.
+ * Doesn't matter unless NAT is happening. */
+ cthelper_get_addr_dst(myct->ct, !dir, &daddr);
+
+ cthelper_get_addr_src(myct->ct, dir, &addr);
+
+ /* Update the ftp info */
+ if ((cmd.l3num == nfct_get_attr_u16(myct->ct, ATTR_L3PROTO)) &&
+ memcmp(&cmd.u3, &addr, sizeof(addr)) != 0) {
+ /* Enrico Scholz's passive FTP to partially RNAT'd ftp
+ server: it really wants us to connect to a
+ different IP address. Simply don't record it for
+ NAT. */
+ if (cmd.l3num == PF_INET) {
+ pr_debug("conntrack_ftp: NOT RECORDING: %pI4 != %pI4\n",
+ &cmd.u3.ip, &addr);
+ } else {
+ pr_debug("conntrack_ftp: NOT RECORDING: %pI6 != %pI6\n",
+ cmd.u3.ip6, &addr);
+ }
+ /* Thanks to Cristiano Lincoln Mattos
+ <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, 0)) {
+ pr_debug("conntrack_ftp: failed to init expectation\n");
+ goto out_update_nl;
+ }
+
+ /* Now, NAT might want to mangle the packet, and register the
+ * (possibly changed) expectation itself. */
+ if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) {
+ ret = nf_nat_ftp(pkt, dir, ctinfo, search[dir][i].ftptype,
+ matchoff, matchlen, myct->ct, exp);
+ goto out_update_nl;
+ }
+
+ /* Can't expect this? Best to drop packet now. */
+ if (cthelper_add_expect(exp) < 0) {
+ pr_debug("conntrack_ftp: cannot add expectation: %s\n",
+ strerror(errno));
+ ret = NF_DROP;
+ goto out_update_nl;
+ }
+
+out_update_nl:
+ if (exp != NULL)
+ nfexp_destroy(exp);
+
+ /* Now if this ends in \n, update ftp info. Seq may have been
+ * adjusted by NAT code. */
+ if (ends_in_nl)
+ update_nl_seq(seq, ftp_info, dir);
+out:
+ return ret;
+}
+
+static struct ctd_helper ftp_helper = {
+ .name = "ftp",
+ .l4proto = IPPROTO_TCP,
+ .cb = ftp_helper_cb,
+ .priv_data_len = sizeof(struct ftp_info),
+ .policy = {
+ [0] = {
+ .name = "ftp",
+ .expect_max = 1,
+ .expect_timeout = 300,
+ },
+ },
+};
+
+void __attribute__ ((constructor)) ftp_init(void);
+
+void ftp_init(void)
+{
+ helper_register(&ftp_helper);
+}
diff --git a/src/main.c b/src/main.c
index 26f6c14..831a3c2 100644
--- a/src/main.c
+++ b/src/main.c
@@ -19,6 +19,7 @@
#include "conntrackd.h"
#include "log.h"
+#include "helper.h"
#include <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"
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.c b/src/nfct.c
index db629e7..b5c9654 100644
--- a/src/nfct.c
+++ b/src/nfct.c
@@ -56,6 +56,8 @@ int main(int argc, char *argv[])
}
if (strncmp(argv[1], "timeout", strlen(argv[1])) == 0) {
subsys = NFCT_SUBSYS_TIMEOUT;
+ } else if (strncmp(argv[1], "helper", strlen(argv[1])) == 0) {
+ subsys = NFCT_SUBSYS_HELPER;
} else if (strncmp(argv[1], "version", strlen(argv[1])) == 0)
subsys = NFCT_SUBSYS_VERSION;
else if (strncmp(argv[1], "help", strlen(argv[1])) == 0)
@@ -71,6 +73,9 @@ int main(int argc, char *argv[])
case NFCT_SUBSYS_TIMEOUT:
ret = nfct_cmd_timeout_parse_params(argc, argv);
break;
+ case NFCT_SUBSYS_HELPER:
+ ret = nfct_cmd_helper_parse_params(argc, argv);
+ break;
case NFCT_SUBSYS_VERSION:
ret = nfct_cmd_version(argc, argv);
break;
@@ -99,6 +104,7 @@ static const char help_msg[] =
"nfct v%s: utility for the Netfilter's Connection Tracking System\n"
"Usage: %s command [parameters]...\n\n"
"Subsystem:\n"
+ " helper\t\tAllows to configure user-space helper\n"
" timeout\t\tAllows definition of fine-grain timeout policies\n"
" version\t\tDisplay version and disclaimer\n"
" help\t\t\tDisplay this help message\n"
diff --git a/src/read_config_lex.l b/src/read_config_lex.l
index 01fe4fc..31fa32e 100644
--- a/src/read_config_lex.l
+++ b/src/read_config_lex.l
@@ -142,6 +142,11 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k]
"TCPWindowTracking" { return T_TCP_WINDOW_TRACKING; }
"ExpectationSync" { return T_EXPECT_SYNC; }
"ErrorQueueLength" { return T_ERROR_QUEUE_LENGTH; }
+"Helper" { return T_HELPER; }
+"QueueNum" { return T_HELPER_QUEUE_NUM; }
+"Policy" { return T_HELPER_POLICY; }
+"ExpectMax" { return T_HELPER_EXPECT_MAX; }
+"ExpectTimeout" { return T_HELPER_EXPECT_TIMEOUT; }
{is_on} { return T_ON; }
{is_off} { return T_OFF; }
diff --git a/src/read_config_yy.y b/src/read_config_yy.y
index b22784c..c9235d3 100644
--- a/src/read_config_yy.y
+++ b/src/read_config_yy.y
@@ -28,8 +28,11 @@
#include "conntrackd.h"
#include "bitops.h"
#include "cidr.h"
+#include "helper.h"
+#include "stack.h"
#include <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
@@ -1561,6 +1576,186 @@ buffer_size: T_STAT_BUFFER_SIZE T_NUMBER
print_err(CTD_CFG_WARN, "`LogFileBufferSize' is deprecated");
};
+helper: T_HELPER '{' helper_list '}'
+{
+ conf.flags |= CTD_HELPER;
+};
+
+helper_list:
+ | helper_list helper_line
+ ;
+
+helper_line: helper_type
+ ;
+
+helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list '}'
+{
+ struct ctd_helper_instance *helper_inst;
+ struct ctd_helper *helper;
+ struct stack_item *e;
+ uint16_t l3proto;
+ uint8_t l4proto;
+
+ if (strcmp($3, "inet") == 0)
+ l3proto = AF_INET;
+ else if (strcmp($3, "inet6") == 0)
+ l3proto = AF_INET6;
+ else {
+ print_err(CTD_CFG_ERROR, "unknown layer 3 protocol");
+ exit(EXIT_FAILURE);
+ }
+
+ if (strcmp($4, "tcp") == 0)
+ l4proto = IPPROTO_TCP;
+ else if (strcmp($4, "udp") == 0)
+ l4proto = IPPROTO_UDP;
+ else {
+ print_err(CTD_CFG_ERROR, "unknown layer 4 protocol");
+ exit(EXIT_FAILURE);
+ }
+
+ /* XXX use configure.ac definitions. */
+ helper = helper_find("/usr/lib/conntrack-tools", $2, l4proto, RTLD_NOW);
+ if (helper == NULL) {
+ print_err(CTD_CFG_ERROR, "Unknown `%s' helper", $2);
+ exit(EXIT_FAILURE);
+ }
+
+ helper_inst = calloc(1, sizeof(struct ctd_helper_instance));
+ if (helper_inst == NULL)
+ break;
+
+ helper_inst->l3proto = l3proto;
+ helper_inst->l4proto = l4proto;
+ helper_inst->helper = helper;
+
+ while ((e = stack_item_pop(&symbol_stack, -1)) != NULL) {
+
+ switch(e->type) {
+ case SYMBOL_HELPER_QUEUE_NUM: {
+ int *qnum = (int *) &e->data;
+
+ helper_inst->queue_num = *qnum;
+ stack_item_free(e);
+ break;
+ }
+ case SYMBOL_HELPER_POLICY_EXPECT_ROOT: {
+ struct ctd_helper_policy *pol =
+ (struct ctd_helper_policy *) &e->data;
+ struct ctd_helper_policy *matching = NULL;
+ int i;
+
+ for (i=0; 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))
@@ -1640,6 +1835,11 @@ init_config(char *filename)
CONFIG(stats).syslog_facility = -1;
CONFIG(netlink).subsys_id = -1;
+ /* Initialize list of user-space helpers */
+ INIT_LIST_HEAD(&CONFIG(cthelper).list);
+
+ stack_init(&symbol_stack);
+
yyrestart(fp);
yyparse();
fclose(fp);
diff --git a/src/run.c b/src/run.c
index 852bec6..3337694 100644
--- a/src/run.c
+++ b/src/run.c
@@ -50,6 +50,9 @@ void killer(int foo)
if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))
ctnl_kill();
+ if (CONFIG(flags) & CTD_HELPER)
+ cthelper_kill();
+
destroy_fds(STATE(fds));
unlink(CONFIG(lockfile));
dlog(LOG_NOTICE, "---- shutdown received ----");
@@ -199,6 +202,9 @@ static int local_handler(int fd, void *data)
if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))
return ctnl_local(fd, type, data);
+ if (CONFIG(flags) & CTD_HELPER)
+ return cthelper_local(fd, type, data);
+
return ret;
}
@@ -250,6 +256,11 @@ init(void)
if (ctnl_init() < 0)
return -1;
+ if (CONFIG(flags) & CTD_HELPER) {
+ if (cthelper_init() < 0)
+ return -1;
+ }
+
time(&STATE(stats).daemon_start_time);
dlog(LOG_NOTICE, "initialization completed");
diff --git a/src/stack.c b/src/stack.c
new file mode 100644
index 0000000..104b7ba
--- /dev/null
+++ b/src/stack.c
@@ -0,0 +1,56 @@
+/*
+ * (C) 2005-2012 by Pablo Neira Ayuso <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/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;
+}