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@xxxxxxxxxx> --- include/linux/netfilter/Makefile.am | 1 + include/linux/netfilter/nfnetlink_osf.h | 119 +++++++ include/osf.h | 5 + src/Makefile.am | 1 + src/nfnl_osf.c | 401 ++++++++++++++++++++++++ src/osf.c | 1 + src/rule.c | 29 +- 7 files changed, 549 insertions(+), 8 deletions(-) create mode 100644 include/linux/netfilter/nfnetlink_osf.h create mode 100644 src/nfnl_osf.c diff --git a/include/linux/netfilter/Makefile.am b/include/linux/netfilter/Makefile.am index f6a8aa2..2c1de18 100644 --- a/include/linux/netfilter/Makefile.am +++ b/include/linux/netfilter/Makefile.am @@ -3,4 +3,5 @@ noinst_HEADERS = nf_conntrack_common.h \ nf_log.h \ nf_nat.h \ nf_tables.h \ + nfnetlink_osf.h \ nfnetlink.h 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/osf.h b/include/osf.h index 715b04e..7f52169 100644 --- a/include/osf.h +++ b/include/osf.h @@ -1,6 +1,11 @@ #ifndef NFTABLES_OSF_H #define NFTABLES_OSF_H +#define OS_SIGNATURES DEFAULT_INCLUDE_PATH "/osf/pf.os" + +extern bool osf_init; + +extern int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del); 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..36b981f --- /dev/null +++ b/src/nfnl_osf.c @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2005 Evgeniy Polyakov <johnpol@xxxxxxxxxx> + * + * + * 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 <osf.h> + +#define OPTDEL ',' +#define OSFPDEL ':' +#define MAXOPTSTRLEN 128 + +bool osf_init; + +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 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 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)); + + if (ctx->debug_mask & NFT_DEBUG_NETLINK) + nft_print(ctx->octx, "Loading '%s'.\n", buffer); + + for (i = 0; i < len && buffer[i] != '\0'; ++i) { + if (buffer[i] == ':') + cnt++; + } + + if (cnt != 8) { + if (ctx->debug_mask & NFT_DEBUG_NETLINK) + nft_print(ctx->octx, "Wrong input line '%s': cnt: %d, must be 8, i: %d, must be %d.\n", 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 = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_OSF << 8) | OSF_MSG_REMOVE; + nlh->nlmsg_flags = NLM_F_REQUEST; + nlh->nlmsg_seq = time(NULL); + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_UNSPEC; + nfg->version = NFNETLINK_V0; + nfg->res_id = 0; + } else { + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_OSF << 8) | OSF_MSG_ADD; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; + nlh->nlmsg_seq = time(NULL); + + 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(int del, struct netlink_ctx *ctx) +{ + FILE *inf; + int err = 0; + char buf[1024]; + + inf = fopen(OS_SIGNATURES, "r"); + if (!inf) { + if (ctx->debug_mask & NFT_DEBUG_NETLINK) + nft_print(ctx->octx, "Failed to open file '%s'\n", OS_SIGNATURES); + + 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, ctx); + if (err) + break; + + memset(buf, 0, sizeof(buf)); + } + + fclose(inf); + return err; +} + +int nfnl_osf_load_fingerprints(struct netlink_ctx *ctx, int del) +{ + uint32_t err; + + err = osf_load_entries(del, ctx); + if (err < 0) + goto err_out_exit; + + return 0; + +err_out_exit: + return err; +} diff --git a/src/osf.c b/src/osf.c index 131d54e..b8457cc 100644 --- a/src/osf.c +++ b/src/osf.c @@ -26,6 +26,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 570d667..cbed5a9 100644 --- a/src/rule.c +++ b/src/rule.c @@ -1323,6 +1323,7 @@ static int do_add_set(struct netlink_ctx *ctx, const struct cmd *cmd, static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl) { uint32_t flags = excl ? NLM_F_EXCL : 0; + uint32_t err; if (ctx->octx->echo) { int ret; @@ -1337,26 +1338,37 @@ static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl) switch (cmd->obj) { case CMD_OBJ_TABLE: - return netlink_add_table_batch(ctx, cmd, flags); + err = netlink_add_table_batch(ctx, cmd, flags); + break; case CMD_OBJ_CHAIN: - return netlink_add_chain_batch(ctx, cmd, flags); + err = netlink_add_chain_batch(ctx, cmd, flags); + break; case CMD_OBJ_RULE: - return netlink_add_rule_batch(ctx, cmd, flags | NLM_F_APPEND); + err = netlink_add_rule_batch(ctx, cmd, flags | NLM_F_APPEND); + break; case CMD_OBJ_SET: - return do_add_set(ctx, cmd, flags); + err = do_add_set(ctx, cmd, flags); + break; case CMD_OBJ_SETELEM: - return do_add_setelems(ctx, cmd, flags); + err = do_add_setelems(ctx, cmd, flags); + break; case CMD_OBJ_COUNTER: case CMD_OBJ_QUOTA: case CMD_OBJ_CT_HELPER: case CMD_OBJ_LIMIT: - return netlink_add_obj(ctx, cmd, flags); + err = netlink_add_obj(ctx, cmd, flags); + break; case CMD_OBJ_FLOWTABLE: - return netlink_add_flowtable(ctx, cmd, flags); + err = netlink_add_flowtable(ctx, cmd, flags); + break; default: BUG("invalid command object type %u\n", cmd->obj); } - return 0; + + if (osf_init) + err = nfnl_osf_load_fingerprints(ctx, 0); + + return err; } static int do_command_replace(struct netlink_ctx *ctx, struct cmd *cmd) @@ -2270,6 +2282,7 @@ struct cmd *cmd_alloc_obj_ct(enum cmd_ops op, int type, const struct handle *h, int do_command(struct netlink_ctx *ctx, struct cmd *cmd) { + switch (cmd->op) { case CMD_ADD: return do_command_add(ctx, cmd, false); -- 2.18.0