[3/3,nft] src: osf: import nfnl_osf.c to load osf fingerprints

Message ID 20180810130200.704-3-ffmancera@riseup.net
State Changes Requested
Delegated to: Pablo Neira
Headers show
Series
  • [1/3,nft] files: osf: copy iptables/utils/pf.os into nftables tree
Related show

Commit Message

Fernando Fernandez Mancera Aug. 10, 2018, 1:02 p.m.
Import iptables/utils/nfnl_osf.c into nftables tree with some changes in order
to load OS fingerprints automatically from pf.os file.

Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
---
 include/linux/netfilter/nfnetlink_osf.h | 119 +++++++
 include/nfnl_osf.h                      |   6 +
 include/osf.h                           |   2 +
 src/Makefile.am                         |   1 +
 src/nfnl_osf.c                          | 449 ++++++++++++++++++++++++
 src/osf.c                               |   2 +
 src/rule.c                              |   5 +
 7 files changed, 584 insertions(+)
 create mode 100644 include/linux/netfilter/nfnetlink_osf.h
 create mode 100644 include/nfnl_osf.h
 create mode 100644 src/nfnl_osf.c

Comments

Fernando Fernandez Mancera Aug. 10, 2018, 4:31 p.m. | #1
I think we should place osf_init in nfnl_osf.h so this way we don't need to include osf.h in rule.c. If you agree I will send another patchset iteration. Thanks.

