]> git.ipfire.org Git - thirdparty/xtables-addons.git/commitdiff
pknock: import pknock trunk@463
authorJan Engelhardt <jengelh@medozas.de>
Tue, 29 Sep 2009 12:00:59 +0000 (14:00 +0200)
committerJan Engelhardt <jengelh@medozas.de>
Tue, 29 Sep 2009 12:00:59 +0000 (14:00 +0200)
extensions/Kbuild
extensions/Mbuild
extensions/libxt_pknock.c [new file with mode: 0644]
extensions/xt_pknock.c [new file with mode: 0644]
extensions/xt_pknock.h [new file with mode: 0644]
mconfig

index ab3dfdd10a51894833c8983e171478ee7e9db426..b86a1dc6f9ac79bce2185d58048f29320c0684d6 100644 (file)
@@ -26,6 +26,7 @@ obj-${build_ipset}       += ipset/
 obj-${build_ipv4options} += xt_ipv4options.o
 obj-${build_length2}     += xt_length2.o
 obj-${build_lscan}       += xt_lscan.o
+obj-${build_pknock}      += xt_pknock.o
 obj-${build_psd}         += xt_psd.o
 obj-${build_quota2}      += xt_quota2.o
 
index 65ae18f6ef7fc5056fff7d4de32d78dbac196e5e..bc0e1d0cea5a1a8bc6bee1e989cb4f25acc19516 100644 (file)
@@ -21,5 +21,6 @@ obj-${build_ipset}       += ipset/
 obj-${build_ipv4options} += libxt_ipv4options.so
 obj-${build_length2}     += libxt_length2.so
 obj-${build_lscan}       += libxt_lscan.so
+obj-${build_pknock}      += libxt_pknock.so
 obj-${build_psd}         += libxt_psd.so
 obj-${build_quota2}      += libxt_quota2.so
