diff options
-rw-r--r-- | Make_global.am | 7 | ||||
-rw-r--r-- | Makefile.am | 20 | ||||
-rw-r--r-- | README | 17 | ||||
-rw-r--r-- | configure.ac | 64 | ||||
-rwxr-xr-x | ct.c | 91 | ||||
-rwxr-xr-x | ct.h | 22 | ||||
-rw-r--r-- | expect.c | 200 | ||||
-rw-r--r-- | helpers.c | 76 | ||||
-rw-r--r-- | include/helper.h | 111 | ||||
-rw-r--r-- | include/linux_list.h | 727 | ||||
-rw-r--r-- | include/myct.h | 43 | ||||
-rwxr-xr-x | l3_ipv4.c | 86 | ||||
-rwxr-xr-x | l4_tcp.c | 88 | ||||
-rwxr-xr-x | l4_udp.c | 88 | ||||
-rwxr-xr-x | main.c | 220 | ||||
-rw-r--r-- | pcaps/nfsv3.pcap | bin | 0 -> 6824 bytes | |||
-rw-r--r-- | pcaps/oracle-tns-redirect.pcap | bin | 0 -> 1095 bytes | |||
-rwxr-xr-x | proto.c | 49 | ||||
-rwxr-xr-x | proto.h | 50 | ||||
-rw-r--r-- | run-test.sh | 11 | ||||
-rw-r--r-- | test.h | 13 |
21 files changed, 1983 insertions, 0 deletions
diff --git a/Make_global.am b/Make_global.am new file mode 100644 index 0000000..06785a1 --- /dev/null +++ b/Make_global.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include -I../../../include + +AM_CFLAGS = -std=gnu99 -W -Wall \ + -Wmissing-prototypes -Wwrite-strings -Wcast-qual -Wfloat-equal -Wshadow -Wpointer-arith -Wbad-function-cast -Wsign-compare -Waggregate-return -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wstrict-prototypes -Wundef \ + -Wno-unused-parameter \ + ${LIBNETFILTER_CONNTRACK_CFLAGS} \ + ${LIBNETFILTER_CTTIMEOUT_CFLAGS} diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..326e525 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ +include $(top_srcdir)/Make_global.am + +check_PROGRAMS = cthelper-test + +cthelper_test_SOURCES = proto.c \ + ct.c \ + l3_ipv4.c \ + l4_tcp.c \ + l4_udp.c \ + expect.c \ + helpers.c \ + main.c + +cthelper_test_LDFLAGS = -dynamic \ + -lpcap \ + -ldl \ + -lmnl \ + -lnetfilter_queue \ + -lnetfilter_conntrack \ + -export-dynamic @@ -0,0 +1,17 @@ +Author: Pablo Neira Ayuso <pablo@netfilter.org> +Date: 06/May/2013 + += conntrackd's helper tests = + +This tree contains tests for the user-space helper infrastructure that +conntrackd provides. + +You can compile it via: + + autoreconf -fi + ./configure + make check + +You can run the tests by invoking the run-test.sh script in this tree. + +-EOF- diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..8b3da5c --- /dev/null +++ b/configure.ac @@ -0,0 +1,64 @@ +AC_INIT(cthelper-test, 0.0.1, pablo@netfilter.org) +AC_CONFIG_AUX_DIR([build-aux]) + +AC_CANONICAL_HOST +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([-Wall foreign subdir-objects + tar-pax no-dist-gzip dist-bzip2 1.6]) + +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 +AC_PROG_INSTALL +AC_PROG_LN_S +AM_PROG_LEX +AC_PROG_YACC + +case "$host" in +*-*-linux*) ;; +*) AC_MSG_ERROR([Linux only, dude!]);; +esac + +PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_QUEUE], [libnetfilter_queue >= 1.0.0]) + +AC_CHECK_HEADERS(arpa/inet.h) +dnl check for inet_pton +AC_CHECK_FUNCS(inet_pton) +dnl Some systems have it, but not IPv6 +if test "$ac_cv_func_inet_pton" = "yes" ; then +AC_MSG_CHECKING(if inet_pton supports IPv6) +AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +int main() + { + struct in6_addr addr6; + if (inet_pton(AF_INET6, "::1", &addr6) < 1) + exit(1); + else + exit(0); + } + ]])],[ AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_INET_PTON_IPV6, 1, [Define to 1 if inet_pton supports IPv6.]) + ],[AC_MSG_RESULT(no)],[AC_MSG_RESULT(no)]) +fi + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT @@ -0,0 +1,91 @@ +#include <stdlib.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#include <linux/if_ether.h> + +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#include "proto.h" +#include "helper.h" +#include "myct.h" +#include "ct.h" + +static LIST_HEAD(ct_list); + +struct nf_ct_entry * +ct_alloc(const uint8_t *pkt, unsigned int l3hdr_len, + struct cthelper_proto_l2l3_helper *l3h, + struct cthelper_proto_l4_helper *l4h) +{ + struct nf_ct_entry *ct; + + ct = calloc(1, sizeof(struct nf_ct_entry)); + if (ct == NULL) + return NULL; + + ct->myct = calloc(1, sizeof(struct myct)); + if (ct->myct == NULL) { + free(ct); + return NULL; + } + ct->myct->ct = nfct_new(); + if (ct->myct->ct == NULL) { + free(ct->myct); + free(ct); + return NULL; + } + /* FIXME: use good private helper size */ + ct->myct->priv_data = calloc(1, 128); + if (ct->myct->priv_data == NULL) { + nfct_destroy(ct->myct->ct); + free(ct->myct); + free(ct); + return NULL; + } + + l3h->l3ct_build(pkt, ct->myct->ct); + l4h->l4ct_build(pkt + l3hdr_len, ct->myct->ct); + + return ct; +} + +struct nf_ct_entry * +ct_find(const uint8_t *pkt, unsigned int l3hdr_len, + struct cthelper_proto_l2l3_helper *l3h, + struct cthelper_proto_l4_helper *l4h, unsigned int *ctinfo) +{ + struct nf_ct_entry *cur; + + list_for_each_entry(cur, &ct_list, head) { + if (l3h->l3ct_cmp_orig(pkt, cur->myct->ct) && + l4h->l4ct_cmp_orig(pkt + l3hdr_len, cur->myct->ct)) { + *ctinfo = 0; + return cur; + } + if (l3h->l3ct_cmp_repl(pkt, cur->myct->ct) && + l4h->l4ct_cmp_repl(pkt + l3hdr_len, cur->myct->ct)) { + *ctinfo = IP_CT_IS_REPLY; + return cur; + } + } + return NULL; +} + +void ct_add(struct nf_ct_entry *ct) +{ + list_add(&ct->head, &ct_list); +} + +void ct_flush(void) +{ + struct nf_ct_entry *cur, *tmp; + + list_for_each_entry_safe(cur, tmp, &ct_list, head) { + list_del(&cur->head); + free(cur->myct->priv_data); + free(cur->myct->ct); + free(cur->myct); + free(cur); + } +} @@ -0,0 +1,22 @@ +#ifndef _CT_H_ +#define _CT_H_ + +#include "include/linux_list.h" +#include "include/myct.h" + +struct nf_ct_entry { + struct list_head head; + struct myct *myct; +}; + +struct cthelper_proto_l2l3_helper; +struct cthelper_proto_l4_helper; + +struct nf_ct_entry *ct_alloc(const uint8_t *pkt, unsigned int l3hdr_len, struct cthelper_proto_l2l3_helper *l3h, struct cthelper_proto_l4_helper *l4h); + +struct nf_ct_entry *ct_find(const uint8_t *pkt, unsigned int l3hdr_len, struct cthelper_proto_l2l3_helper *l3h, struct cthelper_proto_l4_helper *l4h, unsigned int *ctinfo); + +void ct_add(struct nf_ct_entry *ct); +void ct_flush(void); + +#endif diff --git a/expect.c b/expect.c new file mode 100644 index 0000000..7a1fe9a --- /dev/null +++ b/expect.c @@ -0,0 +1,200 @@ +/* + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com> + */ + +#include "include/helper.h" +#include "test.h" + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dlfcn.h> + +int +cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, + uint32_t class, + union nfct_attr_grp_addr *saddr, + union nfct_attr_grp_addr *daddr, + uint8_t l4proto, uint16_t *sport, uint16_t *dport, + uint32_t flags) +{ + struct nf_conntrack *expected, *mask; + + expected = nfct_new(); + if (!expected) + return -1; + + mask = nfct_new(); + if (!mask) + return -1; + + if (saddr) { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } else { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000); + break; + case AF_INET6: + for (i=0; i<4; i++) + memset(addr, 0x00000000, sizeof(uint32_t)); + + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, addr); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } + + if (sport) { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff); + break; + default: + break; + } + } else { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000); + break; + default: + break; + } + } + + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + uint32_t addr[4] = {}; + int i; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip); + nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr(mask, ATTR_IPV6_DST, addr); + break; + default: + break; + } + + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff); + break; + default: + break; + } + + nfexp_set_attr(exp, ATTR_EXP_MASTER, master); + nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected); + nfexp_set_attr(exp, ATTR_EXP_MASK, mask); + + nfct_destroy(expected); + nfct_destroy(mask); + + return 0; +} + +int cthelper_add_expect(struct nf_expect *exp) +{ + cthelper_test_stats.ct_expect_created++; + return 0; +} + +int cthelper_del_expect(struct nf_expect *exp) +{ + return 0; +} + +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/helpers.c b/helpers.c new file mode 100644 index 0000000..3e4e6c8 --- /dev/null +++ b/helpers.c @@ -0,0 +1,76 @@ +/* + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com> + */ + +#include "helper.h" + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dlfcn.h> + +static LIST_HEAD(helper_list); + +void helper_register(struct ctd_helper *helper) +{ + list_add(&helper->head, &helper_list); +} + +static struct ctd_helper * +__helper_find(const char *helper_name, uint8_t l4proto) +{ + struct ctd_helper *cur, *helper = NULL; + + list_for_each_entry(cur, &helper_list, head) { + if (strncmp(cur->name, helper_name, CTD_HELPER_NAME_LEN) != 0) + continue; + + if (cur->l4proto != l4proto) + continue; + + helper = cur; + break; + } + return helper; +} + +struct ctd_helper * +helper_find(const char *libdir_path, + const char *helper_name, uint8_t l4proto, int flag) +{ + char path[PATH_MAX]; + struct ctd_helper *helper; + struct stat sb; + + helper = __helper_find(helper_name, l4proto); + if (helper != NULL) + return helper; + + snprintf(path, sizeof(path), "%s/ct_helper_%s.so", + libdir_path, helper_name); + + if (stat(path, &sb) != 0) { + if (errno == ENOENT) + return NULL; + fprintf(stderr, "%s: %s\n", path, + strerror(errno)); + return NULL; + } + + if (dlopen(path, flag) == NULL) { + fprintf(stderr, "%s: %s\n", path, dlerror()); + return NULL; + } + + return __helper_find(helper_name, l4proto); +} diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..9d96fb7 --- /dev/null +++ b/include/helper.h @@ -0,0 +1,111 @@ +#ifndef _CTD_HELPER_H_ +#define _CTD_HELPER_H_ + +#include <stdint.h> +#include "linux_list.h" +#include "myct.h" + +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +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; + uint32_t queue_len; + 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) + +#if 0 +#define pr_debug(fmt, arg...) \ + printf(fmt, ##arg) +#else +#define pr_debug(fmt, arg...) \ + ({ if (0) printf(fmt, ##arg); 0; }) +#endif + +#endif diff --git a/include/linux_list.h b/include/linux_list.h new file mode 100644 index 0000000..de182a4 --- /dev/null +++ b/include/linux_list.h @@ -0,0 +1,727 @@ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#include <stddef.h> + +#undef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +/** + * container_of - cast a member of a structure out to the containing structure + * + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +/* + * Check at compile time that something is of a particular type. + * Always evaluates to 1 so you may use it easily in comparisons. + */ +#define typecheck(type,x) \ +({ type __dummy; \ + typeof(x) __dummy2; \ + (void)(&__dummy == &__dummy2); \ + 1; \ +}) + +#define prefetch(x) 1 + +/* empty define to make this work in userspace -HW */ +#ifndef smp_wmb +#define smp_wmb() +#endif + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add_rcu(struct list_head * new, + struct list_head * prev, struct list_head * next) +{ + new->next = next; + new->prev = prev; + smp_wmb(); + next->prev = new; + prev->next = new; +} + +/** + * list_add_rcu - add a new entry to rcu-protected list + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as list_add_rcu() + * or list_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * list_for_each_entry_rcu(). + */ +static inline void list_add_rcu(struct list_head *new, struct list_head *head) +{ + __list_add_rcu(new, head, head->next); +} + +/** + * list_add_tail_rcu - add a new entry to rcu-protected list + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as list_add_tail_rcu() + * or list_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * list_for_each_entry_rcu(). + */ +static inline void list_add_tail_rcu(struct list_head *new, + struct list_head *head) +{ + __list_add_rcu(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_del_rcu - deletes entry from list without re-initialization + * @entry: the element to delete from the list. + * + * Note: list_empty on entry does not return true after this, + * the entry is in an undefined state. It is useful for RCU based + * lockfree traversal. + * + * In particular, it means that we can not poison the forward + * pointers that may still be used for walking the list. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as list_del_rcu() + * or list_add_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * list_for_each_entry_rcu(). + * + * Note that the caller is not permitted to immediately free + * the newly deleted entry. Instead, either synchronize_kernel() + * or call_rcu() must be used to defer freeing until an RCU + * grace period has elapsed. + */ +static inline void list_del_rcu(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->prev = LIST_POISON2; +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is + * empty _and_ checks that no other CPU might be + * in the process of still modifying either member + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + * + * @head: the list to test. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next, prefetch(pos->next); pos != (head); \ + pos = pos->next, prefetch(pos->next)) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code, no prefetching is done. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \ + pos = pos->prev, prefetch(pos->prev)) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + prefetch(pos->member.next); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member), \ + prefetch(pos->member.next)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + prefetch(pos->member.prev); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member), \ + prefetch(pos->member.prev)) + +/** + * list_prepare_entry - prepare a pos entry for use as a start point in + * list_for_each_entry_continue + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - iterate over list of given type + * continuing after existing point + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + prefetch(pos->member.next); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member), \ + prefetch(pos->member.next)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_rcu - iterate over an rcu-protected list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_rcu(pos, head) \ + for (pos = (head)->next, prefetch(pos->next); pos != (head); \ + pos = pos->next, ({ smp_read_barrier_depends(); 0;}), prefetch(pos->next)) + +#define __list_for_each_rcu(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next, ({ smp_read_barrier_depends(); 0;})) + +/** + * list_for_each_safe_rcu - iterate over an rcu-protected list safe + * against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_safe_rcu(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, ({ smp_read_barrier_depends(); 0;}), n = pos->next) + +/** + * list_for_each_entry_rcu - iterate over rcu list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_entry_rcu(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + prefetch(pos->member.next); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member), \ + ({ smp_read_barrier_depends(); 0;}), \ + prefetch(pos->member.next)) + + +/** + * list_for_each_continue_rcu - iterate over an rcu-protected list + * continuing after existing point. + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as list_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define list_for_each_continue_rcu(pos, head) \ + for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \ + (pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next)) + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL) + +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + n->next = LIST_POISON1; + n->pprev = LIST_POISON2; +} + +/** + * hlist_del_rcu - deletes entry from hash list without re-initialization + * @n: the element to delete from the hash list. + * + * Note: list_unhashed() on entry does not return true after this, + * the entry is in an undefined state. It is useful for RCU based + * lockfree traversal. + * + * In particular, it means that we can not poison the forward + * pointers that may still be used for walking the hash list. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as hlist_add_head_rcu() + * or hlist_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * hlist_for_each_entry(). + */ +static inline void hlist_del_rcu(struct hlist_node *n) +{ + __hlist_del(n); + n->pprev = LIST_POISON2; +} + +static inline void hlist_del_init(struct hlist_node *n) +{ + if (n->pprev) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + +#define hlist_del_rcu_init hlist_del_init + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + + +/** + * hlist_add_head_rcu - adds the specified element to the specified hlist, + * while permitting racing traversals. + * @n: the element to add to the hash list. + * @h: the list to add to. + * + * The caller must take whatever precautions are necessary + * (such as holding appropriate locks) to avoid racing + * with another list-mutation primitive, such as hlist_add_head_rcu() + * or hlist_del_rcu(), running on this same list. + * However, it is perfectly legal to run concurrently with + * the _rcu list-traversal primitives, such as + * hlist_for_each_entry(), but only if smp_read_barrier_depends() + * is used to prevent memory-consistency problems on Alpha CPUs. + * Regardless of the type of CPU, the list-traversal primitive + * must be guarded by rcu_read_lock(). + * + * OK, so why don't we have an hlist_for_each_entry_rcu()??? + */ +static inline void hlist_add_head_rcu(struct hlist_node *n, + struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + n->pprev = &h->first; + smp_wmb(); + if (first) + first->pprev = &n->next; + h->first = n; +} + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node *n, + struct hlist_node *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from existing point + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +/** + * hlist_for_each_entry_rcu - iterate over rcu list of given type + * @pos: the type * to use as a loop counter. + * @pos: the &struct hlist_node to use as a loop counter. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + * + * This list-traversal primitive may safely run concurrently with + * the _rcu list-mutation primitives such as hlist_add_rcu() + * as long as the traversal is guarded by rcu_read_lock(). + */ +#define hlist_for_each_entry_rcu(tpos, pos, head, member) \ + for (pos = (head)->first; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next, ({ smp_read_barrier_depends(); 0; }) ) + +#endif 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 <libnetfilter_conntrack/libnetfilter_conntrack.h> + +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/l3_ipv4.c b/l3_ipv4.c new file mode 100755 index 0000000..8edfd2e --- /dev/null +++ b/l3_ipv4.c @@ -0,0 +1,86 @@ +#include <stdlib.h> +#include <netinet/ip.h> +#include <linux/if_ether.h> + +#include "proto.h" + +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#define PRINT_CMP(...) + +static void +l3_ipv4_ct_build_tuple(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + nfct_set_attr_u16(ct, ATTR_ORIG_L3PROTO, AF_INET); + nfct_set_attr_u16(ct, ATTR_REPL_L3PROTO, AF_INET); + nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_SRC, iph->saddr); + nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_DST, iph->daddr); + nfct_set_attr_u32(ct, ATTR_REPL_IPV4_SRC, iph->daddr); + nfct_set_attr_u32(ct, ATTR_REPL_IPV4_DST, iph->saddr); +} + +static int +l3_ipv4_ct_cmp_tuple_orig(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + PRINT_CMP("cmp_orig iph->saddr: %x == %x\n", + iph->saddr, nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC)); + PRINT_CMP("cmp_orig iph->daddr: %x == %x\n", + iph->daddr, nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_DST)); + + if (iph->saddr == nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC) && + iph->daddr == nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_DST)) + return 1; + + return 0; +} + +static int +l3_ipv4_ct_cmp_tuple_repl(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + PRINT_CMP("cmp_repl iph->saddr: %x == %x\n", + iph->saddr, nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC)); + PRINT_CMP("cmp_repl iph->daddr: %x == %x\n", + iph->daddr, nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)); + + if (iph->saddr == nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC) && + iph->daddr == nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)) + return 1; + + return 0; +} + +static int l3_ipv4_pkt_l4proto_num(const uint8_t *pkt) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + return iph->protocol; +} + +static int l3_ipv4_pkt_l3hdr_len(const uint8_t *pkt) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + return iph->ihl << 2; +} + +static struct cthelper_proto_l2l3_helper ipv4 = { + .l2protonum = ETH_P_IP, + .l3protonum = AF_INET, + .l2hdr_len = ETH_HLEN, + .l3ct_build = l3_ipv4_ct_build_tuple, + .l3ct_cmp_orig = l3_ipv4_ct_cmp_tuple_orig, + .l3ct_cmp_repl = l3_ipv4_ct_cmp_tuple_repl, + .l3pkt_hdr_len = l3_ipv4_pkt_l3hdr_len, + .l4pkt_proto = l3_ipv4_pkt_l4proto_num, +}; + +void l2l3_ipv4_init(void) +{ + cthelper_proto_l2l3_helper_register(&ipv4); +} diff --git a/l4_tcp.c b/l4_tcp.c new file mode 100755 index 0000000..f27c85d --- /dev/null +++ b/l4_tcp.c @@ -0,0 +1,88 @@ +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#include "proto.h" + +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#define PRINT_CMP(...) + +static void l4_tcp_ct_build_tuple(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + + nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, tcph->source); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, tcph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, tcph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, tcph->source); +} + +static int l4_tcp_ct_cmp_tuple_orig(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + + PRINT_CMP("cmp_orig tcph->source: %u == %u\n", + tcph->source, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_orig tcph->dest: %u == %u\n", + tcph->dest, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (tcph->source == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) && + tcph->dest == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_tcp_ct_cmp_tuple_repl(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + + PRINT_CMP("cmp_repl tcph->source: %u == %u\n", + tcph->source, nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC)); + PRINT_CMP("cmp_repl tcph->dest: %u == %u\n", + tcph->dest, nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)); + + if (tcph->source == nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC) && + tcph->dest == nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_tcp_ct_cmp_port(struct nf_conntrack *ct, uint16_t port) +{ + PRINT_CMP("cmp_port src: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_port dst: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) || + port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int l4_tcp_pkt_no_data(const uint8_t *pkt) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + return tcph->syn || tcph->fin || tcph->rst || !tcph->psh; +} + +static struct cthelper_proto_l4_helper tcp = { + .l4protonum = IPPROTO_TCP, + .l4ct_build = l4_tcp_ct_build_tuple, + .l4ct_cmp_orig = l4_tcp_ct_cmp_tuple_orig, + .l4ct_cmp_repl = l4_tcp_ct_cmp_tuple_repl, + .l4ct_cmp_port = l4_tcp_ct_cmp_port, + .l4pkt_no_data = l4_tcp_pkt_no_data, +}; + +void l4_tcp_init(void) +{ + cthelper_proto_l4_helper_register(&tcp); +} diff --git a/l4_udp.c b/l4_udp.c new file mode 100755 index 0000000..4d52d0a --- /dev/null +++ b/l4_udp.c @@ -0,0 +1,88 @@ +#include <netinet/ip.h> +#include <netinet/udp.h> + +#include "proto.h" + +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#define PRINT_CMP(...) + +static void l4_udp_ct_build_tuple(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct udphdr *udph = (const struct udphdr *)pkt; + + nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_UDP); + nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_UDP); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, udph->source); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, udph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, udph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, udph->source); +} + +static int l4_udp_ct_cmp_tuple_orig(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct udphdr *udph = (const struct udphdr *)pkt; + + PRINT_CMP("cmp_orig udph->source: %u == %u\n", + udph->source, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_orig udph->dest: %u == %u\n", + udph->dest, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (udph->source == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) && + udph->dest == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_udp_ct_cmp_tuple_repl(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct udphdr *udph = (const struct udphdr *)pkt; + + PRINT_CMP("cmp_repl udph->source: %u == %u\n", + udph->source, nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC)); + PRINT_CMP("cmp_repl udph->dest: %u == %u\n", + udph->dest, nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)); + + if (udph->source == nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC) && + udph->dest == nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_udp_ct_cmp_port(struct nf_conntrack *ct, uint16_t port) +{ + PRINT_CMP("cmp_port src: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_port dst: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) || + port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int l4_udp_pkt_no_data(const uint8_t *pkt) +{ + /* UDP has no control packets. */ + return 1; +} + +static struct cthelper_proto_l4_helper tcp = { + .l4protonum = IPPROTO_UDP, + .l4ct_build = l4_udp_ct_build_tuple, + .l4ct_cmp_orig = l4_udp_ct_cmp_tuple_orig, + .l4ct_cmp_repl = l4_udp_ct_cmp_tuple_repl, + .l4ct_cmp_port = l4_udp_ct_cmp_port, + .l4pkt_no_data = l4_udp_pkt_no_data, +}; + +void l4_udp_init(void) +{ + cthelper_proto_l4_helper_register(&tcp); +} @@ -0,0 +1,220 @@ +#include <stdio.h> +#include <pcap.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +#include <string.h> +#include <dlfcn.h> + +#include "ct.h" +#include "proto.h" +#include "include/helper.h" +#include "test.h" + +#include <libnetfilter_queue/pktbuff.h> + +struct cthelper_test_stats cthelper_test_stats; + +enum { + TEST_NORMAL = 0, + TEST_DNAT, +}; + +static int +cthelper_process_packet(const uint8_t *pkt, uint32_t pktlen, + struct ctd_helper *h, int proto, uint16_t port, + int type) +{ + struct pkt_buff *pktb; + struct cthelper_proto_l2l3_helper *l3h; + struct cthelper_proto_l4_helper *l4h; + unsigned int l3hdr_len, l4protonum; + struct nf_ct_entry *ct; + int ret, this_proto; + uint32_t dataoff, ctinfo = 0; + + l3h = cthelper_proto_l2l3_helper_find(pkt, &l4protonum, &l3hdr_len); + if (l3h == NULL) { + fprintf(stderr, "Unsupported layer 3 protocol, skipping.\n"); + return -1; + } + + l4h = cthelper_proto_l4_helper_find(pkt, l4protonum); + if (l4h == NULL) { + fprintf(stderr, "Unsupported layer 4 protocol, skipping.\n"); + return -1; + } + /* get layer 3 header. */ + pkt += l3h->l2hdr_len; + pktlen -= l3h->l2hdr_len; + + /* skip packet with mismatching protocol */ + this_proto = l3h->l4pkt_proto(pkt); + if (this_proto != proto) { + cthelper_test_stats.pkt_mismatch_proto++; + return 0; + } + + /* Look for the fake conntrack. */ + ct = ct_find(pkt, l3hdr_len, l3h, l4h, &ctinfo); + if (ct == NULL) { + /* It doesn't exist any, create one. */ + ct = ct_alloc(pkt, l3hdr_len, l3h, l4h); + if (ct == NULL) { + fprintf(stderr, "Not enough memory\n"); + return -1; + } + ct_add(ct); + ctinfo += IP_CT_NEW; + } else + ctinfo += IP_CT_ESTABLISHED; + + /* skip packets with mismatching ports */ + if (!l4h->l4ct_cmp_port(ct->myct->ct, ntohs(port))) { + cthelper_test_stats.pkt_mismatch_port++; + return -1; + } + + /* + * FIXME: reminder, implement this below in the kernel for cthelper. + */ + + /* This packet contains no data, skip it. */ +/* if (l4h->l4pkt_no_data && l4h->l4pkt_no_data(pkt + l3hdr_len)) { + NFG_DEBUG("skipping packet with no data\n"); + continue; + } */ + + /* Create the fake network buffer. */ + pktb = pktb_alloc(AF_INET, pkt, pktlen, 128); + if (pktb == NULL) { + fprintf(stderr, "Not enough memory\n"); + return -1; + } + + dataoff = l3h->l3pkt_hdr_len(pkt); + if (dataoff > pktb_len(pktb)) { + fprintf(stderr, "wrong layer 3 offset: %d > %d\n", + dataoff, pktb_len(pktb)); + return -1; + } + + /* tweak to run DNAT mangling code using the same PCAP file. */ + if (type == TEST_DNAT) { + struct nf_conntrack *tmp = ct->myct->ct; + /* as long as this is tested, who cares the destination IP? */ + in_addr_t addr = inet_addr("1.1.1.1"); + + /* clone the real conntrack, to add DNAT information */ + ct->myct->ct = nfct_clone(ct->myct->ct); + /* set fake DNAT information */ + nfct_set_attr_u32(ct->myct->ct, ATTR_STATUS, IPS_DST_NAT); + nfct_set_attr_u32(ct->myct->ct, ATTR_ORIG_IPV4_DST, addr); + /* pass it to helper */ + ret = h->cb(pktb, dataoff, ct->myct, ctinfo); + /* restore real conntrack */ + nfct_destroy(ct->myct->ct); + ct->myct->ct = tmp; + + if (pktb_mangled(pktb)) { + uint32_t i; + uint8_t *data = pktb_network_header(pktb); + + printf("\e[1;31mmangled content: "); + + for (i=0; i < pktb_len(pktb); i++) + printf("%c", data[i]); + + printf("\e[0m\n"); + } + } else + ret = h->cb(pktb, dataoff, ct->myct, ctinfo); + + pktb_free(pktb); + + return ret; +} + +static int +cthelper_test(const char *pcapfile, const char *helper_name, + int l4proto, uint16_t port, int type) +{ + struct pcap_pkthdr pcaph; + char errbuf[PCAP_ERRBUF_SIZE]; + const u_char *pkt; + pcap_t *handle; + struct ctd_helper *h; + + h = helper_find("/usr/lib/conntrack-tools", + helper_name, l4proto, RTLD_NOW); + if (h == NULL) { + fprintf(stderr, "couldn't find helper: %s\n", helper_name); + return -1; + } + + handle = pcap_open_offline(pcapfile, errbuf); + if (handle == NULL) { + fprintf(stderr, "couldn't open pcap file %s: %s\n", + pcapfile, errbuf); + return -1; + } + while ((pkt = pcap_next(handle, &pcaph)) != NULL) { + cthelper_test_stats.pkts++; + cthelper_process_packet(pkt, pcaph.caplen, h, l4proto, port, + type); + } + + ct_flush(); + pcap_close(handle); + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret, l4proto, type = TEST_NORMAL; + + if (argc < 5 || argc > 6) { + fprintf(stderr, "Wrong usage:\n"); + fprintf(stderr, "%s <pcap_file> <helper-name> <proto> " + "<port> [dnat]\n", + argv[0]); + fprintf(stderr, "example: %s file.pcap ftp tcp 21\n", argv[0]); + exit(EXIT_FAILURE); + } + if (strncmp("tcp", argv[3], strlen("tcp")) == 0) + l4proto = IPPROTO_TCP; + else if (strncmp("udp", argv[3], strlen("udp")) == 0) + l4proto = IPPROTO_UDP; + else { + fprintf(stderr, "%s not supported, send a patch to Pablo\n", + argv[3]); + exit(EXIT_FAILURE); + } + if (argc == 6) { + if (strncmp("dnat", argv[5], strlen("dnat")) == 0) { + type = TEST_DNAT; + printf("test dnat\n"); + } + } + + /* Initialization of supported layer 3 and 4 protocols here. */ + l2l3_ipv4_init(); + l4_tcp_init(); + l4_udp_init(); + + if (cthelper_test(argv[1], argv[2], l4proto, atoi(argv[4]), type) < 0) + ret = EXIT_FAILURE; + else + ret = EXIT_SUCCESS; + + printf("\e[1;34mTest results: expect_created=%d packets=%d " + "packets_skipped=%d\e[0m\n", + cthelper_test_stats.ct_expect_created, + cthelper_test_stats.pkts, + cthelper_test_stats.pkt_mismatch_proto + + cthelper_test_stats.pkt_mismatch_port); + + return ret; +} diff --git a/pcaps/nfsv3.pcap b/pcaps/nfsv3.pcap Binary files differnew file mode 100644 index 0000000..04606bd --- /dev/null +++ b/pcaps/nfsv3.pcap diff --git a/pcaps/oracle-tns-redirect.pcap b/pcaps/oracle-tns-redirect.pcap Binary files differnew file mode 100644 index 0000000..32f8952 --- /dev/null +++ b/pcaps/oracle-tns-redirect.pcap @@ -0,0 +1,49 @@ +#include <stdlib.h> +#include <netinet/in.h> +#include <linux/if_ether.h> + +#include "linux_list.h" +#include "proto.h" + +static LIST_HEAD(l2l3_helper_list); +static LIST_HEAD(l4_helper_list); + +struct cthelper_proto_l2l3_helper * +cthelper_proto_l2l3_helper_find(const uint8_t *pkt, + unsigned int *l4protonum, + unsigned int *l3hdr_len) +{ + const struct ethhdr *eh = (const struct ethhdr *)pkt; + struct cthelper_proto_l2l3_helper *cur; + + list_for_each_entry(cur, &l2l3_helper_list, head) { + if (ntohs(cur->l2protonum) == eh->h_proto) { + *l4protonum = cur->l4pkt_proto(pkt + ETH_HLEN); + *l3hdr_len = cur->l3pkt_hdr_len(pkt + ETH_HLEN); + return cur; + } + } + return NULL; +} + +void cthelper_proto_l2l3_helper_register(struct cthelper_proto_l2l3_helper *h) +{ + list_add(&h->head, &l2l3_helper_list); +} + +struct cthelper_proto_l4_helper * +cthelper_proto_l4_helper_find(const uint8_t *pkt, unsigned int l4protocol) +{ + struct cthelper_proto_l4_helper *cur; + + list_for_each_entry(cur, &l4_helper_list, head) { + if (cur->l4protonum == l4protocol) + return cur; + } + return NULL; +} + +void cthelper_proto_l4_helper_register(struct cthelper_proto_l4_helper *h) +{ + list_add(&h->head, &l4_helper_list); +} @@ -0,0 +1,50 @@ +#ifndef _HELPER_H_ +#define _HELPER_H_ + +#include <stdint.h> + +#include "include/linux_list.h" + +struct nf_conntrack; + +struct cthelper_proto_l4_helper { + struct list_head head; + + unsigned int l4protonum; + + void (*l4ct_build)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l4ct_cmp_orig)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l4ct_cmp_repl)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l4ct_cmp_port)(struct nf_conntrack *ct, uint16_t port); + + int (*l4pkt_no_data)(const uint8_t *pkt); +}; + +struct cthelper_proto_l2l3_helper { + struct list_head head; + + unsigned int l2protonum; + unsigned int l2hdr_len; + + unsigned int l3protonum; + + void (*l3ct_build)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l3ct_cmp_orig)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l3ct_cmp_repl)(const uint8_t *pkt, struct nf_conntrack *ct); + + int (*l3pkt_hdr_len)(const uint8_t *pkt); + int (*l4pkt_proto)(const uint8_t *pkt); +}; + +struct cthelper_proto_l2l3_helper *cthelper_proto_l2l3_helper_find(const uint8_t *pkt, unsigned int *l4protonum, unsigned int *l3hdr_len); +void cthelper_proto_l2l3_helper_register(struct cthelper_proto_l2l3_helper *h); + +struct cthelper_proto_l4_helper *cthelper_proto_l4_helper_find(const uint8_t *pkt, unsigned int l4protonum); +void cthelper_proto_l4_helper_register(struct cthelper_proto_l4_helper *h); + +/* Initialization of supported protocols here. */ +void l2l3_ipv4_init(void); +void l4_tcp_init(void); +void l4_udp_init(void); + +#endif diff --git a/run-test.sh b/run-test.sh new file mode 100644 index 0000000..fe31602 --- /dev/null +++ b/run-test.sh @@ -0,0 +1,11 @@ +echo "Running test for oracle TNS port 1521" +./cthelper-test pcaps/oracle-tns-redirect.pcap tns tcp 1521 + +echo "Running test for oracle TNS port 1521" +./cthelper-test pcaps/oracle-tns-redirect.pcap tns tcp 1521 dnat + +echo "Running test for NFSv3 UDP port 111" +./cthelper-test pcaps/nfsv3.pcap rpc udp 111 + +echo "Running test for NFSv3 TCP port 111" +./cthelper-test pcaps/nfsv3.pcap rpc tcp 111 @@ -0,0 +1,13 @@ +#ifndef _CTHELPER_TEST_H_ +#define _CTHELPER_TEST_H_ + +struct cthelper_test_stats { + int pkts; + int pkt_mismatch_proto; + int pkt_mismatch_port; + int ct_expect_created; +}; + +extern struct cthelper_test_stats cthelper_test_stats; + +#endif |