El 10 de agosto de 2018 15:02:00 CEST, Fernando Fernandez Mancera <ffmancera@riseup.net> escribió:
>Import iptables/utils/nfnl_osf.c into nftables tree with some changes
>in order
>to load OS fingerprints automatically from pf.os file.
>
>Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
>---
> include/linux/netfilter/nfnetlink_osf.h | 119 +++++++
> include/nfnl_osf.h                      |   6 +
> include/osf.h                           |   2 +
> src/Makefile.am                         |   1 +
> src/nfnl_osf.c                          | 449 ++++++++++++++++++++++++
> src/osf.c                               |   2 +
> src/rule.c                              |   5 +
> 7 files changed, 584 insertions(+)
> create mode 100644 include/linux/netfilter/nfnetlink_osf.h
> create mode 100644 include/nfnl_osf.h
> create mode 100644 src/nfnl_osf.c
>
>diff --git a/include/linux/netfilter/nfnetlink_osf.h
>b/include/linux/netfilter/nfnetlink_osf.h
>new file mode 100644
>index 0000000..15a39d2
>--- /dev/null
>+++ b/include/linux/netfilter/nfnetlink_osf.h
>@@ -0,0 +1,119 @@
>+#ifndef _NF_OSF_H
>+#define _NF_OSF_H
>+
>+#include <linux/types.h>
>+
>+#define MAXGENRELEN	32
>+
>+#define NF_OSF_GENRE	(1 << 0)
>+#define NF_OSF_TTL	(1 << 1)
>+#define NF_OSF_LOG	(1 << 2)
>+#define NF_OSF_INVERT	(1 << 3)
>+
>+#define NF_OSF_LOGLEVEL_ALL		0	/* log all matched fingerprints */
>+#define NF_OSF_LOGLEVEL_FIRST		1	/* log only the first matced
>fingerprint */
>+#define NF_OSF_LOGLEVEL_ALL_KNOWN	2	/* do not log unknown packets */
>+
>+#define NF_OSF_TTL_TRUE			0	/* True ip and fingerprint TTL comparison
>*/
>+
>+/* Check if ip TTL is less than fingerprint one */
>+#define NF_OSF_TTL_LESS			1
>+
>+/* Do not compare ip and fingerprint TTL at all */
>+#define NF_OSF_TTL_NOCHECK		2
>+
>+#define NF_OSF_FLAGMASK		(NF_OSF_GENRE | NF_OSF_TTL | \
>+				 NF_OSF_LOG | NF_OSF_INVERT)
>+/* Wildcard MSS (kind of).
>+ * It is used to implement a state machine for the different wildcard
>values
>+ * of the MSS and window sizes.
>+ */
>+struct nf_osf_wc {
>+	__u32	wc;
>+	__u32	val;
>+};
>+
>+/* This struct represents IANA options
>+ * http://www.iana.org/assignments/tcp-parameters
>+ */
>+struct nf_osf_opt {
>+	__u16			kind, length;
>+	struct nf_osf_wc	wc;
>+};
>+
>+struct nf_osf_info {
>+	char	genre[MAXGENRELEN];
>+	__u32	len;
>+	__u32	flags;
>+	__u32	loglevel;
>+	__u32	ttl;
>+};
>+
>+struct nf_osf_user_finger {
>+	struct nf_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 nf_osf_opt	opt[MAX_IPOPTLEN];
>+};
>+
>+struct nf_osf_nlmsg {
>+	struct nf_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,
>+};
>+
>+/*
>+ * Initial window size option state machine: multiple of mss, mtu or
>+ * plain numeric value. Can also be made as plain numeric value which
>+ * is not a multiple of specified value.
>+ */
>+enum nf_osf_window_size_options {
>+	OSF_WSS_PLAIN	= 0,
>+	OSF_WSS_MSS,
>+	OSF_WSS_MTU,
>+	OSF_WSS_MODULO,
>+	OSF_WSS_MAX,
>+};
>+
>+enum nf_osf_attr_type {
>+	OSF_ATTR_UNSPEC,
>+	OSF_ATTR_FINGER,
>+	OSF_ATTR_MAX,
>+};
>+
>+/*
>+ * Add/remove fingerprint from the kernel.
>+ */
>+enum nf_osf_msg_types {
>+	OSF_MSG_ADD,
>+	OSF_MSG_REMOVE,
>+	OSF_MSG_MAX,
>+};
>+
>+#endif /* _NF_OSF_H */
>diff --git a/include/nfnl_osf.h b/include/nfnl_osf.h
>new file mode 100644
>index 0000000..d9287e9
>--- /dev/null
>+++ b/include/nfnl_osf.h
>@@ -0,0 +1,6 @@
>+#ifndef _NFNL_OSF_H
>+#define _NFNL_OSF_H
>+
>+int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del);
>+
>+#endif	/* _NFNL_OSF_H */
>diff --git a/include/osf.h b/include/osf.h
>index 715b04e..0a35b07 100644
>--- a/include/osf.h
>+++ b/include/osf.h
>@@ -1,6 +1,8 @@
> #ifndef NFTABLES_OSF_H
> #define NFTABLES_OSF_H
> 
>+bool osf_init;
>+
> struct expr *osf_expr_alloc(const struct location *loc);
> 
> #endif /* NFTABLES_OSF_H */
>diff --git a/src/Makefile.am b/src/Makefile.am
>index ed3640e..e569029 100644
>--- a/src/Makefile.am
>+++ b/src/Makefile.am
>@@ -57,6 +57,7 @@ libnftables_la_SOURCES =			\
> 		services.c			\
> 		mergesort.c			\
> 		osf.c				\
>+		nfnl_osf.c			\
> 		tcpopt.c			\
> 		socket.c			\
> 		libnftables.c
>diff --git a/src/nfnl_osf.c b/src/nfnl_osf.c
>new file mode 100644
>index 0000000..07bf682
>--- /dev/null
>+++ b/src/nfnl_osf.c
>@@ -0,0 +1,449 @@
>+/*
>+ * Copyright (c) 2005 Evgeniy Polyakov <johnpol@2ka.mxt.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., 51 Franklin Street, Fifth Floor, Boston, MA 
>02110-1301, USA.
>+ */
>+
>+#include <sys/time.h>
>+
>+#include <ctype.h>
>+#include <errno.h>
>+#include <stdlib.h>
>+#include <string.h>
>+#include <time.h>
>+
>+#include <netinet/ip.h>
>+#include <netinet/tcp.h>
>+
>+#include <linux/unistd.h>
>+
>+#include <libmnl/libmnl.h>
>+
>+#include <linux/netfilter/nfnetlink.h>
>+#include <linux/netfilter/nfnetlink_osf.h>
>+#include <mnl.h>
>+#include <nfnl_osf.h>
>+
>+#define OPTDEL			','
>+#define OSFPDEL 		':'
>+#define MAXOPTSTRLEN		128
>+
>+static struct nf_osf_opt IANA_opts[] = {
>+	{ .kind = 0, .length = 1,},
>+	{ .kind=1, .length=1,},
>+	{ .kind=2, .length=4,},
>+	{ .kind=3, .length=3,},
>+	{ .kind=4, .length=2,},
>+	{ .kind=5, .length=1,},		/* SACK length is not defined */
>+	{ .kind=6, .length=6,},
>+	{ .kind=7, .length=6,},
>+	{ .kind=8, .length=10,},
>+	{ .kind=9, .length=2,},
>+	{ .kind=10, .length=3,},
>+	{ .kind=11, .length=1,},		/* CC: Suppose 1 */
>+	{ .kind=12, .length=1,},		/* the same */
>+	{ .kind=13, .length=1,},		/* and here too */
>+	{ .kind=14, .length=3,},
>+	{ .kind=15, .length=1,},		/* TCP Alternate Checksum Data. Length is
>not defined */
>+	{ .kind=16, .length=1,},
>+	{ .kind=17, .length=1,},
>+	{ .kind=18, .length=3,},
>+	{ .kind=19, .length=18,},
>+	{ .kind=20, .length=1,},
>+	{ .kind=21, .length=1,},
>+	{ .kind=22, .length=1,},
>+	{ .kind=23, .length=1,},
>+	{ .kind=24, .length=1,},
>+	{ .kind=25, .length=1,},
>+	{ .kind=26, .length=1,},
>+};
>+
>+static void uloga(const char *f, struct netlink_ctx *ctx, ...)
>+{
>+	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
>+		return;
>+
>+	nft_print(ctx->octx, "%s", f);
>+}
>+
>+static void ulog(const char *f, struct netlink_ctx *ctx, ...)
>+{
>+	char str[64];
>+	struct tm tm;
>+	struct timeval tv;
>+
>+	gettimeofday(&tv, NULL);
>+	localtime_r((time_t *)&tv.tv_sec, &tm);
>+	strftime(str, sizeof(str), "%F %R:%S", &tm);
>+
>+	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
>+		return;
>+
>+	nft_print(ctx->octx, "%s.%lu %ld %s", str, tv.tv_usec,
>+		  syscall(__NR_gettid), f);
>+}
>+
>+#define ulog_err(f, ctx, a...) uloga(f ": %s [%d].\n", ctx, ##a,
>strerror(errno), errno)
>+
>+static char *nf_osf_strchr(char *ptr, char c)
>+{
>+	char *tmp;
>+
>+	tmp = strchr(ptr, c);
>+	if (tmp)
>+		*tmp = '\0';
>+
>+	while (tmp && tmp + 1 && isspace(*(tmp + 1)))
>+		tmp++;
>+
>+	return tmp;
>+}
>+
>+static void nf_osf_parse_opt(struct nf_osf_opt *opt, __u16 *optnum,
>char *obuf, int olen)
>+{
>+	int i, op;
>+	char *ptr, wc;
>+	unsigned long val;
>+
>+	ptr = &obuf[0];
>+	i = 0;
>+	while (ptr != NULL && i < olen && *ptr != 0) {
>+		val = 0;
>+		op = 0;
>+		wc = OSF_WSS_PLAIN;
>+		switch (obuf[i]) {
>+		case 'N':
>+			op = OSFOPT_NOP;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				*ptr = '\0';
>+				ptr++;
>+				i += (int)(ptr - &obuf[i]);
>+			} else
>+				i++;
>+			break;
>+		case 'S':
>+			op = OSFOPT_SACKP;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				*ptr = '\0';
>+				ptr++;
>+				i += (int)(ptr - &obuf[i]);
>+			} else
>+				i++;
>+			break;
>+		case 'T':
>+			op = OSFOPT_TS;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				*ptr = '\0';
>+				ptr++;
>+				i += (int)(ptr - &obuf[i]);
>+			} else
>+				i++;
>+			break;
>+		case 'W':
>+			op = OSFOPT_WSO;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				switch (obuf[i + 1]) {
>+				case '%':
>+					wc = OSF_WSS_MODULO;
>+					break;
>+				case 'S':
>+					wc = OSF_WSS_MSS;
>+					break;
>+				case 'T':
>+					wc = OSF_WSS_MTU;
>+					break;
>+				default:
>+					wc = OSF_WSS_PLAIN;
>+					break;
>+				}
>+
>+				*ptr = '\0';
>+				ptr++;
>+				if (wc)
>+					val = strtoul(&obuf[i + 2], NULL, 10);
>+				else
>+					val = strtoul(&obuf[i + 1], NULL, 10);
>+				i += (int)(ptr - &obuf[i]);
>+
>+			} else
>+				i++;
>+			break;
>+		case 'M':
>+			op = OSFOPT_MSS;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				if (obuf[i + 1] == '%')
>+					wc = OSF_WSS_MODULO;
>+				*ptr = '\0';
>+				ptr++;
>+				if (wc)
>+					val = strtoul(&obuf[i + 2], NULL, 10);
>+				else
>+					val = strtoul(&obuf[i + 1], NULL, 10);
>+				i += (int)(ptr - &obuf[i]);
>+			} else
>+				i++;
>+			break;
>+		case 'E':
>+			op = OSFOPT_EOL;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				*ptr = '\0';
>+				ptr++;
>+				i += (int)(ptr - &obuf[i]);
>+			} else
>+				i++;
>+			break;
>+		default:
>+			op = OSFOPT_EMPTY;
>+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
>+			if (ptr) {
>+				ptr++;
>+				i += (int)(ptr - &obuf[i]);
>+			} else
>+				i++;
>+			break;
>+		}
>+
>+		if (op != OSFOPT_EMPTY) {
>+			opt[*optnum].kind = IANA_opts[op].kind;
>+			opt[*optnum].length = IANA_opts[op].length;
>+			opt[*optnum].wc.wc = wc;
>+			opt[*optnum].wc.val = val;
>+			(*optnum)++;
>+		}
>+	}
>+}
>+
>+static int osf_load_line(char *buffer, int len, int del, struct
>mnl_socket *nl,
>+			 struct netlink_ctx *ctx)
>+{
>+	int i, cnt = 0;
>+	char obuf[MAXOPTSTRLEN];
>+	struct nf_osf_user_finger f;
>+	char *pbeg, *pend;
>+	struct nlmsghdr *nlh;
>+	struct nfgenmsg *nfg;
>+	char buf[MNL_SOCKET_BUFFER_SIZE];
>+
>+	memset(&f, 0, sizeof(struct nf_osf_user_finger));
>+
>+	ulog("Loading '%s'.\n", ctx, buffer);
>+
>+	for (i = 0; i < len && buffer[i] != '\0'; ++i) {
>+		if (buffer[i] == ':')
>+			cnt++;
>+	}
>+
>+	if (cnt != 8) {
>+		ulog("Wrong input line '%s': cnt: %d, must be 8, i: %d, must be
>%d.\n", ctx, buffer, cnt, i, len);
>+		return -EINVAL;
>+	}
>+
>+	memset(obuf, 0, sizeof(obuf));
>+
>+	pbeg = buffer;
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		if (pbeg[0] == 'S') {
>+			f.wss.wc = OSF_WSS_MSS;
>+			if (pbeg[1] == '%')
>+				f.wss.val = strtoul(&pbeg[2], NULL, 10);
>+			else if (pbeg[1] == '*')
>+				f.wss.val = 0;
>+			else
>+				f.wss.val = strtoul(&pbeg[1], NULL, 10);
>+		} else if (pbeg[0] == 'T') {
>+			f.wss.wc = OSF_WSS_MTU;
>+			if (pbeg[1] == '%')
>+				f.wss.val = strtoul(&pbeg[2], NULL, 10);
>+			else if (pbeg[1] == '*')
>+				f.wss.val = 0;
>+			else
>+				f.wss.val = strtoul(&pbeg[1], NULL, 10);
>+		} else if (pbeg[0] == '%') {
>+			f.wss.wc = OSF_WSS_MODULO;
>+			f.wss.val = strtoul(&pbeg[1], NULL, 10);
>+		} else if (isdigit(pbeg[0])) {
>+			f.wss.wc = OSF_WSS_PLAIN;
>+			f.wss.val = strtoul(&pbeg[0], NULL, 10);
>+		}
>+
>+		pbeg = pend + 1;
>+	}
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		f.ttl = strtoul(pbeg, NULL, 10);
>+		pbeg = pend + 1;
>+	}
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		f.df = strtoul(pbeg, NULL, 10);
>+		pbeg = pend + 1;
>+	}
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		f.ss = strtoul(pbeg, NULL, 10);
>+		pbeg = pend + 1;
>+	}
>+
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		cnt = snprintf(obuf, sizeof(obuf), "%s,", pbeg);
>+		pbeg = pend + 1;
>+	}
>+
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		if (pbeg[0] == '@' || pbeg[0] == '*')
>+			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg + 1);
>+		else
>+			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg);
>+		pbeg = pend + 1;
>+	}
>+
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		cnt = snprintf(f.version, sizeof(f.version), "%s", pbeg);
>+		pbeg = pend + 1;
>+	}
>+
>+	pend = nf_osf_strchr(pbeg, OSFPDEL);
>+	if (pend) {
>+		*pend = '\0';
>+		cnt =
>+		    snprintf(f.subtype, sizeof(f.subtype), "%s", pbeg);
>+		pbeg = pend + 1;
>+	}
>+
>+	nf_osf_parse_opt(f.opt, &f.opt_num, obuf, sizeof(obuf));
>+
>+	memset(buf, 0, sizeof(buf));
>+
>+	if (del) {
>+		nlh = nftnl_nlmsg_build_hdr(buf, (NFNL_SUBSYS_OSF << 8) |
>+					    OSF_MSG_REMOVE, AF_UNSPEC,
>+					    NLM_F_REQUEST | NLM_F_ACK,
>+					    ctx->seqnum);
>+
>+		nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
>+		nfg->nfgen_family = AF_UNSPEC;
>+		nfg->version = NFNETLINK_V0;
>+		nfg->res_id = 0;
>+	} else {
>+		nlh = nftnl_nlmsg_build_hdr(buf, (NFNL_SUBSYS_OSF << 8) |
>+					    OSF_MSG_ADD, AF_UNSPEC,
>+					    NLM_F_REQUEST | NLM_F_CREATE |
>+					    NLM_F_ACK, ctx->seqnum);
>+
>+		nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
>+		nfg->nfgen_family = AF_UNSPEC;
>+		nfg->version = NFNETLINK_V0;
>+		nfg->res_id = 0;
>+
>+		mnl_attr_put(nlh, OSF_ATTR_FINGER, sizeof(struct
>nf_osf_user_finger), &f);
>+	}
>+
>+	return nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, 0, NULL);
>+}
>+
>+static int osf_load_entries(const char *path, int del, struct
>mnl_socket *nl,
>+			    struct netlink_ctx *ctx)
>+{
>+	FILE *inf;
>+	int err = 0;
>+	char buf[1024];
>+
>+	inf = fopen(path, "r");
>+	if (!inf) {
>+		ulog_err("Failed to open file '%s'", ctx, path);
>+		return -1;
>+	}
>+
>+	while(fgets(buf, sizeof(buf), inf)) {
>+		int len;
>+
>+		if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
>+			continue;
>+
>+		len = strlen(buf) - 1;
>+
>+		if (len <= 0)
>+			continue;
>+
>+		buf[len] = '\0';
>+
>+		err = osf_load_line(buf, len, del, nl, ctx);
>+		if (err)
>+			break;
>+
>+		memset(buf, 0, sizeof(buf));
>+	}
>+
>+	fclose(inf);
>+	return err;
>+}
>+
>+int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del)
>+{
>+	int err;
>+	struct mnl_socket *nl;
>+	const char *fingerprints = "files/osf/pf.os";
>+
>+	if (!fingerprints) {
>+		err = -ENOENT;
>+		goto err_out_exit;
>+	}
>+
>+	nl = mnl_socket_open(NETLINK_NETFILTER);
>+	if (nl == NULL) {
>+		err = -EINVAL;
>+		ulog_err("Failed to open mnl socket", ctx);
>+		goto err_out_exit;
>+	}
>+
>+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
>+		err = -EINVAL;
>+		ulog_err("Failed to bind mnl socket", ctx);
>+		goto err_out_exit;
>+	}
>+
>+#ifndef NFNL_SUBSYS_OSF
>+#define NFNL_SUBSYS_OSF	5
>+#endif
>+
>+	err = osf_load_entries(fingerprints, del, nl, ctx);
>+	if (err < 0)
>+		goto err_out_close;
>+
>+	return 0;
>+
>+err_out_close:
>+	mnl_socket_close(nl);
>+err_out_exit:
>+	return err;
>+}
>diff --git a/src/osf.c b/src/osf.c
>index 131d54e..210bfbe 100644
>--- a/src/osf.c
>+++ b/src/osf.c
>@@ -3,6 +3,7 @@
> #include <utils.h>
> #include <string.h>
> #include <osf.h>
>+#include <nfnl_osf.h>
> 
>static void osf_expr_print(const struct expr *expr, struct output_ctx
>*octx)
> {
>@@ -26,6 +27,7 @@ struct expr *osf_expr_alloc(const struct location
>*loc)
> 	const struct datatype *type = &string_type;
> 	struct expr *expr;
> 
>+	osf_init = true;
> 	expr = expr_alloc(loc, &osf_expr_ops, type,
> 			  BYTEORDER_HOST_ENDIAN, len);
> 
>diff --git a/src/rule.c b/src/rule.c
>index 7a7ac73..dc1ce8b 100644
>--- a/src/rule.c
>+++ b/src/rule.c
>@@ -22,6 +22,8 @@
> #include <netdb.h>
> #include <netlink.h>
> #include <json.h>
>+#include <nfnl_osf.h>
>+#include <osf.h>
> 
> #include <libnftnl/common.h>
> #include <libnftnl/ruleset.h>
>@@ -2135,6 +2137,9 @@ int do_command(struct netlink_ctx *ctx, struct
>cmd *cmd)
> 	default:
> 		BUG("invalid command object type %u\n", cmd->obj);
> 	}
>+
>+	if (osf_init)
>+		nfnl_osf_load_fingerprints(ctx, 0);
> }
> 
> static int payload_match_stmt_cmp(const void *p1, const void *p2)
Pablo Neira Ayuso Aug. 11, 2018, 10:03 a.m. | #2
On Fri, Aug 10, 2018 at 03:02:00PM +0200, Fernando Fernandez Mancera wrote:
> Import iptables/utils/nfnl_osf.c into nftables tree with some changes in order
> to load OS fingerprints automatically from pf.os file.
> 
> Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
> ---
>  include/linux/netfilter/nfnetlink_osf.h | 119 +++++++
>  include/nfnl_osf.h                      |   6 +
>  include/osf.h                           |   2 +
>  src/Makefile.am                         |   1 +
>  src/nfnl_osf.c                          | 449 ++++++++++++++++++++++++
>  src/osf.c                               |   2 +
>  src/rule.c                              |   5 +
>  7 files changed, 584 insertions(+)
>  create mode 100644 include/linux/netfilter/nfnetlink_osf.h
>  create mode 100644 include/nfnl_osf.h
>  create mode 100644 src/nfnl_osf.c
> 
> diff --git a/include/linux/netfilter/nfnetlink_osf.h b/include/linux/netfilter/nfnetlink_osf.h
> new file mode 100644
> index 0000000..15a39d2
> --- /dev/null
> +++ b/include/linux/netfilter/nfnetlink_osf.h
> @@ -0,0 +1,119 @@
> +#ifndef _NF_OSF_H
> +#define _NF_OSF_H
> +
> +#include <linux/types.h>
> +
> +#define MAXGENRELEN	32
> +
> +#define NF_OSF_GENRE	(1 << 0)
> +#define NF_OSF_TTL	(1 << 1)
> +#define NF_OSF_LOG	(1 << 2)
> +#define NF_OSF_INVERT	(1 << 3)
> +
> +#define NF_OSF_LOGLEVEL_ALL		0	/* log all matched fingerprints */
> +#define NF_OSF_LOGLEVEL_FIRST		1	/* log only the first matced fingerprint */
> +#define NF_OSF_LOGLEVEL_ALL_KNOWN	2	/* do not log unknown packets */
> +
> +#define NF_OSF_TTL_TRUE			0	/* True ip and fingerprint TTL comparison */
> +
> +/* Check if ip TTL is less than fingerprint one */
> +#define NF_OSF_TTL_LESS			1
> +
> +/* Do not compare ip and fingerprint TTL at all */
> +#define NF_OSF_TTL_NOCHECK		2
> +
> +#define NF_OSF_FLAGMASK		(NF_OSF_GENRE | NF_OSF_TTL | \
> +				 NF_OSF_LOG | NF_OSF_INVERT)
> +/* Wildcard MSS (kind of).
> + * It is used to implement a state machine for the different wildcard values
> + * of the MSS and window sizes.
> + */
> +struct nf_osf_wc {
> +	__u32	wc;
> +	__u32	val;
> +};
> +
> +/* This struct represents IANA options
> + * http://www.iana.org/assignments/tcp-parameters
> + */
> +struct nf_osf_opt {
> +	__u16			kind, length;
> +	struct nf_osf_wc	wc;
> +};
> +
> +struct nf_osf_info {
> +	char	genre[MAXGENRELEN];
> +	__u32	len;
> +	__u32	flags;
> +	__u32	loglevel;
> +	__u32	ttl;
> +};
> +
> +struct nf_osf_user_finger {
> +	struct nf_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 nf_osf_opt	opt[MAX_IPOPTLEN];
> +};
> +
> +struct nf_osf_nlmsg {
> +	struct nf_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,
> +};
> +
> +/*
> + * Initial window size option state machine: multiple of mss, mtu or
> + * plain numeric value. Can also be made as plain numeric value which
> + * is not a multiple of specified value.
> + */
> +enum nf_osf_window_size_options {
> +	OSF_WSS_PLAIN	= 0,
> +	OSF_WSS_MSS,
> +	OSF_WSS_MTU,
> +	OSF_WSS_MODULO,
> +	OSF_WSS_MAX,
> +};
> +
> +enum nf_osf_attr_type {
> +	OSF_ATTR_UNSPEC,
> +	OSF_ATTR_FINGER,
> +	OSF_ATTR_MAX,
> +};
> +
> +/*
> + * Add/remove fingerprint from the kernel.
> + */
> +enum nf_osf_msg_types {
> +	OSF_MSG_ADD,
> +	OSF_MSG_REMOVE,
> +	OSF_MSG_MAX,
> +};
> +
> +#endif /* _NF_OSF_H */
> diff --git a/include/nfnl_osf.h b/include/nfnl_osf.h
> new file mode 100644
> index 0000000..d9287e9
> --- /dev/null
> +++ b/include/nfnl_osf.h
> @@ -0,0 +1,6 @@
> +#ifndef _NFNL_OSF_H
> +#define _NFNL_OSF_H
> +
> +int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del);
> +
> +#endif	/* _NFNL_OSF_H */
> diff --git a/include/osf.h b/include/osf.h
> index 715b04e..0a35b07 100644
> --- a/include/osf.h
> +++ b/include/osf.h
> @@ -1,6 +1,8 @@
>  #ifndef NFTABLES_OSF_H
>  #define NFTABLES_OSF_H
>  
> +bool osf_init;

