diff mbox

Passive OS fingerprint xtables match.

Message ID 20090310151357.GA10658@ioremap.net
State Not Applicable, archived
Delegated to: David Miller
Headers show

Commit Message

Evgeniy Polyakov March 10, 2009, 3:13 p.m. UTC
Hi.

Passive OS fingerprinting netfilter module allows to passively detect
remote OS and perform various netfilter actions based on that knowledge.
This module compares some data (WS, MSS, options and it's order, ttl, df
and others) from packets with SYN bit set with dynamically loaded OS
fingerprints.

Fingerprint matching rules can be downloaded from OpenBSD source tree
and loaded via netlink connector into the kernel via special util found
in archive. It will also listen for events about matching packets.

Archive also contains library file (also attached), which was shipped
with iptables extensions some time ago (at least when ipt_osf existed
in patch-o-matic).

This release implements suggestions found during the code review like
codying style, structure split and tighter packing, bool and %pi4
usage and similar changes.

Fingerprints can be downloaded from
http://www.openbsd.org/cgi-bin/cvsweb/src/etc/pf.os

Example usage:
# modrpobe xt_osf
# ./ucon_osf -f ./pf.os
^C Daemon will listen for incoming match events 
-d switch removes fingerprints
# iptables -I INPUT -j ACCEPT -p tcp -m osf --genre Linux --log 0 --ttl 2 --connector

You will find something like this in the syslog:
ipt_osf: Windows [2000:SP3:Windows XP Pro SP1, 2000 SP3]: 11.22.33.55:4024 -> 11.22.33.44:139

Please consider for inclusion.
Thank you.

Passive OS fingerprint homepage (archives, examples):
http://www.ioremap.net/projects/osf

Signed-off-by: Evgeniy Polyakov <zbr@ioremap.net>

Comments

Jan Engelhardt March 10, 2009, 4:07 p.m. UTC | #1
On Tuesday 2009-03-10 16:13, Evgeniy Polyakov wrote:
>
>[xt_osf]

This starts to get its shape. I'm in favor of it.


>--- /dev/null
>+++ b/include/linux/netfilter/xt_osf.h
>@@ -0,0 +1,110 @@
>+/*
>+ * xt_osf.h
>+ *

Common-sense policy wants that filenames like these not be part of the file.

>+#ifndef _IPT_OSF_H
>+#define _IPT_OSF_H
>+
>+#define MAXGENRELEN		32
>+#define MAXDETLEN		64
>+
>+#define IPT_OSF_GENRE		(1<<0)
>+#define	IPT_OSF_TTL		(1<<1)
>+#define IPT_OSF_LOG		(1<<2)
>+#define IPT_OSF_UNUSED		(1<<3)
>+#define IPT_OSF_CONNECTOR	(1<<4)
>+#define IPT_OSF_INVERT		(1<<5)
>+
>+#define IPT_OSF_LOGLEVEL_ALL	0
>+#define IPT_OSF_LOGLEVEL_FIRST	1
>+#define IPT_OSF_LOGLEVEL_ALL_KNOWN	2
>+
>+#define IPT_OSF_TTL_TRUE	0	/* True ip and fingerprint TTL comparison */
>+#define IPT_OSF_TTL_LESS	1	/* Check if ip TTL is less than fingerprint one */
>+#define IPT_OSF_TTL_NOCHECK	2	/* Do not compare ip and fingerprint TTL at all */
>+
>+struct ipt_osf_info {
>+	char			genre[MAXGENRELEN];
>+	__u32			len;
>+	__u32			flags;
>+	__u32			loglevel;
>+	__u32			ttl;
>+};
>[more struct ipt_osf...]

Since the module's name is xt_osf now, it would make sense to follow
this in the struct names too.

>diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
>index c2bac9c..34aafec 100644
>--- a/net/netfilter/Kconfig
>+++ b/net/netfilter/Kconfig
>@@ -853,6 +853,19 @@ config NETFILTER_XT_MATCH_U32
> 
> 	  Details and examples are in the kernel module source.
> 
>+config NETFILTER_XT_MATCH_OSF
>+	tristate 'Passive OS fingerprint match support'

Just a minor thing, could you add "osf" somewhere in the short-text
so that people instantly know the name of the module (without having
to consult the help text), like the other entries. For example

	tristate '"osf" Passive OS fingerprint match'

>+	depends on NETFILTER_ADVANCED
>+	help
>+	  Passive OS fingerprint matching module. Allows to passively match
>+	  remote operation system analyzing incoming TCP packets with SYN
>+	  bit set.

A bit of rewording I suggest.

	This option selects the Passive OS Fingerprinting match module
	that allows to passively match the
	remote operating system by analyzing incoming TCP SYN packets.

>+
>+	  You should download and install rule loading software from
>+	  http://www.ioremap.net/projects/osf

should -> can

	Rules and loading software can be downloaded from
	http://ioremap.net/projects/osf/

>diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
>index da3d909..ec3a6e0 100644
>--- a/net/netfilter/Makefile
>+++ b/net/netfilter/Makefile
>@@ -89,6 +89,7 @@ obj-$(CONFIG_NETFILTER_XT_MATCH_STRING) += xt_string.o
> obj-$(CONFIG_NETFILTER_XT_MATCH_TCPMSS) += xt_tcpmss.o
> obj-$(CONFIG_NETFILTER_XT_MATCH_TIME) += xt_time.o
> obj-$(CONFIG_NETFILTER_XT_MATCH_U32) += xt_u32.o
>+obj-$(CONFIG_NETFILTER_XT_MATCH_OSF) += xt_osf.o

