Files
xtables-addons/extensions/pknock/xt_pknock.c
2020-10-25 15:41:24 +01:00

1138 lines
26 KiB
C

/*
* 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>
*
* This program is released under the terms of GNU GPL version 2.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#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/proc_fs.h>
#include <linux/spinlock.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 <crypto/hash.h>
#include "xt_pknock.h"
#include "compat_xtables.h"
enum status {
ST_INIT = 1,
ST_MATCHING,
ST_ALLOWED,
};
/**
* @timestamp: seconds, but not since epoch (uses jiffies/HZ)
* @login_sec: seconds at login since the epoch
*/
struct peer {
struct list_head head;
__be32 ip;
uint32_t accepted_knock_count;
unsigned long timestamp;
unsigned long login_sec;
enum status status;
uint8_t proto;
};
/**
* @timer: garbage collector timer
* @max_time: max matching time between ports
*/
struct xt_pknock_rule {
struct list_head head;
char rule_name[XT_PKNOCK_MAX_BUF_LEN+1];
int rule_name_len;
unsigned int ref_count;
struct timer_list timer;
struct list_head *peer_head;
struct proc_dir_entry *status_proc;
unsigned long max_time;
unsigned long autoclose_time;
};
/**
* @port: destination port
*/
struct transport_data {
uint8_t proto;
uint16_t port;
int payload_len;
const unsigned char *payload;
};
MODULE_LICENSE("GPL");
MODULE_AUTHOR("J. Federico Hernandez Scarso, Luis A. Floreani");
MODULE_DESCRIPTION("netfilter match for Port Knocking and SPA");
MODULE_ALIAS("ipt_pknock");
enum {
DEFAULT_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)]))
#define pk_debug(msg, peer) pr_debug( \
"(S) peer: " NIPQUAD_FMT " - %s.\n", \
NIPQUAD((peer)->ip), msg)
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 gc_expir_time = DEFAULT_GC_EXPIRATION_TIME;
static int nl_multicast_group = -1;
static struct list_head *rule_hashtable;
static struct proc_dir_entry *pde;
static DEFINE_SPINLOCK(list_lock);
static struct {
const char *algo;
struct crypto_shash *tfm;
unsigned int size;
struct shash_desc desc;
} crypto = {
.algo = "hmac(sha256)",
.tfm = NULL,
.size = 0
};
module_param(rule_hashsize, int, S_IRUGO);
MODULE_PARM_DESC(rule_hashsize, "Buckets in rule hash table (default: 8)");
module_param(peer_hashsize, int, S_IRUGO);
MODULE_PARM_DESC(peer_hashsize, "Buckets in peer hash table (default: 16)");
module_param(gc_expir_time, int, S_IRUGO);
MODULE_PARM_DESC(gc_expir_time, "Time until garbage collection after valid knock packet (default: 65000 msec)");
module_param(nl_multicast_group, int, S_IRUGO);
MODULE_PARM_DESC(nl_multicast_group, "Netlink multicast group number for pknock messages");
/**
* Calculates a value from 0 to max from a hash of the arguments.
*
* @key
* @len: length
* @initval
* @max
* @return: a 32 bits index
*/
static inline uint32_t
pknock_hash(const void *key, uint32_t len, uint32_t initval, uint32_t max)
{
return jhash(key, len, initval) % max;
}
/**
* Alloc a hashtable with n buckets.
*
* @size
* @return: hashtable
*/
static struct list_head *
alloc_hashtable(unsigned int size)
{
struct list_head *hash;
unsigned int i;
hash = kmalloc(sizeof(*hash) * size, GFP_KERNEL);
if (hash == NULL)
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)
{
const struct xt_pknock_rule *rule = s->private;
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)
{
const struct xt_pknock_rule *rule = s->private;
++*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)
{
const struct list_head *pos, *n;
const struct peer *peer;
unsigned long time;
const struct list_head *peer_head = v;
const struct xt_pknock_rule *rule = s->private;
list_for_each_safe(pos, n, peer_head) {
peer = list_entry(pos, struct peer, head);
seq_printf(s, "src=" NIPQUAD_FMT " ", NIPQUAD(peer->ip));
seq_printf(s, "proto=%s ", (peer->proto == IPPROTO_TCP) ?
"TCP" : "UDP");
seq_printf(s, "status=%s ", status_itoa(peer->status));
seq_printf(s, "accepted_knock_count=%lu ",
(unsigned long)peer->accepted_knock_count);
if (peer->status == ST_MATCHING) {
time = 0;
if (time_before(jiffies / HZ, peer->timestamp +
rule->max_time))
time = peer->timestamp + rule->max_time -
jiffies / HZ;
seq_printf(s, "expir_time=%lu [secs] ", time);
}
if (peer->status == ST_ALLOWED && rule->autoclose_time != 0) {
time = 0;
if (time_before(get_seconds(), peer->login_sec +
rule->autoclose_time * 60))
time = peer->login_sec +
rule->autoclose_time * 60 -
get_seconds();
seq_printf(s, "autoclose_time=%lu [secs] ", time);
}
seq_printf(s, "\n");
}
return 0;
}
static const 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 == 0) {
struct seq_file *sf = file->private_data;
sf->private = PDE_DATA(inode);
}
return ret;
}
static const struct proc_ops pknock_proc_ops = {
.proc_open = pknock_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release,
};
/**
* It updates the rule timer to execute garbage collector.
*
* @rule
*/
static void update_rule_gc_timer(struct xt_pknock_rule *rule)
{
if (timer_pending(&rule->timer))
del_timer(&rule->timer);
rule->timer.expires = jiffies + msecs_to_jiffies(gc_expir_time);
add_timer(&rule->timer);
}
/**
* @peer
* @autoclose_time
*
* Returns true if autoclose due, or false if still valid.
*/
static inline bool
autoclose_time_passed(const struct peer *peer, unsigned int autoclose_time)
{
return peer != NULL && autoclose_time != 0 && time_after(get_seconds(),
peer->login_sec + autoclose_time * 60);
}
/**
* @peer
* @max_time
* @return: 1 time exceeded, 0 still valid
*/
static inline bool
is_interknock_time_exceeded(const struct peer *peer, unsigned int max_time)
{
return peer != NULL && 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 != NULL && peer->login_sec / 60 == get_seconds() / 60;
}
/**
* Garbage collector. It removes the old entries after tis timers have expired.
*
* @r: rule
*/
static void peer_gc(struct timer_list *tl)
{
unsigned int i;
struct xt_pknock_rule *rule = from_timer(rule, tl, timer);
struct peer *peer;
struct list_head *pos, *n;
pr_debug("(S) running %s\n", __func__);
hashtable_for_each_safe(pos, n, rule->peer_head, peer_hashsize, i) {
peer = list_entry(pos, struct peer, head);
/*
* Remove any peer whose (inter-knock) max_time
* or autoclose_time passed.
*/
if ((peer->status != ST_ALLOWED &&
is_interknock_time_exceeded(peer, rule->max_time)) ||
(peer->status == ST_ALLOWED &&
autoclose_time_passed(peer, rule->autoclose_time)))
{
pk_debug("GC-DELETED", peer);
list_del(pos);
kfree(peer);
}
}
}
/**
* Compares length and name equality for the rules.
*/
static inline bool
rulecmp(const struct xt_pknock_mtinfo *info, const struct xt_pknock_rule *rule)
{
if (info->rule_name_len != rule->rule_name_len)
return false;
if (strncmp(info->rule_name, rule->rule_name, info->rule_name_len) != 0)
return false;
return true;
}
/**
* Search the rule and returns a pointer if it exists.
*
* @info
* @return: rule or NULL
*/
static struct xt_pknock_rule *search_rule(const struct xt_pknock_mtinfo *info)
{
struct xt_pknock_rule *rule;
struct list_head *pos, *n;
unsigned 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 xt_pknock_rule, head);
if (rulecmp(info, rule))
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 xt_pknock_mtinfo *info)
{
struct xt_pknock_rule *rule;
struct list_head *pos, *n;
unsigned 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 xt_pknock_rule, head);
if (!rulecmp(info, rule))
continue;
++rule->ref_count;
if (info->option & XT_PKNOCK_OPENSECRET) {
rule->max_time = info->max_time;
rule->autoclose_time = info->autoclose_time;
}
if (info->option & XT_PKNOCK_CHECKIP)
pr_debug("add_rule() (AC) rule found: %s - "
"ref_count: %d\n",
rule->rule_name, rule->ref_count);
return true;
}
rule = kzalloc(sizeof(*rule), GFP_KERNEL);
if (rule == NULL)
return false;
INIT_LIST_HEAD(&rule->head);
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;
rule->autoclose_time = info->autoclose_time;
rule->peer_head = alloc_hashtable(peer_hashsize);
if (rule->peer_head == NULL)
goto out;
timer_setup(&rule->timer, peer_gc, 0);
rule->status_proc = proc_create_data(info->rule_name, 0, pde,
&pknock_proc_ops, rule);
if (rule->status_proc == NULL)
goto out;
list_add(&rule->head, &rule_hashtable[hash]);
pr_debug("(A) rule_name: %s - created.\n", rule->rule_name);
return true;
out:
kfree(rule->peer_head);
kfree(rule);
return false;
}
/**
* It removes a rule only if it exists.
*
* @info
*/
static void
remove_rule(struct xt_pknock_mtinfo *info)
{
struct xt_pknock_rule *rule = NULL;
struct list_head *pos, *n;
struct peer *peer;
unsigned int i;
int found = 0;
unsigned 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 xt_pknock_rule, head);
if (rulecmp(info, rule)) {
found = 1;
rule->ref_count--;
break;
}
}
if (!found) {
pr_debug("(N) rule not found: %s.\n", info->rule_name);
return;
}
if (rule == NULL || rule->ref_count != 0)
return;
hashtable_for_each_safe(pos, n, rule->peer_head, peer_hashsize, i) {
peer = list_entry(pos, struct peer, head);
pk_debug("DELETED", peer);
list_del(pos);
kfree(peer);
}
if (rule->status_proc != NULL)
remove_proc_entry(info->rule_name, pde);
pr_debug("(D) rule deleted: %s.\n", rule->rule_name);
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 struct peer *get_peer(struct xt_pknock_rule *rule, __be32 ip)
{
struct peer *peer;
struct list_head *pos, *n;
unsigned int hash;
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 void reset_knock_status(struct peer *peer)
{
peer->accepted_knock_count = 0;
peer->status = ST_INIT;
}
/**
* It creates a new peer matching status.
*
* @rule
* @ip
* @proto
* @return: peer or NULL
*/
static struct peer *new_peer(__be32 ip, uint8_t proto)
{
struct peer *peer = kmalloc(sizeof(*peer), GFP_ATOMIC);
if (peer == NULL)
return NULL;
INIT_LIST_HEAD(&peer->head);
peer->ip = ip;
peer->proto = proto;
peer->timestamp = jiffies/HZ;
peer->login_sec = 0;
reset_knock_status(peer);
return peer;
}
/**
* It adds a new peer matching status to the list.
*
* @peer
* @rule
*/
static void add_peer(struct peer *peer, struct xt_pknock_rule *rule)
{
unsigned 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 void remove_peer(struct peer *peer)
{
if (peer == NULL)
return;
list_del(&peer->head);
kfree(peer);
}
/**
* @peer
* @info
* @port
* @return: 1 success, 0 failure
*/
static inline bool
is_first_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info,
uint16_t port)
{
return peer == NULL && info->port[0] == port;
}
/**
* @peer
* @info
* @port
* @return: 1 success, 0 failure
*/
static inline bool
is_wrong_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info,
uint16_t port)
{
return peer != NULL && info->port[peer->accepted_knock_count] != port;
}
/**
* @peer
* @info
* @return: 1 success, 0 failure
*/
static inline bool
is_last_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info)
{
return peer != NULL && peer->accepted_knock_count == info->ports_count;
}
/**
* @peer
* @return: 1 success, 0 failure
*/
static inline bool
is_allowed(const struct peer *peer)
{
return peer != NULL && 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 xt_pknock_mtinfo *info,
const struct peer *peer, int multicast_group)
{
#if IS_ENABLED(CONFIG_CONNECTOR)
struct cn_msg *m;
struct xt_pknock_nl_msg msg;
m = kzalloc(sizeof(*m) + sizeof(msg), GFP_ATOMIC);
if (m == NULL)
return false;
m->len = sizeof(msg);
msg.peer_ip = peer->ip;
scnprintf(msg.rule_name, info->rule_name_len + 1, info->rule_name);
memcpy(m + 1, &msg, m->len);
cn_netlink_send(m, 0, multicast_group, GFP_ATOMIC);
kfree(m);
#endif
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, const char *crypt, unsigned int size)
{
unsigned 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 bool
has_secret(const unsigned char *secret, unsigned int secret_len, uint32_t ipsrc,
const unsigned char *payload, unsigned int payload_len)
{
char result[64] = ""; // 64 bytes * 8 = 512 bits
char *hexresult;
unsigned int hexa_size;
int ret;
bool fret = false;
unsigned int epoch_min;
if (payload_len == 0)
return false;
/*
* 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 false;
hexresult = kzalloc(hexa_size, GFP_ATOMIC);
if (hexresult == NULL)
return false;
epoch_min = get_seconds() / 60;
ret = crypto_shash_setkey(crypto.tfm, secret, secret_len);
if (ret != 0) {
printk("crypto_hash_setkey() failed ret=%d\n", ret);
goto out;
}
/*
* The third parameter is the number of bytes INSIDE the sg!
* 4 bytes IP (32 bits) +
* 4 bytes int epoch_min (32 bits)
*/
if ((ret = crypto_shash_update(&crypto.desc, (const void *)&ipsrc, sizeof(ipsrc))) != 0 ||
(ret = crypto_shash_update(&crypto.desc, (const void *)&epoch_min, sizeof(epoch_min))) != 0 ||
(ret = crypto_shash_final(&crypto.desc, result)) != 0) {
printk("crypto_shash_update/final() failed ret=%d\n", ret);
goto out;
}
crypt_to_hex(hexresult, result, crypto.size);
if (memcmp(hexresult, payload, hexa_size) != 0)
pr_debug("secret match failed\n");
else
fret = true;
out:
kfree(hexresult);
return fret;
}
/**
* 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 xt_pknock_mtinfo *info,
const unsigned char *payload, unsigned 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)) {
pk_debug("DENIED (anti-spoof protection)", peer);
return false;
}
/* Check for OPEN secret */
if (has_secret(info->open_secret,
info->open_secret_len, peer->ip,
payload, payload_len))
return true;
return false;
}
/**
* Validates the peer and updates the peer status for an initiating or
* in-sequence knock packet.
*
* @peer
* @info
* @rule
* @hdr
*
* Returns true if allowed, false otherwise.
*/
static bool
update_peer(struct peer *peer, const struct xt_pknock_mtinfo *info,
struct xt_pknock_rule *rule,
const struct transport_data *hdr)
{
unsigned long time;
if (is_wrong_knock(peer, info, hdr->port)) {
pk_debug("DIDN'T MATCH", peer);
/* Peer must start the sequence from scratch. */
if (info->option & XT_PKNOCK_STRICT)
remove_peer(peer);
return false;
}
/* If security is needed. */
if (info->option & XT_PKNOCK_OPENSECRET ) {
if (hdr->proto != IPPROTO_UDP && hdr->proto != IPPROTO_UDPLITE)
return false;
if (!pass_security(peer, info, hdr->payload, hdr->payload_len))
return false;
}
/* Update the gc timer when there is a state change. */
update_rule_gc_timer(rule);
++peer->accepted_knock_count;
if (is_last_knock(peer, info)) {
peer->status = ST_ALLOWED;
pk_debug("ALLOWED", peer);
peer->login_sec = get_seconds();
if (nl_multicast_group > 0)
msg_to_userspace_nl(info, peer, nl_multicast_group);
return true;
}
/* Immediate control over the maximum time between knocks. */
if (info->option & XT_PKNOCK_TIME) {
time = jiffies/HZ;
if (is_interknock_time_exceeded(peer, info->max_time)) {
pk_debug("ST_MATCHING knock received after interknock "
"time passed => destroyed", peer);
pr_debug("max_time: %ld - time: %ld\n",
peer->timestamp + info->max_time,
time);
remove_peer(peer);
return false;
}
peer->timestamp = time;
}
pk_debug("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 bool
is_close_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info,
const unsigned char *payload, unsigned int payload_len)
{
/* Check for CLOSE secret. */
if (has_secret(info->close_secret,
info->close_secret_len, peer->ip,
payload, payload_len))
{
pk_debug("BLOCKED", peer);
return true;
}
return false;
}
static bool pknock_mt(const struct sk_buff *skb,
struct xt_action_param *par)
{
const struct xt_pknock_mtinfo *info = par->matchinfo;
struct xt_pknock_rule *rule;
struct peer *peer;
const struct iphdr *iph = ip_hdr(skb);
unsigned int hdr_len = 0;
__be16 _ports[2];
const __be16 *pptr;
struct transport_data hdr = {0, 0, 0, NULL};
bool ret = false;
pptr = skb_header_pointer(skb, par->thoff, sizeof _ports, &_ports);
if (pptr == NULL) {
/* We've been asked to examine this packet, and we
* can't. Hence, no choice but to drop.
*/
par->hotdrop = true;
return false;
}
hdr.port = ntohs(pptr[1]);
hdr.proto = iph->protocol;
switch (hdr.proto) {
case IPPROTO_TCP:
break;
case IPPROTO_UDP:
case IPPROTO_UDPLITE:
hdr_len = (iph->ihl * 4) + sizeof(struct udphdr);
break;
default:
pr_debug("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. */
rule = search_rule(info);
if (rule == 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 & XT_PKNOCK_CHECKIP) {
ret = is_allowed(peer);
goto out;
}
if (iph->protocol == IPPROTO_UDP || iph->protocol == IPPROTO_UDPLITE) {
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 & XT_PKNOCK_KNOCKPORT) {
ret = is_allowed(peer);
if (ret != 0) {
if (info->option & XT_PKNOCK_CLOSESECRET &&
(iph->protocol == IPPROTO_UDP ||
iph->protocol == IPPROTO_UDPLITE))
{
if (is_close_knock(peer, info, hdr.payload, hdr.payload_len))
{
reset_knock_status(peer);
ret = false;
}
}
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:
/* Handle cur.peer matching and deletion after autoclose_time passed */
if (ret && autoclose_time_passed(peer, rule->autoclose_time)) {
pk_debug("AUTOCLOSE TIME PASSED => BLOCKED", peer);
ret = false;
if (iph->protocol == IPPROTO_TCP ||
!has_logged_during_this_minute(peer))
remove_peer(peer);
}
if (ret)
pk_debug("PASS OK", peer);
spin_unlock_bh(&list_lock);
return ret;
}
#define RETURN_ERR(err) do { pr_err(err); return -EINVAL; } while (false)
static int pknock_mt_check(const struct xt_mtchk_param *par)
{
struct xt_pknock_mtinfo *info = par->matchinfo;
/* Singleton. */
if (rule_hashtable == NULL) {
rule_hashtable = alloc_hashtable(rule_hashsize);
if (rule_hashtable == NULL)
RETURN_ERR("alloc_hashtable() error in checkentry()\n");
get_random_bytes(&ipt_pknock_hash_rnd, sizeof (ipt_pknock_hash_rnd));
}
if (!(info->option & XT_PKNOCK_NAME))
RETURN_ERR("You must specify --name option.\n");
if (info->option & XT_PKNOCK_OPENSECRET && info->ports_count != 1)
RETURN_ERR("--opensecret must have just one knock port\n");
if (info->option & XT_PKNOCK_KNOCKPORT) {
if (info->option & XT_PKNOCK_CHECKIP)
RETURN_ERR("Can't specify --knockports with --checkip.\n");
if (info->option & XT_PKNOCK_OPENSECRET &&
!(info->option & XT_PKNOCK_CLOSESECRET))
RETURN_ERR("--opensecret must go with --closesecret.\n");
if (info->option & XT_PKNOCK_CLOSESECRET &&
!(info->option & XT_PKNOCK_OPENSECRET))
RETURN_ERR("--closesecret must go with --opensecret.\n");
}
if (info->option & XT_PKNOCK_CHECKIP) {
if (info->option & XT_PKNOCK_KNOCKPORT)
RETURN_ERR("Can't specify --checkip with --knockports.\n");
if ((info->option & XT_PKNOCK_OPENSECRET) ||
(info->option & XT_PKNOCK_CLOSESECRET))
RETURN_ERR("Can't specify --opensecret and --closesecret"
" with --checkip.\n");
if (info->option & XT_PKNOCK_TIME)
RETURN_ERR("Can't specify --time with --checkip.\n");
if (info->option & XT_PKNOCK_AUTOCLOSE)
RETURN_ERR("Can't specify --autoclose with --checkip.\n");
} else if (!(info->option & (XT_PKNOCK_OPENSECRET | XT_PKNOCK_TIME))) {
RETURN_ERR("you must specify --time.\n");
}
if (info->option & XT_PKNOCK_OPENSECRET &&
info->open_secret_len == info->close_secret_len &&
memcmp(info->open_secret, info->close_secret,
info->open_secret_len) == 0)
RETURN_ERR("opensecret & closesecret cannot be equal.\n");
if (!add_rule(info))
/* should ENOMEM here */
RETURN_ERR("add_rule() error in checkentry() function.\n");
return 0;
}
static void pknock_mt_destroy(const struct xt_mtdtor_param *par)
{
struct xt_pknock_mtinfo *info = par->matchinfo;
/* Removes a rule only if it exits and ref_count is equal to 0. */
remove_rule(info);
}
static struct xt_match xt_pknock_mt_reg __read_mostly = {
.name = "pknock",
.revision = 1,
.family = NFPROTO_IPV4,
.matchsize = sizeof(struct xt_pknock_mtinfo),
.match = pknock_mt,
.checkentry = pknock_mt_check,
.destroy = pknock_mt_destroy,
.me = THIS_MODULE
};
static int __init xt_pknock_mt_init(void)
{
#if !IS_ENABLED(CONFIG_CONNECTOR)
if (nl_multicast_group != -1)
pr_info("CONFIG_CONNECTOR not present; "
"netlink messages disabled\n");
#endif
if (gc_expir_time < DEFAULT_GC_EXPIRATION_TIME)
gc_expir_time = DEFAULT_GC_EXPIRATION_TIME;
if (request_module(crypto.algo) < 0) {
pr_err("request_module('%s') error.\n",
crypto.algo);
return -ENXIO;
}
crypto.tfm = crypto_alloc_shash(crypto.algo, 0, 0);
if (IS_ERR(crypto.tfm)) {
pr_err("failed to load transform for %s\n",
crypto.algo);
return PTR_ERR(crypto.tfm);
}
crypto.size = crypto_shash_digestsize(crypto.tfm);
crypto.desc.tfm = crypto.tfm;
pde = proc_mkdir("xt_pknock", init_net.proc_net);
if (pde == NULL) {
pr_err("proc_mkdir() error in _init().\n");
return -ENXIO;
}
return xt_register_match(&xt_pknock_mt_reg);
}
static void __exit xt_pknock_mt_exit(void)
{
remove_proc_entry("xt_pknock", init_net.proc_net);
xt_unregister_match(&xt_pknock_mt_reg);
kfree(rule_hashtable);
if (crypto.tfm != NULL)
crypto_free_shash(crypto.tfm);
}
module_init(xt_pknock_mt_init);
module_exit(xt_pknock_mt_exit);