I think you can probably place osf_init in struct netlink_ctx?

>  struct expr *osf_expr_alloc(const struct location *loc);
>  
>  #endif /* NFTABLES_OSF_H */
> diff --git a/src/Makefile.am b/src/Makefile.am
> index ed3640e..e569029 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -57,6 +57,7 @@ libnftables_la_SOURCES =			\
>  		services.c			\
>  		mergesort.c			\
>  		osf.c				\
> +		nfnl_osf.c			\
>  		tcpopt.c			\
>  		socket.c			\
>  		libnftables.c
> diff --git a/src/nfnl_osf.c b/src/nfnl_osf.c
> new file mode 100644
> index 0000000..07bf682
> --- /dev/null
> +++ b/src/nfnl_osf.c
> @@ -0,0 +1,449 @@
> +/*
> + * Copyright (c) 2005 Evgeniy Polyakov <johnpol@2ka.mxt.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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
> + */
> +
> +#include <sys/time.h>
> +
> +#include <ctype.h>
> +#include <errno.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <time.h>
> +
> +#include <netinet/ip.h>
> +#include <netinet/tcp.h>
> +
> +#include <linux/unistd.h>
> +
> +#include <libmnl/libmnl.h>
> +
> +#include <linux/netfilter/nfnetlink.h>
> +#include <linux/netfilter/nfnetlink_osf.h>
> +#include <mnl.h>
> +#include <nfnl_osf.h>
> +
> +#define OPTDEL			','
> +#define OSFPDEL 		':'
> +#define MAXOPTSTRLEN		128
> +
> +static struct nf_osf_opt IANA_opts[] = {
> +	{ .kind = 0, .length = 1,},
> +	{ .kind=1, .length=1,},
> +	{ .kind=2, .length=4,},
> +	{ .kind=3, .length=3,},
> +	{ .kind=4, .length=2,},
> +	{ .kind=5, .length=1,},		/* SACK length is not defined */
> +	{ .kind=6, .length=6,},
> +	{ .kind=7, .length=6,},
> +	{ .kind=8, .length=10,},
> +	{ .kind=9, .length=2,},
> +	{ .kind=10, .length=3,},
> +	{ .kind=11, .length=1,},		/* CC: Suppose 1 */
> +	{ .kind=12, .length=1,},		/* the same */
> +	{ .kind=13, .length=1,},		/* and here too */
> +	{ .kind=14, .length=3,},
> +	{ .kind=15, .length=1,},		/* TCP Alternate Checksum Data. Length is not defined */
> +	{ .kind=16, .length=1,},
> +	{ .kind=17, .length=1,},
> +	{ .kind=18, .length=3,},
> +	{ .kind=19, .length=18,},
> +	{ .kind=20, .length=1,},
> +	{ .kind=21, .length=1,},
> +	{ .kind=22, .length=1,},
> +	{ .kind=23, .length=1,},
> +	{ .kind=24, .length=1,},
> +	{ .kind=25, .length=1,},
> +	{ .kind=26, .length=1,},
> +};
> +
> +static void uloga(const char *f, struct netlink_ctx *ctx, ...)
> +{
> +	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
> +		return;
> +
> +	nft_print(ctx->octx, "%s", f);
> +}

