diff -Nur linux-2.6.1-rc1.org/include/linux/netfilter_ipv4/ipt_osf.h linux-2.6.1-rc1/include/linux/netfilter_ipv4/ipt_osf.h --- linux-2.6.1-rc1.org/include/linux/netfilter_ipv4/ipt_osf.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.1-rc1/include/linux/netfilter_ipv4/ipt_osf.h 2004-01-05 12:41:36.000000000 +0100 @@ -0,0 +1,130 @@ +/* + * ipt_osf.h + * + * Copyright (c) 2003 Evgeniy Polyakov + * + * + * 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 +#define IPT_OSF_SMART 2 +#define IPT_OSF_LOG 4 + +#define IPT_OSF_LOGLEVEL_ALL 0 +#define IPT_OSF_LOGLEVEL_FIRST 1 + +#include + +struct ipt_osf_info +{ + char genre[MAXGENRELEN]; + int len; + unsigned long flags; + int loglevel; + int invert; /* UNSUPPORTED */ +}; + +struct osf_wc +{ + char wc; + unsigned long val; +}; + +/* This struct represents IANA options + * http://www.iana.org/assignments/tcp-parameters + */ +struct osf_opt +{ + unsigned char kind; + unsigned char length; + struct osf_wc wc; +}; + +#ifdef __KERNEL__ + +struct osf_finger +{ + struct list_head flist; + struct osf_wc wss; + unsigned char ttl; + unsigned char df; + unsigned long ss; + char genre[MAXGENRELEN]; + char version[MAXGENRELEN], subtype[MAXGENRELEN]; + + /* Not needed, but for consistency with original table from Michal Zalewski */ + char details[MAXDETLEN]; + + int opt_num; + struct osf_opt opt[MAX_IPOPTLEN]; /* In case it is all NOP or EOL */ + +}; + +/* Defines for IANA option kinds */ + +#define OSFOPT_EOL 0 /* End of options */ +#define OSFOPT_NOP 1 /* NOP */ +#define OSFOPT_MSS 2 /* Maximum segment size */ +#define OSFOPT_WSO 3 /* Window scale option */ +#define OSFOPT_SACKP 4 /* SACK permitted */ +#define OSFOPT_SACK 5 /* SACK */ +#define OSFOPT_ECHO 6 +#define OSFOPT_ECHOREPLY 7 +#define OSFOPT_TS 8 /* Timestamp option */ +#define OSFOPT_POCP 9 /* Partial Order Connection Permitted */ +#define OSFOPT_POSP 10 /* Partial Order Service Profile */ +/* Others are not used in current OSF */ + +static struct osf_opt IANA_opts[] = +{ + {0, 1,}, + {1, 1,}, + {2, 4,}, + {3, 3,}, + {4, 2,}, + {5, 1 ,}, /* SACK length is not defined */ + {6, 6,}, + {7, 6,}, + {8, 10,}, + {9, 2,}, + {10, 3,}, + {11, 1,}, /* CC: Suppose 1 */ + {12, 1,}, /* the same */ + {13, 1,}, /* and here too */ + {14, 3,}, + {15, 1,}, /* TCP Alternate Checksum Data. Length is not defined */ + {16, 1,}, + {17, 1,}, + {18, 3,}, + {19, 18,}, + {20, 1,}, + {21, 1,}, + {22, 1,}, + {23, 1,}, + {24, 1,}, + {25, 1,}, + {26, 1,}, +}; + +#endif /* __KERNEL__ */ + +#endif /* _IPT_OSF_H */ diff -Nur linux-2.6.1-rc1.org/net/ipv4/netfilter/Kconfig linux-2.6.1-rc1/net/ipv4/netfilter/Kconfig --- linux-2.6.1-rc1.org/net/ipv4/netfilter/Kconfig 2004-01-05 12:31:23.000000000 +0100 +++ linux-2.6.1-rc1/net/ipv4/netfilter/Kconfig 2004-01-05 12:41:36.000000000 +0100 @@ -609,5 +609,54 @@ lengths. Details and examples are in the kernel module source. +config IP_NF_MATCH_OSF + tristate 'OSF match support' + depends on IP_NF_IPTABLES + help + The idea of passive OS fingerprint matching exists for quite a long time, + but was created as extension fo OpenBSD pf only some weeks ago. + Original idea was lurked in some OpenBSD mailing list (thanks + grange@open...) and than adopted for Linux netfilter in form of this code. + + Original table was created by Michal Zalewski for + his excellent p0f and than changed a bit for more convenience. + + This module compares some data(WS, MSS, options and it's order, ttl, + df and others) from first SYN packet (actually from packets with SYN + bit set) with hardcoded in fingers[] table ones. + + Example: (Of course this only an example, do not get inspired by this) + + # iptables -N LINUX + # iptables -A LINUX -j LOG --log-prefix "Linux" + + # iptables -A INPUT -p tcp -m osf --genre Linux -j LINUX + # iptables -A INPUT -p tcp -m osf --genre FreeBSD -j REJECT + + NOTE: -p tcp is obviously required as it is a TCP match. + + OSF also has: + --log 1/0. + If present, OSF will log determined genres even if they don't match + desired one. + 0 - log all determined entries, + 1 - only first one. + + Example: + #iptables -I INPUT -j ACCEPT -p tcp -m osf --genre Linux --log 1 --smart + + In syslog you find something like this: + ipt_osf: Windows [Windows XP Pro SP1, 2000 SP3]: 11.22.33.55:4024 -> 11.22.33.44:139 + ipt_osf: Unknown: 16384:106:1:48:020405B401010402 44.33.22.11:1239 -> 11.22.33.44:80 + + --smart + if present, OSF will use some smartness to determine remote OS. + Now only not use TTL( with it far remote machines can be determined). + + Fingerprints can be loaded through /proc/sys/net/ipv4/osf file. + Only one fingerprint per open/close. + + Fingerprints can be downloaded from http://www.openbsd.org/cgi-bin/cvsweb/src/etc/pf.os + endmenu diff -Nur linux-2.6.1-rc1.org/net/ipv4/netfilter/Makefile linux-2.6.1-rc1/net/ipv4/netfilter/Makefile --- linux-2.6.1-rc1.org/net/ipv4/netfilter/Makefile 2004-01-05 12:31:23.000000000 +0100 +++ linux-2.6.1-rc1/net/ipv4/netfilter/Makefile 2004-01-05 12:41:36.000000000 +0100 @@ -51,6 +51,9 @@ obj-$(CONFIG_IP_NF_MATCH_OWNER) += ipt_owner.o obj-$(CONFIG_IP_NF_MATCH_TOS) += ipt_tos.o +obj-$(CONFIG_IP_NF_MATCH_OSF) += ipt_osf.o + + obj-$(CONFIG_IP_NF_MATCH_TIME) += ipt_time.o diff -Nur linux-2.6.1-rc1.org/net/ipv4/netfilter/ipt_osf.c linux-2.6.1-rc1/net/ipv4/netfilter/ipt_osf.c --- linux-2.6.1-rc1.org/net/ipv4/netfilter/ipt_osf.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.1-rc1/net/ipv4/netfilter/ipt_osf.c 2004-01-05 12:41:36.000000000 +0100 @@ -0,0 +1,733 @@ +/* + * ipt_osf.c + * + * Copyright (c) 2003 Evgeniy Polyakov + * + * + * 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 + */ + +/* + * OS fingerprint matching module. + * It simply compares various parameters from SYN packet with + * some hardcoded ones. + * + * Original table was created by Michal Zalewski + * for his p0f. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#define OSF_DEBUG + +#ifdef OSF_DEBUG +#define log(x...) printk(KERN_INFO "ipt_osf: " x) +#define loga(x...) printk(x) +#else +#define log(x...) do {} while(0) +#define loga(x...) do {} while(0) +#endif + +#define FMATCH_WRONG 0 +#define FMATCH_OK 1 +#define FMATCH_OPT_WRONG 2 + +#define OPTDEL ',' +#define OSFPDEL ':' +#define MAXOPTSTRLEN 128 +#define OSFFLUSH "FLUSH" + +static rwlock_t osf_lock = RW_LOCK_UNLOCKED; +static struct list_head finger_list; + +static int match(const struct sk_buff *, const struct net_device *, const struct net_device *, + const void *, int, const void *, u_int16_t, int *); +static int checkentry(const char *, const struct ipt_ip *, void *, + unsigned int, unsigned int); + +static struct ipt_match osf_match = +{ + { NULL, NULL }, + "osf", + &match, + &checkentry, + NULL, + THIS_MODULE +}; + +static inline int smart_dec(unsigned long flags, unsigned char ip_ttl, unsigned char f_ttl) +{ + if (flags & IPT_OSF_SMART) + return 1; + else + return (ip_ttl == f_ttl); +} + +static int +match(const struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + const void *matchinfo, + int offset, + const void *hdr, + u_int16_t datalen, + int *hotdrop) +{ + struct ipt_osf_info *info = (struct ipt_osf_info *)matchinfo; + struct iphdr *ip = skb->nh.iph; + struct tcphdr *tcp; + int fmatch = FMATCH_WRONG, fcount = 0; + unsigned long totlen, optsize = 0, window; + unsigned char df, *optp = NULL, *_optp = NULL; + char check_WSS = 0; + struct list_head *ent; + struct osf_finger *f; + + if (!ip || !info) + return 0; + + tcp = (struct tcphdr *)((u_int32_t *)ip + ip->ihl); + + if (!tcp->syn) + return 0; + else if (tcp->ack) + return 0; + + totlen = ntohs(ip->tot_len); + df = ((ntohs(ip->frag_off) & IP_DF)?1:0); + window = ntohs(tcp->window); + + if (tcp->doff*4 > sizeof(struct tcphdr)) + { + _optp = optp = (char *)(tcp+1); + optsize = tcp->doff*4 - sizeof(struct tcphdr); + } + + + /* Actually we can create hash/table of all genres and search + * only in appropriate part, but here is initial variant, + * so will use slow path. + */ + read_lock(&osf_lock); + list_for_each(ent, &finger_list) + { + f = list_entry(ent, struct osf_finger, flist); + + if (!(info->flags & IPT_OSF_LOG) && strcmp(info->genre, f->genre)) + continue; + + optp = _optp; + fmatch = FMATCH_WRONG; + + if (totlen == f->ss && df == f->df && + smart_dec(info->flags, ip->ttl, f->ttl)) + { + unsigned long foptsize; + int optnum; + unsigned short mss = 0; + + 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: log("Wrong fingerprint wss.wc=%d, %s - %s\n", + f->wss.wc, f->genre, f->details); + check_WSS = 4; + break; + } + if (check_WSS == 4) + continue; + + /* Check options */ + + foptsize = 0; + for (optnum=0; optnumopt_num; ++optnum) + foptsize += f->opt[optnum].length; + + + if (foptsize > MAX_IPOPTLEN || optsize > MAX_IPOPTLEN || optsize != foptsize) + continue; + + if (!optp) + { + fmatch = FMATCH_OK; + loga("\tYEP : matching without options.\n"); + if ((info->flags & IPT_OSF_LOG) && + info->loglevel == IPT_OSF_LOGLEVEL_FIRST) + break; + else + continue; + } + + + for (optnum=0; optnumopt_num; ++optnum) + { + if (f->opt[optnum].kind == (*optp)) + { + unsigned char len = f->opt[optnum].length; + unsigned char *optend = optp + len; + + fmatch = FMATCH_OK; + + if (*optp == OSFOPT_MSS) /* MSS */ + mss = ntohs(*(unsigned short *)(optp+2)); + + if (len != 1) + { + /* Skip kind and length fields*/ + optp += 2; + + if (f->opt[optnum].wc.wc != 0) + { + unsigned long tmp = 0; + + /* Hmmm... It looks a bit ugly. :) */ + memcpy(&tmp, &f->opt[optnum].wc.val, + (len > sizeof(unsigned long)? + sizeof(unsigned long):len)); + + tmp = ntohl(tmp); + if (tmp != f->opt[optnum].wc.val) + fmatch = FMATCH_OPT_WRONG; + } + } + + 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 (window == f->wss.val) + fmatch = FMATCH_OK; + break; + case 1: /* MSS */ + if (window == f->wss.val*mss) + fmatch = FMATCH_OK; + break; + case 2: /* MTU */ + if (window == f->wss.val*(mss+40)) + fmatch = FMATCH_OK; + break; + case 3: /* MOD */ + if (window % f->wss.val == 0) + fmatch = FMATCH_OK; + break; + } + } + + + if (fmatch == FMATCH_OK) + { + fcount++; + log("%s [%s]: %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", + f->genre, f->details, + NIPQUAD(ip->saddr), ntohs(tcp->source), + NIPQUAD(ip->daddr), ntohs(tcp->dest)); + if ((info->flags & IPT_OSF_LOG) && + info->loglevel == IPT_OSF_LOGLEVEL_FIRST) + break; + } + } + } + if (!fcount && (info->flags & IPT_OSF_LOG)) + { + log("Unknown: %lu:%d:%d:%lu:", window, ip->ttl, df, totlen); + if (optp) + { + unsigned char opt[4 * 15 - sizeof(struct tcphdr)]; + unsigned int i, optsize; + + optsize = tcp->doff * 4 - sizeof(struct tcphdr); + if (skb_copy_bits(skb, ip->ihl*4 + sizeof(struct tcphdr), + opt, optsize) < 0) + loga("TRUNCATED"); + else + for (i = 0; i < optsize; i++) + loga("%02X", opt[i]); + + } + loga(" %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", + NIPQUAD(ip->saddr), ntohs(tcp->source), + NIPQUAD(ip->daddr), ntohs(tcp->dest)); + } + read_unlock(&osf_lock); + + return (fmatch == FMATCH_OK)?1:0; +} + +static int +checkentry(const char *tablename, + const struct ipt_ip *ip, + void *matchinfo, + unsigned int matchsize, + unsigned int hook_mask) +{ + if (matchsize != IPT_ALIGN(sizeof(struct ipt_osf_info))) + return 0; + if (ip->proto != IPPROTO_TCP) + return 0; + + return 1; +} + +static char * osf_strchr(char *ptr, char c) +{ + char *tmp; + + tmp = strchr(ptr, c); + + while (tmp && tmp+1 && isspace(*(tmp+1))) + tmp++; + + return tmp; +} + +static struct osf_finger * finger_alloc(void) +{ + struct osf_finger *f; + + f = kmalloc(sizeof(struct osf_finger), GFP_KERNEL); + if (f) + memset(f, 0, sizeof(struct osf_finger)); + + return f; +} + +static void finger_free(struct osf_finger *f) +{ + memset(f, 0, sizeof(struct osf_finger)); + kfree(f); +} + + +static void osf_parse_opt(struct osf_opt *opt, int *optnum, char *obuf, int olen) +{ + int i, op; + char *ptr, wc; + unsigned long val; + + ptr = &obuf[0]; + i = 0; + while (ptr != NULL && i < olen) + { + val = 0; + op = 0; + wc = 0; + switch (obuf[i]) + { + case 'N': + op = OSFOPT_NOP; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + case 'S': + op = OSFOPT_SACKP; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + case 'T': + op = OSFOPT_TS; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + case 'W': + op = OSFOPT_WSO; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + switch (obuf[i+1]) + { + case '%': wc = '%'; break; + case 'S': wc = 'S'; break; + case 'T': wc = 'T'; break; + default: wc = 0; break; + } + + *ptr = '\0'; + ptr++; + if (wc) + val = simple_strtoul(&obuf[i+2], NULL, 10); + else + val = simple_strtoul(&obuf[i+1], NULL, 10); + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + case 'M': + op = OSFOPT_MSS; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + if (obuf[i+1] == '%') + wc = '%'; + *ptr = '\0'; + ptr++; + if (wc) + val = simple_strtoul(&obuf[i+2], NULL, 10); + else + val = simple_strtoul(&obuf[i+1], NULL, 10); + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + case 'E': + op = OSFOPT_EOL; + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + *ptr = '\0'; + ptr++; + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + default: + ptr = osf_strchr(&obuf[i], OPTDEL); + if (ptr) + { + ptr++; + i += (int)(ptr-&obuf[i]); + + } + else + i++; + break; + } + + 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_proc_read(char *buf, char **start, off_t off, int count, int *eof, void *data) +{ + struct list_head *ent; + struct osf_finger *f = NULL; + int i; + + *eof = 1; + count = 0; + + read_lock_bh(&osf_lock); + list_for_each(ent, &finger_list) + { + f = list_entry(ent, struct osf_finger, flist); + + log("%s [%s]", f->genre, f->details); + + count += sprintf(buf+count, "%s - %s[%s] : %s", + f->genre, f->version, + f->subtype, f->details); + + if (f->opt_num) + { + loga(" OPT: "); + //count += sprintf(buf+count, " OPT: "); + for (i=0; iopt_num; ++i) + { + //count += sprintf(buf+count, "%d.%c%lu; ", + // f->opt[i].kind, (f->opt[i].wc.wc)?f->opt[i].wc.wc:' ', f->opt[i].wc.val); + loga("%d.%c%lu; ", + f->opt[i].kind, (f->opt[i].wc.wc)?f->opt[i].wc.wc:' ', f->opt[i].wc.val); + } + } + loga("\n"); + count += sprintf(buf+count, "\n"); + } + read_unlock_bh(&osf_lock); + + return count; +} + +static int osf_proc_write(struct file *file, const char *buffer, unsigned long count, void *data) +{ + int cnt; + unsigned long i; + char obuf[MAXOPTSTRLEN]; + struct osf_finger *finger; + struct list_head *ent, *n; + + char *pbeg, *pend; + + if (count == strlen(OSFFLUSH) && !strncmp(buffer, OSFFLUSH, strlen(OSFFLUSH))) + { + int i = 0; + write_lock_bh(&osf_lock); + list_for_each_safe(ent, n, &finger_list) + { + i++; + finger = list_entry(ent, struct osf_finger, flist); + list_del(&finger->flist); + finger_free(finger); + } + write_unlock_bh(&osf_lock); + + log("Flushed %d entries.\n", i); + + return count; + } + + + cnt = 0; + for (i=0; iwss.wc = 'S'; + if (pbeg[1] == '%') + finger->wss.val = simple_strtoul(pbeg+2, NULL, 10); + else if (pbeg[1] == '*') + finger->wss.val = 0; + else + finger->wss.val = simple_strtoul(pbeg+1, NULL, 10); + } + else if (pbeg[0] == 'T') + { + finger->wss.wc = 'T'; + if (pbeg[1] == '%') + finger->wss.val = simple_strtoul(pbeg+2, NULL, 10); + else if (pbeg[1] == '*') + finger->wss.val = 0; + else + finger->wss.val = simple_strtoul(pbeg+1, NULL, 10); + } + if (isdigit(pbeg[0])) + { + finger->wss.wc = 0; + finger->wss.val = simple_strtoul(pbeg, NULL, 10); + } + + pbeg = pend+1; + } + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + finger->ttl = simple_strtoul(pbeg, NULL, 10); + pbeg = pend+1; + } + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + finger->df = simple_strtoul(pbeg, NULL, 10); + pbeg = pend+1; + } + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + finger->ss = simple_strtoul(pbeg, NULL, 10); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + cnt = snprintf(obuf, sizeof(obuf), "%s", pbeg); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + if (pbeg[0] == '@' || pbeg[0] == '*') + cnt = snprintf(finger->genre, sizeof(finger->genre), "%s", pbeg+1); + else + cnt = snprintf(finger->genre, sizeof(finger->genre), "%s", pbeg); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + cnt = snprintf(finger->version, sizeof(finger->version), "%s", pbeg); + pbeg = pend+1; + } + + pend = osf_strchr(pbeg, OSFPDEL); + if (pend) + { + *pend = '\0'; + cnt = snprintf(finger->subtype, sizeof(finger->subtype), "%s", pbeg); + pbeg = pend+1; + } + + cnt = snprintf(finger->details, + ((count - (pbeg - buffer)+1) > MAXDETLEN)?MAXDETLEN:(count - (pbeg - buffer)+1), + "%s", pbeg); + + log("%s - %s[%s] : %s\n", + finger->genre, finger->version, + finger->subtype, finger->details); + + osf_parse_opt(finger->opt, &finger->opt_num, obuf, sizeof(obuf)); + + + write_lock_bh(&osf_lock); + list_add_tail(&finger->flist, &finger_list); + write_unlock_bh(&osf_lock); + + return count; +} + +static int __init osf_init(void) +{ + int err; + struct proc_dir_entry *p; + + log("Startng OS fingerprint matching module.\n"); + + INIT_LIST_HEAD(&finger_list); + + err = ipt_register_match(&osf_match); + if (err) + { + log("Failed to register OS fingerprint matching module.\n"); + return -ENXIO; + } + + p = create_proc_entry("sys/net/ipv4/osf", S_IFREG | 0644, NULL); + if (!p) + { + ipt_unregister_match(&osf_match); + return -ENXIO; + } + + p->write_proc = osf_proc_write; + p->read_proc = osf_proc_read; + + return 0; +} + +static void __exit osf_fini(void) +{ + struct list_head *ent, *n; + struct osf_finger *f; + + remove_proc_entry("sys/net/ipv4/osf", NULL); + ipt_unregister_match(&osf_match); + + list_for_each_safe(ent, n, &finger_list) + { + f = list_entry(ent, struct osf_finger, flist); + list_del(&f->flist); + finger_free(f); + } + + log("OS fingerprint matching module finished.\n"); +} + +module_init(osf_init); +module_exit(osf_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +MODULE_DESCRIPTION("Passive OS fingerprint matching."); +