From bb2534c7aaf0bdb6521371b8a31af6333d3a6a2d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 21 Feb 2012 16:03:21 +0100 Subject: doc: add ras, q.931 and h.245 to examples configuration file Now it includes: ExpectationSync { ... ras q.931 h.245 } Which are the set of helpers for h.323. Signed-off-by: Pablo Neira Ayuso --- doc/sync/alarm/conntrackd.conf | 4 +++- doc/sync/ftfw/conntrackd.conf | 4 +++- doc/sync/notrack/conntrackd.conf | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'doc') diff --git a/doc/sync/alarm/conntrackd.conf b/doc/sync/alarm/conntrackd.conf index deed291..b9520fb 100644 --- a/doc/sync/alarm/conntrackd.conf +++ b/doc/sync/alarm/conntrackd.conf @@ -198,7 +198,9 @@ Sync { # # ExpectationSync { # ftp - # h323 + # ras + # q.931 + # h.245 # sip # } # diff --git a/doc/sync/ftfw/conntrackd.conf b/doc/sync/ftfw/conntrackd.conf index 0304f0f..53a7d0f 100644 --- a/doc/sync/ftfw/conntrackd.conf +++ b/doc/sync/ftfw/conntrackd.conf @@ -221,7 +221,9 @@ Sync { # # ExpectationSync { # ftp - # h323 + # ras + # q.931 + # h.245 # sip # } # diff --git a/doc/sync/notrack/conntrackd.conf b/doc/sync/notrack/conntrackd.conf index 34e7b32..11f022e 100644 --- a/doc/sync/notrack/conntrackd.conf +++ b/doc/sync/notrack/conntrackd.conf @@ -260,7 +260,9 @@ Sync { # # ExpectationSync { # ftp - # h323 + # ras + # q.931 + # h.245 # sip # } # -- cgit v1.2.3 From 1de3034f8c4f597cbe4be35b2f84e2848e46e64e Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 5 Mar 2012 23:13:12 +0100 Subject: doc: fix example on how to filter events via iptables CT target You have to use this: iptables -I PREROUTING -t raw -j CT --ctevents assured,destroy instead of: iptables -I PREROUTING -t raw -j CT --ctevents assured Otherwise, conntrackd cache gets full since no destroy events are delivered. Reported-by: Kerin Millar Signed-off-by: Pablo Neira Ayuso --- doc/manual/conntrack-tools.tmpl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'doc') diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index 4936a76..dbf836d 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -641,10 +641,11 @@ Sync { broken. The following example shows how to only generate the - assured event: + assured and destroy + events: - # iptables -I PREROUTING -t raw -j CT --ctevents assured + # iptables -I PREROUTING -t raw -j CT --ctevents assured,destroy Assured flows -- cgit v1.2.3 From c88266b35ba130e804422ce2fe0da6704d620bd6 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 16 Jun 2012 17:42:28 +0200 Subject: doc: fix documentation on ExpectationSync and H.323 helper The H.323 helper is actually composed of three helpers: ras q.931 h.245 We have to specify those in the configuration file since h.323 is not any known helper itself. Signed-off-by: Pablo Neira Ayuso --- doc/manual/conntrack-tools.tmpl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'doc') diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index dbf836d..47e6f84 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -689,7 +689,9 @@ Sync { ExpectationSync { ftp sip - h323 + ras # for H.323 + q.931 # for H.323 + h.245 # for H.323 } } } -- cgit v1.2.3 From 5e8f64f46cb1dd71b0a94cb7dad87da00b8c5e32 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 15 May 2012 01:51:29 +0200 Subject: conntrackd: add cthelper infrastructure (+ example FTP helper) This patch adds the user-space helper infrastructure. It also contains the implementation of the FTP helper in user-space. There's one example file that you can use to configure conntrackd as user-space connection tracking helper under: doc/helper/conntrackd.conf Signed-off-by: Pablo Neira Ayuso --- configure.ac | 9 +- doc/helper/conntrackd.conf | 82 ++++ include/Makefile.am | 3 +- include/conntrackd.h | 21 +- include/helper.h | 104 +++++ include/linux/netfilter/Makefile.am | 2 +- include/linux/netfilter/nfnetlink_cthelper.h | 55 +++ include/linux/netfilter/nfnetlink_queue.h | 99 +++++ include/myct.h | 43 ++ include/nfct.h | 10 + include/stack.h | 28 ++ src/Makefile.am | 24 +- src/cthelper.c | 521 ++++++++++++++++++++++ src/expect.c | 214 +++++++++ src/helpers.c | 76 ++++ src/helpers/Makefile.am | 9 + src/helpers/ftp.c | 599 ++++++++++++++++++++++++++ src/main.c | 3 +- src/nfct-extensions/helper.c | 619 +++++++++++++++++++++++++++ src/nfct.c | 6 + src/read_config_lex.l | 5 + src/read_config_yy.y | 200 +++++++++ src/run.c | 11 + src/stack.c | 56 +++ src/utils.c | 243 +++++++++++ 25 files changed, 3030 insertions(+), 12 deletions(-) create mode 100644 doc/helper/conntrackd.conf create mode 100644 include/helper.h create mode 100644 include/linux/netfilter/nfnetlink_cthelper.h create mode 100644 include/linux/netfilter/nfnetlink_queue.h create mode 100644 include/myct.h create mode 100644 include/stack.h create mode 100644 src/cthelper.c create mode 100644 src/expect.c create mode 100644 src/helpers.c create mode 100644 src/helpers/Makefile.am create mode 100644 src/helpers/ftp.c create mode 100644 src/nfct-extensions/helper.c create mode 100644 src/stack.c create mode 100644 src/utils.c (limited to 'doc') diff --git a/configure.ac b/configure.ac index 8cd5626..5d1860d 100644 --- a/configure.ac +++ b/configure.ac @@ -9,6 +9,9 @@ AM_INIT_AUTOMAKE([-Wall foreign subdir-objects dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_SEARCH_LIBS([dlopen], [dl], [libdl_LIBS="$LIBS"; LIBS=""]) +AC_SUBST([libdl_LIBS]) + AC_PROG_CC AC_DISABLE_STATIC AM_PROG_LIBTOOL @@ -52,9 +55,11 @@ else fi PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.0]) -PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.0]) +PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.3]) PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.1]) PKG_CHECK_MODULES([LIBNETFILTER_CTTIMEOUT], [libnetfilter_cttimeout >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_CTHELPER], [libnetfilter_cthelper >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_QUEUE], [libnetfilter_queue >= 1.0.0]) AC_CHECK_HEADERS([linux/capability.h],, [AC_MSG_ERROR([Cannot find linux/capabibility.h])]) @@ -114,5 +119,5 @@ dnl debug/src/Makefile dnl extensions/Makefile dnl src/Makefile]) -AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile src/helpers/Makefile]) AC_OUTPUT diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf new file mode 100644 index 0000000..711b309 --- /dev/null +++ b/doc/helper/conntrackd.conf @@ -0,0 +1,82 @@ +# +# Helper settings +# + +Helper { + # Before this, you have to make sure you have registered the `ftp' + # user-space helper stub via: + # + # nfct helper add ftp inet tcp + # + Type ftp inet tcp { + # + # Set NFQUEUE number you want to use to receive traffic from + # the kernel. + # + QueueNum 0 + # + # Set the Expectation policy for this helper. + # + Policy ftp { + # + # Maximum number of simultaneous expectations + # + ExpectMax 1 + # + # Maximum living time for one expectation (in seconds). + # + ExpectTimeout 300 + } + } +} + +# +# General settings +# +General { + # + # Set the nice value of the daemon, this value goes from -20 + # (most favorable scheduling) to 19 (least favorable). Using a + # very low value reduces the chances to lose state-change events. + # Default is 0 but this example file sets it to most favourable + # scheduling as this is generally a good idea. See man nice(1) for + # more information. + # + Nice -20 + + # + # Select a different scheduler for the daemon, you can select between + # RR and FIFO and the process priority (minimum is 0, maximum is 99). + # See man sched_setscheduler(2) for more information. Using a RT + # scheduler reduces the chances to overrun the Netlink buffer. + # + # Scheduler { + # Type FIFO + # Priority 99 + # } + + # + # Logfile: on (/var/log/conntrackd.log), off, or a filename + # Default: off + # + LogFile on + + # + # Syslog: on, off or a facility name (daemon (default) or local0..7) + # Default: off + # + #Syslog on + + # + # Lockfile + # + LockFile /var/lock/conntrack.lock + + # + # Unix socket configuration + # + UNIX { + Path /var/run/conntrackd.ctl + Backlog 20 + } +} diff --git a/include/Makefile.am b/include/Makefile.am index 138005d..6bd0f7f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -5,5 +5,6 @@ noinst_HEADERS = alarm.h jhash.h cache.h linux_list.h linux_rbtree.h \ debug.h log.h hash.h mcast.h conntrack.h \ network.h filter.h queue.h vector.h cidr.h \ traffic_stats.h netlink.h fds.h event.h bitops.h channel.h \ - process.h origin.h internal.h external.h date.h nfct.h + process.h origin.h internal.h external.h date.h nfct.h \ + helper.h myct.h stack.h diff --git a/include/conntrackd.h b/include/conntrackd.h index ec720ec..19e613c 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -69,6 +69,7 @@ #define CTD_SYNC_NOTRACK (1UL << 4) #define CTD_POLL (1UL << 5) #define CTD_EXPECT (1UL << 6) +#define CTD_HELPER (1UL << 7) /* FILENAME_MAX is 4096 on my system, perhaps too much? */ #ifndef FILENAME_MAXLEN @@ -134,6 +135,9 @@ struct ct_conf { int syslog_facility; size_t buffer_size; } stats; + struct { + struct list_head list; + } cthelper; }; #define STATE(x) st.x @@ -252,13 +256,21 @@ struct ct_stats_state { struct cache *cache; /* internal events cache (netlink) */ }; -union ct_state { +#define STATE_CTH(x) state.cthelper->x + +struct ct_helper_state { + struct mnl_socket *nl; + uint32_t portid; +}; + +struct ct_state { struct ct_sync_state *sync; struct ct_stats_state *stats; + struct ct_helper_state *cthelper; }; extern struct ct_conf conf; -extern union ct_state state; +extern struct ct_state state; extern struct ct_general_state st; struct ct_mode { @@ -273,6 +285,11 @@ void ctnl_kill(void); int ctnl_local(int fd, int type, void *data); int ctnl_init(void); +/* basic cthelper functions */ +void cthelper_kill(void); +int cthelper_local(int fd, int type, void *data); +int cthelper_init(void); + /* conntrackd ctnl modes */ extern struct ct_mode sync_mode; extern struct ct_mode stats_mode; diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..02ff3df --- /dev/null +++ b/include/helper.h @@ -0,0 +1,104 @@ +#ifndef _CTD_HELPER_H_ +#define _CTD_HELPER_H_ + +#include +#include "linux_list.h" +#include "myct.h" + +#include + +struct pkt_buff; + +#define CTD_HELPER_NAME_LEN 16 +#define CTD_HELPER_POLICY_MAX 4 + +struct ctd_helper_policy { + char name[CTD_HELPER_NAME_LEN]; + uint32_t expect_timeout; + uint32_t expect_max; +}; + +struct ctd_helper { + struct list_head head; + char name[CTD_HELPER_NAME_LEN]; + uint8_t l4proto; + int (*cb)(struct pkt_buff *pkt, + uint32_t protoff, + struct myct *ct, + u_int32_t ctinfo); + + struct ctd_helper_policy policy[CTD_HELPER_POLICY_MAX]; + + int priv_data_len; +}; + +struct ctd_helper_instance { + struct list_head head; + uint32_t queue_num; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + struct ctd_helper_policy policy[CTD_HELPER_POLICY_MAX]; +}; + +extern int cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, uint32_t class, union nfct_attr_grp_addr *saddr, union nfct_attr_grp_addr *daddr, uint8_t l4proto, uint16_t *sport, uint16_t *dport, uint32_t flags); +extern int cthelper_add_expect(struct nf_expect *exp); +extern int cthelper_del_expect(struct nf_expect *exp); + +extern void cthelper_get_addr_src(struct nf_conntrack *ct, int dir, union nfct_attr_grp_addr *addr); +extern void cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, union nfct_attr_grp_addr *addr); + +extern int in4_pton(const char *src, int srclen, uint8_t *dst, int delim, const char **end); +extern int in6_pton(const char *src, int srclen, uint8_t *dst, int delim, const char **end); + +extern void helper_register(struct ctd_helper *helper); +struct ctd_helper *helper_find(const char *libdir_path, const char *name, uint8_t l4proto, int flags); + +#define min_t(type, x, y) ({ \ + type __min1 = (x); \ + type __min2 = (y); \ + __min1 < __min2 ? __min1: __min2; }) + +#define max_t(type, x, y) ({ \ + type __max1 = (x); \ + type __max2 = (y); \ + __max1 > __max2 ? __max1: __max2; }) + +#define ARRAY_SIZE MNL_ARRAY_SIZE + +enum ip_conntrack_dir { + IP_CT_DIR_ORIGINAL, + IP_CT_DIR_REPLY, + IP_CT_DIR_MAX +}; + +/* Connection state tracking for netfilter. This is separated from, + but required by, the NAT layer; it can also be used by an iptables + extension. */ +enum ip_conntrack_info { + /* Part of an established connection (either direction). */ + IP_CT_ESTABLISHED, + + /* Like NEW, but related to an existing connection, or ICMP error + (in either direction). */ + IP_CT_RELATED, + + /* Started a new connection to track (only + IP_CT_DIR_ORIGINAL); may be a retransmission. */ + IP_CT_NEW, + + /* >= this indicates reply direction */ + IP_CT_IS_REPLY, + + IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY, + IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY, + IP_CT_NEW_REPLY = IP_CT_NEW + IP_CT_IS_REPLY, + /* Number of distinct IP_CT types (no NEW in reply dirn). */ + IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1 +}; + +#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL) + +#define pr_debug printf + +#endif diff --git a/include/linux/netfilter/Makefile.am b/include/linux/netfilter/Makefile.am index 84315e3..6574060 100644 --- a/include/linux/netfilter/Makefile.am +++ b/include/linux/netfilter/Makefile.am @@ -1 +1 @@ -noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h +noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h nfnetlink_queue.h nfnetlink_cthelper.h diff --git a/include/linux/netfilter/nfnetlink_cthelper.h b/include/linux/netfilter/nfnetlink_cthelper.h new file mode 100644 index 0000000..33659f6 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_cthelper.h @@ -0,0 +1,55 @@ +#ifndef _NFNL_CTHELPER_H_ +#define _NFNL_CTHELPER_H_ + +#define NFCT_HELPER_STATUS_DISABLED 0 +#define NFCT_HELPER_STATUS_ENABLED 1 + +enum nfnl_acct_msg_types { + NFNL_MSG_CTHELPER_NEW, + NFNL_MSG_CTHELPER_GET, + NFNL_MSG_CTHELPER_DEL, + NFNL_MSG_CTHELPER_MAX +}; + +enum nfnl_cthelper_type { + NFCTH_UNSPEC, + NFCTH_NAME, + NFCTH_TUPLE, + NFCTH_QUEUE_NUM, + NFCTH_POLICY, + NFCTH_PRIV_DATA_LEN, + NFCTH_STATUS, + __NFCTH_MAX +}; +#define NFCTH_MAX (__NFCTH_MAX - 1) + +enum nfnl_cthelper_policy_type { + NFCTH_POLICY_SET_UNSPEC, + NFCTH_POLICY_SET_NUM, + NFCTH_POLICY_SET, + NFCTH_POLICY_SET1 = NFCTH_POLICY_SET, + NFCTH_POLICY_SET2, + NFCTH_POLICY_SET3, + NFCTH_POLICY_SET4, + __NFCTH_POLICY_SET_MAX +}; +#define NFCTH_POLICY_SET_MAX (__NFCTH_POLICY_SET_MAX - 1) + +enum nfnl_cthelper_pol_type { + NFCTH_POLICY_UNSPEC, + NFCTH_POLICY_NAME, + NFCTH_POLICY_EXPECT_MAX, + NFCTH_POLICY_EXPECT_TIMEOUT, + __NFCTH_POLICY_MAX +}; +#define NFCTH_POLICY_MAX (__NFCTH_POLICY_MAX - 1) + +enum nfnl_cthelper_tuple_type { + NFCTH_TUPLE_UNSPEC, + NFCTH_TUPLE_L3PROTONUM, + NFCTH_TUPLE_L4PROTONUM, + __NFCTH_TUPLE_MAX, +}; +#define NFCTH_TUPLE_MAX (__NFCTH_TUPLE_MAX - 1) + +#endif /* _NFNL_CTHELPER_H */ diff --git a/include/linux/netfilter/nfnetlink_queue.h b/include/linux/netfilter/nfnetlink_queue.h new file mode 100644 index 0000000..e0d8fd8 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_queue.h @@ -0,0 +1,99 @@ +#ifndef _NFNETLINK_QUEUE_H +#define _NFNETLINK_QUEUE_H + +#include +#include + +enum nfqnl_msg_types { + NFQNL_MSG_PACKET, /* packet from kernel to userspace */ + NFQNL_MSG_VERDICT, /* verdict from userspace to kernel */ + NFQNL_MSG_CONFIG, /* connect to a particular queue */ + NFQNL_MSG_VERDICT_BATCH, /* batchv from userspace to kernel */ + + NFQNL_MSG_MAX +}; + +struct nfqnl_msg_packet_hdr { + __be32 packet_id; /* unique ID of packet in queue */ + __be16 hw_protocol; /* hw protocol (network order) */ + __u8 hook; /* netfilter hook */ +} __attribute__ ((packed)); + +struct nfqnl_msg_packet_hw { + __be16 hw_addrlen; + __u16 _pad; + __u8 hw_addr[8]; +}; + +struct nfqnl_msg_packet_timestamp { + __aligned_be64 sec; + __aligned_be64 usec; +}; + +enum nfqnl_attr_type { + NFQA_UNSPEC, + NFQA_PACKET_HDR, + NFQA_VERDICT_HDR, /* nfqnl_msg_verdict_hrd */ + NFQA_MARK, /* __u32 nfmark */ + NFQA_TIMESTAMP, /* nfqnl_msg_packet_timestamp */ + NFQA_IFINDEX_INDEV, /* __u32 ifindex */ + NFQA_IFINDEX_OUTDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSINDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSOUTDEV, /* __u32 ifindex */ + NFQA_HWADDR, /* nfqnl_msg_packet_hw */ + NFQA_PAYLOAD, /* opaque data payload */ + NFQA_CT, /* nf_conntrack_netlink.h */ + NFQA_CT_INFO, /* enum ip_conntrack_info */ + + __NFQA_MAX +}; +#define NFQA_MAX (__NFQA_MAX - 1) + +struct nfqnl_msg_verdict_hdr { + __be32 verdict; + __be32 id; +}; + + +enum nfqnl_msg_config_cmds { + NFQNL_CFG_CMD_NONE, + NFQNL_CFG_CMD_BIND, + NFQNL_CFG_CMD_UNBIND, + NFQNL_CFG_CMD_PF_BIND, + NFQNL_CFG_CMD_PF_UNBIND, +}; + +struct nfqnl_msg_config_cmd { + __u8 command; /* nfqnl_msg_config_cmds */ + __u8 _pad; + __be16 pf; /* AF_xxx for PF_[UN]BIND */ +}; + +enum nfqnl_config_mode { + NFQNL_COPY_NONE, + NFQNL_COPY_META, + NFQNL_COPY_PACKET, +}; + +struct nfqnl_msg_config_params { + __be32 copy_range; + __u8 copy_mode; /* enum nfqnl_config_mode */ +} __attribute__ ((packed)); + + +enum nfqnl_attr_config { + NFQA_CFG_UNSPEC, + NFQA_CFG_CMD, /* nfqnl_msg_config_cmd */ + NFQA_CFG_PARAMS, /* nfqnl_msg_config_params */ + NFQA_CFG_QUEUE_MAXLEN, /* __u32 */ + NFQA_CFG_MASK, /* identify which flags to change */ + NFQA_CFG_FLAGS, /* value of these flags (__u32) */ + __NFQA_CFG_MAX +}; +#define NFQA_CFG_MAX (__NFQA_CFG_MAX-1) + +/* Flags for NFQA_CFG_FLAGS */ +#define NFQA_CFG_F_FAIL_OPEN (1 << 0) +#define NFQA_CFG_F_CONNTRACK (1 << 1) + +#endif /* _NFNETLINK_QUEUE_H */ diff --git a/include/myct.h b/include/myct.h new file mode 100644 index 0000000..45d9f29 --- /dev/null +++ b/include/myct.h @@ -0,0 +1,43 @@ +#ifndef _MYCT_H_ +#define _MYCT_H_ + +#include "linux_list.h" + +#include + +struct nf_conntrack; + +enum { + MYCT_NONE = 0, + MYCT_ESTABLISHED = (1 << 0), +}; + +enum { + MYCT_DIR_ORIG = 0, + MYCT_DIR_REPL, + MYCT_DIR_MAX, +}; + +union myct_proto { + uint16_t port; + uint16_t all; +}; + +struct myct_man { + union nfct_attr_grp_addr u3; + union myct_proto u; + uint16_t l3num; + uint8_t protonum; +}; + +struct myct_tuple { + struct myct_man src; + struct myct_man dst; +}; + +struct myct { + struct nf_conntrack *ct; + void *priv_data; +}; + +#endif diff --git a/include/nfct.h b/include/nfct.h index d6271cf..5548b03 100644 --- a/include/nfct.h +++ b/include/nfct.h @@ -4,6 +4,7 @@ enum { NFCT_SUBSYS_NONE = 0, NFCT_SUBSYS_TIMEOUT, + NFCT_SUBSYS_HELPER, NFCT_SUBSYS_VERSION, NFCT_SUBSYS_HELP, }; @@ -15,6 +16,7 @@ enum { NFCT_CMD_DELETE, NFCT_CMD_GET, NFCT_CMD_FLUSH, + NFCT_CMD_DISABLE, }; void nfct_perror(const char *msg); @@ -26,4 +28,12 @@ int nfct_cmd_timeout_delete(int argc, char *argv[]); int nfct_cmd_timeout_get(int argc, char *argv[]); int nfct_cmd_timeout_flush(int argc, char *argv[]); +int nfct_cmd_helper_parse_params(int argc, char *argv[]); +int nfct_cmd_helper_list(int argc, char *argv[]); +int nfct_cmd_helper_add(int argc, char *argv[]); +int nfct_cmd_helper_delete(int argc, char *argv[]); +int nfct_cmd_helper_get(int argc, char *argv[]); +int nfct_cmd_helper_flush(int argc, char *argv[]); +int nfct_cmd_helper_disable(int argc, char *argv[]); + #endif diff --git a/include/stack.h b/include/stack.h new file mode 100644 index 0000000..512a30f --- /dev/null +++ b/include/stack.h @@ -0,0 +1,28 @@ +#ifndef _STACK_H_ +#define _STACK_H_ + +#include "linux_list.h" + +struct stack { + struct list_head list; + int items; +}; + +static inline void stack_init(struct stack *s) +{ + INIT_LIST_HEAD(&s->list); +} + +struct stack_item { + struct list_head head; + int type; + int data_len; + char data[0]; +}; + +struct stack_item *stack_item_alloc(int type, size_t data_len); +void stack_item_free(struct stack_item *e); +void stack_item_push(struct stack *s, struct stack_item *e); +struct stack_item *stack_item_pop(struct stack *s, int type); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index fa4ab75..d8074d2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,7 @@ include $(top_srcdir)/Make_global.am +SUBDIRS = helpers + AM_YFLAGS = -d CLEANFILES = read_config_yy.c read_config_lex.c @@ -10,17 +12,24 @@ conntrack_SOURCES = conntrack.c conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} ${LIBMNL_LIBS} nfct_SOURCES = nfct.c \ - nfct-extensions/timeout.c + helpers.c \ + nfct-extensions/timeout.c \ + nfct-extensions/helper.c + nfct_LDADD = ${LIBMNL_LIBS} \ ${LIBNETFILTER_CONNTRACK_LIBS} \ - ${LIBNETFILTER_CTTIMEOUT_LIBS} + ${LIBNETFILTER_CTTIMEOUT_LIBS} \ + ${LIBNETFILTER_CTHELPER_LIBS} \ + ${libdl_LIBS} + +nfct_LDFLAGS = -export-dynamic conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ local.c log.c mcast.c udp.c netlink.c vector.c \ filter.c fds.c event.c process.c origin.c date.c \ cache.c cache-ct.c cache-exp.c \ cache_timer.c \ - ctnl.c \ + ctnl.c cthelper.c \ sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \ traffic_stats.c stats-mode.c \ network.c cidr.c \ @@ -29,11 +38,16 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ tcp.c channel_tcp.c \ external_cache.c external_inject.c \ internal_cache.c internal_bypass.c \ - read_config_yy.y read_config_lex.l + read_config_yy.y read_config_lex.l \ + stack.c helpers.c utils.c expect.c # yacc and lex generate dirty code read_config_yy.o read_config_lex.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls -conntrackd_LDADD = ${LIBNETFILTER_CONNTRACK_LIBS} +conntrackd_LDADD = ${LIBMNL_LIBS} ${LIBNETFILTER_CONNTRACK_LIBS} \ + ${LIBNETFILTER_QUEUE_LIBS} ${LIBNETFILTER_CTHELPER_LIBS} \ + ${libdl_LIBS} + +conntrackd_LDFLAGS = -export-dynamic EXTRA_DIST = read_config_yy.h diff --git a/src/cthelper.c b/src/cthelper.c new file mode 100644 index 0000000..c119869 --- /dev/null +++ b/src/cthelper.c @@ -0,0 +1,521 @@ +/* + * (C) 2006-2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "conntrackd.h" +#include "log.h" +#include "fds.h" +#include "helper.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef __aligned_be64 +#define __aligned_be64 unsigned long long __attribute__((aligned(8))) +#endif + +#include + +#include +#include +#include +#include +#include +#include + +void cthelper_kill(void) +{ + mnl_socket_close(STATE_CTH(nl)); + free(state.cthelper); +} + +int cthelper_local(int fd, int type, void *data) +{ + /* No services to obtain information on helpers yet, sorry */ + return LOCAL_RET_OK; +} + +static struct nlmsghdr * +nfq_hdr_put(char *buf, int type, uint32_t queue_num) +{ + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | type; + nlh->nlmsg_flags = NLM_F_REQUEST; + + struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_UNSPEC; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(queue_num); + + return nlh; +} + +static int +pkt_get(void *pkt, uint32_t pktlen, uint16_t proto, uint32_t *protoff) +{ + switch(proto) { + case ETHERTYPE_IP: { + struct iphdr *ip = (struct iphdr *) pkt; + + /* No room for IPv4 header. */ + if (pktlen < sizeof(struct iphdr)) { + dlog(LOG_ERR, "no room for IPv4 header"); + return -1; + } + + /* this is not IPv4, skip. */ + if (ip->version != 4) { + dlog(LOG_ERR, "not IPv4, skipping"); + return -1; + } + + *protoff = 4 * ip->ihl; + + switch (ip->protocol) { + case IPPROTO_TCP: { + struct tcphdr *tcph = + (struct tcphdr *) ((char *)pkt + *protoff); + + /* No room for IPv4 header plus TCP header. */ + if (pktlen < *protoff + sizeof(struct tcphdr) + || pktlen < *protoff + tcph->doff * 4) { + dlog(LOG_ERR, "no room for IPv4 + TCP header, skip"); + return -1; + } + return 0; + } + case IPPROTO_UDP: + /* No room for IPv4 header plus UDP header. */ + if (pktlen < *protoff + sizeof(struct udphdr)) { + dlog(LOG_ERR, "no room for IPv4 + UDP header, skip"); + return -1; + } + return 0; + default: + dlog(LOG_ERR, "not TCP/UDP, skipping"); + return -1; + } + break; + } + case ETHERTYPE_IPV6: + dlog(LOG_ERR, "no IPv6 support sorry"); + return 0; + default: + /* Unknown layer 3 protocol. */ + dlog(LOG_ERR, "unknown layer 3 protocol (%d), skipping", proto); + return -1; + } + return 0; +} + +static int +pkt_verdict_issue(struct ctd_helper_instance *cur, struct myct *myct, + uint16_t queue_num, uint32_t id, uint32_t verdict, + struct pkt_buff *pktb) +{ + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlattr *nest; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num); + + /* save private data and send it back to kernel-space. */ + nfct_set_attr_l(myct->ct, ATTR_HELPER_INFO, myct->priv_data, + cur->helper->priv_data_len); + + nfq_nlmsg_verdict_put(nlh, id, verdict); + if (pktb_mangled(pktb)) + nfq_nlmsg_verdict_put_pkt(nlh, pktb_data(pktb), pktb_len(pktb)); + + nest = mnl_attr_nest_start(nlh, NFQA_CT); + if (nest == NULL) + return -1; + + nfct_nlmsg_build(nlh, myct->ct); + mnl_attr_nest_end(nlh, nest); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static int +pkt_verdict_error(uint16_t queue_num, uint32_t id) +{ + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num); + nfq_nlmsg_verdict_put(nlh, id, NF_ACCEPT); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); + return -1; + } + return 0; +} + +static struct ctd_helper_instance * +helper_run(struct pkt_buff *pktb, uint32_t protoff, struct myct *myct, + uint32_t ctinfo, uint32_t queue_num, int *verdict) +{ + struct ctd_helper_instance *cur, *helper = NULL; + + list_for_each_entry(cur, &CONFIG(cthelper).list, head) { + if (cur->queue_num == queue_num) { + const void *priv_data; + + /* retrieve helper private data. */ + priv_data = nfct_get_attr(myct->ct, ATTR_HELPER_INFO); + if (priv_data != NULL) { + myct->priv_data = + calloc(1, cur->helper->priv_data_len); + + if (myct->priv_data == NULL) + continue; + + memcpy(myct->priv_data, priv_data, + cur->helper->priv_data_len); + } + + *verdict = cur->helper->cb(pktb, protoff, myct, ctinfo); + helper = cur; + break; + } + } + return helper; +} + +static int nfq_queue_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfqnl_msg_packet_hdr *ph = NULL; + struct nlattr *attr[NFQA_MAX+1] = {}; + struct nfgenmsg *nfg; + uint8_t *pkt; + uint16_t l3num; + uint32_t id, ctinfo, queue_num = 0, protoff = 0, pktlen; + struct nf_conntrack *ct = NULL; + struct myct *myct; + struct ctd_helper_instance *helper; + struct pkt_buff *pktb; + int verdict = NF_ACCEPT; + + if (nfq_nlmsg_parse(nlh, attr) < 0) { + dlog(LOG_ERR, "problems parsing message from kernel"); + return MNL_CB_ERROR; + } + + ph = (struct nfqnl_msg_packet_hdr *) + mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); + if (ph == NULL) { + dlog(LOG_ERR, "problems retrieving metaheader"); + return MNL_CB_ERROR; + } + + id = ntohl(ph->packet_id); + + if (!attr[NFQA_PAYLOAD]) { + dlog(LOG_ERR, "packet with no payload"); + goto err; + } + if (!attr[NFQA_CT] || !attr[NFQA_CT_INFO]) { + dlog(LOG_ERR, "no CT attached to this packet"); + goto err; + } + + pkt = mnl_attr_get_payload(attr[NFQA_PAYLOAD]); + pktlen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]); + + nfg = mnl_nlmsg_get_payload(nlh); + l3num = nfg->nfgen_family; + queue_num = ntohs(nfg->res_id); + + if (pkt_get(pkt, pktlen, ntohs(ph->hw_protocol), &protoff)) + goto err; + + ct = nfct_new(); + if (ct == NULL) + goto err; + + if (nfct_payload_parse(mnl_attr_get_payload(attr[NFQA_CT]), + mnl_attr_get_payload_len(attr[NFQA_CT]), + l3num, ct) < 0) { + dlog(LOG_ERR, "cannot convert message to CT"); + goto err; + } + + myct = calloc(1, sizeof(struct myct)); + if (myct == NULL) + goto err; + + myct->ct = ct; + ctinfo = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO])); + + /* XXX: 256 bytes enough for possible NAT mangling in helpers? */ + pktb = pktb_alloc(AF_INET, pkt, pktlen, 256); + if (pktb == NULL) + goto err; + + /* Misconfiguration: if no helper found, accept the packet. */ + helper = helper_run(pktb, protoff, myct, ctinfo, queue_num, &verdict); + if (!helper) + goto err_pktb; + + if (pkt_verdict_issue(helper, myct, queue_num, id, verdict, pktb) < 0) + goto err_pktb; + + if (ct != NULL) + nfct_destroy(ct); + if (myct && myct->priv_data != NULL) + free(myct->priv_data); + if (myct != NULL) + free(myct); + + return MNL_CB_OK; +err_pktb: + pktb_free(pktb); +err: + /* In case of error, we don't want to disrupt traffic. We accept all. + * This is connection tracking after all. The policy is not to drop + * packet unless we enter some inconsistent state. + */ + pkt_verdict_error(queue_num, id); + + if (ct != NULL) + nfct_destroy(ct); + + return MNL_CB_OK; +} + +static void nfq_cb(void *data) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret; + + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + if (ret == -1) { + dlog(LOG_ERR, "failed to receive message: %s", strerror(errno)); + return; + } + + ret = mnl_cb_run(buf, ret, 0, STATE_CTH(portid), nfq_queue_cb, NULL); + if (ret < 0){ + dlog(LOG_ERR, "failed to process message"); + return; + } +} + +static int cthelper_setup(struct ctd_helper_instance *cur) +{ + struct nfct_helper *t; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t seq; + int j, ret; + + t = nfct_helper_alloc(); + if (t == NULL) { + dlog(LOG_ERR, "cannot allocate object for helper"); + return -1; + } + + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, cur->helper->name); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_QUEUE_NUM, cur->queue_num); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, cur->l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, cur->l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, + NFCT_HELPER_STATUS_ENABLED); + + dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d", + cur->helper->name, cur->queue_num); + + for (j=0; jhelper->policy[j].name[0]) + break; + + p = nfct_helper_policy_alloc(); + if (p == NULL) { + dlog(LOG_ERR, "cannot allocate object for helper"); + return -1; + } + /* FIXME: get existing policy values from the kernel first. */ + nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, + cur->helper->policy[j].name); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, + cur->helper->policy[j].expect_timeout); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, + cur->helper->policy[j].expect_max); + + dlog(LOG_NOTICE, "policy name=%s expect_timeout=%d expect_max=%d", + cur->helper->policy[j].name, + cur->helper->policy[j].expect_timeout, + cur->helper->policy[j].expect_max); + + nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); + } + + if (j == 0) { + dlog(LOG_ERR, "you have to define one policy for helper"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "sending cthelper configuration"); + return -1; + } + + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, STATE_CTH(portid), NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + } + if (ret == -1) { + dlog(LOG_ERR, "trying to configure cthelper `%s': %s", + cur->helper->name, strerror(errno)); + return -1; + } + + return 0; +} + +static int cthelper_nfqueue_setup(struct ctd_helper_instance *cur) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_BIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send bind command"); + return -1; + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num); + nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); + mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_CONNTRACK)); + mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(0xffffffff)); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send configuration"); + return -1; + } + + return 0; +} + +static int cthelper_configure(struct ctd_helper_instance *cur) +{ + /* First, configure cthelper. */ + if (cthelper_setup(cur) < 0) + return -1; + + /* Now, we are ready to configure nfqueue attached to this helper. */ + if (cthelper_nfqueue_setup(cur) < 0) + return -1; + + dlog(LOG_NOTICE, "helper `%s' configured successfully", + cur->helper->name); + + return 0; +} + +static int nfq_configure(void) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_UNBIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send pf unbind command"); + return -1; + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_BIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send pf bind command"); + return -1; + } + + return 0; +} + +int cthelper_init(void) +{ + struct ctd_helper_instance *cur; + int ret; + + state.cthelper = calloc(1, sizeof(struct ct_helper_state)); + if (state.cthelper == NULL) { + dlog(LOG_ERR, "can't allocate memory for cthelper struct"); + return -1; + } + + STATE_CTH(nl) = mnl_socket_open(NETLINK_NETFILTER); + if (STATE_CTH(nl) == NULL) { + dlog(LOG_ERR, "cannot open nfq socket"); + return -1; + } + fcntl(mnl_socket_get_fd(STATE_CTH(nl)), F_SETFL, O_NONBLOCK); + + if (mnl_socket_bind(STATE_CTH(nl), 0, MNL_SOCKET_AUTOPID) < 0) { + dlog(LOG_ERR, "cannot bind nfq socket"); + return -1; + } + STATE_CTH(portid) = mnl_socket_get_portid(STATE_CTH(nl)); + + if (nfq_configure()) + return -1; + + list_for_each_entry(cur, &CONFIG(cthelper).list, head) { + ret = cthelper_configure(cur); + if (ret < 0) + return ret; + } + + register_fd(mnl_socket_get_fd(STATE_CTH(nl)), nfq_cb, NULL, STATE(fds)); + + return 0; +} diff --git a/src/expect.c b/src/expect.c new file mode 100644 index 0000000..6069770 --- /dev/null +++ b/src/expect.c @@ -0,0 +1,214 @@ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int +cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, + uint32_t class, + union nfct_attr_grp_addr *saddr, + union nfct_attr_grp_addr *daddr, + uint8_t l4proto, uint16_t *sport, uint16_t *dport, + uint32_t flags) +{ + struct nf_conntrack *expected, *mask; + + expected = nfct_new(); + if (!expected) + return -1; + + mask = nfct_new(); + if (!mask) + return -1; + + if (saddr) { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } else { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000); + break; + case AF_INET6: + for (i=0; i<4; i++) + memset(addr, 0x00000000, sizeof(uint32_t)); + + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, addr); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } + + if (sport) { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff); + break; + default: + break; + } + } else { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000); + break; + default: + break; + } + } + + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + uint32_t addr[4] = {}; + int i; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip); + nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr(mask, ATTR_IPV6_DST, addr); + break; + default: + break; + } + + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff); + break; + default: + break; + } + + nfexp_set_attr(exp, ATTR_EXP_MASTER, master); + nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected); + nfexp_set_attr(exp, ATTR_EXP_MASK, mask); + nfexp_set_attr_u32(exp, ATTR_EXP_FLAGS, flags); + + nfct_destroy(expected); + nfct_destroy(mask); + + return 0; +} + +static int cthelper_expect_cmd(struct nf_expect *exp, int cmd) +{ + int ret; + struct nfct_handle *h; + + h = nfct_open(EXPECT, 0); + if (!h) + return -1; + + ret = nfexp_query(h, cmd, exp); + + nfct_close(h); + return ret; +} + +int cthelper_add_expect(struct nf_expect *exp) +{ + return cthelper_expect_cmd(exp, NFCT_Q_CREATE_UPDATE); +} + +int cthelper_del_expect(struct nf_expect *exp) +{ + return cthelper_expect_cmd(exp, NFCT_Q_DESTROY); +} + +void +cthelper_get_addr_src(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_SRC, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_SRC, addr); + break; + } +} + +void +cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_DST, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_DST, addr); + break; + } +} diff --git a/src/helpers.c b/src/helpers.c new file mode 100644 index 0000000..3e4e6c8 --- /dev/null +++ b/src/helpers.c @@ -0,0 +1,76 @@ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(helper_list); + +void helper_register(struct ctd_helper *helper) +{ + list_add(&helper->head, &helper_list); +} + +static struct ctd_helper * +__helper_find(const char *helper_name, uint8_t l4proto) +{ + struct ctd_helper *cur, *helper = NULL; + + list_for_each_entry(cur, &helper_list, head) { + if (strncmp(cur->name, helper_name, CTD_HELPER_NAME_LEN) != 0) + continue; + + if (cur->l4proto != l4proto) + continue; + + helper = cur; + break; + } + return helper; +} + +struct ctd_helper * +helper_find(const char *libdir_path, + const char *helper_name, uint8_t l4proto, int flag) +{ + char path[PATH_MAX]; + struct ctd_helper *helper; + struct stat sb; + + helper = __helper_find(helper_name, l4proto); + if (helper != NULL) + return helper; + + snprintf(path, sizeof(path), "%s/ct_helper_%s.so", + libdir_path, helper_name); + + if (stat(path, &sb) != 0) { + if (errno == ENOENT) + return NULL; + fprintf(stderr, "%s: %s\n", path, + strerror(errno)); + return NULL; + } + + if (dlopen(path, flag) == NULL) { + fprintf(stderr, "%s: %s\n", path, dlerror()); + return NULL; + } + + return __helper_find(helper_name, l4proto); +} diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am new file mode 100644 index 0000000..2c9d63b --- /dev/null +++ b/src/helpers/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/Make_global.am + +pkglib_LTLIBRARIES = ct_helper_ftp.la + +ct_helper_ftp_la_SOURCES = ftp.c +ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + + diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c new file mode 100644 index 0000000..962020b --- /dev/null +++ b/src/helpers/ftp.c @@ -0,0 +1,599 @@ +/* + * (C) 2010-2012 by Pablo Neira Ayuso + * + * Based on: kernel-space FTP extension for connection tracking. + * + * This port has been sponsored by Vyatta Inc. + * + * Original copyright notice: + * + * (C) 1999-2001 Paul `Rusty' Russell + * (C) 2002-2004 Netfilter Core Team + * (C) 2003,2004 USAGI/WIDE Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include /* for isdigit */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +static bool loose; /* XXX: export this as config option. */ + +#define NUM_SEQ_TO_REMEMBER 2 + +/* This structure exists only once per master */ +struct ftp_info { + /* Valid seq positions for cmd matching after newline */ + uint32_t seq_aft_nl[MYCT_DIR_MAX][NUM_SEQ_TO_REMEMBER]; + /* 0 means seq_match_aft_nl not set */ + int seq_aft_nl_num[MYCT_DIR_MAX]; +}; + +enum nf_ct_ftp_type { + /* PORT command from client */ + NF_CT_FTP_PORT, + /* PASV response from server */ + NF_CT_FTP_PASV, + /* EPRT command from client */ + NF_CT_FTP_EPRT, + /* EPSV response from server */ + NF_CT_FTP_EPSV, +}; + +static int +get_ipv6_addr(const char *src, size_t dlen, struct in6_addr *dst, u_int8_t term) +{ + const char *end; + int ret = in6_pton(src, min_t(size_t, dlen, 0xffff), + (uint8_t *)dst, term, &end); + if (ret > 0) + return (int)(end - src); + return 0; +} + +static int try_number(const char *data, size_t dlen, uint32_t array[], + int array_size, char sep, char term) +{ + uint32_t len; + int i; + + memset(array, 0, sizeof(array[0])*array_size); + + /* Keep data pointing at next char. */ + for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { + if (*data >= '0' && *data <= '9') { + array[i] = array[i]*10 + *data - '0'; + } + else if (*data == sep) + i++; + else { + /* Unexpected character; true if it's the + terminator and we're finished. */ + if (*data == term && i == array_size - 1) + return len; + pr_debug("Char %u (got %u nums) `%u' unexpected\n", + len, i, *data); + return 0; + } + } + pr_debug("Failed to fill %u numbers separated by %c\n", + array_size, sep); + return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, int start, size_t dlen, char delim, + struct myct_man *cmd) +{ + uint16_t tmp_port = 0; + uint32_t i; + + for (i = start; i < dlen; i++) { + /* Finished? */ + if (data[i] == delim) { + if (tmp_port == 0) + break; + cmd->u.port = htons(tmp_port); + pr_debug("get_port: return %d\n", tmp_port); + return i + 1; + } + else if (data[i] >= '0' && data[i] <= '9') + tmp_port = tmp_port*10 + data[i] - '0'; + else { /* Some other crap */ + pr_debug("get_port: invalid char.\n"); + break; + } + } + return 0; +} + +/* Returns 0, or length of numbers: 192,168,1,1,5,6 */ +static int try_rfc959(const char *data, size_t dlen, struct myct_man *cmd, + uint16_t l3protonum, char term) +{ + int length; + uint32_t array[6]; + + length = try_number(data, dlen, array, 6, ',', term); + if (length == 0) + return 0; + + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | + (array[2] << 8) | array[3]); + cmd->u.port = htons((array[4] << 8) | array[5]); + return length; +} + +/* Returns 0, or length of numbers: |1|132.235.1.2|6275| or |2|3ffe::1|6275| */ +static int try_eprt(const char *data, size_t dlen, + struct myct_man *cmd, uint16_t l3protonum, char term) +{ + char delim; + int length; + + /* First character is delimiter, then "1" for IPv4 or "2" for IPv6, + then delimiter again. */ + if (dlen <= 3) { + pr_debug("EPRT: too short\n"); + return 0; + } + delim = data[0]; + if (isdigit(delim) || delim < 33 || delim > 126 || data[2] != delim) { + pr_debug("try_eprt: invalid delimitter.\n"); + return 0; + } + + if ((l3protonum == PF_INET && data[1] != '1') || + (l3protonum == PF_INET6 && data[1] != '2')) { + pr_debug("EPRT: invalid protocol number.\n"); + return 0; + } + + pr_debug("EPRT: Got %c%c%c\n", delim, data[1], delim); + if (data[1] == '1') { + uint32_t array[4]; + + /* Now we have IP address. */ + length = try_number(data + 3, dlen - 3, array, 4, '.', delim); + if (length != 0) + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) + | (array[2] << 8) | array[3]); + } else { + /* Now we have IPv6 address. */ + length = get_ipv6_addr(data + 3, dlen - 3, + (struct in6_addr *)cmd->u3.ip6, delim); + } + + if (length == 0) + return 0; + pr_debug("EPRT: Got IP address!\n"); + /* Start offset includes initial "|1|", and trailing delimiter */ + return get_port(data, 3 + length + 1, dlen, delim, cmd); +} + +/* Returns 0, or length of numbers: |||6446| */ +static int try_epsv_response(const char *data, size_t dlen, + struct myct_man *cmd, + uint16_t l3protonum, char term) +{ + char delim; + + /* Three delimiters. */ + if (dlen <= 3) return 0; + delim = data[0]; + if (isdigit(delim) || delim < 33 || delim > 126 || + data[1] != delim || data[2] != delim) + return 0; + + return get_port(data, 3, dlen, delim, cmd); +} + +static struct ftp_search { + const char *pattern; + size_t plen; + char skip; + char term; + enum nf_ct_ftp_type ftptype; + int (*getnum)(const char *, size_t, struct myct_man *, uint16_t, char); +} search[MYCT_DIR_MAX][2] = { + [MYCT_DIR_ORIG] = { + { + .pattern = "PORT", + .plen = sizeof("PORT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = NF_CT_FTP_PORT, + .getnum = try_rfc959, + }, + { + .pattern = "EPRT", + .plen = sizeof("EPRT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = NF_CT_FTP_EPRT, + .getnum = try_eprt, + }, + }, + [MYCT_DIR_REPL] = { + { + .pattern = "227 ", + .plen = sizeof("227 ") - 1, + .skip = '(', + .term = ')', + .ftptype = NF_CT_FTP_PASV, + .getnum = try_rfc959, + }, + { + .pattern = "229 ", + .plen = sizeof("229 ") - 1, + .skip = '(', + .term = ')', + .ftptype = NF_CT_FTP_EPSV, + .getnum = try_epsv_response, + }, + }, +}; + +static int ftp_find_pattern(struct pkt_buff *pkt, + unsigned int dataoff, unsigned int dlen, + const char *pattern, size_t plen, + char skip, char term, + unsigned int *matchoff, unsigned int *matchlen, + struct myct_man *cmd, + int (*getnum)(const char *, size_t, + struct myct_man *cmd, + uint16_t, char), + int dir) +{ + char *data = (char *)pktb_network_header(pkt) + dataoff; + int numlen; + uint32_t i; + + if (dlen == 0) + return 0; + + /* short packet, skip partial matching. */ + if (dlen <= plen) + return 0; + + if (strncmp(data, pattern, plen) != 0) + return 0; + + pr_debug("Pattern matches!\n"); + + /* Now we've found the constant string, try to skip + to the 'skip' character */ + for (i = plen; data[i] != skip; i++) + if (i == dlen - 1) return 0; + + /* Skip over the last character */ + i++; + + pr_debug("Skipped up to `%c'!\n", skip); + + numlen = getnum(data + i, dlen - i, cmd, PF_INET, term); + if (!numlen) + return 0; + + pr_debug("Match succeded!\n"); + return 1; +} + +/* Look up to see if we're just after a \n. */ +static int find_nl_seq(uint32_t seq, struct ftp_info *info, int dir) +{ + int i; + + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) + if (info->seq_aft_nl[dir][i] == seq) + return 1; + return 0; +} + +/* We don't update if it's older than what we have. */ +static void update_nl_seq(uint32_t nl_seq, struct ftp_info *info, int dir) +{ + int i, oldest; + + /* Look for oldest: if we find exact match, we're done. */ + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) { + if (info->seq_aft_nl[dir][i] == nl_seq) + return; + } + + if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) { + info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq; + } else { + if (before(info->seq_aft_nl[dir][0], info->seq_aft_nl[dir][1])) + oldest = 0; + else + oldest = 1; + + if (after(nl_seq, info->seq_aft_nl[dir][oldest])) + info->seq_aft_nl[dir][oldest] = nl_seq; + } +} + +static int nf_nat_ftp_fmt_cmd(enum nf_ct_ftp_type type, + char *buffer, size_t buflen, + uint32_t addr, uint16_t port) +{ + switch (type) { + case NF_CT_FTP_PORT: + case NF_CT_FTP_PASV: + return snprintf(buffer, buflen, "%u,%u,%u,%u,%u,%u", + ((unsigned char *)&addr)[0], + ((unsigned char *)&addr)[1], + ((unsigned char *)&addr)[2], + ((unsigned char *)&addr)[3], + port >> 8, + port & 0xFF); + case NF_CT_FTP_EPRT: + return snprintf(buffer, buflen, "|1|%pI4|%u|", &addr, port); + case NF_CT_FTP_EPSV: + return snprintf(buffer, buflen, "|||%u|", port); + } + + return 0; +} + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int nf_nat_ftp(struct pkt_buff *pkt, + int dir, + int ctinfo, + enum nf_ct_ftp_type type, + unsigned int matchoff, + unsigned int matchlen, + struct nf_conntrack *ct, + struct nf_expect *exp) +{ + union nfct_attr_grp_addr newip; + uint16_t port; + char buffer[sizeof("|1|255.255.255.255|65535|")]; + unsigned int buflen; + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port; + + pr_debug("FTP_NAT: type %i, off %u len %u\n", type, matchoff, matchlen); + + /* Connection will come from wherever this packet goes, hence !dir */ + cthelper_get_addr_dst(ct, !dir, &newip); + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + + if (port == 0) + return NF_DROP; + + buflen = nf_nat_ftp_fmt_cmd(type, buffer, sizeof(buffer), + newip.ip, port); + if (!buflen) + goto out; + + if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) + goto out; + + return NF_ACCEPT; + +out: + cthelper_del_expect(exp); + return NF_DROP; +} + +static int +ftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct tcphdr *th; + unsigned int dataoff; + unsigned int matchoff = 0, matchlen = 0; /* makes gcc happy. */ + unsigned int datalen; + unsigned int i; + int found = 0, ends_in_nl; + uint32_t seq; + int ret = NF_ACCEPT; + struct myct_man cmd; + union nfct_attr_grp_addr addr; + union nfct_attr_grp_addr daddr; + int dir = CTINFO2DIR(ctinfo); + struct ftp_info *ftp_info = myct->priv_data; + struct nf_expect *exp = NULL; + + memset(&cmd, 0, sizeof(struct myct_man)); + memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + + /* Until there's been traffic both ways, don't look in packets. */ + if (ctinfo != IP_CT_ESTABLISHED && + ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("ftp: Conntrackinfo = %u\n", ctinfo); + goto out; + } + + th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + + dataoff = protoff + th->doff * 4; + datalen = pktb_len(pkt) - dataoff; + + ends_in_nl = (pktb_network_header(pkt)[pktb_len(pkt) - 1] == '\n'); + seq = ntohl(th->seq) + datalen; + + /* Look up to see if we're just after a \n. */ + if (!find_nl_seq(ntohl(th->seq), ftp_info, dir)) { + /* Now if this ends in \n, update ftp info. */ + pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n", + ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)", + ftp_info->seq_aft_nl[dir][0], + ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)", + ftp_info->seq_aft_nl[dir][1]); + goto out_update_nl; + } + + /* Initialize IP/IPv6 addr to expected address (it's not mentioned + in EPSV responses) */ + cmd.l3num = nfct_get_attr_u16(myct->ct, ATTR_L3PROTO); + nfct_get_attr_grp(myct->ct, ATTR_GRP_ORIG_ADDR_SRC, &cmd.u3); + + for (i = 0; i < ARRAY_SIZE(search[dir]); i++) { + found = ftp_find_pattern(pkt, dataoff, datalen, + search[dir][i].pattern, + search[dir][i].plen, + search[dir][i].skip, + search[dir][i].term, + &matchoff, &matchlen, + &cmd, + search[dir][i].getnum, + dir); + if (found) break; + } + if (found == 0) /* No match */ + goto out_update_nl; + + pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n", + matchlen, pktb_network_header(pkt) + matchoff, + matchlen, ntohl(th->seq) + matchoff); + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_dst(myct->ct, !dir, &daddr); + + cthelper_get_addr_src(myct->ct, dir, &addr); + + /* Update the ftp info */ + if ((cmd.l3num == nfct_get_attr_u16(myct->ct, ATTR_L3PROTO)) && + memcmp(&cmd.u3, &addr, sizeof(addr)) != 0) { + /* Enrico Scholz's passive FTP to partially RNAT'd ftp + server: it really wants us to connect to a + different IP address. Simply don't record it for + NAT. */ + if (cmd.l3num == PF_INET) { + pr_debug("conntrack_ftp: NOT RECORDING: %pI4 != %pI4\n", + &cmd.u3.ip, &addr); + } else { + pr_debug("conntrack_ftp: NOT RECORDING: %pI6 != %pI6\n", + cmd.u3.ip6, &addr); + } + /* Thanks to Cristiano Lincoln Mattos + for reporting this potential + problem (DMZ machines opening holes to internal + networks, or the packet filter itself). */ + if (!loose) { + ret = NF_ACCEPT; + goto out; + } + memcpy(&daddr, &cmd.u3, sizeof(cmd.u3)); + } + + exp = nfexp_new(); + if (exp == NULL) + goto out_update_nl; + + cthelper_get_addr_src(myct->ct, !dir, &addr); + + if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, IPPROTO_TCP, + NULL, &cmd.u.port, 0)) { + pr_debug("conntrack_ftp: failed to init expectation\n"); + goto out_update_nl; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_ftp(pkt, dir, ctinfo, search[dir][i].ftptype, + matchoff, matchlen, myct->ct, exp); + goto out_update_nl; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("conntrack_ftp: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + goto out_update_nl; + } + +out_update_nl: + if (exp != NULL) + nfexp_destroy(exp); + + /* Now if this ends in \n, update ftp info. Seq may have been + * adjusted by NAT code. */ + if (ends_in_nl) + update_nl_seq(seq, ftp_info, dir); +out: + return ret; +} + +static struct ctd_helper ftp_helper = { + .name = "ftp", + .l4proto = IPPROTO_TCP, + .cb = ftp_helper_cb, + .priv_data_len = sizeof(struct ftp_info), + .policy = { + [0] = { + .name = "ftp", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) ftp_init(void); + +void ftp_init(void) +{ + helper_register(&ftp_helper); +} diff --git a/src/main.c b/src/main.c index 26f6c14..831a3c2 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include "conntrackd.h" #include "log.h" +#include "helper.h" #include #include @@ -31,7 +32,7 @@ #include struct ct_general_state st; -union ct_state state; +struct ct_state state; static const char usage_daemon_commands[] = "Daemon mode commands:\n" diff --git a/src/nfct-extensions/helper.c b/src/nfct-extensions/helper.c new file mode 100644 index 0000000..e8f85bb --- /dev/null +++ b/src/nfct-extensions/helper.c @@ -0,0 +1,619 @@ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This code has been sponsored by Vyatta Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nfct.h" +#include "helper.h" + +static void +nfct_cmd_helper_usage(char *argv[]) +{ + fprintf(stderr, "nfct v%s: Missing command\n" + "%s helper list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); +} + +int +nfct_cmd_helper_parse_params(int argc, char *argv[]) +{ + int cmd = NFCT_CMD_NONE, ret = 0; + + if (argc < 3) { + fprintf(stderr, "nfct v%s: Missing command\n" + "%s helper list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); + exit(EXIT_FAILURE); + } + if (strncmp(argv[2], "list", strlen(argv[2])) == 0) + cmd = NFCT_CMD_LIST; + else if (strncmp(argv[2], "add", strlen(argv[2])) == 0) + cmd = NFCT_CMD_ADD; + else if (strncmp(argv[2], "delete", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DELETE; + else if (strncmp(argv[2], "get", strlen(argv[2])) == 0) + cmd = NFCT_CMD_GET; + else if (strncmp(argv[2], "flush", strlen(argv[2])) == 0) + cmd = NFCT_CMD_FLUSH; + else if (strncmp(argv[2], "disable", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DISABLE; + else { + fprintf(stderr, "nfct v%s: Unknown command: %s\n", + VERSION, argv[2]); + nfct_cmd_helper_usage(argv); + exit(EXIT_FAILURE); + } + switch(cmd) { + case NFCT_CMD_LIST: + ret = nfct_cmd_helper_list(argc, argv); + break; + case NFCT_CMD_ADD: + ret = nfct_cmd_helper_add(argc, argv); + break; + case NFCT_CMD_DELETE: + ret = nfct_cmd_helper_delete(argc, argv); + break; + case NFCT_CMD_GET: + ret = nfct_cmd_helper_get(argc, argv); + break; + case NFCT_CMD_FLUSH: + ret = nfct_cmd_helper_flush(argc, argv); + break; + case NFCT_CMD_DISABLE: + ret = nfct_cmd_helper_disable(argc, argv); + break; + } + + return ret; +} + +static int nfct_helper_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfct_helper *t; + char buf[4096]; + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + goto err; + } + + if (nfct_helper_nlmsg_parse_payload(nlh, t) < 0) { + nfct_perror("nfct_helper_nlmsg_parse_payload"); + goto err_free; + } + + nfct_helper_snprintf(buf, sizeof(buf), t, 0, 0); + printf("%s\n", buf); + +err_free: + nfct_helper_free(t); +err: + return MNL_CB_OK; +} + +int nfct_cmd_helper_list(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + unsigned int seq, portid; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, + NLM_F_DUMP, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_add(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + int ret, j; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct helper add name " + "family protocol"); + return -1; + } + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + + /* XXX use prefix defined in configure.ac. */ + helper = helper_find("/usr/lib/conntrack-tools", + argv[3], l4proto, RTLD_LAZY); + if (helper == NULL) { + nfct_perror("that helper is not supported"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PRIV_DATA_LEN, + helper->priv_data_len); + + for (j=0; jpolicy[j].name[0]) + break; + + p = nfct_helper_policy_alloc(); + if (p == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, + helper->policy[j].name); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, + helper->policy[j].expect_timeout); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, + helper->policy[j].expect_max); + + nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_delete(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + int ret; + + if (argc < 4) { + nfct_perror("missing helper policy name"); + return -1; + } else if (argc > 6) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + + if (argc >= 5) { + uint16_t l3proto; + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + } + + if (argc == 6) { + uint8_t l4proto; + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, + NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_get(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + int ret; + + if (argc < 4) { + nfct_perror("missing helper policy name"); + return -1; + } else if (argc > 6) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + + if (argc >= 5) { + uint16_t l3proto; + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + } + + if (argc == 6) { + uint8_t l4proto; + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, + NLM_F_ACK, seq); + + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_flush(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, + NLM_F_ACK, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_disable(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + int ret; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct helper add name " + "family protocol"); + return -1; + } + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + + /* XXX use prefix defined in configure.ac. */ + helper = helper_find("/usr/lib/conntrack-tools", + argv[3], l4proto, RTLD_LAZY); + if (helper == NULL) { + nfct_perror("that helper is not supported"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, + NFCT_HELPER_STATUS_DISABLED); + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + diff --git a/src/nfct.c b/src/nfct.c index db629e7..b5c9654 100644 --- a/src/nfct.c +++ b/src/nfct.c @@ -56,6 +56,8 @@ int main(int argc, char *argv[]) } if (strncmp(argv[1], "timeout", strlen(argv[1])) == 0) { subsys = NFCT_SUBSYS_TIMEOUT; + } else if (strncmp(argv[1], "helper", strlen(argv[1])) == 0) { + subsys = NFCT_SUBSYS_HELPER; } else if (strncmp(argv[1], "version", strlen(argv[1])) == 0) subsys = NFCT_SUBSYS_VERSION; else if (strncmp(argv[1], "help", strlen(argv[1])) == 0) @@ -71,6 +73,9 @@ int main(int argc, char *argv[]) case NFCT_SUBSYS_TIMEOUT: ret = nfct_cmd_timeout_parse_params(argc, argv); break; + case NFCT_SUBSYS_HELPER: + ret = nfct_cmd_helper_parse_params(argc, argv); + break; case NFCT_SUBSYS_VERSION: ret = nfct_cmd_version(argc, argv); break; @@ -99,6 +104,7 @@ static const char help_msg[] = "nfct v%s: utility for the Netfilter's Connection Tracking System\n" "Usage: %s command [parameters]...\n\n" "Subsystem:\n" + " helper\t\tAllows to configure user-space helper\n" " timeout\t\tAllows definition of fine-grain timeout policies\n" " version\t\tDisplay version and disclaimer\n" " help\t\t\tDisplay this help message\n" diff --git a/src/read_config_lex.l b/src/read_config_lex.l index 01fe4fc..31fa32e 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -142,6 +142,11 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "TCPWindowTracking" { return T_TCP_WINDOW_TRACKING; } "ExpectationSync" { return T_EXPECT_SYNC; } "ErrorQueueLength" { return T_ERROR_QUEUE_LENGTH; } +"Helper" { return T_HELPER; } +"QueueNum" { return T_HELPER_QUEUE_NUM; } +"Policy" { return T_HELPER_POLICY; } +"ExpectMax" { return T_HELPER_EXPECT_MAX; } +"ExpectTimeout" { return T_HELPER_EXPECT_TIMEOUT; } {is_on} { return T_ON; } {is_off} { return T_OFF; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index b22784c..c9235d3 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -28,8 +28,11 @@ #include "conntrackd.h" #include "bitops.h" #include "cidr.h" +#include "helper.h" +#include "stack.h" #include #include +#include #include #include @@ -48,6 +51,15 @@ static void print_err(int err, const char *msg, ...); static void __kernel_filter_start(void); static void __kernel_filter_add_state(int value); static void __max_dedicated_links_reached(void); + +struct stack symbol_stack; + +enum { + SYMBOL_HELPER_QUEUE_NUM, + SYMBOL_HELPER_POLICY_EXPECT_ROOT, + SYMBOL_HELPER_EXPECT_POLICY_LEAF, +}; + %} %union { @@ -74,6 +86,8 @@ static void __max_dedicated_links_reached(void); %token T_SCHEDULER T_TYPE T_PRIO T_NETLINK_EVENTS_RELIABLE %token T_DISABLE_INTERNAL_CACHE T_DISABLE_EXTERNAL_CACHE T_ERROR_QUEUE_LENGTH %token T_OPTIONS T_TCP_WINDOW_TRACKING T_EXPECT_SYNC +%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_POLICY T_HELPER_EXPECT_MAX +%token T_HELPER_EXPECT_TIMEOUT %token T_IP T_PATH_VAL %token T_NUMBER @@ -96,6 +110,7 @@ line : ignore_protocol | general | sync | stats + | helper ; logfile_bool : T_LOG T_ON @@ -1561,6 +1576,186 @@ buffer_size: T_STAT_BUFFER_SIZE T_NUMBER print_err(CTD_CFG_WARN, "`LogFileBufferSize' is deprecated"); }; +helper: T_HELPER '{' helper_list '}' +{ + conf.flags |= CTD_HELPER; +}; + +helper_list: + | helper_list helper_line + ; + +helper_line: helper_type + ; + +helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list '}' +{ + struct ctd_helper_instance *helper_inst; + struct ctd_helper *helper; + struct stack_item *e; + uint16_t l3proto; + uint8_t l4proto; + + if (strcmp($3, "inet") == 0) + l3proto = AF_INET; + else if (strcmp($3, "inet6") == 0) + l3proto = AF_INET6; + else { + print_err(CTD_CFG_ERROR, "unknown layer 3 protocol"); + exit(EXIT_FAILURE); + } + + if (strcmp($4, "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp($4, "udp") == 0) + l4proto = IPPROTO_UDP; + else { + print_err(CTD_CFG_ERROR, "unknown layer 4 protocol"); + exit(EXIT_FAILURE); + } + + /* XXX use configure.ac definitions. */ + helper = helper_find("/usr/lib/conntrack-tools", $2, l4proto, RTLD_NOW); + if (helper == NULL) { + print_err(CTD_CFG_ERROR, "Unknown `%s' helper", $2); + exit(EXIT_FAILURE); + } + + helper_inst = calloc(1, sizeof(struct ctd_helper_instance)); + if (helper_inst == NULL) + break; + + helper_inst->l3proto = l3proto; + helper_inst->l4proto = l4proto; + helper_inst->helper = helper; + + while ((e = stack_item_pop(&symbol_stack, -1)) != NULL) { + + switch(e->type) { + case SYMBOL_HELPER_QUEUE_NUM: { + int *qnum = (int *) &e->data; + + helper_inst->queue_num = *qnum; + stack_item_free(e); + break; + } + case SYMBOL_HELPER_POLICY_EXPECT_ROOT: { + struct ctd_helper_policy *pol = + (struct ctd_helper_policy *) &e->data; + struct ctd_helper_policy *matching = NULL; + int i; + + for (i=0; ipolicy[i].name, + pol->name) != 0) + continue; + + matching = pol; + break; + } + if (matching == NULL) { + print_err(CTD_CFG_ERROR, + "Unknown policy `%s' in helper " + "configuration", pol->name); + exit(EXIT_FAILURE); + } + /* FIXME: First set default policy, then change only + * tuned fields, not everything. + */ + memcpy(&helper->policy[i], pol, + sizeof(struct ctd_helper_policy)); + + stack_item_free(e); + break; + } + default: + print_err(CTD_CFG_ERROR, + "Unexpected symbol parsing helper policy"); + exit(EXIT_FAILURE); + break; + } + } + list_add(&helper_inst->head, &CONFIG(cthelper).list); +}; + +helper_type_list: + | helper_type_list helper_type_line + ; + +helper_type_line: helper_type + ; + +helper_type: T_HELPER_QUEUE_NUM T_NUMBER +{ + int *qnum; + struct stack_item *e; + + e = stack_item_alloc(SYMBOL_HELPER_QUEUE_NUM, sizeof(int)); + qnum = (int *) e->data; + *qnum = $2; + stack_item_push(&symbol_stack, e); +}; + +helper_type: T_HELPER_POLICY T_STRING '{' helper_policy_list '}' +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + print_err(CTD_CFG_ERROR, + "Helper policy configuration empty, fix your " + "configuration file, please"); + exit(EXIT_FAILURE); + break; + } + + policy = (struct ctd_helper_policy *) &e->data; + strncpy(policy->name, $2, CTD_HELPER_NAME_LEN); + policy->name[CTD_HELPER_NAME_LEN-1] = '\0'; + /* Now object is complete. */ + e->type = SYMBOL_HELPER_POLICY_EXPECT_ROOT; + stack_item_push(&symbol_stack, e); +}; + +helper_policy_list: + | helper_policy_list helper_policy_line + ; + +helper_policy_line: helper_policy_expect_max + | helper_policy_expect_timeout + ; + +helper_policy_expect_max: T_HELPER_EXPECT_MAX T_NUMBER +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, + sizeof(struct ctd_helper_policy)); + } + policy = (struct ctd_helper_policy *) &e->data; + policy->expect_max = $2; + stack_item_push(&symbol_stack, e); +}; + +helper_policy_expect_timeout: T_HELPER_EXPECT_TIMEOUT T_NUMBER +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, + sizeof(struct ctd_helper_policy)); + } + policy = (struct ctd_helper_policy *) &e->data; + policy->expect_timeout = $2; + stack_item_push(&symbol_stack, e); +}; + %% int __attribute__((noreturn)) @@ -1640,6 +1835,11 @@ init_config(char *filename) CONFIG(stats).syslog_facility = -1; CONFIG(netlink).subsys_id = -1; + /* Initialize list of user-space helpers */ + INIT_LIST_HEAD(&CONFIG(cthelper).list); + + stack_init(&symbol_stack); + yyrestart(fp); yyparse(); fclose(fp); diff --git a/src/run.c b/src/run.c index 852bec6..3337694 100644 --- a/src/run.c +++ b/src/run.c @@ -50,6 +50,9 @@ void killer(int foo) if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) ctnl_kill(); + if (CONFIG(flags) & CTD_HELPER) + cthelper_kill(); + destroy_fds(STATE(fds)); unlink(CONFIG(lockfile)); dlog(LOG_NOTICE, "---- shutdown received ----"); @@ -199,6 +202,9 @@ static int local_handler(int fd, void *data) if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) return ctnl_local(fd, type, data); + if (CONFIG(flags) & CTD_HELPER) + return cthelper_local(fd, type, data); + return ret; } @@ -250,6 +256,11 @@ init(void) if (ctnl_init() < 0) return -1; + if (CONFIG(flags) & CTD_HELPER) { + if (cthelper_init() < 0) + return -1; + } + time(&STATE(stats).daemon_start_time); dlog(LOG_NOTICE, "initialization completed"); diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..104b7ba --- /dev/null +++ b/src/stack.c @@ -0,0 +1,56 @@ +/* + * (C) 2005-2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +#include "stack.h" + +struct stack_item * +stack_item_alloc(int type, size_t data_len) +{ + struct stack_item *e; + + e = calloc(1, sizeof(struct stack_item) + data_len); + if (e == NULL) + return NULL; + + e->data_len = data_len; + e->type = type; + + return e; +} + +void stack_item_free(struct stack_item *e) +{ + free(e); +} + +void stack_item_push(struct stack *s, struct stack_item *e) +{ + list_add(&e->head, &s->list); +} + +struct stack_item *stack_item_pop(struct stack *s, int type) +{ + struct stack_item *cur, *tmp, *found = NULL; + + list_for_each_entry_safe(cur, tmp, &s->list, head) { + if (cur->type != type && type != -1) + continue; + + list_del(&cur->head); + found = cur; + break; + } + + return found; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..fabec24 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,243 @@ +/* The following code has been extracted from the kenrel sources, if there is + * any problem, blame for mangling it. --pablo */ + +/* + * Generic address resultion entity + * + * Authors: + * net_random Alan Cox + * net_ratelimit Andi Kleen + * in{4,6}_pton YOSHIFUJI Hideaki, Copyright (C)2006 USAGI/WIDE Project + * + * Created by Alexey Kuznetsov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* + * lib/hexdump.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See README and COPYING for + * more details. + */ + +#include +#include +#include /* for memcpy */ + +#include "helper.h" + +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +#define IN6PTON_XDIGIT 0x00010000 +#define IN6PTON_DIGIT 0x00020000 +#define IN6PTON_COLON_MASK 0x00700000 +#define IN6PTON_COLON_1 0x00100000 /* single : requested */ +#define IN6PTON_COLON_2 0x00200000 /* second : requested */ +#define IN6PTON_COLON_1_2 0x00400000 /* :: requested */ +#define IN6PTON_DOT 0x00800000 /* . */ +#define IN6PTON_DELIM 0x10000000 +#define IN6PTON_NULL 0x20000000 /* first/tail */ +#define IN6PTON_UNKNOWN 0x40000000 + +static inline int xdigit2bin(char c, int delim) +{ + int val; + + if (c == delim || c == '\0') + return IN6PTON_DELIM; + if (c == ':') + return IN6PTON_COLON_MASK; + if (c == '.') + return IN6PTON_DOT; + + val = hex_to_bin(c); + if (val >= 0) + return val | IN6PTON_XDIGIT | (val < 10 ? IN6PTON_DIGIT : 0); + + if (delim == -1) + return IN6PTON_DELIM; + return IN6PTON_UNKNOWN; +} + +int in4_pton(const char *src, int srclen, + uint8_t *dst, + int delim, const char **end) +{ + const char *s; + uint8_t *d; + uint8_t dbuf[4]; + int ret = 0; + int i; + int w = 0; + + if (srclen < 0) + srclen = strlen(src); + s = src; + d = dbuf; + i = 0; + while(1) { + int c; + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & (IN6PTON_DIGIT | IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK))) { + goto out; + } + if (c & (IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (w == 0) + goto out; + *d++ = w & 0xff; + w = 0; + i++; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (i != 4) + goto out; + break; + } + goto cont; + } + w = (w * 10) + c; + if ((w & 0xffff) > 255) { + goto out; + } +cont: + if (i >= 4) + goto out; + s++; + srclen--; + } + ret = 1; + memcpy(dst, dbuf, sizeof(dbuf)); +out: + if (end) + *end = s; + return ret; +} + +int in6_pton(const char *src, int srclen, + uint8_t *dst, + int delim, const char **end) +{ + const char *s, *tok = NULL; + uint8_t *d, *dc = NULL; + uint8_t dbuf[16]; + int ret = 0; + int i; + int state = IN6PTON_COLON_1_2 | IN6PTON_XDIGIT | IN6PTON_NULL; + int w = 0; + + memset(dbuf, 0, sizeof(dbuf)); + + s = src; + d = dbuf; + if (srclen < 0) + srclen = strlen(src); + + while (1) { + int c; + + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & state)) + goto out; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + /* process one 16-bit word */ + if (!(state & IN6PTON_NULL)) { + *d++ = (w >> 8) & 0xff; + *d++ = w & 0xff; + } + w = 0; + if (c & IN6PTON_DELIM) { + /* We've processed last word */ + break; + } + /* + * COLON_1 => XDIGIT + * COLON_2 => XDIGIT|DELIM + * COLON_1_2 => COLON_2 + */ + switch (state & IN6PTON_COLON_MASK) { + case IN6PTON_COLON_2: + dc = d; + state = IN6PTON_XDIGIT | IN6PTON_DELIM; + if (dc - dbuf >= (int)sizeof(dbuf)) + state |= IN6PTON_NULL; + break; + case IN6PTON_COLON_1|IN6PTON_COLON_1_2: + state = IN6PTON_XDIGIT | IN6PTON_COLON_2; + break; + case IN6PTON_COLON_1: + state = IN6PTON_XDIGIT; + break; + case IN6PTON_COLON_1_2: + state = IN6PTON_COLON_2; + break; + default: + state = 0; + } + tok = s + 1; + goto cont; + } + + if (c & IN6PTON_DOT) { + ret = in4_pton(tok ? tok : s, srclen + (int)(s - tok), d, delim, &s); + if (ret > 0) { + d += 4; + break; + } + goto out; + } + + w = (w << 4) | (0xff & c); + state = IN6PTON_COLON_1 | IN6PTON_DELIM; + if (!(w & 0xf000)) { + state |= IN6PTON_XDIGIT; + } + if (!dc && d + 2 < dbuf + sizeof(dbuf)) { + state |= IN6PTON_COLON_1_2; + state &= ~IN6PTON_DELIM; + } + if (d + 2 >= dbuf + sizeof(dbuf)) { + state &= ~(IN6PTON_COLON_1|IN6PTON_COLON_1_2); + } +cont: + if ((dc && d + 4 < dbuf + sizeof(dbuf)) || + d + 4 == dbuf + sizeof(dbuf)) { + state |= IN6PTON_DOT; + } + if (d >= dbuf + sizeof(dbuf)) { + state &= ~(IN6PTON_XDIGIT|IN6PTON_COLON_MASK); + } + s++; + srclen--; + } + + i = 15; d--; + + if (dc) { + while(d >= dc) + dst[i--] = *d--; + while(i >= dc - dbuf) + dst[i--] = 0; + while(i >= 0) + dst[i--] = *d--; + } else + memcpy(dst, dbuf, sizeof(dbuf)); + + ret = 1; +out: + if (end) + *end = s; + return ret; +} -- cgit v1.2.3 From 969d93f14fffadb5cae67a7662484c1e064bbff1 Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 May 2012 14:31:35 +0200 Subject: conntrackd: RPC helper added to cthelper How to use this helper in a few steps: 1) You can enable this helper via: nfct helper add rpc inet tcp nfct helper add rpc inet udp 2) Configure /etc/conntrackd/conntrackd.conf and launch it. 3) You can test this helper locally with the following rule-set: iptables -A OUTPUT -t raw -p udp -m udp --dport 111 -j CT --helper rpc iptables -A OUTPUT -t raw -p tcp -m tcp --dport 111 -j CT --helper rpc iptables -A OUTPUT -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 111 -j ACCEPT iptables -A OUTPUT -p udp -m state --state NEW,ESTABLISHED -m udp --dport 111 -j ACCEPT iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -P OUTPUT DROP 4) Configure NFS and export some local directory. Then, mount it with version 3. mount.nfs -onfsvers=3 127.0.0.1:/srv/cvs /mnt/ You should see permanent expectations created for this. Signed-off-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 14 ++ src/helpers/Makefile.am | 7 +- src/helpers/rpc.c | 488 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 src/helpers/rpc.c (limited to 'doc') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 711b309..2bf99fa 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -28,6 +28,20 @@ Helper { ExpectTimeout 300 } } + Type rpc inet tcp { + QueueNum 1 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } + Type rpc inet udp { + QueueNum 2 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index 2c9d63b..f441b29 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,9 +1,12 @@ include $(top_srcdir)/Make_global.am -pkglib_LTLIBRARIES = ct_helper_ftp.la +pkglib_LTLIBRARIES = ct_helper_ftp.la \ + ct_helper_rpc.la ct_helper_ftp_la_SOURCES = ftp.c ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) - +ct_helper_rpc_la_SOURCES = rpc.c +ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/rpc.c b/src/helpers/rpc.c new file mode 100644 index 0000000..82493c2 --- /dev/null +++ b/src/helpers/rpc.c @@ -0,0 +1,488 @@ +/* + * (C) 2012 by Jozsef Kadlecsik + * (C) 2012 by Pablo Neira Ayuso + * + * Based on: RPC extension for conntrack. + * + * This port has been sponsored by Vyatta Inc. + * + * Original copyright notice: + * + * (C) 2000 by Marcelo Barbosa Lima + * (C) 2001 by Rusty Russell + * (C) 2002,2003 by Ian (Larry) Latter + * (C) 2004,2005 by David Stes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* RFC 1050: RPC: Remote Procedure Call Protocol Specification Version 2 */ +/* RFC 1014: XDR: External Data Representation Standard */ +#define SUPPORTED_RPC_VERSION 2 + +struct rpc_info { + /* XID */ + uint32_t xid; + /* program */ + uint32_t pm_prog; + /* program version */ + uint32_t pm_vers; + /* transport protocol: TCP|UDP */ + uint32_t pm_prot; +}; + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_rpc(struct pkt_buff *pkt, int dir, struct nf_expect *exp, + uint8_t proto, uint32_t *port_ptr) +{ + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port, port; + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, proto); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + nfct_destroy(nat_tuple); + + if (port == 0) + return NF_DROP; + + *port_ptr = htonl(port); + + return NF_ACCEPT; +} + +#define OFFSET(o, n) ((o) += n) +#define ROUNDUP(n) ((((n) + 3)/4)*4) + +static int +rpc_call(const uint32_t *data, uint32_t offset, uint32_t datalen, + struct rpc_info *rpc_info) +{ + uint32_t p, r; + + /* RPC CALL message body */ + + /* call_body { + * rpcvers + * prog + * vers + * proc + * opaque_auth cred + * opaque_auth verf + * pmap + * } + * + * opaque_auth { + * flavour + * opaque[len] <= MAX_AUTH_BYTES + * } + */ + if (datalen < OFFSET(offset, 4*4 + 2*2*4)) { + pr_debug("RPC CALL: too short packet: %u < %u\n", + datalen, offset); + return -1; + } + /* Check rpcversion */ + p = IXDR_GET_INT32(data); + if (p != SUPPORTED_RPC_VERSION) { + pr_debug("RPC CALL: wrong rpcvers %u != %u\n", + p, SUPPORTED_RPC_VERSION); + return -1; + } + /* Skip non-portmap requests */ + p = IXDR_GET_INT32(data); + if (p != PMAPPROG) { + pr_debug("RPC CALL: not portmap %u != %lu\n", + p, PMAPPROG); + return -1; + } + /* Check portmap version */ + p = IXDR_GET_INT32(data); + if (p != PMAPVERS) { + pr_debug("RPC CALL: wrong portmap version %u != %lu\n", + p, PMAPVERS); + return -1; + } + /* Skip non PMAPPROC_GETPORT requests */ + p = IXDR_GET_INT32(data); + if (p != PMAPPROC_GETPORT) { + pr_debug("RPC CALL: not PMAPPROC_GETPORT %u != %lu\n", + p, PMAPPROC_GETPORT); + return -1; + } + /* Check and skip credentials */ + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC CALL: cred: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC CALL: invalid sized cred %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + if (datalen < OFFSET(offset, r)) { + pr_debug("RPC CALL: too short to carry cred: %u < %u, %u\n", + datalen, offset, r); + return -1; + } + data += r/4; + /* Check and skip verifier */ + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC CALL: verf: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC CALL: invalid sized verf %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + if (datalen < OFFSET(offset, r)) { + pr_debug("RPC CALL: too short to carry verf: %u < %u, %u\n", + datalen, offset, r); + return -1; + } + data += r/4; + /* pmap { + * prog + * vers + * prot + * port + * } + */ + /* Check CALL size */ + if (datalen != offset + 4*4) { + pr_debug("RPC CALL: invalid size to carry pmap: %u != %u\n", + datalen, offset + 4*4); + return -1; + } + rpc_info->pm_prog = IXDR_GET_INT32(data); + rpc_info->pm_vers = IXDR_GET_INT32(data); + rpc_info->pm_prot = IXDR_GET_INT32(data); + /* Check supported protocols */ + if (!(rpc_info->pm_prot == IPPROTO_TCP + || rpc_info->pm_prot == IPPROTO_UDP)) { + pr_debug("RPC CALL: unsupported protocol %u", + rpc_info->pm_prot); + return -1; + } + p = IXDR_GET_INT32(data); + /* Check port: must be zero */ + if (p != 0) { + pr_debug("RPC CALL: port is nonzero %u\n", + ntohl(p)); + return -1; + } + pr_debug("RPC CALL: processed: xid %u, prog %u, vers %u, prot %u\n", + rpc_info->xid, rpc_info->pm_prog, + rpc_info->pm_vers, rpc_info->pm_prot); + + return 0; +} + +static int +rpc_reply(uint32_t *data, uint32_t offset, uint32_t datalen, + struct rpc_info *rpc_info, uint32_t **port_ptr) +{ + uint16_t port; + uint32_t p, r; + + /* RPC REPLY message body */ + + /* reply_body { + * reply_stat + * xdr_union { + * accepted_reply + * rejected_reply + * } + * } + * accepted_reply { + * opaque_auth verf + * accept_stat + * xdr_union { + * port + * struct mismatch_info + * } + * } + */ + + /* Check size: reply status */ + if (datalen < OFFSET(offset, 4)) { + pr_debug("RPC REPL: too short, missing rp_stat: %u < %u\n", + datalen, offset); + return -1; + } + p = IXDR_GET_INT32(data); + /* Check accepted request */ + if (p != MSG_ACCEPTED) { + pr_debug("RPC REPL: not accepted %u != %u\n", + p, MSG_ACCEPTED); + return -1; + } + /* Check and skip verifier */ + if (datalen < OFFSET(offset, 2*4)) { + pr_debug("RPC REPL: too short, missing verf: %u < %u\n", + datalen, offset); + return -1; + } + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC REPL: verf: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC REPL: invalid sized verf %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + /* verifier + ac_stat + port */ + if (datalen != OFFSET(offset, r) + 2*4) { + pr_debug("RPC REPL: invalid size to carry verf and " + "success: %u != %u\n", + datalen, offset + 2*4); + return -1; + } + data += r/4; + /* Check success */ + p = IXDR_GET_INT32(data); + if (p != SUCCESS) { + pr_debug("RPC REPL: not success %u != %u\n", + p, SUCCESS); + return -1; + } + /* Get port */ + *port_ptr = data; + port = IXDR_GET_INT32(data); /* -Wunused-but-set-parameter */ + if (port == 0) { + pr_debug("RPC REPL: port is zero\n"); + return -1; + } + pr_debug("RPC REPL: processed: xid %u, prog %u, vers %u, " + "prot %u, port %u\n", + rpc_info->xid, rpc_info->pm_prog, rpc_info->pm_vers, + rpc_info->pm_prot, port); + return 0; +} + +static int +rpc_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + int dir = CTINFO2DIR(ctinfo); + unsigned int offset = protoff, datalen; + uint32_t *data, *port_ptr = NULL, xid; + uint16_t port; + uint8_t proto = nfct_get_attr_u8(myct->ct, ATTR_L4PROTO); + enum msg_type rm_dir; + struct rpc_info *rpc_info = myct->priv_data; + union nfct_attr_grp_addr addr, daddr; + struct nf_expect *exp = NULL; + int ret = NF_ACCEPT; + + /* Until there's been traffic both ways, don't look into TCP packets. */ + if (proto == IPPROTO_TCP + && ctinfo != IP_CT_ESTABLISHED + && ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("TCP RPC: Conntrackinfo = %u\n", ctinfo); + return ret; + } + if (proto == IPPROTO_TCP) { + struct tcphdr *th = + (struct tcphdr *) (pktb_network_header(pkt) + protoff); + offset += th->doff * 4; + } else { + offset += sizeof(struct udphdr); + } + /* Skip broken headers */ + if (offset % 4) { + pr_debug("RPC: broken header: offset %u%%4 != 0\n", offset); + return ret; + } + + /* Take into Record Fragment header */ + if (proto == IPPROTO_TCP) + offset += 4; + + datalen = pktb_len(pkt); + data = (uint32_t *)(pktb_network_header(pkt) + offset); + + /* rpc_msg { + * xid + * direction + * xdr_union { + * call_body + * reply_body + * } + * } + */ + + /* Check minimal msg size: xid + direction */ + if (datalen < OFFSET(offset, 2*4)) { + pr_debug("RPC: too short packet: %u < %u\n", + datalen, offset); + return ret; + } + xid = IXDR_GET_INT32(data); + rm_dir = IXDR_GET_INT32(data); + + /* Check direction */ + if (!((rm_dir == CALL && dir == MYCT_DIR_ORIG) + || (rm_dir == REPLY && dir == MYCT_DIR_REPL))) { + pr_debug("RPC: rm_dir != dir %u != %u\n", rm_dir, dir); + goto out; + } + + if (rm_dir == CALL) { + if (rpc_call(data, offset, datalen, rpc_info) < 0) + goto out; + + rpc_info->xid = xid; + + return ret; + } else { + /* Check XID */ + if (xid != rpc_info->xid) { + pr_debug("RPC REPL: XID does not match: %u != %u\n", + xid, rpc_info->xid); + goto out; + } + if (rpc_reply(data, offset, datalen, rpc_info, &port_ptr) < 0) + goto out; + + port = IXDR_GET_INT32(port_ptr); + port = htons(port); + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_dst(myct->ct, !dir, &daddr); + cthelper_get_addr_src(myct->ct, !dir, &addr); + + exp = nfexp_new(); + if (exp == NULL) + goto out; + + if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, + rpc_info->pm_prot, + NULL, &port, NF_CT_EXPECT_PERMANENT)) { + pr_debug("RPC: failed to init expectation\n"); + goto out_exp; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_rpc(pkt, dir, exp, rpc_info->pm_prot, + port_ptr); + goto out_exp; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("RPC: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + } + } + +out_exp: + nfexp_destroy(exp); +out: + rpc_info->xid = 0; + return ret; +} + +static struct ctd_helper rpc_helper_tcp = { + .name = "rpc", + .l4proto = IPPROTO_TCP, + .cb = rpc_helper_cb, + .priv_data_len = sizeof(struct rpc_info), + .policy = { + { + .name = "rpc", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +static struct ctd_helper rpc_helper_udp = { + .name = "rpc", + .l4proto = IPPROTO_UDP, + .cb = rpc_helper_cb, + .priv_data_len = sizeof(struct rpc_info), + .policy = { + { + .name = "rpc", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) rpc_init(void); + +void rpc_init(void) +{ + helper_register(&rpc_helper_tcp); + helper_register(&rpc_helper_udp); +} -- cgit v1.2.3 From 40f4330e6b50ed2b198549b1006c6fcb349f5a3b Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 May 2012 14:43:20 +0200 Subject: conntrackd: TNS helper added to cthelper Signed-off-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 7 + src/helpers/Makefile.am | 7 +- src/helpers/tns.c | 407 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 src/helpers/tns.c (limited to 'doc') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 2bf99fa..80f1f92 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -42,6 +42,13 @@ Helper { ExpectTimeout 300 } } + Type tns inet tcp { + QueueNum 3 + Policy tns { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index f441b29..589b4f3 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,7 +1,8 @@ include $(top_srcdir)/Make_global.am pkglib_LTLIBRARIES = ct_helper_ftp.la \ - ct_helper_rpc.la + ct_helper_rpc.la \ + ct_helper_tns.la ct_helper_ftp_la_SOURCES = ftp.c ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) @@ -10,3 +11,7 @@ ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) ct_helper_rpc_la_SOURCES = rpc.c ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_tns_la_SOURCES = tns.c +ct_helper_tns_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/tns.c b/src/helpers/tns.c new file mode 100644 index 0000000..5833fea --- /dev/null +++ b/src/helpers/tns.c @@ -0,0 +1,407 @@ +/* + * (C) 2012 by Jozsef Kadlecsik + * (C) 2012 by Pablo Neira Ayuso + * + * Sponsored by Vyatta Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include /* for isdigit */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* TNS SQL*Net Version 2 */ +enum tns_types { + TNS_TYPE_CONNECT = 1, + TNS_TYPE_ACCEPT = 2, + TNS_TYPE_ACK = 3, + TNS_TYPE_REFUSE = 4, + TNS_TYPE_REDIRECT = 5, + TNS_TYPE_DATA = 6, + TNS_TYPE_NULL = 7, + TNS_TYPE_ABORT = 9, + TNS_TYPE_RESEND = 11, + TNS_TYPE_MARKER = 12, + TNS_TYPE_ATTENTION = 13, + TNS_TYPE_CONTROL = 14, + TNS_TYPE_MAX = 19, +}; + +struct tns_header { + uint16_t len; + uint16_t csum; + uint8_t type; + uint8_t reserved; + uint16_t header_csum; +}; + +struct tns_redirect { + uint16_t data_len; +}; + +struct tns_info { + /* Scan next DATA|REDIRECT packet */ + bool parse; +}; + +static int try_number(const char *data, size_t dlen, uint32_t array[], + int array_size, char sep, char term) +{ + uint32_t len; + int i; + + memset(array, 0, sizeof(array[0])*array_size); + + /* Keep data pointing at next char. */ + for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { + if (*data >= '0' && *data <= '9') { + array[i] = array[i]*10 + *data - '0'; + } + else if (*data == sep) + i++; + else { + /* Skip spaces. */ + if (*data == ' ') + continue; + /* Unexpected character; true if it's the + terminator and we're finished. */ + if (*data == term && i == array_size - 1) + return len; + pr_debug("Char %u (got %u nums) `%c' unexpected\n", + len, i, *data); + return 0; + } + } + pr_debug("Failed to fill %u numbers separated by %c\n", + array_size, sep); + return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, size_t dlen, char delim, + struct myct_man *cmd) +{ + uint16_t tmp_port = 0; + uint32_t i; + + for (i = 0; i < dlen; i++) { + /* Finished? */ + if (data[i] == delim) { + if (tmp_port == 0) + break; + cmd->u.port = htons(tmp_port); + pr_debug("get_port: return %d\n", tmp_port); + return i + 1; + } + else if (data[i] >= '0' && data[i] <= '9') + tmp_port = tmp_port*10 + data[i] - '0'; + else if (data[i] == ' ') /* Skip spaces */ + continue; + else { /* Some other crap */ + pr_debug("get_port: invalid char `%c'\n", data[i]); + break; + } + } + return 0; +} + +/* (ADDRESS=(PROTOCOL=tcp)(DEV=x)(HOST=a.b.c.d)(PORT=a)) */ +/* FIXME: handle hostnames */ + +/* Returns 0, or length of port number */ +static unsigned int +find_pattern(struct pkt_buff *pkt, unsigned int dataoff, size_t dlen, + struct myct_man *cmd, unsigned int *numoff) +{ + const char *data = (const char *)pktb_network_header(pkt) + dataoff + + sizeof(struct tns_header); + int length, offset, ret; + uint32_t array[4]; + const char *p, *start; + + p = strstr(data, "("); + if (!p) + return 0; + + p = strstr(p+1, "HOST="); + if (!p) { + pr_debug("HOST= not found\n"); + return 0; + } + + start = p + strlen("HOST="); + offset = (int)(p - data) + strlen("HOST="); + *numoff = offset + sizeof(struct tns_header); + data += offset; + + length = try_number(data, dlen - offset, array, 4, '.', ')'); + if (length == 0) + return 0; + + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | + (array[2] << 8) | array[3]); + + p = strstr(data+length, "("); + if (!p) + return 0; + + p = strstr(p, "PORT="); + if (!p) { + pr_debug("PORT= not found\n"); + return 0; + } + + p += strlen("PORT="); + ret = get_port(p, dlen - offset - length, ')', cmd); + if (ret == 0) + return 0; + + p += ret; + return (int)(p - start); +} + +static inline uint16_t +nton(uint16_t len, unsigned int matchoff, unsigned int matchlen) +{ + uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen; + + return htons(l); +} + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp, + struct nf_conntrack *ct, int dir, + unsigned int matchoff, unsigned int matchlen) +{ + union nfct_attr_grp_addr newip; + char buffer[sizeof("255.255.255.255)(PORT=65535)")]; + unsigned int buflen; + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port, port; + + /* Connection will come from wherever this packet goes, hence !dir */ + cthelper_get_addr_dst(ct, !dir, &newip); + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + nfct_destroy(nat_tuple); + + if (port == 0) + return NF_DROP; + + buflen = snprintf(buffer, sizeof(buffer), + "%u.%u.%u.%u)(PORT=%u)", + ((unsigned char *)&newip.ip)[0], + ((unsigned char *)&newip.ip)[1], + ((unsigned char *)&newip.ip)[2], + ((unsigned char *)&newip.ip)[3], port); + if (!buflen) + goto out; + + if (!nfq_tcp_mangle_ipv4(pkt, matchoff, matchlen, buffer, buflen)) + goto out; + + if (buflen != matchlen) { + /* FIXME: recalculate checksum */ + tns->csum = 0; + tns->header_csum = 0; + + tns->len = nton(tns->len, matchlen, buflen); + if (tns->type == TNS_TYPE_REDIRECT) { + struct tns_redirect *r; + + r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header)); + + r->data_len = nton(r->data_len, matchlen, buflen); + } + } + + return NF_ACCEPT; + +out: + cthelper_del_expect(exp); + return NF_DROP; +} + +static int +tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct tcphdr *th; + struct tns_header *tns; + int dir = CTINFO2DIR(ctinfo); + unsigned int dataoff, datalen, numoff = 0, numlen; + struct tns_info *tns_info = myct->priv_data; + union nfct_attr_grp_addr addr; + struct nf_expect *exp = NULL; + struct myct_man cmd; + int ret = NF_ACCEPT; + + memset(&cmd, 0, sizeof(struct myct_man)); + memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + + /* Until there's been traffic both ways, don't look into TCP packets. */ + if (ctinfo != IP_CT_ESTABLISHED + && ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("TNS: Conntrackinfo = %u\n", ctinfo); + goto out; + } + /* Parse server direction only */ + if (dir != MYCT_DIR_REPL) { + pr_debug("TNS: skip client direction\n"); + goto out; + } + + th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + + dataoff = protoff + th->doff * 4; + datalen = pktb_len(pkt); + + if (datalen < sizeof(struct tns_header)) { + pr_debug("TNS: skip packet with short header\n"); + goto out; + } + + tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff); + + if (tns->type == TNS_TYPE_REDIRECT) { + struct tns_redirect *redirect; + + dataoff += sizeof(struct tns_header); + datalen -= sizeof(struct tns_header); + redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff); + tns_info->parse = false; + + if (ntohs(redirect->data_len) == 0) { + tns_info->parse = true; + goto out; + } + goto parse; + } + + /* Parse only the very next DATA packet */ + if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) { + tns_info->parse = false; + goto out; + } +parse: + numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff); + tns_info->parse = false; + if (!numlen) + goto out; + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_src(myct->ct, !dir, &addr); + + exp = nfexp_new(); + if (exp == NULL) + goto out; + + if (cthelper_expect_init(exp, myct->ct, 0, + &addr, &cmd.u3, + IPPROTO_TCP, + NULL, &cmd.u.port, 0)) { + pr_debug("TNS: failed to init expectation\n"); + goto out_exp; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. + */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, + numoff + sizeof(struct tns_header), numlen); + goto out_exp; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("TNS: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + goto out_exp; + } + goto out; + +out_exp: + nfexp_destroy(exp); +out: + return ret; +} + +static struct ctd_helper tns_helper = { + .name = "tns", + .l4proto = IPPROTO_TCP, + .cb = tns_helper_cb, + .priv_data_len = sizeof(struct tns_info), + .policy = { + [0] = { + .name = "tns", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) tns_init(void); + +void tns_init(void) +{ + helper_register(&tns_helper); +} -- cgit v1.2.3 From febb3cceac1889fb6558b8ef40ac733072fdcd47 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 10 Sep 2012 13:17:24 +0200 Subject: conntrackd: cthelper: add QueueLen option This patch adds the QueueLen option, that allows you to increase the maximum number of packets waiting in the nfnetlink_queue to receive a verdict from userspace. Rising the default value (1024) is useful to avoid hitting the following error message: "nf_queue: full at X entries, dropping packets(s)". Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 13 +++++++++++++ include/helper.h | 1 + src/cthelper.c | 6 ++++-- src/read_config_lex.l | 1 + src/read_config_yy.y | 23 +++++++++++++++++++++-- 5 files changed, 40 insertions(+), 4 deletions(-) (limited to 'doc') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 80f1f92..56f5162 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -14,6 +14,16 @@ Helper { # the kernel. # QueueNum 0 + + # + # Maximum number of packets waiting in the queue to receive + # a verdict from user-space. Default is 1024. + # + # Rise value if you hit the following error message: + # "nf_queue: full at X entries, dropping packets(s)" + # + QueueLen 10240 + # # Set the Expectation policy for this helper. # @@ -30,6 +40,7 @@ Helper { } Type rpc inet tcp { QueueNum 1 + QueueLen 10240 Policy rpc { ExpectMax 1 ExpectTimeout 300 @@ -37,6 +48,7 @@ Helper { } Type rpc inet udp { QueueNum 2 + QueueLen 10240 Policy rpc { ExpectMax 1 ExpectTimeout 300 @@ -44,6 +56,7 @@ Helper { } Type tns inet tcp { QueueNum 3 + QueueLen 10240 Policy tns { ExpectMax 1 ExpectTimeout 300 diff --git a/include/helper.h b/include/helper.h index 329fd2d..9d96fb7 100644 --- a/include/helper.h +++ b/include/helper.h @@ -35,6 +35,7 @@ struct ctd_helper { struct ctd_helper_instance { struct list_head head; uint32_t queue_num; + uint32_t queue_len; uint16_t l3proto; uint8_t l4proto; struct ctd_helper *helper; diff --git a/src/cthelper.c b/src/cthelper.c index c119869..307be96 100644 --- a/src/cthelper.c +++ b/src/cthelper.c @@ -353,8 +353,9 @@ static int cthelper_setup(struct ctd_helper_instance *cur) nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, NFCT_HELPER_STATUS_ENABLED); - dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d", - cur->helper->name, cur->queue_num); + dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d and " + "queuelen=%d", cur->helper->name, cur->queue_num, + cur->queue_len); for (j=0; jqueue_len)); if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { dlog(LOG_ERR, "failed to send configuration"); diff --git a/src/read_config_lex.l b/src/read_config_lex.l index 31fa32e..bec2d81 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -144,6 +144,7 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "ErrorQueueLength" { return T_ERROR_QUEUE_LENGTH; } "Helper" { return T_HELPER; } "QueueNum" { return T_HELPER_QUEUE_NUM; } +"QueueLen" { return T_HELPER_QUEUE_LEN; } "Policy" { return T_HELPER_POLICY; } "ExpectMax" { return T_HELPER_EXPECT_MAX; } "ExpectTimeout" { return T_HELPER_EXPECT_TIMEOUT; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index c9235d3..72a9654 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -56,6 +56,7 @@ struct stack symbol_stack; enum { SYMBOL_HELPER_QUEUE_NUM, + SYMBOL_HELPER_QUEUE_LEN, SYMBOL_HELPER_POLICY_EXPECT_ROOT, SYMBOL_HELPER_EXPECT_POLICY_LEAF, }; @@ -86,8 +87,8 @@ enum { %token T_SCHEDULER T_TYPE T_PRIO T_NETLINK_EVENTS_RELIABLE %token T_DISABLE_INTERNAL_CACHE T_DISABLE_EXTERNAL_CACHE T_ERROR_QUEUE_LENGTH %token T_OPTIONS T_TCP_WINDOW_TRACKING T_EXPECT_SYNC -%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_POLICY T_HELPER_EXPECT_MAX -%token T_HELPER_EXPECT_TIMEOUT +%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_QUEUE_LEN T_HELPER_POLICY +%token T_HELPER_EXPECT_TIMEOUT T_HELPER_EXPECT_MAX %token T_IP T_PATH_VAL %token T_NUMBER @@ -1639,6 +1640,13 @@ helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list '}' stack_item_free(e); break; } + case SYMBOL_HELPER_QUEUE_LEN: { + int *qlen = (int *) &e->data; + + helper_inst->queue_len = *qlen; + stack_item_free(e); + break; + } case SYMBOL_HELPER_POLICY_EXPECT_ROOT: { struct ctd_helper_policy *pol = (struct ctd_helper_policy *) &e->data; @@ -1696,6 +1704,17 @@ helper_type: T_HELPER_QUEUE_NUM T_NUMBER stack_item_push(&symbol_stack, e); }; +helper_type: T_HELPER_QUEUE_LEN T_NUMBER +{ + int *qlen; + struct stack_item *e; + + e = stack_item_alloc(SYMBOL_HELPER_QUEUE_LEN, sizeof(int)); + qlen = (int *) e->data; + *qlen = $2; + stack_item_push(&symbol_stack, e); +}; + helper_type: T_HELPER_POLICY T_STRING '{' helper_policy_list '}' { struct stack_item *e; -- cgit v1.2.3 From 3f845636159298fb18b6d6c455066d0344a61bee Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 3 Oct 2012 22:19:25 +0200 Subject: conntrackd: fix crash if ExpectationSync is enabled on old Linux kernels ExpectationSync requires Linux kernel >= 3.5 to work sanely, document this. Still, we don't want to crash if someone enables expectation sync with old Linux kernels (like 2.6.32). Reported-by: James Gutholm Tested-by: James Gutholm Signed-off-by: Pablo Neira Ayuso --- doc/manual/conntrack-tools.tmpl | 7 +++++++ doc/sync/alarm/conntrackd.conf | 3 ++- doc/sync/ftfw/conntrackd.conf | 3 ++- doc/sync/notrack/conntrackd.conf | 3 ++- src/build.c | 3 ++- src/filter.c | 12 +++++++++++- 6 files changed, 26 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index 47e6f84..63a53e4 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -660,6 +660,13 @@ Sync { Synchronization of expectations + Check your Linux kernel version first + + The synchronization of expectations require a Linux kernel >= 3.5 + to work appropriately. + + + The connection tracking system provides helpers that allows you to filter multi-flow application protocols like FTP, H.323 and SIP among many others. These protocols usually split the control and data traffic in diff --git a/doc/sync/alarm/conntrackd.conf b/doc/sync/alarm/conntrackd.conf index b9520fb..0223745 100644 --- a/doc/sync/alarm/conntrackd.conf +++ b/doc/sync/alarm/conntrackd.conf @@ -194,7 +194,8 @@ Sync { # Set this option on if you want to enable the synchronization # of expectations. You have to specify the list of helpers that - # you want to enable. Default is off. + # you want to enable. Default is off. This feature requires + # a Linux kernel >= 3.5. # # ExpectationSync { # ftp diff --git a/doc/sync/ftfw/conntrackd.conf b/doc/sync/ftfw/conntrackd.conf index 53a7d0f..65e7b77 100644 --- a/doc/sync/ftfw/conntrackd.conf +++ b/doc/sync/ftfw/conntrackd.conf @@ -217,7 +217,8 @@ Sync { # Set this option on if you want to enable the synchronization # of expectations. You have to specify the list of helpers that - # you want to enable. Default is off. + # you want to enable. Default is off. This feature requires + # a Linux kernel >= 3.5. # # ExpectationSync { # ftp diff --git a/doc/sync/notrack/conntrackd.conf b/doc/sync/notrack/conntrackd.conf index 11f022e..3d036fb 100644 --- a/doc/sync/notrack/conntrackd.conf +++ b/doc/sync/notrack/conntrackd.conf @@ -256,7 +256,8 @@ Sync { # Set this option on if you want to enable the synchronization # of expectations. You have to specify the list of helpers that - # you want to enable. Default is off. + # you want to enable. Default is off. This feature requires + # a Linux kernel >= 3.5. # # ExpectationSync { # ftp diff --git a/src/build.c b/src/build.c index 7d4ef12..e15eb4f 100644 --- a/src/build.c +++ b/src/build.c @@ -356,7 +356,8 @@ void exp2msg(const struct nf_expect *exp, struct nethdr *n) exp_build_u32(exp, ATTR_EXP_NAT_DIR, n, NTA_EXP_NAT_DIR); } - exp_build_str(exp, ATTR_EXP_HELPER_NAME, n, NTA_EXP_HELPER_NAME); + if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME)) + exp_build_str(exp, ATTR_EXP_HELPER_NAME, n, NTA_EXP_HELPER_NAME); if (nfexp_attr_is_set(exp, ATTR_EXP_FN)) exp_build_str(exp, ATTR_EXP_FN, n, NTA_EXP_FN); } diff --git a/src/filter.c b/src/filter.c index 39dd4ca..02a8078 100644 --- a/src/filter.c +++ b/src/filter.c @@ -473,7 +473,17 @@ int exp_filter_find(struct exp_filter *f, const struct nf_expect *exp) return 1; list_for_each_entry(item, &f->list, head) { - const char *name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME); + const char *name; + + if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME)) + name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME); + else { + /* No helper name, this is likely to be a kernel older + * which does not include the helper name, just skip + * this so we don't crash. + */ + return 0; + } /* we allow partial matching to support things like sip-PORT. */ if (strncasecmp(item->helper_name, name, -- cgit v1.2.3 From affe4656f3aeeba4040f9d63efd7719ef0345ae9 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 8 Oct 2012 12:22:28 +0200 Subject: doc: detail user-space helper support This patch adds documentation on how to enable user-space helper support. Signed-off-by: Pablo Neira Ayuso --- doc/manual/conntrack-tools.tmpl | 155 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 5 deletions(-) (limited to 'doc') diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index 63a53e4..f21a4ff 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -19,7 +19,7 @@ - 2008-2011 + 2008-2012 Pablo Neira Ayuso @@ -37,7 +37,7 @@ This document details how to install and configure the conntrack-tools - >= 1.0.0. This document will evolve in the future to cover new features + >= 1.4.0. This document will evolve in the future to cover new features and changes. @@ -827,7 +827,154 @@ Sync { -Troubleshooting + + +User-space helpers + + Check your Linux kernel version first + + The user-space helper infrastructure requires a Linux kernel >= 3.6 + to work appropriately. + + + +Connection tracking helpers allows you to filter multi-flow protocols +that usually separate control and data traffic into different flows. +These protocols usually violate network layering by including layer 3/4 +details, eg. IP address and TCP/UDP ports, in their application protocol +(which resides in layer 7). This is problematic for gateways since they +operate at packet-level, ie. layers 3/4, and therefore they miss this +important information to filter these protocols appropriately. + +Helpers inspect packet content (at layer 7) and create the so-called +expectations. These expectations are added to one internal table +that resides in the gateway. For each new packet arriving to the +gateway, the gateway first looks up for matching expectations. If +there is any, then this flow is accepted since it's been expected. +Note this lookup only occurs for the first packet that is part of one +newly established flow, not for all packets. + +Since 1.4.0, conntrackd provides the infrastructure to develop +helpers in user-space. The main features of the user-space infrastructure +for helpers are: + + + +Rapid connection tracking helper development, as developing code +in user-space is usually faster. + +Reliability: A buggy helper does not crash the kernel. If the helper +fails, ie. the conntrackd crashes, Moreover, we can monitor the helper process +and restart it in case of problems. + +Security: Avoid complex string matching and mangling in +kernel-space running in privileged mode. Going further, we can even think +about running user-space helper as a non-root process. + +It allows the development of very specific helpers for +proprietary protocols that are not standard. This is the case of the SQL*net +helper. Implementing this in kernel-space may be problematic, since +this may not be accepted for ainline inclusion in the Linux kernel. +As an alternative, we can still distribute this support as separate +patches. However, my personal experience is that, given that the +kernel API/ABI is not stable, changes in the interface lead to the +breakage of the patch. This highly increase the overhead in the +maintainance. + + + +Currently, the infrastructure supports the following user-space helpers: + + + +Oracle*TNS, to support its special Redirect message. +NFSv3, mind that version 4 does not require this helper. +FTP (this helper is also available in kernel-space). + + +The following steps describe how to enable the RPC portmapper helper for NFSv3 (this is similar for other helpers): + + +Register user-space helper: + + +nfct helper add rpc inet udp +nfct helper add rpc inet tcp + + +This registers the portmapper helper for both UDP and TCP (NFSv3 traffic goes both over TCP and UDP). + + +Add iptables rule using the CT target: + + +# iptables -I OUTPUT -t raw -p udp --dport 111 -j CT --helper rpc +# iptables -I OUTPUT -t raw -p tcp --dport 111 -j CT --helper rpc + + +With this, packets matching port TCP/UDP/111 are passed to user-space for +inspection. If there is no instance of conntrackd configured to support +user-space helpers, no inspection happens and packets are not sent to +user-space. + +Add configuration to conntrackd.conf: + + +Helper { + Type rpc inet udp { + QueueNum 1 + QueueLen 10240 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } + Type rpc inet tcp { + QueueNum 2 + QueueLen 10240 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } +} + + +This configures conntrackd to use NFQUEUE queue numbers 1 and 2 to send traffic +for inspection to user-space + + If you have some custom libnetfilter_queue application + + Make sure your queue numbers do not collide with those used in your + conntrackd.conf file. + + + + + + + +Now you can test this (assuming you have some working NFSv3 setup) with: + + +mount -t nfs -onfsvers=3 mynfs.server.info:/srv/cvs /mnt/ + + + + +You should see new expectations being added via: + + +# conntrack -E expect + [NEW] 300 proto=17 src=1.2.3.4 dst=1.2.3.4 sport=0 dport=54834 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=1.2.3.4 master-dst=1.2.3.4 sport=58190 dport=111 PERMANENT class=0 helper=rpc + [NEW] 300 proto=6 src=1.2.3.4 dst=1.2.3.4 sport=0 dport=2049 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=1.2.3.4 master-dst=1.2.3.4 sport=55450 dport=111 PERMANENT class=0 helper=rpc + [NEW] 300 proto=17 src=1.2.3.4 dst=1.2.3.4 sport=0 dport=58031 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=1.2.3.4 master-dst=1.2.3.4 sport=56309 dport=111 PERMANENT class=0 helper=rpc + + + + + +Troubleshooting Problems with conntrackd? The following list of questions should help for troubleshooting: @@ -1033,8 +1180,6 @@ not enough space errors: 0 - - -- cgit v1.2.3 From 36118bfc4901b0978d2c8f17912fe91ec66f35e8 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 6 Aug 2013 14:21:04 +0200 Subject: conntrackd: helpers: add DHCPv6 helper This patch adds support for the DHCPv6 helper. 1) nfct helper add dhcpv6 inet6 udp 2) ip6tables -I OUTPUT -t raw -p udp --sport 546 -j CT --helper dhcpv6 3) run conntrackd You should see: % conntrack -L exp -f ipv6 279 proto=17 src=:: dst=ff02::1:2 sport=0 dport=546 mask-src=:: mask-dst=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff sport=0 dport=65535 master-src=fe80::221:ccff:fe4a:7f9c master-dst=ff02::1:2 sport=546 dport=547 PERMANENT class=0 helper=dhcpv6 Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 8 +++ src/helpers/Makefile.am | 7 ++- src/helpers/dhcpv6.c | 123 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/helpers/dhcpv6.c (limited to 'doc') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 56f5162..358ad10 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -62,6 +62,14 @@ Helper { ExpectTimeout 300 } } + Type dhcpv6 inet6 udp { + QueueNum 4 + QueueLen 10240 + Policy dhcpv6 { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index 589b4f3..59524f5 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,9 +1,14 @@ include $(top_srcdir)/Make_global.am -pkglib_LTLIBRARIES = ct_helper_ftp.la \ +pkglib_LTLIBRARIES = ct_helper_dhcpv6.la \ + ct_helper_ftp.la \ ct_helper_rpc.la \ ct_helper_tns.la +ct_helper_dhcpv6_la_SOURCES = dhcpv6.c +ct_helper_dhcpv6_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_dhcpv6_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + 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/dhcpv6.c b/src/helpers/dhcpv6.c new file mode 100644 index 0000000..73632ec --- /dev/null +++ b/src/helpers/dhcpv6.c @@ -0,0 +1,123 @@ +/* + * (C) 2013 by Pablo Neira Ayuso + * + * Adapted from: + * + * DHCPv6 multicast connection tracking helper. + * + * (c) 2012 Google Inc. + * + * Original author: Darren Willis + * + * 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 "conntrackd.h" +#include "helper.h" +#include "myct.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DHCPV6_CLIENT_PORT 546 + +static uint16_t dhcpv6_port; + +/* Timeouts for DHCPv6 replies, in seconds, indexed by message type. */ +static const int dhcpv6_timeouts[] = { + 0, /* No message has type 0. */ + 120, /* Solicit. */ + 0, /* Advertise. */ + 30, /* Request. */ + 4, /* Confirm. */ + 600, /* Renew. */ + 600, /* Rebind. */ + 0, /* Reply. */ + 1, /* Release. */ + 1, /* Decline. */ + 0, /* Reconfigure. */ + 120, /* Information Request. */ + 0, /* Relay-forward. */ + 0 /* Relay-reply. */ +}; + +static inline int ipv6_addr_is_multicast(const struct in6_addr *addr) +{ + return (addr->s6_addr32[0] & htonl(0xFF000000)) == htonl(0xFF000000); +} + +static int +dhcpv6_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct iphdr *iph = (struct iphdr *)pktb_network_header(pkt); + struct ip6_hdr *ip6h = (struct ip6_hdr *)pktb_network_header(pkt); + int dir = CTINFO2DIR(ctinfo); + union nfct_attr_grp_addr addr; + struct nf_expect *exp; + uint8_t *dhcpv6_msg_type; + + if (iph->version != 6 || !ipv6_addr_is_multicast(&ip6h->ip6_dst)) + return NF_ACCEPT; + + dhcpv6_msg_type = pktb_network_header(pkt) + protoff + sizeof(struct udphdr); + if (*dhcpv6_msg_type > ARRAY_SIZE(dhcpv6_timeouts)) { + printf("Dropping DHCPv6 message with bad type %u\n", + *dhcpv6_msg_type); + return NF_DROP; + } + + exp = nfexp_new(); + if (exp == NULL) + return NF_ACCEPT; + + cthelper_get_addr_src(myct->ct, dir, &addr); + + if (cthelper_expect_init(exp, myct->ct, 0, NULL, &addr, + IPPROTO_UDP, NULL, &dhcpv6_port, + NF_CT_EXPECT_PERMANENT)) { + nfexp_destroy(exp); + return NF_DROP; + } + + myct->exp = exp; + + if (dhcpv6_timeouts[*dhcpv6_msg_type] > 0) { + nfct_set_attr_u32(myct->ct, ATTR_TIMEOUT, + dhcpv6_timeouts[*dhcpv6_msg_type]); + } + + return NF_ACCEPT; +} + +static struct ctd_helper dhcpv6_helper = { + .name = "dhcpv6", + .l4proto = IPPROTO_UDP, + .cb = dhcpv6_helper_cb, + .policy = { + [0] = { + .name = "dhcpv6", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) dhcpv6_init(void); + +void dhcpv6_init(void) +{ + dhcpv6_port = htons(DHCPV6_CLIENT_PORT); + helper_register(&dhcpv6_helper); +} -- cgit v1.2.3 From 92246dcc1fdcf222302a42926e0e95af2c30463e Mon Sep 17 00:00:00 2001 From: Ash Hughes Date: Sat, 8 Mar 2014 21:13:34 +0000 Subject: conntrackd: userspace SSDP helper Here is a patch which adds a userspace conntrack helper for the SSDP protocol. This is based on the code found at: http://marc.info/?t=132945775100001&r=1&w=2 I'm not sure how to get my laptop to play at IPv6, so I've not tested this part, but I've tested the IPv4 section and it works. Signed-off-by: Ash Hughes Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 8 +++ doc/manual/conntrack-tools.tmpl | 1 + src/helpers/Makefile.am | 7 ++- src/helpers/ssdp.c | 134 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/helpers/ssdp.c (limited to 'doc') diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 358ad10..d2d94a9 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -70,6 +70,14 @@ Helper { ExpectTimeout 300 } } + Type ssdp inet udp { + QueueNum 5 + QueueLen 10240 + Policy ssdp { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index f21a4ff..d23dec5 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -890,6 +890,7 @@ maintainance. Oracle*TNS, to support its special Redirect message. NFSv3, mind that version 4 does not require this helper. FTP (this helper is also available in kernel-space). +SSDP. The following steps describe how to enable the RPC portmapper helper for NFSv3 (this is similar for other helpers): diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index fe28e83..78ef7aa 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -6,7 +6,8 @@ pkglib_LTLIBRARIES = ct_helper_amanda.la \ ct_helper_rpc.la \ ct_helper_tftp.la \ ct_helper_tns.la \ - ct_helper_sane.la + ct_helper_sane.la \ + ct_helper_ssdp.la ct_helper_amanda_la_SOURCES = amanda.c ct_helper_amanda_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) @@ -35,3 +36,7 @@ ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) ct_helper_sane_la_SOURCES = sane.c ct_helper_sane_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_sane_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_ssdp_la_SOURCES = ssdp.c +ct_helper_ssdp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_ssdp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/ssdp.c b/src/helpers/ssdp.c new file mode 100644 index 0000000..2713f23 --- /dev/null +++ b/src/helpers/ssdp.c @@ -0,0 +1,134 @@ +/* + * SSDP connection tracking helper + * (SSDP = Simple Service Discovery Protocol) + * For documentation about SSDP see + * http://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol + * + * Copyright (C) 2014 Ashley Hughes + * Based on the SSDP conntrack helper (nf_conntrack_ssdp.c), + * :http://marc.info/?t=132945775100001&r=1&w=2 + * (C) 2012 Ian Pilcher + * + * 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 "helper.h" +#include "myct.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSDP_MCAST_ADDR "239.255.255.250" +#define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */ +#define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */ + +#define SSDP_M_SEARCH "M-SEARCH" +#define SSDP_M_SEARCH_SIZE (sizeof SSDP_M_SEARCH - 1) + +static int ssdp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + int ret = NF_ACCEPT; + union nfct_attr_grp_addr daddr, saddr, taddr; + struct iphdr *net_hdr = (struct iphdr *)pktb_network_header(pkt); + int good_packet = 0; + struct nf_expect *exp; + u_int16_t port; + unsigned int dataoff; + void *sb_ptr; + + cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr); + switch (nfct_get_attr_u8(myct->ct, ATTR_L3PROTO)) { + case AF_INET: + inet_pton(AF_INET, SSDP_MCAST_ADDR, &(taddr.ip)); + if (daddr.ip == taddr.ip) + good_packet = 1; + break; + case AF_INET6: + inet_pton(AF_INET6, UPNP_MCAST_LL_ADDR, &(taddr.ip6)); + if (daddr.ip6[0] == taddr.ip6[0] && + daddr.ip6[1] == taddr.ip6[1] && + daddr.ip6[2] == taddr.ip6[2] && + daddr.ip6[3] == taddr.ip6[3]) { + good_packet = 1; + break; + } + inet_pton(AF_INET6, UPNP_MCAST_SL_ADDR, &(taddr.ip6)); + if (daddr.ip6[0] == taddr.ip6[0] && + daddr.ip6[1] == taddr.ip6[1] && + daddr.ip6[2] == taddr.ip6[2] && + daddr.ip6[3] == taddr.ip6[3]) { + good_packet = 1; + break; + } + break; + default: + break; + } + + if (!good_packet) { + pr_debug("ssdp_help: destination address not multicast; ignoring\n"); + return NF_ACCEPT; + } + + /* No data? Ignore */ + dataoff = net_hdr->ihl*4 + sizeof(struct udphdr); + if (dataoff >= pktb_len(pkt)) { + pr_debug("ssdp_help: UDP payload too small for M-SEARCH; ignoring\n"); + return NF_ACCEPT; + } + + sb_ptr = pktb_network_header(pkt) + dataoff; + + if (memcmp(sb_ptr, SSDP_M_SEARCH, SSDP_M_SEARCH_SIZE) != 0) { + pr_debug("ssdp_help: UDP payload does not begin with 'M-SEARCH'; ignoring\n"); + return NF_ACCEPT; + } + + cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr); + cthelper_get_port_src(myct->ct, MYCT_DIR_ORIG, &port); + + exp = nfexp_new(); + if (exp == NULL) + return NF_DROP; + + if (cthelper_expect_init(exp, myct->ct, 0, NULL, &saddr, + IPPROTO_UDP, NULL, &port, + NF_CT_EXPECT_PERMANENT)) { + nfexp_destroy(exp); + return NF_DROP; + } + myct->exp = exp; + + return ret; +} + +static struct ctd_helper ssdp_helper = { + .name = "ssdp", + .l4proto = IPPROTO_UDP, + .priv_data_len = 0, + .cb = ssdp_helper_cb, + .policy = { + [0] = { + .name = "ssdp", + .expect_max = 1, + .expect_timeout = 5 * 60, + }, + }, +}; + +static void __attribute__ ((constructor)) ssdp_init(void) +{ + helper_register(&ssdp_helper); +} -- cgit v1.2.3 From a8f74d021676096eaa40af72e6d91787408fe44d Mon Sep 17 00:00:00 2001 From: Arturo Borrero Gonzalez Date: Thu, 20 Aug 2015 13:56:42 +0200 Subject: doc/debian.conntrackd.init.d: drop file This file is likely dead code. It's outdated. Also I think distributors should manage themselves to integrate daemons in their operating systems. Following this idea, this file doesn't belong here. Signed-off-by: Arturo Borrero Gonzalez Signed-off-by: Pablo Neira Ayuso --- doc/debian.conntrackd.init.d | 48 -------------------------------------------- 1 file changed, 48 deletions(-) delete mode 100644 doc/debian.conntrackd.init.d (limited to 'doc') diff --git a/doc/debian.conntrackd.init.d b/doc/debian.conntrackd.init.d deleted file mode 100644 index ba847dd..0000000 --- a/doc/debian.conntrackd.init.d +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh -# -# /etc/init.d/conntrackd -# -# Maximilian Wilhelm -# -- Mon, 06 Nov 2006 18:39:07 +0100 -# - -export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin - -NAME="conntrackd" -DAEMON=`command -v conntrackd` -CONFIG="/etc/conntrack/conntrackd.conf" -PIDFILE="/var/run/${NAME}.pid" - - -# Gracefully exit if there is no daemon (debian way of life) -if [ ! -x "${DAEMON}" ]; then - exit 0 -fi - -# Check for config file -if [ ! -f /etc/conntrackd/conntrackd.conf ]; then - echo "Error: There is no config file for $NAME" >&2 - exit 1; -fi - -case "$1" in - start) - echo -n "Starting $NAME: " - start-stop-daemon --start --quiet --make-pidfile --pidfile "/var/run/${NAME}.pid" --background --exec "${DAEMON}" && echo "done." || echo "FAILED!" - ;; - stop) - echo -n "Stopping $NAME:" - start-stop-daemon --stop --quiet --oknodo --pidfile "/var/run/${NAME}.pid" && echo "done." || echo "FAILED!" - ;; - - restart) - $0 start - $0 stop - ;; - - *) - echo "Usage: /etc/init.d/conntrackd {start|stop|restart}" - exit 1 -esac - -exit 0 -- cgit v1.2.3 From 882bb111285a3a4465995b4af03040a291145d7b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 21 Aug 2015 19:18:38 +0200 Subject: nfct: update syntax in documentation Since dd73ceecdbe8 ("nfct: Update syntax to specify command before subsystem") the command comes before the object type. Update documentation accordingly. Signed-off-by: Pablo Neira Ayuso --- README.nfct | 6 +++--- doc/helper/conntrackd.conf | 2 +- doc/manual/conntrack-tools.tmpl | 4 ++-- nfct.8 | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'doc') diff --git a/README.nfct b/README.nfct index 4d8e6cc..89dd328 100644 --- a/README.nfct +++ b/README.nfct @@ -9,11 +9,11 @@ more similar to `ip' and `nftables' tools (in the long run!). The `nfct' command line tool allows you to define custom timeout policies: -# nfct timeout add custom-tcp-policy1 inet tcp established 100 +# nfct add timeout custom-tcp-policy1 inet tcp established 100 You can also retrieve the existing timeout policies with: -# nfct timeout list +# nfct list timeout .tcp-policy = { .l3proto = 2, .l4proto = 6, @@ -39,7 +39,7 @@ Then, you can use the timeout policy with iptables: You can define policies for other protocols as well, eg: -# nfct timeout add custom-udp-policy1 inet udp unreplied 10 replied 20 +# nfct add timeout custom-udp-policy1 inet udp unreplied 10 replied 20 And attach them via iptables: diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index d2d94a9..5c07509 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -6,7 +6,7 @@ Helper { # Before this, you have to make sure you have registered the `ftp' # user-space helper stub via: # - # nfct helper add ftp inet tcp + # nfct add helper ftp inet tcp # Type ftp inet tcp { # diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index d23dec5..87a792e 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -899,8 +899,8 @@ maintainance. Register user-space helper: -nfct helper add rpc inet udp -nfct helper add rpc inet tcp +nfct add helper rpc inet udp +nfct add helper rpc inet tcp This registers the portmapper helper for both UDP and TCP (NFSv3 traffic goes both over TCP and UDP). diff --git a/nfct.8 b/nfct.8 index 863fe12..336d9cd 100644 --- a/nfct.8 +++ b/nfct.8 @@ -40,7 +40,7 @@ Displays the version information. Displays the help message. .SH EXAMPLE .TP -.B nfct timeout add test-tcp inet tcp established 100 close 10 close_wait 10 +.B nfct add timeout test-tcp inet tcp established 100 close 10 close_wait 10 .TP This creates a timeout policy for tcp using 100 seconds for the ESTABLISHED state, 10 seconds for CLOSE state and 10 seconds for the CLOSE_WAIT state. .TP -- cgit v1.2.3