I think you can use uloga() all the time, so you can remove ulog()
function.

> +static void ulog(const char *f, struct netlink_ctx *ctx, ...)
> +{
> +	char str[64];
> +	struct tm tm;
> +	struct timeval tv;
> +
> +	gettimeofday(&tv, NULL);
> +	localtime_r((time_t *)&tv.tv_sec, &tm);
> +	strftime(str, sizeof(str), "%F %R:%S", &tm);
> +
> +	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
> +		return;
> +
> +	nft_print(ctx->octx, "%s.%lu %ld %s", str, tv.tv_usec,
> +		  syscall(__NR_gettid), f);
> +}
> +
> +#define ulog_err(f, ctx, a...) uloga(f ": %s [%d].\n", ctx, ##a, strerror(errno), errno)

And this macro too.

Other than that, this looks good to me, thanks.
Fernando Fernandez Mancera Aug. 11, 2018, 3:17 p.m. | #3
On 08/11/2018 12:03 PM, Pablo Neira Ayuso wrote:
>> +#endif /* _NF_OSF_H */
>> diff --git a/include/nfnl_osf.h b/include/nfnl_osf.h
>> new file mode 100644
>> index 0000000..d9287e9
>> --- /dev/null
>> +++ b/include/nfnl_osf.h
>> @@ -0,0 +1,6 @@
>> +#ifndef _NFNL_OSF_H
>> +#define _NFNL_OSF_H
>> +
>> +int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del);
>> +
>> +#endif	/* _NFNL_OSF_H */
>> diff --git a/include/osf.h b/include/osf.h
>> index 715b04e..0a35b07 100644
>> --- a/include/osf.h
>> +++ b/include/osf.h
>> @@ -1,6 +1,8 @@
>>   #ifndef NFTABLES_OSF_H
>>   #define NFTABLES_OSF_H
>>   
>> +bool osf_init;
> 
> I think you can probably place osf_init in struct netlink_ctx?
>