diff --git a/extensions/libxt_pknock.c b/extensions/libxt_pknock.c
new file mode 100644 (file)
index 0000000..581e5bb
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * Shared library add-on to iptables to add Port Knocking and SPA matching
+ * support.
+ *
+ * (C) 2006-2009 J. Federico Hernandez <fede.hernandez@gmail.com>
+ * (C) 2006 Luis Floreani <luis.floreani@gmail.com>
+ *
+ * $Id$
+ *
+ * This program is released under the terms of GNU GPL version 2.
+ */
+#include <getopt.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <xtables.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+//#include <linux/netfilter_ipv4/ipt_pknock.h>
+#include "xt_pknock.h"
+
+static const struct option pknock_opts[] = {
+       /* .name, .has_arg, .flag, .val */
+       { "knockports", 1,      0, 'k' },
+       { "t",                  1,      0, 't' },
+       { "time",               1,      0, 't' },
+       { "name",               1,      0, 'n' },
+       { "opensecret", 1,      0, 'a' },
+       { "closesecret",1,      0, 'z' },
+       { "strict",             0,      0, 'x' },
+       { "checkip",    0,      0, 'c' },
+       { "chkip",              0,      0, 'c' },
+       { .name = NULL }
+};
+
+/* Function which prints out usage message. */
+static void pknock_help(void)
+{
+       printf("pknock match options:\n"
+               " --knockports port[,port,port,...]     "
+                       "Matches destination port(s).\n"
+               " --time seconds\n"
+               " --t ...                               "
+                       "Time between port match.\n"
+               " --secure                              "
+                       "hmac must be in the packets.\n"
+               " --strict                              "
+                       "Knocks sequence must be exact.\n"
+               " --name rule_name                      "
+                       "Rule name.\n"
+               " --checkip                             "
+                       "Matches if the source ip is in the list.\n"
+               " --chkip\n");
+}
+
+static unsigned int
+parse_ports(const char *portstring, uint16_t *ports, const char *proto)
+{
+       char *buffer, *cp, *next;
+       unsigned int i;
+
+       buffer = strdup(portstring);
+       if (!buffer) xtables_error(OTHER_PROBLEM, "strdup failed");
+
+       for (cp=buffer, i=0; cp && i<IPT_PKNOCK_MAX_PORTS; cp=next, i++)
+       {
+               next=strchr(cp, ',');
+               if (next) *next++='\0';
+               ports[i] = xtables_parse_port(cp, proto);
+       }
+       
+       if (cp) xtables_error(PARAMETER_PROBLEM, "too many ports specified");
+
+       free(buffer);
+       return i;
+}
+
+static char *
+proto_to_name(uint8_t proto)
+{
+       switch (proto) {
+       case IPPROTO_TCP:
+               return "tcp";
+       case IPPROTO_UDP:
+               return "udp";
+       default:
+               return NULL;
+       }
+}
+
+static const char *
+check_proto(uint16_t pnum, uint8_t invflags)
+{
+       char *proto;
+
+       if (invflags & XT_INV_PROTO)
+               xtables_error(PARAMETER_PROBLEM, PKNOCK "only works with TCP and UDP.");
+
+       if ((proto = proto_to_name(pnum)) != NULL)
+               return proto;
+       else if (!pnum)
+               xtables_error(PARAMETER_PROBLEM, PKNOCK "needs `-p tcp' or `-p udp'");
+       else
+               xtables_error(PARAMETER_PROBLEM, PKNOCK "only works with TCP and UDP.");
+}
+
+/* Function which parses command options; returns true if it ate an option */
+static int 
+__pknock_parse(int c, char **argv, int invert, unsigned int *flags,
+               struct xt_entry_match **match, uint16_t pnum,
+               uint16_t invflags)
+{
+       const char *proto;
+       struct ipt_pknock *info = (struct ipt_pknock *) (*match)->data;
+
+       switch (c) {
+       case 'k': /* --knockports */
+               if (*flags & IPT_PKNOCK_KNOCKPORT)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --knockports twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+               proto = check_proto(pnum, invflags);
+
+               info->ports_count = parse_ports(optarg, info->port, proto);
+               info->option |= IPT_PKNOCK_KNOCKPORT;
+               *flags |= IPT_PKNOCK_KNOCKPORT;
+#if DEBUG
+               printf("ports_count: %d\n", info->ports_count);
+#endif
+               break;
+
+       case 't': /* --time */
+               if (*flags & IPT_PKNOCK_TIME)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --time twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+
+               info->max_time = atoi(optarg);
+               info->option |= IPT_PKNOCK_TIME;
+               *flags |= IPT_PKNOCK_TIME;
+               break;
+
+       case 'n': /* --name */
+               if (*flags & IPT_PKNOCK_NAME)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --name twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+
+               memset(info->rule_name, 0, IPT_PKNOCK_MAX_BUF_LEN + 1);
+               strncpy(info->rule_name, optarg, IPT_PKNOCK_MAX_BUF_LEN);
+
+               info->rule_name_len = strlen(info->rule_name);
+               info->option |= IPT_PKNOCK_NAME;
+               *flags |= IPT_PKNOCK_NAME;
+#if DEBUG
+               printf("info->rule_name: %s\n", info->rule_name);
+#endif
+               break;
+
+       case 'a': /* --opensecret */
+               if (*flags & IPT_PKNOCK_OPENSECRET)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --opensecret twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+
+               memset(info->open_secret, 0, IPT_PKNOCK_MAX_PASSWD_LEN + 1);
+               strncpy(info->open_secret, optarg, IPT_PKNOCK_MAX_PASSWD_LEN);
+
+               info->open_secret_len = strlen(info->open_secret);
+               info->option |= IPT_PKNOCK_OPENSECRET;
+               *flags |= IPT_PKNOCK_OPENSECRET;
+               break;
+
+       case 'z': /* --closesecret */
+               if (*flags & IPT_PKNOCK_CLOSESECRET)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --closesecret twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+
+               memset(info->close_secret, 0, IPT_PKNOCK_MAX_PASSWD_LEN + 1);
+               strncpy(info->close_secret, optarg, IPT_PKNOCK_MAX_PASSWD_LEN);
+
+               info->close_secret_len = strlen(info->close_secret);
+               info->option |= IPT_PKNOCK_CLOSESECRET;
+               *flags |= IPT_PKNOCK_CLOSESECRET;
+               break;
+
+       case 'c': /* --checkip */
+               if (*flags & IPT_PKNOCK_CHECKIP)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --checkip twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+
+               info->option |= IPT_PKNOCK_CHECKIP;
+               *flags |= IPT_PKNOCK_CHECKIP;
+               break;
+
+       case 'x': /* --strict */
+               if (*flags & IPT_PKNOCK_STRICT)
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot use --strict twice.\n");
+
+               xtables_check_inverse(argv[optind-1], &invert, &optind, 0);
+
+               info->option |= IPT_PKNOCK_STRICT;
+               *flags |= IPT_PKNOCK_STRICT;
+               break;
+
+       default:
+               return 0;
+       }
+
+       if (invert)
+               xtables_error(PARAMETER_PROBLEM, PKNOCK "does not support invert.");
+
+       return 1;
+}
+
+static int pknock_parse(int c, char **argv, int invert, unsigned int *flags,
+                               const void *e, struct xt_entry_match **match)
+{
+       const struct ipt_entry *entry = e;
+       return __pknock_parse(c, argv, invert, flags, match, 
+                       entry->ip.proto, entry->ip.invflags);
+}
+
+/* Final check. */
+static void pknock_check(unsigned int flags)
+{
+       if (!flags)
+               xtables_error(PARAMETER_PROBLEM, PKNOCK "expection an option.\n");
+
+       if (!(flags & IPT_PKNOCK_NAME))
+               xtables_error(PARAMETER_PROBLEM, PKNOCK
+                       "--name option is required.\n");
+
+       if (flags & IPT_PKNOCK_KNOCKPORT) {
+               if (flags & IPT_PKNOCK_CHECKIP) {
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot specify --knockports with --checkip.\n");
+               }
+               if ((flags & IPT_PKNOCK_OPENSECRET)
+                       && !(flags & IPT_PKNOCK_CLOSESECRET))
+               {
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "--opensecret must go with --closesecret.\n");
+               }
+               if ((flags & IPT_PKNOCK_CLOSESECRET)
+                       && !(flags & IPT_PKNOCK_OPENSECRET))
+               {
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "--closesecret must go with --opensecret.\n");
+               }
+       }
+
+       if (flags & IPT_PKNOCK_CHECKIP) {
+               if (flags & IPT_PKNOCK_KNOCKPORT) {
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot specify --checkip with --knockports.\n");
+               }
+               if ((flags & IPT_PKNOCK_OPENSECRET)
+                       || (flags & IPT_PKNOCK_CLOSESECRET))
+               {
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot specify --opensecret and"
+                               " --closesecret with --checkip.\n");
+               }
+               if (flags & IPT_PKNOCK_TIME) {
+                       xtables_error(PARAMETER_PROBLEM, PKNOCK
+                               "cannot specify --time with --checkip.\n");
+               }
+       }
+}
+
+/* Prints out the matchinfo. */
+static void pknock_print(const void *ip, 
+                                               const struct xt_entry_match *match, int numeric)
+{
+       const struct ipt_pknock *info;
+       int i;
+
+       info = (const struct ipt_pknock *)match->data;
+
+       printf("pknock ");
+       if (info->option & IPT_PKNOCK_KNOCKPORT) {
+               printf("knockports ");
+               for (i=0; i<info->ports_count; i++)
+                       printf("%s%d", i ? "," : "", info->port[i]);
+               printf(" ");
+       }
+       if (info->option & IPT_PKNOCK_TIME)
+               printf("time %ld ", info->max_time);
+       if (info->option & IPT_PKNOCK_NAME)
+               printf("name %s ", info->rule_name);
+       if (info->option & IPT_PKNOCK_OPENSECRET)
+               printf("opensecret ");
+       if (info->option & IPT_PKNOCK_CLOSESECRET)
+               printf("closesecret ");
+}
+
+/* Saves the union ipt_matchinfo in parsable form to stdout. */
+static void pknock_save(const void *ip, const struct xt_entry_match *match)
+{
+       int i;
+       const struct ipt_pknock *info = (const struct ipt_pknock *)match->data;
+
+       if (info->option & IPT_PKNOCK_KNOCKPORT) {
+               printf("--knockports ");
+               for (i=0; i<info->ports_count; i++)
+                       printf("%s%d", i ? "," : "", info->port[i]);
+               printf(" ");
+       }
+       if (info->option & IPT_PKNOCK_TIME)
+               printf("--time %ld ", info->max_time);
+       if (info->option & IPT_PKNOCK_NAME)
+               printf("--name %s ", info->rule_name);
+       if (info->option & IPT_PKNOCK_OPENSECRET)
+               printf("--opensecret ");
+       if (info->option & IPT_PKNOCK_CLOSESECRET)
+               printf("--closesecret ");
+       if (info->option & IPT_PKNOCK_STRICT)
+               printf("--strict ");
+       if (info->option & IPT_PKNOCK_CHECKIP)
+               printf("--checkip ");
+}
+
+static struct xtables_match pknock_match = {
+       .name           = "pknock",
+       .version        = XTABLES_VERSION,
+       .family         = AF_INET,
+       .size           = XT_ALIGN(sizeof (struct ipt_pknock)),
+       .userspacesize  = XT_ALIGN(sizeof (struct ipt_pknock)),
+       .help           = pknock_help,
+       .parse          = pknock_parse,
+       .final_check    = pknock_check,
+       .print          = pknock_print,
+       .save           = pknock_save,
+       .extra_opts     = pknock_opts
+};
+
+void _init(void) 
+{
+       xtables_register_match(&pknock_match);
+}
diff --git a/extensions/xt_pknock.c b/extensions/xt_pknock.c
new file mode 100644 (file)
index 0000000..da4c170
--- /dev/null
@@ -0,0 +1,1241 @@
+/*
+ * Kernel module to implement Port Knocking and SPA matching support.
+ *
+ * (C) 2006-2009 J. Federico Hernandez Scarso <fede.hernandez@gmail.com>
+ * (C) 2006 Luis A. Floreani <luis.floreani@gmail.com>
+ *
+ * $Id$
+ *
+ * This program is released under the terms of GNU GPL version 2.
+ */
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/in.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/jhash.h>
+#include <linux/random.h>
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <linux/seq_file.h>
+#include <linux/connector.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+//#include <linux/netfilter_ipv4/ipt_pknock.h>
+#include "xt_pknock.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("J. Federico Hernandez Scarso, Luis A. Floreani");
+MODULE_DESCRIPTION("netfilter match for Port Knocking and SPA");
+
+enum {
+       GC_EXPIRATION_TIME      = 65000, /* in msecs */
+       DEFAULT_RULE_HASH_SIZE  = 8,
+       DEFAULT_PEER_HASH_SIZE  = 16,
+};
+
+#define hashtable_for_each_safe(pos, n, head, size, i) \
+       for ((i) = 0; (i) < (size); (i)++)                                      \
+               list_for_each_safe((pos), (n), (&head[(i)]))
+
+#if DEBUG
+       #define DEBUGP(msg, peer) printk(KERN_INFO PKNOCK       \
+                       "(S) peer: %u.%u.%u.%u - %s.\n",                        \
+                       NIPQUAD((peer)->ip), msg)
+       #define duprintf(format, args...) printk(format, ## args);
+#else
+       #define DEBUGP(msg, peer)
+       #define duprintf(format, args...)
+#endif
+
+static uint32_t ipt_pknock_hash_rnd;
+
+static unsigned int rule_hashsize      = DEFAULT_RULE_HASH_SIZE;
+static unsigned int peer_hashsize      = DEFAULT_PEER_HASH_SIZE;
+static unsigned int ipt_pknock_gc_expir_time = GC_EXPIRATION_TIME;
+static int nl_multicast_group          = -1;
+
+static struct list_head *rule_hashtable        = NULL;
+static struct proc_dir_entry *pde = NULL;
+
+static DEFINE_SPINLOCK(list_lock);
+
+static struct ipt_pknock_crypto crypto = {
+       .algo   = "hmac(sha256)",
+       .tfm    = NULL,
+       .size   = 0
+};
+
+module_param(rule_hashsize, int, S_IRUGO);
+module_param(peer_hashsize, int, S_IRUGO);
+module_param(ipt_pknock_gc_expir_time, int, S_IRUGO);
+module_param(nl_multicast_group, int, S_IRUGO);
+
+/**
+ * Calculates a value from 0 to max from a hash of the arguments.
+ *
+ * @key
+ * @len: length
+ * @initval
+ * @max
+ * @return: a 32 bits index
+ */
+static uint32_t
+pknock_hash(const void *key, uint32_t len, uint32_t initval, uint32_t max)
+{
+       return jhash(key, len, initval) % max;
+}
+
+/**
+ * @return: the epoch minute
+ */
+static int
+get_epoch_minute(void)
+{
+       struct timespec t = CURRENT_TIME;
+       return (int)(t.tv_sec/60);
+}
+
+/**
+ * Alloc a hashtable with n buckets.
+ *
+ * @size
+ * @return: hashtable
+ */
+static struct list_head *
+alloc_hashtable(int size)
+{
+       struct list_head *hash = NULL;
+       unsigned int i;
+
+       if ((hash = kmalloc(sizeof(*hash) * size, GFP_ATOMIC)) == NULL) {
+               printk(KERN_ERR PKNOCK 
+                                               "kmalloc() error in alloc_hashtable() function.\n");
+               return NULL;
+       }
+
+       for (i = 0; i < size; i++)
+               INIT_LIST_HEAD(&hash[i]);
+
+       return hash;
+}
+
+/**
+ * This function converts the status from integer to string.
+ *
+ * @status
+ * @return: status
+ */
+static inline const char *
+status_itoa(enum status status)
+{
+       switch (status) {
+               case ST_INIT:           return "INIT";
+               case ST_MATCHING:       return "MATCHING";
+               case ST_ALLOWED:        return "ALLOWED";
+               default:                        return "UNKNOWN";
+       }
+}
+
+/**
+ * @s
+ * @pos
+ * @return: private value used by the iterator
+ */
+static void *
+pknock_seq_start(struct seq_file *s, loff_t *pos)
+{
+       struct proc_dir_entry *pde = s->private;
+       struct ipt_pknock_rule *rule = pde->data;
+
+       spin_lock_bh(&list_lock);
+
+       if (*pos >= peer_hashsize)
+               return NULL;
+
+       return rule->peer_head + *pos;
+}
+
+/**
+ * @s
+ * @v
+ * @pos
+ * @return: next value for the iterator
+ */
+static void *
+pknock_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+       struct proc_dir_entry *pde = s->private;
+       struct ipt_pknock_rule *rule = pde->data;
+
+       (*pos)++;
+       if (*pos >= peer_hashsize)
+               return NULL;
+
+       return rule->peer_head + *pos;
+}
+
+/**
+ * @s
+ * @v
+ */
+static void
+pknock_seq_stop(struct seq_file *s, void *v)
+{
+       spin_unlock_bh(&list_lock);
+}
+
+/**
+ * @s
+ * @v
+ * @return: 0 if OK
+ */
+static int
+pknock_seq_show(struct seq_file *s, void *v)
+{
+       struct list_head *pos = NULL, *n = NULL;
+       struct peer *peer = NULL;
+       unsigned long expir_time = 0;
+        uint32_t ip;
+
+       struct list_head *peer_head = (struct list_head *)v;
+
+       struct proc_dir_entry *pde = s->private;
+       struct ipt_pknock_rule *rule = pde->data;
+
+       list_for_each_safe(pos, n, peer_head) {
+               peer = list_entry(pos, struct peer, head);
+               ip = htonl(peer->ip);
+               expir_time = time_before(jiffies/HZ,
+                                               peer->timestamp + rule->max_time)
+                               ? ((peer->timestamp + rule->max_time)-(jiffies/HZ)) : 0;
+
+               seq_printf(s, "src=%u.%u.%u.%u ", NIPQUAD(ip));
+               seq_printf(s, "proto=%s ", (peer->proto == IPPROTO_TCP) ?
+                                                "TCP" : "UDP");
+               seq_printf(s, "status=%s ", status_itoa(peer->status));
+               seq_printf(s, "expir_time=%ld ", expir_time);
+               seq_printf(s, "next_port_id=%d ", peer->id_port_knocked-1);
+               seq_printf(s, "\n");
+       }
+
+       return 0;
+}
+
+static struct seq_operations pknock_seq_ops = {
+       .start = pknock_seq_start,
+       .next = pknock_seq_next,
+       .stop = pknock_seq_stop,
+       .show = pknock_seq_show
+};
+
+/**
+ * @inode
+ * @file
+ */
+static int
+pknock_proc_open(struct inode *inode, struct file *file)
+{
+       int ret = seq_open(file, &pknock_seq_ops);
+       if (!ret) {
+               struct seq_file *sf = file->private_data;
+               sf->private = PDE(inode);
+       }
+       return ret;
+}
+
+static struct file_operations pknock_proc_ops = {
+       .owner = THIS_MODULE,
+       .open = pknock_proc_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = seq_release
+};
+
+/**
+ * It updates the rule timer to execute garbage collector.
+ *
+ * @rule
+ */
+static inline void
+update_rule_timer(struct ipt_pknock_rule *rule)
+{
+       if (timer_pending(&rule->timer))
+               del_timer(&rule->timer);
+
+       rule->timer.expires = jiffies + msecs_to_jiffies(ipt_pknock_gc_expir_time);
+       add_timer(&rule->timer);
+}
+
+/**
+ * @peer
+ * @max_time
+ * @return: 1 time exceeded, 0 still valid
+ */
+static inline bool
+is_time_exceeded(struct peer *peer, int max_time)
+{
+       return peer && time_after(jiffies/HZ, peer->timestamp + max_time);
+}
+
+/**
+ * @peer
+ * @return: 1 has logged, 0 otherwise
+ */
+static inline bool
+has_logged_during_this_minute(const struct peer *peer)
+{
+       return peer && (peer->login_min == get_epoch_minute());
+}
+
+/**
+ * Garbage collector. It removes the old entries after timer has expired.
+ *
+ * @r: rule
+ */
+static void
+peer_gc(unsigned long r)
+{
+       int i;
+       struct ipt_pknock_rule *rule = (struct ipt_pknock_rule *)r;
+       struct peer *peer = NULL;
+       struct list_head *pos = NULL, *n = NULL;
+
+       hashtable_for_each_safe(pos, n, rule->peer_head, peer_hashsize, i) {
+               peer = list_entry(pos, struct peer, head);
+
+               if (!has_logged_during_this_minute(peer) && 
+                                               is_time_exceeded(peer, rule->max_time))
+               {
+                       DEBUGP("DESTROYED", peer);
+                       list_del(pos);
+                       kfree(peer);
+               }
+       }
+}
+
+/**
+ * Compares length and name equality for the rules.
+ *
+ * @info
+ * @rule
+ * @return: 0 equals, 1 otherwise
+ */
+static inline int
+rulecmp(const struct ipt_pknock *info, const struct ipt_pknock_rule *rule)
+{
+       if (info->rule_name_len != rule->rule_name_len)
+               return 1;
+       if (strncmp(info->rule_name, rule->rule_name, info->rule_name_len) != 0)
+               return 1;
+       return 0;
+}
+
+/**
+ * Search the rule and returns a pointer if it exists.
+ *
+ * @info
+ * @return: rule or NULL
+ */
+static inline struct ipt_pknock_rule *
+search_rule(const struct ipt_pknock *info)
+{
+       struct ipt_pknock_rule *rule = NULL;
+       struct list_head *pos = NULL, *n = NULL;
+       int hash = pknock_hash(info->rule_name, info->rule_name_len,
+                                       ipt_pknock_hash_rnd, rule_hashsize);
+
+       list_for_each_safe(pos, n, &rule_hashtable[hash]) {
+                       rule = list_entry(pos, struct ipt_pknock_rule, head);
+                       if (rulecmp(info, rule) == 0) 
+                                       return rule;
+       }
+       return NULL;
+}
+
+/**
+ * It adds a rule to list only if it doesn't exist.
+ *
+ * @info
+ * @return: 1 success, 0 failure
+ */
+static bool
+add_rule(struct ipt_pknock *info)
+{
+       struct ipt_pknock_rule *rule = NULL;
+       struct list_head *pos = NULL, *n = NULL;
+       int hash = pknock_hash(info->rule_name, info->rule_name_len,
+                                ipt_pknock_hash_rnd, rule_hashsize);
+
+       list_for_each_safe(pos, n, &rule_hashtable[hash]) {
+               rule = list_entry(pos, struct ipt_pknock_rule, head);
+
+               if (rulecmp(info, rule) == 0) {
+                       rule->ref_count++;
+#if DEBUG
+                       if (info->option & IPT_PKNOCK_CHECKIP) {
+                               printk(KERN_DEBUG PKNOCK "add_rule() (AC)"
+                                       " rule found: %s - "
+                                       "ref_count: %d\n",
+                                       rule->rule_name,
+                                       rule->ref_count);
+                       }
+#endif
+                       return true;
+               }
+       }
+
+       if ((rule = kmalloc(sizeof (*rule), GFP_ATOMIC)) == NULL) {
+               printk(KERN_ERR PKNOCK "kmalloc() error in add_rule().\n");
+               return false;
+       }
+
+       INIT_LIST_HEAD(&rule->head);
+
+       memset(rule->rule_name, 0, IPT_PKNOCK_MAX_BUF_LEN + 1);
+       strncpy(rule->rule_name, info->rule_name, info->rule_name_len);
+       rule->rule_name_len = info->rule_name_len;
+
+       rule->ref_count = 1;
+       rule->max_time  = info->max_time;
+
+       if (!(rule->peer_head = alloc_hashtable(peer_hashsize))) {
+               printk(KERN_ERR PKNOCK "alloc_hashtable() error in add_rule().\n");
+               return false;
+       }
+
+       init_timer(&rule->timer);
+       rule->timer.function    = peer_gc;
+       rule->timer.data        = (unsigned long)rule;
+
+       rule->status_proc = create_proc_entry(info->rule_name, 0, pde);
+       if (!rule->status_proc) {
+               printk(KERN_ERR PKNOCK "create_proc_entry() error in add_rule()"
+                        " function.\n");
+                kfree(rule);
+                return false;
+       }
+
+       rule->status_proc->proc_fops = &pknock_proc_ops;
+       rule->status_proc->data = rule;
+
+       list_add(&rule->head, &rule_hashtable[hash]);
+#if DEBUG
+       printk(KERN_INFO PKNOCK "(A) rule_name: %s - created.\n", rule->rule_name);
+#endif
+       return true;
+}
+
+/**
+ * It removes a rule only if it exists.
+ *
+ * @info
+ */
+static void
+remove_rule(struct ipt_pknock *info)
+{
+       struct ipt_pknock_rule *rule = NULL;
+       struct list_head *pos = NULL, *n = NULL;
+       struct peer *peer = NULL;
+       int i;
+#if DEBUG
+       int found = 0;
+#endif
+       int hash = pknock_hash(info->rule_name, info->rule_name_len,
+                                ipt_pknock_hash_rnd, rule_hashsize);
+
+       if (list_empty(&rule_hashtable[hash])) return;
+
+       list_for_each_safe(pos, n, &rule_hashtable[hash]) {
+               rule = list_entry(pos, struct ipt_pknock_rule, head);
+
+               if (rulecmp(info, rule) == 0) {
+#if DEBUG
+                       found = 1;
+#endif
+                       rule->ref_count--;
+                       break;
+               }
+       }
+#if DEBUG
+       if (!found) {
+               printk(KERN_INFO PKNOCK "(N) rule not found: %s.\n", info->rule_name);
+               return;
+       }
+#endif
+       if (rule && rule->ref_count == 0) {
+               hashtable_for_each_safe(pos, n, rule->peer_head, peer_hashsize, i) {
+                       peer = list_entry(pos, struct peer, head);
+
+                       if (peer != NULL) {
+                               DEBUGP("DELETED", peer);
+                               list_del(pos);
+                               kfree(peer);
+                       }
+               }
+
+               if (rule->status_proc)
+                       remove_proc_entry(info->rule_name, pde);
+#if DEBUG
+               printk(KERN_INFO PKNOCK "(D) rule deleted: %s.\n", rule->rule_name);
+#endif
+               if (timer_pending(&rule->timer))
+                       del_timer(&rule->timer);
+
+               list_del(&rule->head);
+               kfree(rule->peer_head);
+               kfree(rule);
+       }
+}
+
+/**
+ * If peer status exist in the list it returns peer status, if not it returns NULL.
+ *
+ * @rule
+ * @ip
+ * @return: peer or NULL
+ */
+static inline struct peer *
+get_peer(struct ipt_pknock_rule *rule, uint32_t ip)
+{
+       struct peer *peer = NULL;
+       struct list_head *pos = NULL, *n = NULL;
+       int hash;
+
+       ip = ntohl(ip);
+
+       hash = pknock_hash(&ip, sizeof(ip), ipt_pknock_hash_rnd, peer_hashsize);
+
+       list_for_each_safe(pos, n, &rule->peer_head[hash]) {
+               peer = list_entry(pos, struct peer, head);
+               if (peer->ip == ip) return peer;
+       }
+       return NULL;
+}
+
+/**
+ * Reset the knock sequence status of the peer.
+ *
+ * @peer
+ */
+static inline void
+reset_knock_status(struct peer *peer)
+{
+       peer->id_port_knocked = 1;
+       peer->status = ST_INIT;
+}
+
+/**
+ * It creates a new peer matching status.
+ *
+ * @rule
+ * @ip
+ * @proto
+ * @return: peer or NULL
+ */
+static inline struct peer *
+new_peer(uint32_t ip, uint8_t proto)
+{
+       struct peer *peer = NULL;
+
+       if ((peer = kmalloc(sizeof (*peer), GFP_ATOMIC)) == NULL) {
+               printk(KERN_ERR PKNOCK "kmalloc() error in new_peer().\n");
+               return NULL;
+       }
+
+       INIT_LIST_HEAD(&peer->head);
+       peer->ip        = ntohl(ip);
+       peer->proto     = proto;
+       peer->timestamp = jiffies/HZ;
+       peer->login_min = 0;
+       reset_knock_status(peer);
+
+       return peer;
+}
+
+/**
+ * It adds a new peer matching status to the list.
+ *
+ * @peer
+ * @rule
+ */
+static inline void
+add_peer(struct peer *peer, struct ipt_pknock_rule *rule)
+{
+       int hash = pknock_hash(&peer->ip, sizeof(peer->ip),
+                                ipt_pknock_hash_rnd, peer_hashsize);
+       list_add(&peer->head, &rule->peer_head[hash]);
+}
+
+/**
+ * It removes a peer matching status.
+ *
+ * @peer
+ */
+static inline void
+remove_peer(struct peer *peer)
+{
+       list_del(&peer->head);
+       if (peer) kfree(peer);
+}
+
+/**
+ * @peer
+ * @info
+ * @port
+ * @return: 1 success, 0 failure
+ */
+static inline bool
+is_first_knock(const struct peer *peer, const struct ipt_pknock *info,
+                uint16_t port)
+{
+       return (peer == NULL && info->port[0] == port) ? 1 : 0;
+}
+
+/**
+ * @peer
+ * @info
+ * @port
+ * @return: 1 success, 0 failure
+ */
+static inline bool
+is_wrong_knock(const struct peer *peer, const struct ipt_pknock *info,
+               uint16_t port)
+{
+       return peer && (info->port[peer->id_port_knocked-1] != port);
+}
+
+/**
+ * @peer
+ * @info
+ * @return: 1 success, 0 failure
+ */
+static inline bool
+is_last_knock(const struct peer *peer, const struct ipt_pknock *info)
+{
+       return peer && (peer->id_port_knocked-1 == info->ports_count);
+}
+
+/**
+ * @peer
+ * @return: 1 success, 0 failure
+ */
+static inline bool
+is_allowed(const struct peer *peer)
+{
+       return peer && (peer->status == ST_ALLOWED);
+}
+
+/**
+ * Sends a message to user space through netlink sockets.
+ *
+ * @info
+ * @peer
+ * @return: 1 success, 0 otherwise
+ */
+static bool
+msg_to_userspace_nl(const struct ipt_pknock *info,
+                const struct peer *peer, int multicast_group)
+{
+       struct cn_msg *m;
+       struct ipt_pknock_nl_msg msg;
+
+       m = kmalloc(sizeof(*m) + sizeof(msg), GFP_ATOMIC);
+       if (!m) {
+               printk(KERN_ERR PKNOCK "kmalloc() error in "
+                        "msg_to_userspace_nl().\n");
+               return false;
+       }
+
+       memset(m, 0, sizeof(*m) + sizeof(msg));
+       m->seq = 0;
+       m->len = sizeof(msg);
+
+       msg.peer_ip = peer->ip;
+       scnprintf(msg.rule_name, info->rule_name_len + 1, info->rule_name);
+
+       memcpy(m + 1, (char *)&msg, m->len);
+
+       cn_netlink_send(m, multicast_group, GFP_ATOMIC);
+
+       kfree(m);
+       return true;
+}
+
+/**
+ * Transforms a sequence of characters to hexadecimal.
+ *
+ * @out: the hexadecimal result
+ * @crypt: the original sequence
+ * @size
+ */
+static void
+crypt_to_hex(char *out, char *crypt, int size)
+{
+       int i;
+       for (i=0; i < size; i++) {
+               unsigned char c = crypt[i];
+               *out++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1);
+               *out++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1);
+       }
+}
+
+/**
+ * Checks that the payload has the hmac(secret+ipsrc+epoch_min).
+ *
+ * @secret
+ * @secret_len
+ * @ipsrc
+ * @payload
+ * @payload_len
+ * @return: 1 success, 0 failure
+ */
+static int
+has_secret(unsigned char *secret, int secret_len, uint32_t ipsrc,
+        unsigned char *payload, int payload_len)
+{
+       struct scatterlist sg[2];
+       char result[64]; // 64 bytes * 8 = 512 bits
+       char *hexresult = NULL;
+       int hexa_size;
+       int ret = 0;
+       int epoch_min;
+
+       if (payload_len == 0)
+               return 0;
+
+       /*
+        * hexa:  4bits
+        * ascii: 8bits
+        * hexa = ascii * 2
+        */
+       hexa_size = crypto.size * 2;
+
+       /* + 1 cause we MUST add NULL in the payload */
+       if (payload_len != hexa_size + 1)
+               return 0;
+
+       hexresult = kmalloc(sizeof(char) * hexa_size, GFP_ATOMIC);
+       if (hexresult == NULL) {
+               printk(KERN_ERR PKNOCK "kmalloc() error in has_secret().\n");
+               return 0;
+       }
+
+       memset(result, 0, 64);
+       memset(hexresult, 0, (sizeof(char) * hexa_size));
+
+       epoch_min = get_epoch_minute();
+
+       sg_set_buf(&sg[0], &ipsrc, sizeof(ipsrc));
+       sg_set_buf(&sg[1], &epoch_min, sizeof(epoch_min));
+
+       ret = crypto_hash_setkey(crypto.tfm, secret, secret_len);
+       if (ret) {
+               printk("crypto_hash_setkey() failed ret=%d\n", ret);
+               return ret;
+       }
+
+       /*
+        * The third parameter is the number of bytes INSIDE the sg!
+        * 4 bytes IP (32 bits) +
+        * 4 bytes int epoch_min (32 bits)
+        */
+       ret = crypto_hash_digest(&crypto.desc, sg, 8, result);
+       if (ret) {
+               printk("crypto_hash_digest() failed ret=%d\n", ret);
+               return ret;
+       }
+
+       crypt_to_hex(hexresult, result, crypto.size);
+
+       if (memcmp(hexresult, payload, hexa_size) != 0) {
+#if DEBUG
+               printk(KERN_INFO PKNOCK "secret match failed\n");
+#endif
+               goto out;
+       }
+
+       ret = 1;
+
+out:
+       if (hexresult != NULL) kfree(hexresult);
+       return ret;
+}
+
+/**
+ * If the peer pass the security policy.
+ *
+ * @peer
+ * @info
+ * @payload
+ * @payload_len
+ * @return: 1 if pass security, 0 otherwise
+ */
+static bool
+pass_security(struct peer *peer, const struct ipt_pknock *info,
+        unsigned char *payload, int payload_len)
+{
+       if (is_allowed(peer))
+               return true;
+
+       /* The peer can't log more than once during the same minute. */
+       if (has_logged_during_this_minute(peer)) {
+               DEBUGP("BLOCKED", peer);
+               return false;
+       }
+       /* Check for OPEN secret */
+       if (!has_secret((unsigned char *)info->open_secret,
+                                       (int)info->open_secret_len, htonl(peer->ip),
+                                       payload, payload_len))
+       {
+               return false;
+       }
+       return true;
+}
+
+/**
+ * It updates the peer matching status.
+ *
+ * @peer
+ * @info
+ * @rule
+ * @hdr
+ * @return: 1 if allowed, 0 otherwise
+ */
+static bool
+update_peer(struct peer *peer, const struct ipt_pknock *info,
+               struct ipt_pknock_rule *rule,
+               const struct transport_data *hdr)
+{
+       unsigned long time;
+
+       if (is_wrong_knock(peer, info, hdr->port)) {
+               DEBUGP("DIDN'T MATCH", peer);
+               /* Peer must start the sequence from scratch. */
+               if (info->option & IPT_PKNOCK_STRICT)
+                       reset_knock_status(peer);
+
+               return false;
+       }
+
+       /* If security is needed. */
+       if (info->option & IPT_PKNOCK_OPENSECRET ) {
+               if (hdr->proto != IPPROTO_UDP)
+                       return false;
+
+               if (!pass_security(peer, info, hdr->payload, hdr->payload_len)) {
+                       return false;
+               }
+       }
+
+       /* Just update the timer when there is a state change. */
+       update_rule_timer(rule);
+
+       peer->id_port_knocked++;
+
+       if (is_last_knock(peer, info)) {
+               peer->status = ST_ALLOWED;
+
+               DEBUGP("ALLOWED", peer);
+
+               if (nl_multicast_group > 0)
+                       msg_to_userspace_nl(info, peer, nl_multicast_group);
+
+               peer->login_min = get_epoch_minute();
+               return true;
+       }
+
+       /* Controls the max matching time between ports. */
+       if (info->option & IPT_PKNOCK_TIME) {
+               time = jiffies/HZ;
+
+               if (is_time_exceeded(peer, info->max_time)) {
+#if DEBUG
+                       DEBUGP("TIME EXCEEDED", peer);
+                       DEBUGP("DESTROYED", peer);
+                       printk(KERN_INFO PKNOCK "max_time: %ld - time: %ld\n",
+                                       peer->timestamp + info->max_time,
+                                       time);
+#endif
+                       remove_peer(peer);
+                       return false;
+               }
+               peer->timestamp = time;
+       }
+       DEBUGP("MATCHING", peer);
+       peer->status = ST_MATCHING;
+       return false;
+}
+
+/**
+ * Make the peer no more ALLOWED sending a payload with a special secret for
+ * closure.
+ *
+ * @peer
+ * @info
+ * @payload
+ * @payload_len
+ * @return: 1 if close knock, 0 otherwise
+ */
+static inline bool
+is_close_knock(const struct peer *peer, const struct ipt_pknock *info,
+               unsigned char *payload, int payload_len)
+{
+       /* Check for CLOSE secret. */
+       if (has_secret((unsigned char *)info->close_secret,
+                               (int)info->close_secret_len, htonl(peer->ip),
+                               payload, payload_len))
+       {
+               DEBUGP("RESET", peer);
+               return true;
+       }
+       return false;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+static bool
+#else
+static int
+#endif
+match(const struct sk_buff *skb,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       const struct xt_match_param *par
+#else
+       const struct net_device *in,
+       const struct net_device *out,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
+       const struct xt_match *match,
+#endif
+       const void *matchinfo,
+       int offset,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
+       unsigned int protoff,
+#endif
+       bool *hotdrop
+#endif
+)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       const struct ipt_pknock *info = par->matchinfo;
+#else
+       const struct ipt_pknock *info = matchinfo;
+#endif
+       struct ipt_pknock_rule *rule = NULL;
+       struct peer *peer = NULL;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+       struct iphdr *iph = ip_hdr(skb);
+#else
+       struct iphdr *iph = skb->nh.iph;
+#endif
+       int hdr_len = 0;
+       __be16 _ports[2], *pptr = NULL;
+       struct transport_data hdr = {0, 0, 0, NULL};
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+       bool ret = false;
+#else
+       int ret = 0;
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       pptr = skb_header_pointer(skb, par->thoff, sizeof _ports, &_ports);
+#else
+       pptr = skb_header_pointer(skb, protoff, sizeof _ports, &_ports);
+#endif
+
+       if (pptr == NULL) {
+               /* We've been asked to examine this packet, and we
+                * can't. Hence, no choice but to drop.
+                */
+               duprintf("Dropping evil offset=0 tinygram.\n");
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+               *par->hotdrop = true;
+#else
+               *hotdrop = true;
+#endif
+               return false;
+#else
+               *hotdrop = 1;
+               return 0;
+#endif
+       }
+
+       hdr.port = ntohs(pptr[1]);
+
+       switch ((hdr.proto = iph->protocol)) {
+       case IPPROTO_TCP:
+               break;
+
+       case IPPROTO_UDP:
+               hdr_len = (iph->ihl * 4) + sizeof(struct udphdr);
+               break;
+
+       default:
+               printk(KERN_INFO PKNOCK 
+                                               "IP payload protocol is neither tcp nor udp.\n");
+               return false;
+       }
+
+       spin_lock_bh(&list_lock);
+
+       /* Searches a rule from the list depending on info structure options. */
+       if ((rule = search_rule(info)) == NULL) {
+               printk(KERN_INFO PKNOCK "The rule %s doesn't exist.\n", 
+                                               info->rule_name);
+               goto out;
+       }
+
+       /* Gives the peer matching status added to rule depending on ip src. */
+       peer = get_peer(rule, iph->saddr);
+
+       if (info->option & IPT_PKNOCK_CHECKIP) {
+               ret = is_allowed(peer);
+               goto out;
+       }
+       
+       if (iph->protocol == IPPROTO_UDP) {
+               hdr.payload = (void *)iph + hdr_len;
+               hdr.payload_len = skb->len - hdr_len;
+       }
+       
+       /* Sets, updates, removes or checks the peer matching status. */
+       if (info->option & IPT_PKNOCK_KNOCKPORT) {
+               if ((ret = is_allowed(peer))) {
+                       if (info->option & IPT_PKNOCK_CLOSESECRET && 
+                                                       iph->protocol == IPPROTO_UDP)
+                       {
+                               if (is_close_knock(peer, info, hdr.payload, hdr.payload_len))
+                               {
+                                       reset_knock_status(peer);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+                                       ret = false;
+#else
+                                       ret = 0;
+#endif
+                               }
+                       }
+                               goto out;
+               }
+
+               if (is_first_knock(peer, info, hdr.port)) {
+                       peer = new_peer(iph->saddr, iph->protocol);
+                       add_peer(peer, rule);
+               }
+
+               if (peer == NULL) goto out;
+
+               update_peer(peer, info, rule, &hdr);
+       }
+
+out:
+#if DEBUG
+       if (ret) DEBUGP("PASS OK", peer);
+#endif
+       spin_unlock_bh(&list_lock);
+       return ret;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+#define RETURN_ERR(err) do { printk(KERN_ERR PKNOCK err); return false; } while (0)
+#else
+#define RETURN_ERR(err) do { printk(KERN_ERR PKNOCK err); return 0; } while (0)
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+static bool
+#else
+static int
+#endif
+checkentry(
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       const struct xt_mtchk_param *par
+#else
+       const char *tablename,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
+       const void *ip,
+#else
+       const struct ipt_ip *ip,
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)       
+        const struct xt_match *match,
+#endif
+       void *matchinfo,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17)
+       unsigned int matchsize,
+#endif
+       unsigned int hook_mask
+#endif
+)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       struct ipt_pknock *info = par->matchinfo;
+#else
+       struct ipt_pknock *info = matchinfo;
+#endif
+
+       /* Singleton. */
+       if (!rule_hashtable) {
+               if (!(rule_hashtable = alloc_hashtable(rule_hashsize)))
+                       RETURN_ERR("alloc_hashtable() error in checkentry()\n");
+
+               get_random_bytes(&ipt_pknock_hash_rnd, sizeof (ipt_pknock_hash_rnd));
+       }
+
+       if (!add_rule(info))
+               RETURN_ERR("add_rule() error in checkentry() function.\n");
+
+       if (!(info->option & IPT_PKNOCK_NAME))
+               RETURN_ERR("You must specify --name option.\n");
+
+       if ((info->option & IPT_PKNOCK_OPENSECRET) && (info->ports_count != 1))
+               RETURN_ERR("--opensecret must have just one knock port\n");
+
+       if (info->option & IPT_PKNOCK_KNOCKPORT) {
+               if (info->option & IPT_PKNOCK_CHECKIP) {
+                       RETURN_ERR("Can't specify --knockports with --checkip.\n");
+               }
+               if ((info->option & IPT_PKNOCK_OPENSECRET) &&
+                               !(info->option & IPT_PKNOCK_CLOSESECRET)) 
+               {
+                       RETURN_ERR("--opensecret must go with --closesecret.\n");
+               }
+               if ((info->option & IPT_PKNOCK_CLOSESECRET) &&
+                               !(info->option & IPT_PKNOCK_OPENSECRET)) 
+               {
+                       RETURN_ERR("--closesecret must go with --opensecret.\n");
+               }
+       }
+
+       if (info->option & IPT_PKNOCK_CHECKIP) {
+               if (info->option & IPT_PKNOCK_KNOCKPORT)
+               {
+                       RETURN_ERR("Can't specify --checkip with --knockports.\n");
+               }
+               if ((info->option & IPT_PKNOCK_OPENSECRET) ||
+                               (info->option & IPT_PKNOCK_CLOSESECRET))
+               {
+                       RETURN_ERR("Can't specify --opensecret and --closesecret"
+                                                       " with --checkip.\n");
+               }
+               if (info->option & IPT_PKNOCK_TIME)
+                       RETURN_ERR("Can't specify --time with --checkip.\n");
+       }
+
+       if (info->option & IPT_PKNOCK_OPENSECRET) {
+               if (info->open_secret_len == info->close_secret_len) {
+                       if (memcmp(info->open_secret, info->close_secret,
+                                               info->open_secret_len) == 0)
+                       {
+                               RETURN_ERR("opensecret & closesecret cannot be equal.\n");
+                       }
+               }
+       }
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
+       return true;
+#else
+       return 1;
+#endif
+}
+
+static void
+destroy(
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       const struct xt_mtdtor_param *par
+#else
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
+       const struct xt_match *match, void *matchinfo
+#else
+       void *matchinfo, unsigned int matchsize
+#endif
+#endif
+)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+       struct ipt_pknock *info = par->matchinfo;
+#else
+       struct ipt_pknock *info = matchinfo;
+#endif
+       /* Removes a rule only if it exits and ref_count is equal to 0. */
+       remove_rule(info);
+}
+
+static struct xt_match ipt_pknock_match __read_mostly = {
+       .name           = "pknock",
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)       
+       .family         = NFPROTO_IPV4,
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)       
+       .matchsize      = sizeof (struct ipt_pknock),
+#endif
+       .match          = match,
+       .checkentry     = checkentry,
+       .destroy        = destroy,
+       .me                     = THIS_MODULE
+};
+
+static int __init ipt_pknock_init(void)
+{
+       printk(KERN_INFO PKNOCK "register.\n");
+
+       if (request_module(crypto.algo) < 0) {
+               printk(KERN_ERR PKNOCK "request_module('%s') error.\n",
+                        crypto.algo);
+               return -1;
+       }
+
+       crypto.tfm = crypto_alloc_hash(crypto.algo, 0, CRYPTO_ALG_ASYNC);
+
+       if (crypto.tfm == NULL) {
+               printk(KERN_ERR PKNOCK "failed to load transform for %s\n", 
+                                               crypto.algo);
+               return -1;
+       }
+
+       crypto.size = crypto_hash_digestsize(crypto.tfm);
+       crypto.desc.tfm = crypto.tfm;
+       crypto.desc.flags = 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+       if (!(pde = proc_mkdir("ipt_pknock", init_net.proc_net))) {
+#else
+       if (!(pde = proc_mkdir("ipt_pknock", proc_net))) {
+#endif
+               printk(KERN_ERR PKNOCK "proc_mkdir() error in _init().\n");
+               return -1;
+       }
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
+       return xt_register_match(&ipt_pknock_match);
+#else
+       return ipt_register_match(&ipt_pknock_match);
+#endif
+}
+
+static void __exit ipt_pknock_fini(void)
+{
+       printk(KERN_INFO PKNOCK "unregister.\n");
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+       remove_proc_entry("ipt_pknock", init_net.proc_net);
+#else
+       remove_proc_entry("ipt_pknock", proc_net);
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
+       xt_unregister_match(&ipt_pknock_match);
+#else
+       ipt_unregister_match(&ipt_pknock_match);
+#endif
+       kfree(rule_hashtable);
+
+       if (crypto.tfm != NULL) crypto_free_hash(crypto.tfm);
+}
+
+module_init(ipt_pknock_init);
+module_exit(ipt_pknock_fini);
diff --git a/extensions/xt_pknock.h b/extensions/xt_pknock.h
new file mode 100644 (file)
index 0000000..3d0f467
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Kernel module to implement Port Knocking and SPA matching support.
+ *
+ * (C) 2006-2008 J. Federico Hernandez <fede.hernandez@gmail.com>
+ * (C) 2006 Luis Floreani <luis.floreani@gmail.com>
+ *
+ * $Id$
+ *
+ * This program is released under the terms of GNU GPL version 2.
+ */
+#ifndef _IPT_PKNOCK_H
+#define _IPT_PKNOCK_H
+
+#define PKNOCK "ipt_pknock: "
+
+#define IPT_PKNOCK_KNOCKPORT   0x01
+#define IPT_PKNOCK_TIME                        0x02
+#define IPT_PKNOCK_NAME                        0x04
+#define IPT_PKNOCK_STRICT              0x08
+#define IPT_PKNOCK_CHECKIP             0x10
+#define IPT_PKNOCK_OPENSECRET  0x20
+#define IPT_PKNOCK_CLOSESECRET 0x40
+
+#define IPT_PKNOCK_MAX_PORTS           15
+#define IPT_PKNOCK_MAX_BUF_LEN         32
+#define IPT_PKNOCK_MAX_PASSWD_LEN      32
+
+#define DEBUG 1
+
+struct ipt_pknock {
+       char            rule_name[IPT_PKNOCK_MAX_BUF_LEN + 1];
+       int                     rule_name_len;
+       char            open_secret[IPT_PKNOCK_MAX_PASSWD_LEN + 1];
+       int                     open_secret_len;
+       char            close_secret[IPT_PKNOCK_MAX_PASSWD_LEN + 1];
+       int                     close_secret_len;
+       uint8_t ports_count;    /* number of ports */
+       uint16_t        port[IPT_PKNOCK_MAX_PORTS]; /* port[,port,port,...] */
+       unsigned long   max_time;       /* max matching time between ports */
+       uint8_t option;         /* --time, --knock-port, ... */
+};
+
+struct ipt_pknock_nl_msg {
+       char            rule_name[IPT_PKNOCK_MAX_BUF_LEN + 1];
+       uint32_t        peer_ip;
+};
+
+enum status {ST_INIT=1, ST_MATCHING, ST_ALLOWED};
+
+#ifdef __KERNEL__
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+struct peer {
+       struct list_head head;
+       uint32_t                ip;
+       uint8_t         proto;
+       uint32_t                id_port_knocked;
+       enum status             status;
+       unsigned long   timestamp;
+       int                             login_min;      /* the login epoch minute */
+};
+
+#include <linux/proc_fs.h>
+
+struct ipt_pknock_rule {
+       struct list_head        head;
+       char                            rule_name[IPT_PKNOCK_MAX_BUF_LEN + 1];
+       int                                     rule_name_len;
+       unsigned int            ref_count;
+       struct timer_list       timer;          /* garbage collector timer */
+       struct list_head        *peer_head;
+       struct proc_dir_entry   *status_proc;
+       unsigned long           max_time; /* max matching time between ports */
+};
+
+#include <linux/crypto.h>
+
+struct ipt_pknock_crypto {
+       char                            *algo;
+       struct crypto_hash      *tfm;
+       int                                     size;
+       struct hash_desc        desc;
+};
+
+struct transport_data {
+       uint8_t proto;
+       uint16_t        port;   /* destination port */
+       int                     payload_len;
+       unsigned char   *payload;
+};
+
+#endif /* __KERNEL__ */
+#endif /* _IPT_PKNOCK_H */
diff --git a/mconfig b/mconfig
index cbeaa69edb0c24af29c9098cb5616f7d56e1326b..b10731b2ba214578f3eb2221dd2842c1fdd2324a 100644 (file)
--- a/mconfig
+++ b/mconfig
@@ -21,5 +21,6 @@ build_ipset=m
 build_ipv4options=m
 build_length2=m
 build_lscan=m
+build_pknock=m
 build_psd=m
 build_quota2=m