Please have the list sorted alphabetically. (This keeps merge conflicts
down because everybody does not try to append at the end.)

>diff --git a/net/netfilter/xt_osf.c b/net/netfilter/xt_osf.c
>new file mode 100644
>index 0000000..f8b5a16
>--- /dev/null
>+++ b/net/netfilter/xt_osf.c
>+
>+static void ipt_osf_send_connector(struct ipt_osf_user_finger *f,
>+				   const struct sk_buff *skb)
>+{
>+	struct ipt_osf_message *msg = &per_cpu(ipt_osf_mbuf, smp_processor_id());
>+	struct ipt_osf_nlmsg *data = &msg->nlmsg;
>+	struct iphdr *iph = ip_hdr(skb);
>+	struct tcphdr *tcph = tcp_hdr(skb);

iph and tcph are only used in memcpy and could be directly substituted:

	memcpy(&data->ip, ip_hdr(skb), ...)
	memcpy(&data->tcp, tcp_hdr(skb), ...)

>+	memcpy(&msg->cmsg.id, &cn_osf_id, sizeof(struct cn_msg));
>+	msg->cmsg.seq = osf_seq++;
>+	msg->cmsg.ack = 0;
>+	msg->cmsg.len = sizeof(struct ipt_osf_nlmsg);
>+
>+	memcpy(&data->f, f, sizeof(struct ipt_osf_user_finger));
>+	memcpy(&data->ip, iph, sizeof(struct iphdr));

xt_osf does not look at IP options, is that so?

>+	memcpy(&data->tcp, tcph, sizeof(struct tcphdr));

xt_osf does not look at TCP options, is not it?

Note that you cannot directly use tcp_hdr(skb) as the
skb->transport_header has not yet been initialized (it still points
at skb->network_header) because the packet has not yet been seen by
the next handler. This affects PREROUTING, and INPUT chains (and
BROUTING, for ebtables people, but irrelevant here).

The packet may also be fragmented or non-linear.

You actually do the right thing in ipt_osf_match_packet.

>+static inline int ipt_osf_ttl(const struct sk_buff *skb, struct ipt_osf_info *info,

const struct ipt_osf_info *

>+			    unsigned char f_ttl)
>+{
>+	struct iphdr *ip = ip_hdr(skb);
const struct iphdr *iph

>+	if (info->flags & IPT_OSF_TTL) {
>+		if (info->ttl == IPT_OSF_TTL_TRUE)
>+			return (ip->ttl == f_ttl);

redundant pair of ()

>+		if (info->ttl == IPT_OSF_TTL_NOCHECK)
>+			return 1;
>+		else if (ip->ttl <= f_ttl)
>+			return 1;
>+		else {
>+			struct in_device *in_dev = in_dev_get(skb->dev);
>+			int ret = 0;
>+
>+			for_ifa(in_dev) {
>+				if (inet_ifa_match(ip->saddr, ifa)) {
>+					ret = (ip->ttl == f_ttl);
>+					break;
>+				}
>+			}
>+			endfor_ifa(in_dev);
>+
>+			in_dev_put(in_dev);
>+			return ret;
>+		}
>+	}
>+	
>+	return (ip->ttl == f_ttl);

redundant pair of ()

>+}
>+
>+static bool ipt_osf_match_packet(const struct sk_buff *skb,
>+		const struct xt_match_param *p)
>+{
>+	struct ipt_osf_info *info = (struct ipt_osf_info *)p->matchinfo;

no cast needed. const advised.

>+	struct iphdr *ip;
>+	struct tcphdr _tcph, *tcp;
const

>+	int fmatch = FMATCH_WRONG, fcount = 0;
>+	unsigned int optsize = 0, check_WSS = 0;
>+	u16 window, totlen, mss = 0;
>+	bool df;
>+	unsigned char *optp = NULL, *_optp = NULL;
const

>+	unsigned char opts[MAX_IPOPTLEN];

>+	struct ipt_osf_finger *kf;
>+	struct ipt_osf_user_finger *f;
>+	struct ipt_osf_finger_storage *st;
three const, as far as possible

>+
>+	if (!info)
>+		return false;
>+
>+	ip = ip_hdr(skb);
>+	if (!ip)
>+		return false;

ip_hdr is always returning non-NULL anyway.

>+
>+			for (optnum = 0; optnum < f->opt_num; ++optnum) {
>+				if (f->opt[optnum].kind == (*optp)) {
>+					__u32 len = f->opt[optnum].length;
>+					__u8 *optend = optp + len;
const

>[...]
>+	}
>+
>+	if (fcount)
>+		fmatch = FMATCH_OK;
>+
>+	return fmatch == FMATCH_OK;
>+}
>+
>+static struct xt_match ipt_osf_match = {
>+	.name 		= "osf",
>+	.revision	= 0,
>+	.family		= NFPROTO_IPV4,
>+	.proto		= IPPROTO_TCP,
>+	.hooks      	= (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_PRE_ROUTING),

Would allow FORWARD work?

>+	.match 		= ipt_osf_match_packet,
>+	.matchsize	= sizeof(struct ipt_osf_info),
>+	.me		= THIS_MODULE,
>+};
>+


-Jan
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jan Engelhardt March 10, 2009, 4:12 p.m. UTC | #2
>static void osf_init(struct xt_entry_match *m)
>{
>}
>
You can remove this function, as it does no initialization.
Implicit NULL in struct xtables_match is handled.

>		case '2': /* --ttl */
>			if (*flags & IPT_OSF_TTL)
>				exit_error(PARAMETER_PROBLEM, "Can't specify multiple ttl parameter");
>			*flags |= IPT_OSF_TTL;
>			info->flags |= IPT_OSF_TTL;
>			info->ttl = atoi(argv[optind-1]);

This would allow specifying --ttl 12345.

Use of xtables_strtoui (v1.4.3-rc1+git) for bounds checking,
and use of optarg seem beneficial:

	unsigned int num;

	if (!xtables_strtoui(optarg, NULL, &num, 0, UINT8_MAX))
		exit_error(PARAMETER_PROBLEM, "*shrug*");
	info->ttl = num;

>		case '3': /* --log */
>			if (*flags & IPT_OSF_LOG)
>				exit_error(PARAMETER_PROBLEM, "Can't specify multiple log parameter");
>			*flags |= IPT_OSF_LOG;
>			info->loglevel = atoi(argv[optind-1]);

similarly.

>			info->flags |= IPT_OSF_LOG;
>			break;
>[...]
>	return 1;
>}
>
>static void osf_final_check(unsigned int flags)
>{
>	if (!flags)
>		exit_error(PARAMETER_PROBLEM, "OS fingerprint match: You must specify `--genre'");
>}
>
>static struct xtables_match osf_match = {
>	.name		= "osf",
>	.version	= XTABLES_VERSION,
>	.size		= XT_ALIGN(sizeof(struct ipt_osf_info)),
>	.userspacesize	= XT_ALIGN(sizeof(struct ipt_osf_info)),
>	.help		= osf_help,
>	.init		= osf_init,
>	.parse		= osf_parse,
>	.print		= osf_print,
>	.final_check	= osf_final_check,
>	.save		= osf_save,
>	.extra_opts	= osf_opts
>};

Needs .family = NFPROTO_IPV4.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jesper Dangaard Brouer March 10, 2009, 9:01 p.m. UTC | #3
On Tue, 10 Mar 2009, Evgeniy Polyakov wrote:

> +struct ipt_osf_finger {
> +	struct rcu_head			rcu_head;
> +	struct list_head		finger_entry;
> +	struct ipt_osf_user_finger	finger;
> +};

> +struct ipt_osf_finger_storage
> +{
> +	struct list_head		finger_list;
> +	spinlock_t			finger_lock;
> +};
> +
> +/*
> + * Indexed by dont-fragment bit.
> + * It is the only constant value in the fingerprint.
> + */
> +struct ipt_osf_finger_storage ipt_osf_fingers[2];
> +
> +static DEFINE_PER_CPU(struct ipt_osf_message, ipt_osf_mbuf);

> +static void __exit ipt_osf_fini(void)
> +{
> +	struct ipt_osf_finger *f;
> +	int i;
> +
> +	cn_del_callback(&cn_osf_id);
> +	xt_unregister_match(&ipt_osf_match);
> +
> +	rcu_read_lock();
> +	for (i=0; i<ARRAY_SIZE(ipt_osf_fingers); ++i) {
> +		struct ipt_osf_finger_storage *st = &ipt_osf_fingers[i];
> +
> +		list_for_each_entry_rcu(f, &st->finger_list, finger_entry) {

spin_lock(&st->finger_lock); //???
> +			list_del_rcu(&f->finger_entry);
spin_unlock(&st->finger_lock);

> +			call_rcu(&f->rcu_head, ipt_osf_finger_free_rcu);
> +		}
> +	}
> +	rcu_read_unlock();

Should the list_del_rcu() not be protected by a spinlock?


> +	rcu_barrier();

In some of my code I call synchronize_net(), is it enough to call 
rcu_barrier()?

What is the difference between:

  synchronize_rcu()
  synchronize_net()
  rcu_barrier()

Hilsen
   Jesper Brouer

--
-------------------------------------------------------------------
MSc. Master of Computer Science
Dept. of Computer Science, University of Copenhagen
Author of http://www.adsl-optimizer.dk
-------------------------------------------------------------------
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Evgeniy Polyakov March 10, 2009, 9:54 p.m. UTC | #4
Hi.

On Tue, Mar 10, 2009 at 10:01:30PM +0100, Jesper Dangaard Brouer (hawk@diku.dk) wrote:
> >+static void __exit ipt_osf_fini(void)
> >+{
> >+	struct ipt_osf_finger *f;
> >+	int i;
> >+
> >+	cn_del_callback(&cn_osf_id);
> >+	xt_unregister_match(&ipt_osf_match);
> >+
> >+	rcu_read_lock();
> >+	for (i=0; i<ARRAY_SIZE(ipt_osf_fingers); ++i) {
> >+		struct ipt_osf_finger_storage *st = &ipt_osf_fingers[i];
> >+
> >+		list_for_each_entry_rcu(f, &st->finger_list, finger_entry) {
> 
> spin_lock(&st->finger_lock); //???
> >+			list_del_rcu(&f->finger_entry);
> spin_unlock(&st->finger_lock);
> 
> >+			call_rcu(&f->rcu_head, ipt_osf_finger_free_rcu);
> >+		}
> >+	}
> >+	rcu_read_unlock();
> 
> Should the list_del_rcu() not be protected by a spinlock?

Not required at this place - all users are already unregistered and
no code can access this list except module exit path.

> >+	rcu_barrier();
> 
> In some of my code I call synchronize_net(), is it enough to call 
> rcu_barrier()?

It is enough here, rcu_barrier() will wait until all scheduled
call_rcu() are completed, that's what we need. But in some cases we
should only wait for the whole grace period to elapse, then one has to use
synchronize_rcu() and friends. rcu_barrier() will wait for the callbacks
to be executed, while they are executed after grace period has elapsed,
so it implicitly includes synchronize_rcu(), but effectively they are
the same: both functions register rcu callback and wait for the
completion, rcu_barrier() is a bit more enhanced, since it has several
types.

> What is the difference between:
> 
>  synchronize_rcu()
>  synchronize_net()

Those are essentially the same - synchronize_net() has additional
might_sleep()  call. Both will wait until grace period elapced - i.e.
all currently RCU protected sections completed.

>  rcu_barrier()

It will wait until all scheduled rcu callbacks are executed.

So from the description they look different, but implementation
suggestes that effectively they are the same, except that there are a
bit different invocation types for the barrier.
Pablo Neira Ayuso March 11, 2009, 9:54 a.m. UTC | #5
Evgeniy Polyakov wrote:
> Hi.
> 
> Passive OS fingerprinting netfilter module allows to passively detect
> remote OS and perform various netfilter actions based on that knowledge.
> This module compares some data (WS, MSS, options and it's order, ttl, df
> and others) from packets with SYN bit set with dynamically loaded OS
> fingerprints.
> 
> Fingerprint matching rules can be downloaded from OpenBSD source tree
> and loaded via netlink connector into the kernel via special util found
> in archive. It will also listen for events about matching packets.

I like this feature. We have nfnetlink so I don't see why we should use
the netlink connector instead.

BTW, is there any difference with regards to userspace p0f apart from
having this integrated into iptables?
Evgeniy Polyakov March 11, 2009, 10 a.m. UTC | #6
Hi Pablo.

On Wed, Mar 11, 2009 at 10:54:21AM +0100, Pablo Neira Ayuso (pablo@netfilter.org) wrote:
> > Fingerprint matching rules can be downloaded from OpenBSD source tree
> > and loaded via netlink connector into the kernel via special util found
> > in archive. It will also listen for events about matching packets.
> 
> I like this feature. We have nfnetlink so I don't see why we should use
> the netlink connector instead.

OSF exists about 6 years already, netlink configuration was added in
2005, I do not remember if nfnetlink existed those days (IIRC it did
not, since I reused ULOG netlink first), right now I just cleanup
what was written before.

> BTW, is there any difference with regards to userspace p0f apart from
> having this integrated into iptables?

There should be no major differences, there are some tweaks for the
MTU comparison, maybe something else.
Evgeniy Polyakov March 11, 2009, 9:43 p.m. UTC | #7
Hi Jan.

Thanks for the review, I will incorporate the changes and respin it soon.
Patrick McHardy March 16, 2009, 2:40 p.m. UTC | #8
Jesper Dangaard Brouer wrote:
> On Tue, 10 Mar 2009, Evgeniy Polyakov wrote:
> 
>> +            call_rcu(&f->rcu_head, ipt_osf_finger_free_rcu);
>> +        }
>> +    }
>> +    rcu_read_unlock();
> 
> Should the list_del_rcu() not be protected by a spinlock?
> 
> 
>> +    rcu_barrier();
> 
> In some of my code I call synchronize_net(), is it enough to call 
> rcu_barrier()?
> 
> What is the difference between:
> 
>  synchronize_rcu()
>  synchronize_net()
>  rcu_barrier()

synchronize_net() is just a call to synchronize_rcu(), so their
functionality is equivalent. synchronize_net() is however only
supposed to synchronize with RX packet processing, which is usually
not enough for netfilter. So I prefer synchronize_rcu() for clarity.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Patrick McHardy March 16, 2009, 2:42 p.m. UTC | #9
Evgeniy Polyakov wrote:
> Hi Pablo.
> 
> On Wed, Mar 11, 2009 at 10:54:21AM +0100, Pablo Neira Ayuso (pablo@netfilter.org) wrote:
>>> Fingerprint matching rules can be downloaded from OpenBSD source tree
>>> and loaded via netlink connector into the kernel via special util found
>>> in archive. It will also listen for events about matching packets.
>> I like this feature. We have nfnetlink so I don't see why we should use
>> the netlink connector instead.

I fully agree.

> OSF exists about 6 years already, netlink configuration was added in
> 2005, I do not remember if nfnetlink existed those days (IIRC it did
> not, since I reused ULOG netlink first), right now I just cleanup
> what was written before.

We do have nfnetlink today however, so this argument does no longer
apply. I don't mind the order in which things are fixed up of course,
but before merging, it needs to be converted to nfnetlink.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/linux/connector.h b/include/linux/connector.h
index 34f2789..da6595e 100644
--- a/include/linux/connector.h
+++ b/include/linux/connector.h
@@ -39,6 +39,8 @@ 
 #define CN_IDX_V86D			0x4
 #define CN_VAL_V86D_UVESAFB		0x1
 #define CN_IDX_BB			0x5	/* BlackBoard, from the TSP GPL sampling framework */
+#define CN_IDX_OSF			0x6	/* Passive OS fingerprint iptables module */
+#define CN_VAL_OSF			0x0
 
 #define CN_NETLINK_USERS		6
 
diff --git a/include/linux/netfilter/xt_osf.h b/include/linux/netfilter/xt_osf.h
new file mode 100644
index 0000000..a7696be
--- /dev/null
+++ b/include/linux/netfilter/xt_osf.h
@@ -0,0 +1,110 @@ 
+/*
+ * xt_osf.h
+ *
+ * Copyright (c) 2003 Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _IPT_OSF_H
+#define _IPT_OSF_H
+
+#define MAXGENRELEN		32
+#define MAXDETLEN		64
+
+#define IPT_OSF_GENRE		(1<<0)
+#define	IPT_OSF_TTL		(1<<1)
+#define IPT_OSF_LOG		(1<<2)
+#define IPT_OSF_UNUSED		(1<<3)
+#define IPT_OSF_CONNECTOR	(1<<4)
+#define IPT_OSF_INVERT		(1<<5)
+
+#define IPT_OSF_LOGLEVEL_ALL	0
+#define IPT_OSF_LOGLEVEL_FIRST	1
+#define IPT_OSF_LOGLEVEL_ALL_KNOWN	2
+
+#define IPT_OSF_TTL_TRUE	0	/* True ip and fingerprint TTL comparison */
+#define IPT_OSF_TTL_LESS	1	/* Check if ip TTL is less than fingerprint one */
+#define IPT_OSF_TTL_NOCHECK	2	/* Do not compare ip and fingerprint TTL at all */
+
+struct ipt_osf_info {
+	char			genre[MAXGENRELEN];
+	__u32			len;
+	__u32			flags;
+	__u32			loglevel;
+	__u32			ttl;
+};
+
+/*
+ * Wildcard MSS (kind of).
+ * It is used to implement a state machine for the different wildcard values
+ * of the MSS and window sizes.
+ */
+struct ipt_osf_wc {
+	__u32			wc;
+	__u32			val;
+};
+
+/*
+ * This struct represents IANA options
+ * http://www.iana.org/assignments/tcp-parameters
+ */
+struct ipt_osf_opt {
+	__u16			kind, length;
+	struct ipt_osf_wc	wc;
+};
+
+struct ipt_osf_user_finger {
+	struct ipt_osf_wc	wss;
+
+	__u8			ttl, df;
+	__u16			ss, mss;
+	__u16			opt_num;
+
+	char			genre[MAXGENRELEN];
+	char			version[MAXGENRELEN];
+	char			subtype[MAXGENRELEN];
+
+	/* MAX_IPOPTLEN is maximum if all options are NOPs or EOLs */
+	struct ipt_osf_opt	opt[MAX_IPOPTLEN];
+};
+
+struct ipt_osf_nlmsg {
+	struct ipt_osf_user_finger	f;
+	struct iphdr		ip;
+	struct tcphdr		tcp;
+};
+
+/* Defines for IANA option kinds */
+
+enum iana_options {
+	OSFOPT_EOL = 0,		/* End of options */
+	OSFOPT_NOP, 		/* NOP */
+	OSFOPT_MSS, 		/* Maximum segment size */
+	OSFOPT_WSO, 		/* Window scale option */
+	OSFOPT_SACKP,		/* SACK permitted */
+	OSFOPT_SACK,		/* SACK */
+	OSFOPT_ECHO,
+	OSFOPT_ECHOREPLY,
+	OSFOPT_TS,		/* Timestamp option */
+	OSFOPT_POCP,		/* Partial Order Connection Permitted */
+	OSFOPT_POSP,		/* Partial Order Service Profile */
+
+	/* Others are not used in the current OSF */
+	OSFOPT_EMPTY = 255,
+};
+
+#endif				/* _IPT_OSF_H */
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
index c2bac9c..34aafec 100644
--- a/net/netfilter/Kconfig
+++ b/net/netfilter/Kconfig
@@ -853,6 +853,19 @@  config NETFILTER_XT_MATCH_U32
 
 	  Details and examples are in the kernel module source.
 
+config NETFILTER_XT_MATCH_OSF
+	tristate 'Passive OS fingerprint match support'
+	depends on NETFILTER_ADVANCED
+	help
+	  Passive OS fingerprint matching module. Allows to passively match
+	  remote operation system analyzing incoming TCP packets with SYN
+	  bit set.
+
+	  You should download and install rule loading software from
+	  http://www.ioremap.net/projects/osf
+
+	  To compile it as a module, choose M here.  If unsure, say N.
+
 endif # NETFILTER_XTABLES
 
 endmenu
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
index da3d909..ec3a6e0 100644
--- a/net/netfilter/Makefile
+++ b/net/netfilter/Makefile
@@ -89,6 +89,7 @@  obj-$(CONFIG_NETFILTER_XT_MATCH_STRING) += xt_string.o
 obj-$(CONFIG_NETFILTER_XT_MATCH_TCPMSS) += xt_tcpmss.o
 obj-$(CONFIG_NETFILTER_XT_MATCH_TIME) += xt_time.o
 obj-$(CONFIG_NETFILTER_XT_MATCH_U32) += xt_u32.o
+obj-$(CONFIG_NETFILTER_XT_MATCH_OSF) += xt_osf.o
 
 # IPVS
 obj-$(CONFIG_IP_VS) += ipvs/
diff --git a/net/netfilter/xt_osf.c b/net/netfilter/xt_osf.c
new file mode 100644
index 0000000..f8b5a16
--- /dev/null
+++ b/net/netfilter/xt_osf.c
@@ -0,0 +1,475 @@ 
+/*
+ * Copyright (c) 2003+ Evgeniy Polyakov <zbr@ioremap.net>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <linux/connector.h>
+#include <linux/if.h>
+#include <linux/inetdevice.h>
+#include <linux/ip.h>
+#include <linux/list.h>
+#include <linux/percpu.h>
+#include <linux/rculist.h>
+#include <linux/smp.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+
+#include <net/ip.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_osf.h>
+
+struct ipt_osf_finger {
+	struct rcu_head			rcu_head;
+	struct list_head		finger_entry;
+	struct ipt_osf_user_finger	finger;
+};
+
+enum osf_fmatch_states {
+	/* Packet does not match the fingerprint */
+	FMATCH_WRONG = 0,
+	/* Packet matches the fingerprint */
+	FMATCH_OK,
+	/* Options do not match the fingerprint, but header does */
+	FMATCH_OPT_WRONG,
+};
+
+struct ipt_osf_finger_storage
+{
+	struct list_head		finger_list;
+	spinlock_t			finger_lock;
+};
+
+/*
+ * Indexed by dont-fragment bit.
+ * It is the only constant value in the fingerprint.
+ */
+struct ipt_osf_finger_storage ipt_osf_fingers[2];
+
+struct ipt_osf_message {
+	struct cn_msg		cmsg;
+	struct ipt_osf_nlmsg	nlmsg;
+};
+
+static DEFINE_PER_CPU(struct ipt_osf_message, ipt_osf_mbuf);
+
+static struct cb_id cn_osf_id = { CN_IDX_OSF, CN_VAL_OSF };
+static u32 osf_seq;
+
+static void ipt_osf_send_connector(struct ipt_osf_user_finger *f,
+				   const struct sk_buff *skb)
+{
+	struct ipt_osf_message *msg = &per_cpu(ipt_osf_mbuf, smp_processor_id());
+	struct ipt_osf_nlmsg *data = &msg->nlmsg;
+	struct iphdr *iph = ip_hdr(skb);
+	struct tcphdr *tcph = tcp_hdr(skb);
+
+	memcpy(&msg->cmsg.id, &cn_osf_id, sizeof(struct cn_msg));
+	msg->cmsg.seq = osf_seq++;
+	msg->cmsg.ack = 0;
+	msg->cmsg.len = sizeof(struct ipt_osf_nlmsg);
+
+	memcpy(&data->f, f, sizeof(struct ipt_osf_user_finger));
+	memcpy(&data->ip, iph, sizeof(struct iphdr));
+	memcpy(&data->tcp, tcph, sizeof(struct tcphdr));
+
+	cn_netlink_send(&msg->cmsg, CN_IDX_OSF, GFP_ATOMIC);
+}
+
+static inline int ipt_osf_ttl(const struct sk_buff *skb, struct ipt_osf_info *info,
+			    unsigned char f_ttl)
+{
+	struct iphdr *ip = ip_hdr(skb);
+
+	if (info->flags & IPT_OSF_TTL) {
+		if (info->ttl == IPT_OSF_TTL_TRUE)
+			return (ip->ttl == f_ttl);
+		if (info->ttl == IPT_OSF_TTL_NOCHECK)
+			return 1;
+		else if (ip->ttl <= f_ttl)
+			return 1;
+		else {
+			struct in_device *in_dev = in_dev_get(skb->dev);
+			int ret = 0;
+
+			for_ifa(in_dev) {
+				if (inet_ifa_match(ip->saddr, ifa)) {
+					ret = (ip->ttl == f_ttl);
+					break;
+				}
+			}
+			endfor_ifa(in_dev);
+
+			in_dev_put(in_dev);
+			return ret;
+		}
+	}
+	
+	return (ip->ttl == f_ttl);
+}
+
+static bool ipt_osf_match_packet(const struct sk_buff *skb,
+		const struct xt_match_param *p)
+{
+	struct ipt_osf_info *info = (struct ipt_osf_info *)p->matchinfo;
+	struct iphdr *ip;
+	struct tcphdr _tcph, *tcp;
+	int fmatch = FMATCH_WRONG, fcount = 0;
+	unsigned int optsize = 0, check_WSS = 0;
+	u16 window, totlen, mss = 0;
+	bool df;
+	unsigned char *optp = NULL, *_optp = NULL;
+	unsigned char opts[MAX_IPOPTLEN];
+	struct ipt_osf_finger *kf;
+	struct ipt_osf_user_finger *f;
+	struct ipt_osf_finger_storage *st;
+
+	if (!info)
+		return false;
+
+	ip = ip_hdr(skb);
+	if (!ip)
+		return false;
+
+	tcp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(struct tcphdr), &_tcph);
+	if (!tcp)
+		return false;
+
+	if (!tcp->syn)
+		return false;
+
+	totlen = ntohs(ip->tot_len);
+	df = ntohs(ip->frag_off) & IP_DF;
+	window = ntohs(tcp->window);
+
+	if (tcp->doff * 4 > sizeof(struct tcphdr)) {
+		optsize = tcp->doff * 4 - sizeof(struct tcphdr);
+
+		if (optsize > sizeof(opts))
+			optsize = sizeof(opts);
+
+		_optp = optp = skb_header_pointer(skb, ip_hdrlen(skb) + sizeof(struct tcphdr), 
+				optsize, opts);
+	}
+
+	st = &ipt_osf_fingers[df];
+	
+	rcu_read_lock();
+	list_for_each_entry_rcu(kf, &st->finger_list, finger_entry) {
+		f = &kf->finger;
+
+		if (!(info->flags & IPT_OSF_LOG) && strcmp(info->genre, f->genre))
+			continue;
+
+		optp = _optp;
+		fmatch = FMATCH_WRONG;
+
+		if (totlen == f->ss && ipt_osf_ttl(skb, info, f->ttl)) {
+			int foptsize, optnum;
+
+			check_WSS = 0;
+
+			switch (f->wss.wc) {
+			case 0:
+				check_WSS = 0;
+				break;
+			case 'S':
+				check_WSS = 1;
+				break;
+			case 'T':
+				check_WSS = 2;
+				break;
+			case '%':
+				check_WSS = 3;
+				break;
+			default:
+				check_WSS = 4;
+				break;
+			}
+			if (check_WSS == 4)
+				continue;
+
+			/* Check options */
+
+			foptsize = 0;
+			for (optnum = 0; optnum < f->opt_num; ++optnum)
+				foptsize += f->opt[optnum].length;
+
+			if (foptsize > MAX_IPOPTLEN || optsize > MAX_IPOPTLEN || optsize != foptsize)
+				continue;
+
+			for (optnum = 0; optnum < f->opt_num; ++optnum) {
+				if (f->opt[optnum].kind == (*optp)) {
+					__u32 len = f->opt[optnum].length;
+					__u8 *optend = optp + len;
+					int loop_cont = 0;
+
+					fmatch = FMATCH_OK;
+
+					switch (*optp) {
+					case OSFOPT_MSS:
+						mss = optp[3];
+						mss <<= 8;
+						mss |= optp[2];
+
+						mss = ntohs(mss);
+						break;
+					case OSFOPT_TS:
+						loop_cont = 1;
+						break;
+					}
+
+					optp = optend;
+				} else
+					fmatch = FMATCH_OPT_WRONG;
+
+				if (fmatch != FMATCH_OK)
+					break;
+			}
+
+			if (fmatch != FMATCH_OPT_WRONG) {
+				fmatch = FMATCH_WRONG;
+
+				switch (check_WSS) {
+				case 0:
+					if (f->wss.val == 0 || window == f->wss.val)
+						fmatch = FMATCH_OK;
+					break;
+				case 1:	/* MSS */
+#define SMART_MSS_1	1460
+#define SMART_MSS_2	1448
+					if (window == f->wss.val * mss ||
+					    window == f->wss.val * SMART_MSS_1 ||
+					    window == f->wss.val * SMART_MSS_2)
+						fmatch = FMATCH_OK;
+					break;
+				case 2:	/* MTU */
+					if (window == f->wss.val * (mss + 40) ||
+					    window == f->wss.val * (SMART_MSS_1 + 40) ||
+					    window == f->wss.val * (SMART_MSS_2 + 40))
+						fmatch = FMATCH_OK;
+					break;
+				case 3:	/* MOD */
+					if ((window % f->wss.val) == 0)
+						fmatch = FMATCH_OK;
+					break;
+				}
+			}
+			
+			if (fmatch != FMATCH_OK)
+				continue;
+
+			fcount++;
+			if (info->flags & IPT_OSF_LOG)
+				printk(KERN_INFO "%s [%s:%s] : "
+					"%pi4:%d -> %pi4:%d hops=%d\n",
+					f->genre, f->version, f->subtype, 
+					&ip->saddr, ntohs(tcp->source), 
+					&ip->daddr, ntohs(tcp->dest), 
+					f->ttl - ip->ttl);
+
+			if (info->flags & IPT_OSF_CONNECTOR)
+				ipt_osf_send_connector(f, skb);
+
+			if ((info->flags & IPT_OSF_LOG) &&
+			    info->loglevel == IPT_OSF_LOGLEVEL_FIRST)
+				break;
+		}
+	}
+	rcu_read_unlock();
+
+	if (!fcount && (info->flags & (IPT_OSF_LOG | IPT_OSF_CONNECTOR))) {
+		unsigned int i;
+		struct ipt_osf_user_finger fg;
+
+		memset(&fg, 0, sizeof(fg));
+#if 1
+		if (info->flags & IPT_OSF_LOG) {
+			if (info->loglevel != IPT_OSF_LOGLEVEL_ALL_KNOWN)
+				printk(KERN_INFO "Unknown: win: %u, mss: %u, "
+					"totlen: %u, df: %d, ttl: %u : ",
+					window, mss, totlen, df, ip->ttl);
+			else
+				printk(KERN_INFO "");
+			if (_optp) {
+				optp = _optp;
+				for (i = 0; i < optsize; i++)
+					printk("%02X ", optp[i]);
+			}
+
+			printk("%pi4:%u -> %pi4:%u\n",
+			     &ip->saddr, ntohs(tcp->source),
+			     &ip->daddr, ntohs(tcp->dest));
+		}
+#endif
+		if (info->flags & IPT_OSF_CONNECTOR) {
+			fg.wss.val = window;
+			fg.ttl = ip->ttl;
+			fg.df = df;
+			fg.ss = totlen;
+			fg.mss = mss;
+			strncpy(fg.genre, "Unknown", MAXGENRELEN);
+
+			ipt_osf_send_connector(&fg, skb);
+		}
+	}
+
+	if (fcount)
+		fmatch = FMATCH_OK;
+
+	return fmatch == FMATCH_OK;
+}
+
+static struct xt_match ipt_osf_match = {
+	.name 		= "osf",
+	.revision	= 0,
+	.family		= NFPROTO_IPV4,
+	.proto		= IPPROTO_TCP,
+	.hooks      	= (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_PRE_ROUTING),
+	.match 		= ipt_osf_match_packet,
+	.matchsize	= sizeof(struct ipt_osf_info),
+	.me		= THIS_MODULE,
+};
+
+static void ipt_osf_finger_free_rcu(struct rcu_head *rcu_head)
+{
+	struct ipt_osf_finger *f = container_of(rcu_head, struct ipt_osf_finger, rcu_head);
+
+	kfree(f);
+}
+
+static void osf_cn_callback(void *data)
+{
+	struct cn_msg *msg = data;
+	struct ipt_osf_user_finger *f = (struct ipt_osf_user_finger *)(msg + 1);
+	struct ipt_osf_finger *kf = NULL, *sf;
+	struct ipt_osf_finger_storage *st = &ipt_osf_fingers[!!f->df];
+
+	/*
+	 * If msg->ack is set to 0 then we add attached fingerprint,
+	 * otherwise remove, and in this case we do not need to allocate data.
+	 */
+	if (!msg->ack) {
+		kf = kmalloc(sizeof(struct ipt_osf_finger), GFP_KERNEL);
+		if (!kf)
+			return;
+
+		memcpy(&kf->finger, f, sizeof(struct ipt_osf_user_finger));
+	}
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(sf, &st->finger_list, finger_entry) {
+		if (memcmp(&sf->finger, f, sizeof(struct ipt_osf_user_finger)))
+			continue;
+
+		if (msg->ack) {
+			spin_lock_bh(&st->finger_lock);
+			list_del_rcu(&sf->finger_entry);
+			spin_unlock_bh(&st->finger_lock);
+			call_rcu(&sf->rcu_head, ipt_osf_finger_free_rcu);
+		} else {
+			kfree(kf);
+			kf = NULL;
+		}
+
+		break;
+	}
+
+	if (kf) {
+		spin_lock_bh(&st->finger_lock);
+		list_add_tail_rcu(&kf->finger_entry, &st->finger_list);
+		spin_unlock_bh(&st->finger_lock);
+#if 0
+		printk(KERN_INFO "Added rule for %s:%s:%s.\n",
+			kf->finger.genre, kf->finger.version, kf->finger.subtype);
+#endif
+	}
+	rcu_read_unlock();
+}
+
+static int __init ipt_osf_init(void)
+{
+	int err = -EINVAL;
+	int i;
+
+	for (i=0; i<ARRAY_SIZE(ipt_osf_fingers); ++i) {
+		struct ipt_osf_finger_storage *st = &ipt_osf_fingers[i];
+
+		INIT_LIST_HEAD(&st->finger_list);
+		spin_lock_init(&st->finger_lock);
+	}
+
+	err = cn_add_callback(&cn_osf_id, "osf", osf_cn_callback);
+	if (err) {
+		printk(KERN_ERR "Failed (%d) to register OSF connector.\n", err);
+		goto err_out_exit;
+	}
+
+	err = xt_register_match(&ipt_osf_match);
+	if (err) {
+		printk(KERN_ERR "Failed (%d) to register OS fingerprint "
+				"matching module.\n", err);
+		goto err_out_remove;
+	}
+
+	printk(KERN_INFO "Started passive OS fingerprint matching module.\n");
+
+	return 0;
+
+err_out_remove:
+	cn_del_callback(&cn_osf_id);
+err_out_exit:
+	return err;
+}
+
+static void __exit ipt_osf_fini(void)
+{
+	struct ipt_osf_finger *f;
+	int i;
+
+	cn_del_callback(&cn_osf_id);
+	xt_unregister_match(&ipt_osf_match);
+
+	rcu_read_lock();
+	for (i=0; i<ARRAY_SIZE(ipt_osf_fingers); ++i) {
+		struct ipt_osf_finger_storage *st = &ipt_osf_fingers[i];
+
+		list_for_each_entry_rcu(f, &st->finger_list, finger_entry) {
+			list_del_rcu(&f->finger_entry);
+			call_rcu(&f->rcu_head, ipt_osf_finger_free_rcu);
+		}
+	}
+	rcu_read_unlock();
+
+	rcu_barrier();
+
+	printk(KERN_INFO "Passive OS fingerprint matching module finished.\n");
+}
+
+module_init(ipt_osf_init);
+module_exit(ipt_osf_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
+MODULE_DESCRIPTION("Passive OS fingerprint matching.");