If we place osf_init in struct netlink_ctx we will need to modify 
osf_expr_alloc() and I am not sure if we can get access to netlink_ctx 
from netlink_parse_osf() in netlink_delinearize.c. Also we will need 
access to netlink_ctx from parser_bison.y.

So I propose to add osf_init in nfnl_osf.h in order to have only one 
extra include in rule.c. Thanks.

>>   struct expr *osf_expr_alloc(const struct location *loc);
>>   
>>   #endif /* NFTABLES_OSF_H */
>> diff --git a/src/Makefile.am b/src/Makefile.am
>> index ed3640e..e569029 100644
>> --- a/src/Makefile.am
>> +++ b/src/Makefile.am
>> @@ -57,6 +57,7 @@ libnftables_la_SOURCES =			\
>>   		services.c			\
>>   		mergesort.c			\
>>   		osf.c				\
>> +		nfnl_osf.c			\
>>   		tcpopt.c			\
>>   		socket.c			\
>>   		libnftables.c
>> diff --git a/src/nfnl_osf.c b/src/nfnl_osf.c
>> new file mode 100644
>> index 0000000..07bf682
>> --- /dev/null
>> +++ b/src/nfnl_osf.c
>> @@ -0,0 +1,449 @@
>> +/*
>> + * Copyright (c) 2005 Evgeniy Polyakov <johnpol@2ka.mxt.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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
>> + */
>> +
>> +#include <sys/time.h>
>> +
>> +#include <ctype.h>
>> +#include <errno.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <time.h>
>> +
>> +#include <netinet/ip.h>
>> +#include <netinet/tcp.h>
>> +
>> +#include <linux/unistd.h>
>> +
>> +#include <libmnl/libmnl.h>
>> +
>> +#include <linux/netfilter/nfnetlink.h>
>> +#include <linux/netfilter/nfnetlink_osf.h>
>> +#include <mnl.h>
>> +#include <nfnl_osf.h>
>> +
>> +#define OPTDEL			','
>> +#define OSFPDEL 		':'
>> +#define MAXOPTSTRLEN		128
>> +
>> +static struct nf_osf_opt IANA_opts[] = {
>> +	{ .kind = 0, .length = 1,},
>> +	{ .kind=1, .length=1,},
>> +	{ .kind=2, .length=4,},
>> +	{ .kind=3, .length=3,},
>> +	{ .kind=4, .length=2,},
>> +	{ .kind=5, .length=1,},		/* SACK length is not defined */
>> +	{ .kind=6, .length=6,},
>> +	{ .kind=7, .length=6,},
>> +	{ .kind=8, .length=10,},
>> +	{ .kind=9, .length=2,},
>> +	{ .kind=10, .length=3,},
>> +	{ .kind=11, .length=1,},		/* CC: Suppose 1 */
>> +	{ .kind=12, .length=1,},		/* the same */
>> +	{ .kind=13, .length=1,},		/* and here too */
>> +	{ .kind=14, .length=3,},
>> +	{ .kind=15, .length=1,},		/* TCP Alternate Checksum Data. Length is not defined */
>> +	{ .kind=16, .length=1,},
>> +	{ .kind=17, .length=1,},
>> +	{ .kind=18, .length=3,},
>> +	{ .kind=19, .length=18,},
>> +	{ .kind=20, .length=1,},
>> +	{ .kind=21, .length=1,},
>> +	{ .kind=22, .length=1,},
>> +	{ .kind=23, .length=1,},
>> +	{ .kind=24, .length=1,},
>> +	{ .kind=25, .length=1,},
>> +	{ .kind=26, .length=1,},
>> +};
>> +
>> +static void uloga(const char *f, struct netlink_ctx *ctx, ...)
>> +{
>> +	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
>> +		return;
>> +
>> +	nft_print(ctx->octx, "%s", f);
>> +}
> 
> I think you can use uloga() all the time, so you can remove ulog()
> function.
> 

I agree. Changes done.

>> +static void ulog(const char *f, struct netlink_ctx *ctx, ...)
>> +{
>> +	char str[64];
>> +	struct tm tm;
>> +	struct timeval tv;
>> +
>> +	gettimeofday(&tv, NULL);
>> +	localtime_r((time_t *)&tv.tv_sec, &tm);
>> +	strftime(str, sizeof(str), "%F %R:%S", &tm);
>> +
>> +	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
>> +		return;
>> +
>> +	nft_print(ctx->octx, "%s.%lu %ld %s", str, tv.tv_usec,
>> +		  syscall(__NR_gettid), f);
>> +}
>> +
>> +#define ulog_err(f, ctx, a...) uloga(f ": %s [%d].\n", ctx, ##a, strerror(errno), errno)
> 
> And this macro too.
> 
> Other than that, this looks good to me, thanks.
>
Pablo Neira Ayuso Aug. 13, 2018, 9:59 a.m. | #4
On Sat, Aug 11, 2018 at 05:17:29PM +0200, Fernando Fernandez Mancera wrote:
[...]
> If we place osf_init in struct netlink_ctx we will need to modify
> osf_expr_alloc() and I am not sure if we can get access to netlink_ctx from
> netlink_parse_osf() in netlink_delinearize.c. Also we will need access to
> netlink_ctx from parser_bison.y.
> 
> So I propose to add osf_init in nfnl_osf.h in order to have only one extra
> include in rule.c. Thanks.

OK.

Would you send a v2?

Thanks.

Patch

diff --git a/include/linux/netfilter/nfnetlink_osf.h b/include/linux/netfilter/nfnetlink_osf.h
new file mode 100644
index 0000000..15a39d2
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink_osf.h
@@ -0,0 +1,119 @@ 
+#ifndef _NF_OSF_H
+#define _NF_OSF_H
+
+#include <linux/types.h>
+
+#define MAXGENRELEN	32
+
+#define NF_OSF_GENRE	(1 << 0)
+#define NF_OSF_TTL	(1 << 1)
+#define NF_OSF_LOG	(1 << 2)
+#define NF_OSF_INVERT	(1 << 3)
+
+#define NF_OSF_LOGLEVEL_ALL		0	/* log all matched fingerprints */
+#define NF_OSF_LOGLEVEL_FIRST		1	/* log only the first matced fingerprint */
+#define NF_OSF_LOGLEVEL_ALL_KNOWN	2	/* do not log unknown packets */
+
+#define NF_OSF_TTL_TRUE			0	/* True ip and fingerprint TTL comparison */
+
+/* Check if ip TTL is less than fingerprint one */
+#define NF_OSF_TTL_LESS			1
+
+/* Do not compare ip and fingerprint TTL at all */
+#define NF_OSF_TTL_NOCHECK		2
+
+#define NF_OSF_FLAGMASK		(NF_OSF_GENRE | NF_OSF_TTL | \
+				 NF_OSF_LOG | NF_OSF_INVERT)
+/* Wildcard MSS (kind of).
+ * It is used to implement a state machine for the different wildcard values
+ * of the MSS and window sizes.
+ */
+struct nf_osf_wc {
+	__u32	wc;
+	__u32	val;
+};
+
+/* This struct represents IANA options
+ * http://www.iana.org/assignments/tcp-parameters
+ */
+struct nf_osf_opt {
+	__u16			kind, length;
+	struct nf_osf_wc	wc;
+};
+
+struct nf_osf_info {
+	char	genre[MAXGENRELEN];
+	__u32	len;
+	__u32	flags;
+	__u32	loglevel;
+	__u32	ttl;
+};
+
+struct nf_osf_user_finger {
+	struct nf_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 nf_osf_opt	opt[MAX_IPOPTLEN];
+};
+
+struct nf_osf_nlmsg {
+	struct nf_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,
+};
+
+/*
+ * Initial window size option state machine: multiple of mss, mtu or
+ * plain numeric value. Can also be made as plain numeric value which
+ * is not a multiple of specified value.
+ */
+enum nf_osf_window_size_options {
+	OSF_WSS_PLAIN	= 0,
+	OSF_WSS_MSS,
+	OSF_WSS_MTU,
+	OSF_WSS_MODULO,
+	OSF_WSS_MAX,
+};
+
+enum nf_osf_attr_type {
+	OSF_ATTR_UNSPEC,
+	OSF_ATTR_FINGER,
+	OSF_ATTR_MAX,
+};
+
+/*
+ * Add/remove fingerprint from the kernel.
+ */
+enum nf_osf_msg_types {
+	OSF_MSG_ADD,
+	OSF_MSG_REMOVE,
+	OSF_MSG_MAX,
+};
+
+#endif /* _NF_OSF_H */
diff --git a/include/nfnl_osf.h b/include/nfnl_osf.h
new file mode 100644
index 0000000..d9287e9
--- /dev/null
+++ b/include/nfnl_osf.h
@@ -0,0 +1,6 @@ 
+#ifndef _NFNL_OSF_H
+#define _NFNL_OSF_H
+
+int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del);
+
+#endif	/* _NFNL_OSF_H */
diff --git a/include/osf.h b/include/osf.h
index 715b04e..0a35b07 100644
--- a/include/osf.h
+++ b/include/osf.h
@@ -1,6 +1,8 @@ 
 #ifndef NFTABLES_OSF_H
 #define NFTABLES_OSF_H
 
+bool osf_init;
+
 struct expr *osf_expr_alloc(const struct location *loc);
 
 #endif /* NFTABLES_OSF_H */
diff --git a/src/Makefile.am b/src/Makefile.am
index ed3640e..e569029 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -57,6 +57,7 @@  libnftables_la_SOURCES =			\
 		services.c			\
 		mergesort.c			\
 		osf.c				\
+		nfnl_osf.c			\
 		tcpopt.c			\
 		socket.c			\
 		libnftables.c
diff --git a/src/nfnl_osf.c b/src/nfnl_osf.c
new file mode 100644
index 0000000..07bf682
--- /dev/null
+++ b/src/nfnl_osf.c
@@ -0,0 +1,449 @@ 
+/*
+ * Copyright (c) 2005 Evgeniy Polyakov <johnpol@2ka.mxt.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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+#include <linux/unistd.h>
+
+#include <libmnl/libmnl.h>
+
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nfnetlink_osf.h>
+#include <mnl.h>
+#include <nfnl_osf.h>
+
+#define OPTDEL			','
+#define OSFPDEL 		':'
+#define MAXOPTSTRLEN		128
+
+static struct nf_osf_opt IANA_opts[] = {
+	{ .kind = 0, .length = 1,},
+	{ .kind=1, .length=1,},
+	{ .kind=2, .length=4,},
+	{ .kind=3, .length=3,},
+	{ .kind=4, .length=2,},
+	{ .kind=5, .length=1,},		/* SACK length is not defined */
+	{ .kind=6, .length=6,},
+	{ .kind=7, .length=6,},
+	{ .kind=8, .length=10,},
+	{ .kind=9, .length=2,},
+	{ .kind=10, .length=3,},
+	{ .kind=11, .length=1,},		/* CC: Suppose 1 */
+	{ .kind=12, .length=1,},		/* the same */
+	{ .kind=13, .length=1,},		/* and here too */
+	{ .kind=14, .length=3,},
+	{ .kind=15, .length=1,},		/* TCP Alternate Checksum Data. Length is not defined */
+	{ .kind=16, .length=1,},
+	{ .kind=17, .length=1,},
+	{ .kind=18, .length=3,},
+	{ .kind=19, .length=18,},
+	{ .kind=20, .length=1,},
+	{ .kind=21, .length=1,},
+	{ .kind=22, .length=1,},
+	{ .kind=23, .length=1,},
+	{ .kind=24, .length=1,},
+	{ .kind=25, .length=1,},
+	{ .kind=26, .length=1,},
+};
+
+static void uloga(const char *f, struct netlink_ctx *ctx, ...)
+{
+	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
+		return;
+
+	nft_print(ctx->octx, "%s", f);
+}
+
+static void ulog(const char *f, struct netlink_ctx *ctx, ...)
+{
+	char str[64];
+	struct tm tm;
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
+	localtime_r((time_t *)&tv.tv_sec, &tm);
+	strftime(str, sizeof(str), "%F %R:%S", &tm);
+
+	if (!(ctx->debug_mask & NFT_DEBUG_NETLINK))
+		return;
+
+	nft_print(ctx->octx, "%s.%lu %ld %s", str, tv.tv_usec,
+		  syscall(__NR_gettid), f);
+}
+
+#define ulog_err(f, ctx, a...) uloga(f ": %s [%d].\n", ctx, ##a, strerror(errno), errno)
+
+static char *nf_osf_strchr(char *ptr, char c)
+{
+	char *tmp;
+
+	tmp = strchr(ptr, c);
+	if (tmp)
+		*tmp = '\0';
+
+	while (tmp && tmp + 1 && isspace(*(tmp + 1)))
+		tmp++;
+
+	return tmp;
+}
+
+static void nf_osf_parse_opt(struct nf_osf_opt *opt, __u16 *optnum, char *obuf, int olen)
+{
+	int i, op;
+	char *ptr, wc;
+	unsigned long val;
+
+	ptr = &obuf[0];
+	i = 0;
+	while (ptr != NULL && i < olen && *ptr != 0) {
+		val = 0;
+		op = 0;
+		wc = OSF_WSS_PLAIN;
+		switch (obuf[i]) {
+		case 'N':
+			op = OSFOPT_NOP;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'S':
+			op = OSFOPT_SACKP;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'T':
+			op = OSFOPT_TS;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'W':
+			op = OSFOPT_WSO;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				switch (obuf[i + 1]) {
+				case '%':
+					wc = OSF_WSS_MODULO;
+					break;
+				case 'S':
+					wc = OSF_WSS_MSS;
+					break;
+				case 'T':
+					wc = OSF_WSS_MTU;
+					break;
+				default:
+					wc = OSF_WSS_PLAIN;
+					break;
+				}
+
+				*ptr = '\0';
+				ptr++;
+				if (wc)
+					val = strtoul(&obuf[i + 2], NULL, 10);
+				else
+					val = strtoul(&obuf[i + 1], NULL, 10);
+				i += (int)(ptr - &obuf[i]);
+
+			} else
+				i++;
+			break;
+		case 'M':
+			op = OSFOPT_MSS;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				if (obuf[i + 1] == '%')
+					wc = OSF_WSS_MODULO;
+				*ptr = '\0';
+				ptr++;
+				if (wc)
+					val = strtoul(&obuf[i + 2], NULL, 10);
+				else
+					val = strtoul(&obuf[i + 1], NULL, 10);
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'E':
+			op = OSFOPT_EOL;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		default:
+			op = OSFOPT_EMPTY;
+			ptr = nf_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		}
+
+		if (op != OSFOPT_EMPTY) {
+			opt[*optnum].kind = IANA_opts[op].kind;
+			opt[*optnum].length = IANA_opts[op].length;
+			opt[*optnum].wc.wc = wc;
+			opt[*optnum].wc.val = val;
+			(*optnum)++;
+		}
+	}
+}
+
+static int osf_load_line(char *buffer, int len, int del, struct mnl_socket *nl,
+			 struct netlink_ctx *ctx)
+{
+	int i, cnt = 0;
+	char obuf[MAXOPTSTRLEN];
+	struct nf_osf_user_finger f;
+	char *pbeg, *pend;
+	struct nlmsghdr *nlh;
+	struct nfgenmsg *nfg;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+
+	memset(&f, 0, sizeof(struct nf_osf_user_finger));
+
+	ulog("Loading '%s'.\n", ctx, buffer);
+
+	for (i = 0; i < len && buffer[i] != '\0'; ++i) {
+		if (buffer[i] == ':')
+			cnt++;
+	}
+
+	if (cnt != 8) {
+		ulog("Wrong input line '%s': cnt: %d, must be 8, i: %d, must be %d.\n", ctx, buffer, cnt, i, len);
+		return -EINVAL;
+	}
+
+	memset(obuf, 0, sizeof(obuf));
+
+	pbeg = buffer;
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		if (pbeg[0] == 'S') {
+			f.wss.wc = OSF_WSS_MSS;
+			if (pbeg[1] == '%')
+				f.wss.val = strtoul(&pbeg[2], NULL, 10);
+			else if (pbeg[1] == '*')
+				f.wss.val = 0;
+			else
+				f.wss.val = strtoul(&pbeg[1], NULL, 10);
+		} else if (pbeg[0] == 'T') {
+			f.wss.wc = OSF_WSS_MTU;
+			if (pbeg[1] == '%')
+				f.wss.val = strtoul(&pbeg[2], NULL, 10);
+			else if (pbeg[1] == '*')
+				f.wss.val = 0;
+			else
+				f.wss.val = strtoul(&pbeg[1], NULL, 10);
+		} else if (pbeg[0] == '%') {
+			f.wss.wc = OSF_WSS_MODULO;
+			f.wss.val = strtoul(&pbeg[1], NULL, 10);
+		} else if (isdigit(pbeg[0])) {
+			f.wss.wc = OSF_WSS_PLAIN;
+			f.wss.val = strtoul(&pbeg[0], NULL, 10);
+		}
+
+		pbeg = pend + 1;
+	}
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		f.ttl = strtoul(pbeg, NULL, 10);
+		pbeg = pend + 1;
+	}
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		f.df = strtoul(pbeg, NULL, 10);
+		pbeg = pend + 1;
+	}
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		f.ss = strtoul(pbeg, NULL, 10);
+		pbeg = pend + 1;
+	}
+
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		cnt = snprintf(obuf, sizeof(obuf), "%s,", pbeg);
+		pbeg = pend + 1;
+	}
+
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		if (pbeg[0] == '@' || pbeg[0] == '*')
+			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg + 1);
+		else
+			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg);
+		pbeg = pend + 1;
+	}
+
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		cnt = snprintf(f.version, sizeof(f.version), "%s", pbeg);
+		pbeg = pend + 1;
+	}
+
+	pend = nf_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		cnt =
+		    snprintf(f.subtype, sizeof(f.subtype), "%s", pbeg);
+		pbeg = pend + 1;
+	}
+
+	nf_osf_parse_opt(f.opt, &f.opt_num, obuf, sizeof(obuf));
+
+	memset(buf, 0, sizeof(buf));
+
+	if (del) {
+		nlh = nftnl_nlmsg_build_hdr(buf, (NFNL_SUBSYS_OSF << 8) |
+					    OSF_MSG_REMOVE, AF_UNSPEC,
+					    NLM_F_REQUEST | NLM_F_ACK,
+					    ctx->seqnum);
+
+		nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+		nfg->nfgen_family = AF_UNSPEC;
+		nfg->version = NFNETLINK_V0;
+		nfg->res_id = 0;
+	} else {
+		nlh = nftnl_nlmsg_build_hdr(buf, (NFNL_SUBSYS_OSF << 8) |
+					    OSF_MSG_ADD, AF_UNSPEC,
+					    NLM_F_REQUEST | NLM_F_CREATE |
+					    NLM_F_ACK, ctx->seqnum);
+
+		nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+		nfg->nfgen_family = AF_UNSPEC;
+		nfg->version = NFNETLINK_V0;
+		nfg->res_id = 0;
+
+		mnl_attr_put(nlh, OSF_ATTR_FINGER, sizeof(struct nf_osf_user_finger), &f);
+	}
+
+	return nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, 0, NULL);
+}
+
+static int osf_load_entries(const char *path, int del, struct mnl_socket *nl,
+			    struct netlink_ctx *ctx)
+{
+	FILE *inf;
+	int err = 0;
+	char buf[1024];
+
+	inf = fopen(path, "r");
+	if (!inf) {
+		ulog_err("Failed to open file '%s'", ctx, path);
+		return -1;
+	}
+
+	while(fgets(buf, sizeof(buf), inf)) {
+		int len;
+
+		if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
+			continue;
+
+		len = strlen(buf) - 1;
+
+		if (len <= 0)
+			continue;
+
+		buf[len] = '\0';
+
+		err = osf_load_line(buf, len, del, nl, ctx);
+		if (err)
+			break;
+
+		memset(buf, 0, sizeof(buf));
+	}
+
+	fclose(inf);
+	return err;
+}
+
+int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del)
+{
+	int err;
+	struct mnl_socket *nl;
+	const char *fingerprints = "files/osf/pf.os";
+
+	if (!fingerprints) {
+		err = -ENOENT;
+		goto err_out_exit;
+	}
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		err = -EINVAL;
+		ulog_err("Failed to open mnl socket", ctx);
+		goto err_out_exit;
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		err = -EINVAL;
+		ulog_err("Failed to bind mnl socket", ctx);
+		goto err_out_exit;
+	}
+
+#ifndef NFNL_SUBSYS_OSF
+#define NFNL_SUBSYS_OSF	5
+#endif
+
+	err = osf_load_entries(fingerprints, del, nl, ctx);
+	if (err < 0)
+		goto err_out_close;
+
+	return 0;
+
+err_out_close:
+	mnl_socket_close(nl);
+err_out_exit:
+	return err;
+}
diff --git a/src/osf.c b/src/osf.c
index 131d54e..210bfbe 100644
--- a/src/osf.c
+++ b/src/osf.c
@@ -3,6 +3,7 @@ 
 #include <utils.h>
 #include <string.h>
 #include <osf.h>
+#include <nfnl_osf.h>
 
 static void osf_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
@@ -26,6 +27,7 @@  struct expr *osf_expr_alloc(const struct location *loc)
 	const struct datatype *type = &string_type;
 	struct expr *expr;
 
+	osf_init = true;
 	expr = expr_alloc(loc, &osf_expr_ops, type,
 			  BYTEORDER_HOST_ENDIAN, len);
 
diff --git a/src/rule.c b/src/rule.c
index 7a7ac73..dc1ce8b 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -22,6 +22,8 @@ 
 #include <netdb.h>
 #include <netlink.h>
 #include <json.h>
+#include <nfnl_osf.h>
+#include <osf.h>
 
 #include <libnftnl/common.h>
 #include <libnftnl/ruleset.h>
@@ -2135,6 +2137,9 @@  int do_command(struct netlink_ctx *ctx, struct cmd *cmd)
 	default:
 		BUG("invalid command object type %u\n", cmd->obj);
 	}
+
+	if (osf_init)
+		nfnl_osf_load_fingerprints(ctx, 0);
 }
 
 static int payload_match_stmt_cmp(const void *p1, const void *p2)