From c292a50da6142da7cfb9a0c74fad2ea40e385f20 Mon Sep 17 00:00:00 2001 From: Planet-Lab Support Date: Mon, 10 Jan 2005 20:29:30 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create branch 'planetlab-3_2-branch'. --- .cvsignore | 8 + extensions/.addrtype-test | 5 + extensions/.cvsignore | 1 + extensions/.pool-test | 2 + extensions/libipt_POOL.c | 155 +++ extensions/libipt_pool.c | 144 ++ extensions/libipt_pool.c.orig | 144 ++ include/libippool/ip_pool_support.h | 26 + ip6tables.8 | 821 ++++++++++++ ippool/Makefile | 18 + ippool/ippool.c | 589 ++++++++ ippool/libippool.c | 72 + iptables-config | 37 + iptables.8 | 1072 +++++++++++++++ iptables.init | 320 +++++ iptables.spec | 371 ++++++ libiptc/.cvsignore | 1 + libiptc/libip6tc.c.orig | 449 +++++++ libiptc2/foo.diff | 391 ++++++ libiptc2/libip4tc.c | 507 +++++++ libiptc2/libip6tc.c | 449 +++++++ libiptc2/libiptc.c | 1830 +++++++++++++++++++++++++ libiptc2/libiptc.cvs.c | 1920 +++++++++++++++++++++++++++ libiptc2/libiptc2.c | 1758 ++++++++++++++++++++++++ libiptc2/linux_list.h | 161 +++ libiptc2/linux_listhelp.h | 99 ++ 26 files changed, 11350 insertions(+) create mode 100644 .cvsignore create mode 100755 extensions/.addrtype-test create mode 100644 extensions/.cvsignore create mode 100755 extensions/.pool-test create mode 100644 extensions/libipt_POOL.c create mode 100644 extensions/libipt_pool.c create mode 100644 extensions/libipt_pool.c.orig create mode 100644 include/libippool/ip_pool_support.h create mode 100644 ip6tables.8 create mode 100644 ippool/Makefile create mode 100644 ippool/ippool.c create mode 100644 ippool/libippool.c create mode 100644 iptables-config create mode 100644 iptables.8 create mode 100755 iptables.init create mode 100644 iptables.spec create mode 100644 libiptc/.cvsignore create mode 100644 libiptc/libip6tc.c.orig create mode 100644 libiptc2/foo.diff create mode 100644 libiptc2/libip4tc.c create mode 100644 libiptc2/libip6tc.c create mode 100644 libiptc2/libiptc.c create mode 100644 libiptc2/libiptc.cvs.c create mode 100644 libiptc2/libiptc2.c create mode 100644 libiptc2/linux_list.h create mode 100644 libiptc2/linux_listhelp.h diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..7bdbcc3 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,8 @@ +.makefirst +ip6tables +ip6tables-restore +ip6tables-save +iptables +iptables-restore +iptables-save +*.d diff --git a/extensions/.addrtype-test b/extensions/.addrtype-test new file mode 100755 index 0000000..cda582b --- /dev/null +++ b/extensions/.addrtype-test @@ -0,0 +1,5 @@ +#!/bin/bash + +if test -f $KERNEL_DIR/include/linux/netfilter_ipv4/ipt_addrtype.h; then + echo "addrtype" +fi diff --git a/extensions/.cvsignore b/extensions/.cvsignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/extensions/.cvsignore @@ -0,0 +1 @@ +*.d diff --git a/extensions/.pool-test b/extensions/.pool-test new file mode 100755 index 0000000..54d04bf --- /dev/null +++ b/extensions/.pool-test @@ -0,0 +1,2 @@ +#! /bin/sh +[ -f $KERNEL_DIR/include/linux/netfilter_ipv4/ip_pool.h ] && echo pool POOL diff --git a/extensions/libipt_POOL.c b/extensions/libipt_POOL.c new file mode 100644 index 0000000..931dcfd --- /dev/null +++ b/extensions/libipt_POOL.c @@ -0,0 +1,155 @@ +/* Shared library add-on to iptables to add IP pool mangling target. */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* FIXME --RR */ +#define ip_pool_init ip_POOL_init +#define ip_pool_get_index ip_POOL_get_index +#define ip_pool_get_name ip_POOL_get_name +#include "../ippool/libippool.c" + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"POOL v%s options:\n" +" --add-srcip \n" +" --del-srcip \n" +" --add-dstip \n" +" --del-dstip \n" +" add/del src/dst IP from pool.\n\n", +IPTABLES_VERSION); +} + +static struct option opts[] = { + { "add-srcip", 1, 0, '1' }, + { "del-srcip", 1, 0, '2' }, + { "add-dstip", 1, 0, '3' }, + { "del-dstip", 1, 0, '4' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *target, unsigned int *nfcache) +{ + struct ipt_pool_info *ipi = (struct ipt_pool_info *) target->data; + + ipi->src = ipi->dst = IP_POOL_NONE; + ipi->flags = 0; + + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +/* Function which parses command options; returns true if it + ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target) +{ + struct ipt_pool_info *ipi = (struct ipt_pool_info *) (*target)->data; + switch (c) { + case '1': /* --add-srcip */ + ipi->src = ip_pool_get_index(optarg); + ipi->flags &= ~IPT_POOL_DEL_SRC; + break; + case '2': /* --del-srcip */ + ipi->src = ip_pool_get_index(optarg); + ipi->flags |= IPT_POOL_DEL_SRC; + break; + case '3': /* --add-dstip */ + ipi->dst = ip_pool_get_index(optarg); + ipi->flags &= ~IPT_POOL_DEL_DST; + break; + case '4': /* --del-dstip */ + ipi->dst = ip_pool_get_index(optarg); + ipi->flags |= IPT_POOL_DEL_DST; + break; + default: + return 0; + } + return 1; +} + +/* Final check; don't care. */ +static void final_check(unsigned int flags) +{ +} + +/* Prints out the targinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + char buf[256]; + struct ipt_pool_info *ipi = (struct ipt_pool_info *) target->data; + + printf("POOL"); + if (ipi->src != IP_POOL_NONE) { + printf(" --%s-srcip %s", + (ipi->flags & IPT_POOL_DEL_SRC) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->src, numeric)); + } + if (ipi->dst != IP_POOL_NONE) { + printf(" --%s-dstip %s", + (ipi->flags & IPT_POOL_DEL_DST) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->dst, numeric)); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + char buf[256]; + struct ipt_pool_info *ipi = (struct ipt_pool_info *) target->data; + + printf("-j POOL"); + if (ipi->src != IP_POOL_NONE) { + printf(" --%s-srcip %s", + (ipi->flags & IPT_POOL_DEL_SRC) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->src, 0)); + } + if (ipi->dst != IP_POOL_NONE) { + printf(" --%s-dstip %s", + (ipi->flags & IPT_POOL_DEL_DST) ? "del" : "add", + ip_pool_get_name(buf, sizeof(buf), ipi->dst, 0)); + } +} + +static +struct iptables_target ipt_pool_target += { NULL, + "POOL", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_pool_info)), + IPT_ALIGN(sizeof(struct ipt_pool_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&ipt_pool_target); +} diff --git a/extensions/libipt_pool.c b/extensions/libipt_pool.c new file mode 100644 index 0000000..082d76c --- /dev/null +++ b/extensions/libipt_pool.c @@ -0,0 +1,144 @@ +/* Shared library add-on to iptables to add IP address pool matching. */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* FIXME --RR */ +#include "../ippool/libippool.c" + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"pool v%s options:\n" +" [!] --srcpool NAME|INDEX\n" +" [!] --dstpool NAME|INDEX\n" +" Pool index (or name from %s) to match\n" +"\n", IPTABLES_VERSION, IPPOOL_CONF); +} + +static struct option opts[] = { + { "srcpool", 1, 0, '1' }, + { "dstpool", 1, 0, '2' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *match, unsigned int *nfcache) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + info->src = IP_POOL_NONE; + info->dst = IP_POOL_NONE; + info->flags = 0; + /* Can't cache this - XXX */ + *nfcache |= NFC_UNKNOWN; +} + +/* Function which parses command options; returns true if it ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)(*match)->data; + + switch (c) { + case '1': + check_inverse(optarg, &invert, &optind, 0); + info->src = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_SRC; + *flags = 1; + break; + case '2': + check_inverse(optarg, &invert, &optind, 0); + info->dst = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_DST; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; must have specified --srcpool or --dstpool. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, "You must specify either `--srcpool or --dstpool'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%ssrcpool %s ", + (info->flags & IPT_POOL_INV_SRC) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%sdstpool %s ", + (info->flags & IPT_POOL_INV_DST) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +/* Saves the matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%s--srcpool %s ", + (info->flags & IPT_POOL_INV_SRC) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%s--dstpool %s ", + (info->flags & IPT_POOL_INV_DST) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +static +struct iptables_match pool += { NULL, + "pool", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_pool_info)), + IPT_ALIGN(sizeof(struct ipt_pool_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&pool); +} diff --git a/extensions/libipt_pool.c.orig b/extensions/libipt_pool.c.orig new file mode 100644 index 0000000..666599d --- /dev/null +++ b/extensions/libipt_pool.c.orig @@ -0,0 +1,144 @@ +/* Shared library add-on to iptables to add IP address pool matching. */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* FIXME --RR */ +#include "../ippool/libippool.c" + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"pool v%s options:\n" +" [!] --srcpool NAME|INDEX\n" +" [!] --dstpool NAME|INDEX\n" +" Pool index (or name from %s) to match\n" +"\n", IPTABLES_VERSION, IPPOOL_CONF); +} + +static struct option opts[] = { + { "srcpool", 1, 0, '1' }, + { "dstpool", 1, 0, '2' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *match, unsigned int *nfcache) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + info->src = IP_POOL_NONE; + info->dst = IP_POOL_NONE; + info->flags = 0; + /* Can't cache this - XXX */ + *nfcache |= NFC_UNKNOWN; +} + +/* Function which parses command options; returns true if it ate an option */ +static int +parse(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_pool_info *info = + (struct ipt_pool_info *)(*match)->data; + + switch (c) { + case '1': + check_inverse(optarg, &invert, &optind, 0); + info->src = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_SRC; + *flags = 1; + break; + case '2': + check_inverse(optarg, &invert, &optind, 0); + info->dst = ip_pool_get_index(argv[optind-1]); + if (invert) info->flags |= IPT_POOL_INV_DST; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; must have specified --srcpool or --dstpool. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, "You must specify either `--srcpool or --dstpool'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%ssrcpool %s ", + (info->flags & IPT_POOL_INV_SRC) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%sdstpool %s ", + (info->flags & IPT_POOL_INV_DST) ? "!" : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +/* Saves the matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + char buf[256]; + struct ipt_pool_info *info = + (struct ipt_pool_info *)match->data; + + if (info->src != IP_POOL_NONE) + printf("%s--srcpool %s", + (info->flags & IPT_POOL_INV_SRC) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->src, 0)); + if (info->dst != IP_POOL_NONE) + printf("%s--dstpool %s", + (info->flags & IPT_POOL_INV_DST) ? "! " : "", + ip_pool_get_name(buf, sizeof(buf), info->dst, 0)); +} + +static +struct iptables_match pool += { NULL, + "pool", + IPTABLES_VERSION, + IPT_ALIGN(sizeof(struct ipt_pool_info)), + IPT_ALIGN(sizeof(struct ipt_pool_info)), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&pool); +} diff --git a/include/libippool/ip_pool_support.h b/include/libippool/ip_pool_support.h new file mode 100644 index 0000000..4a87f02 --- /dev/null +++ b/include/libippool/ip_pool_support.h @@ -0,0 +1,26 @@ +/* support function prototypes for IP pool management (config file, mostly) */ +#ifndef _IP_POOL_SUPPORT_H +#define _IP_POOL_SUPPORT_H + +#include +#include +#include + +#ifndef IPPOOL_CONF +#define IPPOOL_CONF "/etc/ippool.conf" +#endif + +/* called just to draw in this support .o */ +void ip_pool_init(void); + +/* given a pool name (or number), return pool index, possibly reading .conf */ +ip_pool_t ip_pool_get_index(char *name); + +/* given a pool index, and a buffer to store a name, search for the index + * in the .conf file, and give the textual name, if present; if not, the + * numeric index is returned. If numeric_flag == 1, the numeric index is + * always returned + */ +char *ip_pool_get_name(char *buf, int size, ip_pool_t index, int numeric_flag); + +#endif /*_IP_POOL_SUPPORT_H*/ diff --git a/ip6tables.8 b/ip6tables.8 new file mode 100644 index 0000000..53a310c --- /dev/null +++ b/ip6tables.8 @@ -0,0 +1,821 @@ +.TH IP6TABLES 8 "Mar 09, 2002" "" "" +.\" +.\" Man page written by Andras Kis-Szabo +.\" It is based on iptables man page. +.\" +.\" iptables page by Herve Eychenne +.\" It is based on ipchains man page. +.\" +.\" ipchains page by Paul ``Rusty'' Russell March 1997 +.\" Based on the original ipfwadm man page by Jos Vos +.\" +.\" 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., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +ip6tables \- IPv6 packet filter administration +.SH SYNOPSIS +.BR "ip6tables [-t table] -[AD] " "chain rule-specification [options]" +.br +.BR "ip6tables [-t table] -I " "chain [rulenum] rule-specification [options]" +.br +.BR "ip6tables [-t table] -R " "chain rulenum rule-specification [options]" +.br +.BR "ip6tables [-t table] -D " "chain rulenum [options]" +.br +.BR "ip6tables [-t table] -[LFZ] " "[chain] [options]" +.br +.BR "ip6tables [-t table] -N " "chain" +.br +.BR "ip6tables [-t table] -X " "[chain]" +.br +.BR "ip6tables [-t table] -P " "chain target [options]" +.br +.BR "ip6tables [-t table] -E " "old-chain-name new-chain-name" +.SH DESCRIPTION +.B Ip6tables +is used to set up, maintain, and inspect the tables of IPv6 packet +filter rules in the Linux kernel. Several different tables +may be defined. Each table contains a number of built-in +chains and may also contain user-defined chains. + +Each chain is a list of rules which can match a set of packets. Each +rule specifies what to do with a packet that matches. This is called +a `target', which may be a jump to a user-defined chain in the same +table. + +.SH TARGETS +A firewall rule specifies criteria for a packet, and a target. If the +packet does not match, the next rule in the chain is the examined; if +it does match, then the next rule is specified by the value of the +target, which can be the name of a user-defined chain or one of the +special values +.IR ACCEPT , +.IR DROP , +.IR QUEUE , +or +.IR RETURN . +.PP +.I ACCEPT +means to let the packet through. +.I DROP +means to drop the packet on the floor. +.I QUEUE +means to pass the packet to userspace (if supported by the kernel). +.I RETURN +means stop traversing this chain and resume at the next rule in the +previous (calling) chain. If the end of a built-in chain is reached +or a rule in a built-in chain with target +.I RETURN +is matched, the target specified by the chain policy determines the +fate of the packet. +.SH TABLES +There are currently two independent tables (which tables are present +at any time depends on the kernel configuration options and which +modules are present), as nat table has not been implemented yet. +.TP +.BI "-t, --table " "table" +This option specifies the packet matching table which the command +should operate on. If the kernel is configured with automatic module +loading, an attempt will be made to load the appropriate module for +that table if it is not already there. + +The tables are as follows: +.RS +.TP .4i +.BR "filter" : +This is the default table (if no -t option is passed). It contains +the built-in chains +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for packets being routed through the box), and +.B OUTPUT +(for locally-generated packets). +.TP +.BR "mangle" : +This table is used for specialized packet alteration. Until kernel +2.4.17 it had two built-in chains: +.B PREROUTING +(for altering incoming packets before routing) and +.B OUTPUT +(for altering locally-generated packets before routing). +Since kernel 2.4.18, three other built-in chains are also supported: +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for altering packets being routed through the box), and +.B POSTROUTING +(for altering packets as they are about to go out). +.RE +.SH OPTIONS +The options that are recognized by +.B ip6tables +can be divided into several different groups. +.SS COMMANDS +These options specify the specific action to perform. Only one of them +can be specified on the command line unless otherwise specified +below. For all the long versions of the command and option names, you +need to use only enough letters to ensure that +.B ip6tables +can differentiate it from all other options. +.TP +.BI "-A, --append " "chain rule-specification" +Append one or more rules to the end of the selected chain. +When the source and/or destination names resolve to more than one +address, a rule will be added for each possible address combination. +.TP +.BI "-D, --delete " "chain rule-specification" +.ns +.TP +.BI "-D, --delete " "chain rulenum" +Delete one or more rules from the selected chain. There are two +versions of this command: the rule can be specified as a number in the +chain (starting at 1 for the first rule) or a rule to match. +.TP +.B "-I, --insert" +Insert one or more rules in the selected chain as the given rule +number. So, if the rule number is 1, the rule or rules are inserted +at the head of the chain. This is also the default if no rule number +is specified. +.TP +.BI "-R, --replace " "chain rulenum rule-specification" +Replace a rule in the selected chain. If the source and/or +destination names resolve to multiple addresses, the command will +fail. Rules are numbered starting at 1. +.TP +.BR "-L, --list " "[\fIchain\fP]" +List all rules in the selected chain. If no chain is selected, all +chains are listed. As every other iptables command, it applies to the +specified table (filter is the default), so mangle rules get listed by +.nf + ip6tables -t mangle -n -L +.fi +Please note that it is often used with the +.B -n +option, in order to avoid long reverse DNS lookups. +It is legal to specify the +.B -Z +(zero) option as well, in which case the chain(s) will be atomically +listed and zeroed. The exact output is affected by the other +arguments given. The exact rules are suppressed until you use +.nf + ip6tables -L -v +.fi +.TP +.BR "-F, --flush " "[\fIchain\fP]" +Flush the selected chain (all the chains in the table if none is given). +This is equivalent to deleting all the rules one by one. +.TP +.BR "-Z, --zero " "[\fIchain\fP]" +Zero the packet and byte counters in all chains. It is legal to +specify the +.B "-L, --list" +(list) option as well, to see the counters immediately before they are +cleared. (See above.) +.TP +.BI "-N, --new-chain " "chain" +Create a new user-defined chain by the given name. There must be no +target of that name already. +.TP +.BR "-X, --delete-chain " "[\fIchain\fP]" +Delete the optional user-defined chain specified. There must be no references +to the chain. If there are, you must delete or replace the referring +rules before the chain can be deleted. If no argument is given, it +will attempt to delete every non-builtin chain in the table. +.TP +.BI "-P, --policy " "chain target" +Set the policy for the chain to the given target. See the section +.B TARGETS +for the legal targets. Only built-in (non-user-defined) chains can have +policies, and neither built-in nor user-defined chains can be policy +targets. +.TP +.BI "-E, --rename-chain " "old-chain new-chain" +Rename the user specified chain to the user supplied name. This is +cosmetic, and has no effect on the structure of the table. +.TP +.B -h +Help. +Give a (currently very brief) description of the command syntax. +.SS PARAMETERS +The following parameters make up a rule specification (as used in the +add, delete, insert, replace and append commands). +.TP +.BR "-p, --protocol " "[!] \fIprotocol\fP" +The protocol of the rule or of the packet to check. +The specified protocol can be one of +.IR tcp , +.IR udp , +.IR ipv6-icmp|icmpv6 , +or +.IR all , +or it can be a numeric value, representing one of these protocols or a +different one. A protocol name from /etc/protocols is also allowed. +A "!" argument before the protocol inverts the +test. The number zero is equivalent to +.IR all . +Protocol +.I all +will match with all protocols and is taken as default when this +option is omitted. +.TP +.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]" +Source specification. +.I Address +can be either a hostname (please note that specifying +any name to be resolved with a remote query such as DNS is a really bad idea), +a network IPv6 address (with /mask), or a plain IPv6 address. +(the network name isn't supported now). +The +.I mask +can be either a network mask or a plain number, +specifying the number of 1's at the left side of the network mask. +Thus, a mask of +.I 64 +is equivalent to +.IR ffff:ffff:ffff:ffff:0000:0000:0000:0000 . +A "!" argument before the address specification inverts the sense of +the address. The flag +.B --src +is an alias for this option. +.TP +.BR "-d, --destination " "[!] \fIaddress\fP[/\fImask\fP]" +Destination specification. +See the description of the +.B -s +(source) flag for a detailed description of the syntax. The flag +.B --dst +is an alias for this option. +.TP +.BI "-j, --jump " "target" +This specifies the target of the rule; i.e., what to do if the packet +matches it. The target can be a user-defined chain (other than the +one this rule is in), one of the special builtin targets which decide +the fate of the packet immediately, or an extension (see +.B EXTENSIONS +below). If this +option is omitted in a rule, then matching the rule will have no +effect on the packet's fate, but the counters on the rule will be +incremented. +.TP +.BR "-i, --in-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be received (only for +packets entering the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +.BR "-o, --out-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be sent (for packets +entering the +.BR FORWARD +and +.B OUTPUT +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +.\" Currently not supported (header-based) +.\" +.\" .B "[!] " "-f, --fragment" +.\" This means that the rule only refers to second and further fragments +.\" of fragmented packets. Since there is no way to tell the source or +.\" destination ports of such a packet (or ICMP type), such a packet will +.\" not match any rules which specify them. When the "!" argument +.\" precedes the "-f" flag, the rule will only match head fragments, or +.\" unfragmented packets. +.\" .TP +.B "-c, --set-counters " "PKTS BYTES" +This enables the administrator to initialize the packet and byte +counters of a rule (during +.B INSERT, +.B APPEND, +.B REPLACE +operations). +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +.B "-v, --verbose" +Verbose output. This option makes the list command show the interface +name, the rule options (if any), and the TOS masks. The packet and +byte counters are also listed, with the suffix 'K', 'M' or 'G' for +1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see +the +.B -x +flag to change this). +For appending, insertion, deletion and replacement, this causes +detailed information on the rule or rules to be printed. +.TP +.B "-n, --numeric" +Numeric output. +IP addresses and port numbers will be printed in numeric format. +By default, the program will try to display them as host names, +network names, or services (whenever applicable). +.TP +.B "-x, --exact" +Expand numbers. +Display the exact value of the packet and byte counters, +instead of only the rounded number in K's (multiples of 1000) +M's (multiples of 1000K) or G's (multiples of 1000M). This option is +only relevant for the +.B -L +command. +.TP +.B "--line-numbers" +When listing rules, add line numbers to the beginning of each rule, +corresponding to that rule's position in the chain. +.TP +.B "--modprobe=command" +When adding or inserting rules into a chain, use +.B command +to load any necessary modules (targets, match extensions, etc). +.SH MATCH EXTENSIONS +ip6tables can use extended packet matching modules. These are loaded +in two ways: implicitly, when +.B -p +or +.B --protocol +is specified, or with the +.B -m +or +.B --match +options, followed by the matching module name; after these, various +extra command line options become available, depending on the specific +module. You can specify multiple extended match modules in one line, +and you can use the +.B -h +or +.B --help +options after the module has been specified to receive help specific +to that module. + +The following are included in the base package, and most of these can +be preceded by a +.B ! +to invert the sense of the match. +.SS tcp +These extensions are loaded if `--protocol tcp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. This can either be a service +name or a port number. An inclusive range can also be specified, +using the format +.IR port : port . +If the first port is omitted, "0" is assumed; if the last is omitted, +"65535" is assumed. +If the second port greater then the first they will be swapped. +The flag +.B --sport +is a convenient alias for this option. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. The flag +.B --dport +is a convenient alias for this option. +.TP +.BR "--tcp-flags " "[!] \fImask\fP \fIcomp\fP" +Match when the TCP flags are as specified. The first argument is the +flags which we should examine, written as a comma-separated list, and +the second argument is a comma-separated list of flags which must be +set. Flags are: +.BR "SYN ACK FIN RST URG PSH ALL NONE" . +Hence the command +.nf + ip6tables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST SYN +.fi +will only match packets with the SYN flag set, and the ACK, FIN and +RST flags unset. +.TP +.B "[!] --syn" +Only match TCP packets with the SYN bit set and the ACK and RST bits +cleared. Such packets are used to request TCP connection initiation; +for example, blocking such packets coming in an interface will prevent +incoming TCP connections, but outgoing TCP connections will be +unaffected. +It is equivalent to \fB--tcp-flags SYN,RST,ACK SYN\fP. +If the "!" flag precedes the "--syn", the sense of the +option is inverted. +.TP +.BR "--tcp-option " "[!] \fInumber\fP" +Match if TCP option set. +.SS udp +These extensions are loaded if `--protocol udp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. +See the description of the +.B --source-port +option of the TCP extension for details. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. +See the description of the +.B --destination-port +option of the TCP extension for details. +.SS ipv6-icmp +This extension is loaded if `--protocol ipv6-icmp' or `--protocol icmpv6' is +specified. It provides the following option: +.TP +.BR "--icmpv6-type " "[!] \fItypename\fP" +This allows specification of the ICMP type, which can be a numeric +IPv6-ICMP type, or one of the IPv6-ICMP type names shown by the command +.nf + ip6tables -p ipv6-icmp -h +.fi +.SS mac +.TP +.BR "--mac-source " "[!] \fIaddress\fP" +Match source MAC address. It must be of the form XX:XX:XX:XX:XX:XX. +Note that this only makes sense for packets coming from an Ethernet device +and entering the +.BR PREROUTING , +.B FORWARD +or +.B INPUT +chains. +.SS limit +This module matches at a limited rate using a token bucket filter. +A rule using this extension will match until this limit is reached +(unless the `!' flag is used). It can be used in combination with the +.B LOG +target to give limited logging, for example. +.TP +.BI "--limit " "rate" +Maximum average matching rate: specified as a number, with an optional +`/second', `/minute', `/hour', or `/day' suffix; the default is +3/hour. +.TP +.BI "--limit-burst " "number" +Maximum initial number of packets to match: this number gets +recharged by one every time the limit specified above is not reached, +up to this number; the default is 5. +.SS multiport +This module matches a set of source or destination ports. Up to 15 +ports can be specified. It can only be used in conjunction with +.B "-p tcp" +or +.BR "-p udp" . +.TP +.BR "--source-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the source port is one of the given ports. The flag +.B --sports +is a convenient alias for this option. +.TP +.BR "--destination-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the destination port is one of the given ports. The flag +.B --dports +is a convenient alias for this option. +.TP +.BR "--ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the both the source and destination ports are equal to each +other and to one of the given ports. +.SS mark +This module matches the netfilter mark field associated with a packet +(which can be set using the +.B MARK +target below). +.TP +.BR "--mark " "\fIvalue\fP[/\fImask\fP]" +Matches packets with the given unsigned mark value (if a mask is +specified, this is logically ANDed with the mask before the +comparison). +.SS owner +This module attempts to match various characteristics of the packet +creator, for locally-generated packets. It is only valid in the +.B OUTPUT +chain, and even this some packets (such as ICMP ping responses) may +have no owner, and hence never match. This is regarded as experimental. +.TP +.BI "--uid-owner " "userid" +Matches if the packet was created by a process with the given +effective user id. +.TP +.BI "--gid-owner " "groupid" +Matches if the packet was created by a process with the given +effective group id. +.TP +.BI "--pid-owner " "processid" +Matches if the packet was created by a process with the given +process id. +.TP +.BI "--sid-owner " "sessionid" +Matches if the packet was created by a process in the given session +group. +.\" .SS state +.\" This module, when combined with connection tracking, allows access to +.\" the connection tracking state for this packet. +.\" .TP +.\" .BI "--state " "state" +.\" Where state is a comma separated list of the connection states to +.\" match. Possible states are +.\" .B INVALID +.\" meaning that the packet is associated with no known connection, +.\" .B ESTABLISHED +.\" meaning that the packet is associated with a connection which has seen +.\" packets in both directions, +.\" .B NEW +.\" meaning that the packet has started a new connection, or otherwise +.\" associated with a connection which has not seen packets in both +.\" directions, and +.\" .B RELATED +.\" meaning that the packet is starting a new connection, but is +.\" associated with an existing connection, such as an FTP data transfer, +.\" or an ICMP error. +.\" .SS unclean +.\" This module takes no options, but attempts to match packets which seem +.\" malformed or unusual. This is regarded as experimental. +.\" .SS tos +.\" This module matches the 8 bits of Type of Service field in the IP +.\" header (ie. including the precedence bits). +.\" .TP +.\" .BI "--tos " "tos" +.\" The argument is either a standard name, (use +.\" .br +.\" iptables -m tos -h +.\" .br +.\" to see the list), or a numeric value to match. +.SH TARGET EXTENSIONS +ip6tables can use extended target modules: the following are included +in the standard distribution. +.SS LOG +Turn on kernel logging of matching packets. When this option is set +for a rule, the Linux kernel will print some information on all +matching packets (like most IPv6 IPv6-header fields) via the kernel log +(where it can be read with +.I dmesg +or +.IR syslogd (8)). +This is a "non-terminating target", i.e. rule traversal continues at +the next rule. So if you want to LOG the packets you refuse, use two +separate rules with the same matching criteria, first using target LOG +then DROP (or REJECT). +.TP +.BI "--log-level " "level" +Level of logging (numeric or see \fIsyslog.conf\fP(5)). +.TP +.BI "--log-prefix " "prefix" +Prefix log messages with the specified prefix; up to 29 letters long, +and useful for distinguishing messages in the logs. +.TP +.B --log-tcp-sequence +Log TCP sequence numbers. This is a security risk if the log is +readable by users. +.TP +.B --log-tcp-options +Log options from the TCP packet header. +.TP +.B --log-ip-options +Log options from the IPv6 packet header. +.SS MARK +This is used to set the netfilter mark value associated with the +packet. It is only valid in the +.B mangle +table. +.TP +.BI "--set-mark " "mark" +.SS REJECT +This is used to send back an error packet in response to the matched +packet: otherwise it is equivalent to +.B DROP +so it is a terminating TARGET, ending rule traversal. +This target is only valid in the +.BR INPUT , +.B FORWARD +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. The following option controls the nature of the error packet +returned: +.TP +.BI "--reject-with " "type" +The type given can be +.nf +.B " icmp6-no-route" +.B " no-route" +.B " icmp6-adm-prohibited" +.B " adm-prohibited" +.B " icmp6-addr-unreachable" +.B " addr-unreach" +.B " icmp6-port-unreachable" +.B " port-unreach" +.fi +which return the appropriate IPv6-ICMP error message (\fBport-unreach\fP is +the default). Finally, the option +.B tcp-reset +can be used on rules which only match the TCP protocol: this causes a +TCP RST packet to be sent back. This is mainly useful for blocking +.I ident +(113/tcp) probes which frequently occur when sending mail to broken mail +hosts (which won't accept your mail otherwise). +.\" .SS TOS +.\" This is used to set the 8-bit Type of Service field in the IP header. +.\" It is only valid in the +.\" .B mangle +.\" table. +.\" .TP +.\" .BI "--set-tos " "tos" +.\" You can use a numeric TOS values, or use +.\" .br +.\" iptables -j TOS -h +.\" .br +.\" to see the list of valid TOS names. +.\" .SS MIRROR +.\" This is an experimental demonstration target which inverts the source +.\" and destination fields in the IP header and retransmits the packet. +.\" It is only valid in the +.\" .BR INPUT , +.\" .B FORWARD +.\" and +.\" .B PREROUTING +.\" chains, and user-defined chains which are only called from those +.\" chains. Note that the outgoing packets are +.\" .B NOT +.\" seen by any packet filtering chains, connection tracking or NAT, to +.\" avoid loops and other problems. +.\" .SS SNAT +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B POSTROUTING +.\" chain. It specifies that the source address of the packet should be +.\" modified (and all future packets in this connection will also be +.\" mangled), and rules should cease being examined. It takes one option: +.\" .TP +.\" .BR "--to-source " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +.\" which can specify a single new source IP address, an inclusive range +.\" of IP addresses, and optionally, a port range (which is only valid if +.\" the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" ). +.\" If no port range is specified, then source ports below 512 will be +.\" mapped to other ports below 512: those between 512 and 1023 inclusive +.\" will be mapped to ports below 1024, and other ports will be mapped to +.\" 1024 or above. Where possible, no port alteration will occur. +.\" .SS DNAT +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B PREROUTING +.\" and +.\" .B OUTPUT +.\" chains, and user-defined chains which are only called from those +.\" chains. It specifies that the destination address of the packet +.\" should be modified (and all future packets in this connection will +.\" also be mangled), and rules should cease being examined. It takes one +.\" option: +.\" .TP +.\" .BR "--to-destination " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +.\" which can specify a single new destination IP address, an inclusive +.\" range of IP addresses, and optionally, a port range (which is only +.\" valid if the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" ). +.\" If no port range is specified, then the destination port will never be +.\" modified. +.\" .SS MASQUERADE +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B POSTROUTING +.\" chain. It should only be used with dynamically assigned IP (dialup) +.\" connections: if you have a static IP address, you should use the SNAT +.\" target. Masquerading is equivalent to specifying a mapping to the IP +.\" address of the interface the packet is going out, but also has the +.\" effect that connections are +.\" .I forgotten +.\" when the interface goes down. This is the correct behavior when the +.\" next dialup is unlikely to have the same interface address (and hence +.\" any established connections are lost anyway). It takes one option: +.\" .TP +.\" .BR "--to-ports " "\fIport\fP[-\fIport\fP]" +.\" This specifies a range of source ports to use, overriding the default +.\" .B SNAT +.\" source port-selection heuristics (see above). This is only valid +.\" if the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" . +.\" .SS REDIRECT +.\" This target is only valid in the +.\" .B nat +.\" table, in the +.\" .B PREROUTING +.\" and +.\" .B OUTPUT +.\" chains, and user-defined chains which are only called from those +.\" chains. It alters the destination IP address to send the packet to +.\" the machine itself (locally-generated packets are mapped to the +.\" 127.0.0.1 address). It takes one option: +.\" .TP +.\" .BR "--to-ports " "\fIport\fP[-\fIport\fP]" +.\" This specifies a destination port or range of ports to use: without +.\" this, the destination port is never altered. This is only valid +.\" if the rule also specifies +.\" .B "-p tcp" +.\" or +.\" .BR "-p udp" . +.SH DIAGNOSTICS +Various error messages are printed to standard error. The exit code +is 0 for correct functioning. Errors which appear to be caused by +invalid or abused command line parameters cause an exit code of 2, and +other errors cause an exit code of 1. +.SH BUGS +Bugs? What's this? ;-) +Well... the counters are not reliable on sparc64. +.SH COMPATIBILITY WITH IPCHAINS +This +.B ip6tables +is very similar to ipchains by Rusty Russell. The main difference is +that the chains +.B INPUT +and +.B OUTPUT +are only traversed for packets coming into the local host and +originating from the local host respectively. Hence every packet only +passes through one of the three chains (except loopback traffic, which +involves both INPUT and OUTPUT chains); previously a forwarded packet +would pass through all three. +.PP +The other main difference is that +.B -i +refers to the input interface; +.B -o +refers to the output interface, and both are available for packets +entering the +.B FORWARD +chain. +.\" .PP The various forms of NAT have been separated out; +.\" .B iptables +.\" is a pure packet filter when using the default `filter' table, with +.\" optional extension modules. This should simplify much of the previous +.\" confusion over the combination of IP masquerading and packet filtering +.\" seen previously. So the following options are handled differently: +.\" .br +.\" -j MASQ +.\" .br +.\" -M -S +.\" .br +.\" -M -L +.\" .br +There are several other changes in ip6tables. +.SH SEE ALSO +.BR ip6tables-save (8), +.BR ip6tables-restore(8), +.BR iptables (8), +.BR iptables-save (8), +.BR iptables-restore (8). +.P +The packet-filtering-HOWTO details iptables usage for +packet filtering, the NAT-HOWTO details NAT, +the netfilter-extensions-HOWTO details the extensions that are +not in the standard distribution, +and the netfilter-hacking-HOWTO details the netfilter internals. +.br +See +.BR "http://www.netfilter.org/" . +.SH AUTHORS +Rusty Russell wrote iptables, in early consultation with Michael +Neuling. +.PP +Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic packet +selection framework in iptables, then wrote the mangle table, the owner match, +the mark stuff, and ran around doing cool stuff everywhere. +.PP +James Morris wrote the TOS target, and tos match. +.PP +Jozsef Kadlecsik wrote the REJECT target. +.PP +Harald Welte wrote the ULOG target, TTL match+target and libipulog. +.PP +The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Jozsef Kadlecsik, +James Morris, Harald Welte and Rusty Russell. +.PP +ip6tables man page created by Andras Kis-Szabo, based on +iptables man page written by Herve Eychenne . +.\" .. and did I mention that we are incredibly cool people? +.\" .. sexy, too .. +.\" .. witty, charming, powerful .. +.\" .. and most of all, modest .. diff --git a/ippool/Makefile b/ippool/Makefile new file mode 100644 index 0000000..100c2e4 --- /dev/null +++ b/ippool/Makefile @@ -0,0 +1,18 @@ +EXTRAS+=$(shell [ -f $(KERNEL_DIR)/include/linux/netfilter_ipv4/ip_pool.h ] && echo ippool/libippool.a ippool/ippool) + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(KERN_TARGETS) $(SHARED_LIBS) $(EXTRAS) + +else +EXTRA_DEPENDS+=$(shell [ -f $(KERNEL_DIR)/include/linux/netfilter_ipv4/ip_pool.h ] && echo ippool/libippool.d) + +ippool/libippool.a: ippool/libippool.a(ippool/libippool.o) + +ippool/libippool.d: %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | sed -e 's@^.*\.o:@$*.d $*.a($*.o):@' > $@ + +ippool/ippool.o: ippool/ippool.c +ippool/ippool: ippool/ippool.o ippool/libippool.a + $(CC) $(CFLAGS) -o $@ $^ +endif diff --git a/ippool/ippool.c b/ippool/ippool.c new file mode 100644 index 0000000..3a680d3 --- /dev/null +++ b/ippool/ippool.c @@ -0,0 +1,589 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* translate errnos, as returned by our *sockopt() functions */ +static char *errno2msg(int op, int err) +{ + switch(err) { + case ERANGE: + return "Address out of pool range"; + } + return strerror(err); +} + +static void usage(char *msg) +{ + if (msg) fprintf(stderr, "ERROR: %s\n", msg); + fprintf(stderr, +"Usage: ippool [-LADCNF] [POOL] [args]\n" +"The POOL argument is either a number, or a name from %s\n" +, IPPOOL_CONF); + fprintf(stderr, +"ippool [-n] [-l|-L POOL] [-u] [-v]\n" + "\tLists all (-l), or a single (-L) pool.\n" + "\tWith -u, summarized usage counts are listed.\n" + "\tWith -v, each pool membership is shown, one per line.\n" +"ippool [-n] [-f|-F [POOL]]\n" + "\tflushes POOL (or all pools.)\n" +"ippool [-n] [-x|-X [POOL]]\n" + "\tremoves POOL (or all pools.)\n" +"ippool [-n] -B\n" + "\tcreates all fully specified pools found in the config file.\n" +"ippool [-n] -N POOL [-t type] [FIRST LAST]\n" + "\tcreates POOL of IP addresses FIRST to LAST (inclusive). If a\n" + "\tpool name from the config file %s is used, type and\n" + "\taddress information can be defined there. The -t argument\n" + "\tgives the type (default bitmap).\n" +"ippool [-n] -A POOL ADDR\n" + "\tadds ADDR to POOL\n" +"ippool [-n] -D POOL ADDR\n" + "\tremoves ADDR from POOL\n" +"ippool [-n] -C POOL ADDR\n" + "\ttests ADDR against membership in POOL\n" +, IPPOOL_CONF); + exit(1); +} + +/* config file parsing */ + +#define IP_POOL_T_NONE 0 +#define IP_POOL_T_BITMAP 1 + +static int conf_type = IP_POOL_T_NONE; +static unsigned long conf_addr = 0; +static unsigned long conf_addr2 = 0; + +#define SCAN_EOF (IP_POOL_NONE-1) + +static ip_pool_t get_index_line( + FILE *fp, + char **namep, + char **typep, + char **argp +) { + char *p; + ip_pool_t index; + static char buf[256]; + + if (namep) *namep = 0; + if (typep) *typep = 0; + if (argp) *argp = 0; + + if (!fgets(buf, sizeof(buf), fp)) return SCAN_EOF; + + p = strtok(buf, " \t\n"); + if (!p || *p == '#') return IP_POOL_NONE; + index = atoi(p); + + p = strtok(0, " \t\n"); + if (!p || *p == '#') return index; + if (namep) *namep = p; + + p = strtok(0, " \t\n"); + if (!p || *p == '#') return index; + if (typep) *typep = p; + + p = strtok(0, "#\n"); + if (argp) *argp = p; + + return index; +} + +static ip_pool_t get_index(char *name) +{ + FILE *fp; + char *poolname, *type, *arg, *p; + ip_pool_t res; + + if (isdigit(*name)) + return atoi(name); + fp = fopen(IPPOOL_CONF, "r"); + if (!fp) { + fprintf(stderr, "cannot open %s - no pool names", IPPOOL_CONF); + exit(1); + } + while (SCAN_EOF != (res=get_index_line(fp, &poolname, &type, &arg))) { + if (poolname && 0 == strcmp(poolname, name)) { + if (!type || (0 == strcmp(type, "bitmap"))) { + conf_type = IP_POOL_T_BITMAP; + p = strtok(arg, " \t"); + if (p) { + conf_addr = inet_addr(p); + p = strtok(0, " \t"); + if (p) + conf_addr2 = inet_addr(p); + else + conf_addr = 0; + } + } + break; + } + } + fclose(fp); + if (res == SCAN_EOF) { + fprintf(stderr, "pool '%s' not found in %s\n", + name, IPPOOL_CONF); + exit(1); + } + return res; +} + +static char *get_name(ip_pool_t index) +{ + FILE *fp; + static char sbuf[256]; + int ok = 0; + + fp = fopen(IPPOOL_CONF, "r"); + if (fp) { + while (fgets(sbuf, sizeof(sbuf), fp)) { + char *p = strtok(sbuf, " \t\n"); + + if (!p || *p == '#') continue; + if (index != atoi(p)) continue; + p = strtok(0, " \t\n"); + if (!p || *p == '#') continue; + memmove(sbuf, p, strlen(p)+1); + ok = 1; + break; + } + fclose(fp); + } + if (!ok) sprintf(sbuf, "%d", index); + return sbuf; +} + +/* user/kernel interaction */ + +static int fd = -1; +static int flag_n = 0; /* do not do anything; just brag about it */ +static int flag_v = 0; /* be verbose (list members) */ +static int flag_q = 0; /* be quiet */ +static int flag_u = 0; /* show usage counts in listings */ +static char *flag_t = "bitmap"; /* pool type */ + +static ip_pool_t high_nr(void) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_HIGH_NR; + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + fprintf(stderr, + "IP_POOL_HIGH_NR failed: %s\n", + errno2msg(IP_POOL_HIGH_NR, errno)); + exit(1); + } + return req.index; +} + +static void do_list(ip_pool_t pool) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + u_int32_t first_ip; + u_int32_t last_ip; + + req.op = IP_POOL_LOOKUP; + req.index = pool; + req.addr = req.addr2 = 0; + reqlen = sizeof(req); + if (0 == getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + struct in_addr ina; + ina.s_addr = req.addr; + printf("%s %s", get_name(req.index), inet_ntoa(ina)); + ina.s_addr = req.addr2; + printf(" %s", inet_ntoa(ina)); + first_ip = ntohl(req.addr); + last_ip = ntohl(req.addr2); + if (flag_u) { + req.op = IP_POOL_USAGE; + req.index = pool; + reqlen = sizeof(req); + if (0 == getsockopt(fd, SOL_IP, SO_IP_POOL, + &req, &reqlen)) { + printf(" %d %d", req.addr, req.addr2); + } else { + printf(" - -"); + } + } + printf("\n"); + if (flag_v) { + while (first_ip <= last_ip) { + req.op = IP_POOL_TEST_ADDR; + req.index = pool; + ina.s_addr = req.addr = htonl(first_ip); + reqlen = sizeof(req); + if (0 == getsockopt(fd, SOL_IP, SO_IP_POOL, + &req, &reqlen)) { + if (req.addr) printf("\t%s\n", + inet_ntoa(ina)); + } + first_ip++; + } + } + } +} + +static void do_list_all(void) +{ + ip_pool_t i, highest = high_nr(); + + for (i=0; i<=highest; i++) + do_list(i); +} + +static void do_flush(ip_pool_t pool) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_FLUSH; + req.index = pool; + if (flag_n) { + printf("ippool -F %d\n", req.index); + return; + } + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + int err = errno; + fprintf(stderr, + "IP_POOL_FLUSH %s failed: %s\n", + get_name(pool), errno2msg(IP_POOL_FLUSH, err)); + exit(1); + } +} + +static void do_flush_all(void) +{ + ip_pool_t i, highest = high_nr(); + + for (i=0; i<=highest; i++) + do_flush(i); +} + +static void do_destroy(ip_pool_t pool) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_DESTROY; + req.index = pool; + if (flag_n) { + printf("ippool -X %d\n", req.index); + return; + } + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + int err = errno; + fprintf(stderr, + "IP_POOL_DESTROY %s failed: %s\n", + get_name(pool), errno2msg(IP_POOL_DESTROY, err)); + exit(1); + } +} + +static void do_destroy_all(void) +{ + ip_pool_t i, highest = high_nr(); + + for (i=0; i<=highest; i++) + do_destroy(i); +} + +static int do_adddel(ip_pool_t pool, char *straddr, int op) +{ + struct ip_pool_request req; + int res; + int reqlen = sizeof(req); + + req.op = op; + req.index = pool; + req.addr = inet_addr(straddr); + req.addr2 = 0; + if (flag_n) { + printf("ippool -%c %s %s\n", + (op == IP_POOL_ADD_ADDR) ? 'A' : 'D', + get_name(req.index), straddr); + return 0; + } + res = getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen); + if (0 > res) { + int err = errno; + fprintf(stderr, + "IP_POOL_ADD/DEL %s in %s failed: %s\n", + straddr, get_name(pool), errno2msg(op, err)); + exit(1); + } + if (!flag_q) + printf("%s %s %d %d\n", get_name(pool), straddr, req.addr, + op == IP_POOL_ADD_ADDR ? 1 : 0); + return req.addr; +} + +static int do_check(ip_pool_t pool, char *straddr) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_TEST_ADDR; + req.index = pool; + req.addr = inet_addr(straddr); + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + int err = errno; + fprintf(stderr, + "IP_POOL_TEST_ADDR %s in %s failed: %s\n", + straddr, get_name(pool), + errno2msg(IP_POOL_TEST_ADDR, err)); + exit(1); + } + if (!flag_q) + printf("%s %s %d\n", get_name(req.index), straddr, req.addr); + return !req.addr; +} + +static void do_new(ip_pool_t pool, int argc, char **argv) +{ + struct ip_pool_request req; + int reqlen = sizeof(req); + + req.op = IP_POOL_INIT; + req.index = pool; + if (argc >= 2) { + conf_type = IP_POOL_T_BITMAP; + conf_addr = inet_addr(argv[0]); + conf_addr2 = inet_addr(argv[1]); + } + if (conf_type != IP_POOL_T_BITMAP || conf_addr == 0 || conf_addr2 == 0) + usage("bad pool specification"); + req.addr = conf_addr; + req.addr2 = conf_addr2; + if (flag_n) { + printf("ippool -N %s [-T %s] ...\n", get_name(pool), + conf_type == IP_POOL_T_BITMAP + ? "bitmap" + : "???"); + return; + } + if (0 > getsockopt(fd, SOL_IP, SO_IP_POOL, &req, &reqlen)) { + struct in_addr ina; + int err = errno; + ina.s_addr = conf_addr; + fprintf(stderr, + "IP_POOL_INIT %s [%s-", + get_name(pool), inet_ntoa(ina)); + ina.s_addr = conf_addr2; + fprintf(stderr, + "%s] failed: %s\n", + inet_ntoa(ina), errno2msg(IP_POOL_INIT, err)); + exit(1); + } +} + +static void do_new_all(void) +{ + FILE *fp; + char *poolname, *type, *arg, *p; + int res; + + fp = fopen(IPPOOL_CONF, "r"); + if (!fp) { + fprintf(stderr, "cannot open %s - no pool names", IPPOOL_CONF); + exit(1); + } + while (SCAN_EOF != (res=get_index_line(fp, &poolname, &type, &arg))) { + if (poolname && type && (0 == strcmp(type, "bitmap"))) { + conf_type = IP_POOL_T_BITMAP; + p = strtok(arg, " \t"); + if (p) { + conf_addr = inet_addr(p); + p = strtok(0, " \t"); + if (p) + conf_addr2 = inet_addr(p); + else + conf_addr = 0; + } + if (conf_addr != 0) { + if (flag_v) + printf("ippool -N %s (%s) [%s]\n", + poolname, type, arg); + do_new(res, 0, 0); + } + } + } + fclose(fp); +} + +int main(int argc, char **argv) +{ + int opt; + int op; +#define OP_NONE 0 +#define OP_LIST 1 +#define OP_LIST_ALL 2 +#define OP_FLUSH 3 +#define OP_FLUSH_ALL 4 +#define OP_DESTROY 5 +#define OP_DESTROY_ALL 6 +#define OP_ADD 7 +#define OP_DEL 8 +#define OP_CHECK 9 +#define OP_NEW 10 +#define OP_NEW_ALL 11 +#define OP_HIGHEST 12 + char *op_pool; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "cannot get DGRAM socket: %s\n", + strerror(errno)); + exit(1); + } + op_pool = 0; + op = OP_NONE; + /* GRRR. I thought getopt() would allow an "L*" specifier, for an -L + * taking an optional argument. Does not work. Bad. + * Adding -l for -L without argument, also -f/-F and -x/-X. + */ + while (EOF != (opt=getopt( argc, argv, "HhnvuqA:D:C:N:t:L:F:X:lfxB"))) + switch(opt) { + case 'l': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_LIST_ALL; + break; + case 'L': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_LIST; + op_pool = optarg; + break; + case 'f': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_FLUSH_ALL; + break; + case 'F': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_FLUSH; + op_pool = optarg; + break; + case 'x': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_DESTROY_ALL; + break; + case 'X': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_DESTROY; + op_pool = optarg; + break; + case 'A': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_ADD; + op_pool = optarg; + break; + case 'D': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_DEL; + op_pool = optarg; + break; + case 'C': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_CHECK; + op_pool = optarg; + break; + case 'B': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_NEW_ALL; + break; + case 'N': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_NEW; + op_pool = optarg; + break; + case 'H': + if (op != OP_NONE) usage("conflicting operations"); + op = OP_HIGHEST; + break; + case 't': + flag_t = optarg; + break; + case 'n': + flag_n = 1; + break; + case 'v': + flag_v = 1; + break; + case 'u': + flag_u = 1; + break; + case 'q': + flag_q = 1; + break; + case 'h': + usage(0); + default: + usage("bad option"); + } + if (op == OP_NONE) + usage("no operation specified"); + if (op == OP_LIST_ALL) { + do_list_all(); + return 0; + } + if (op == OP_LIST) { + do_list(get_index(op_pool)); + return 0; + } + if (op == OP_FLUSH_ALL) { + do_flush_all(); + return 0; + } + if (op == OP_FLUSH) { + do_flush(get_index(op_pool)); + return 0; + } + if (op == OP_DESTROY_ALL) { + do_destroy_all(); + return 0; + } + if (op == OP_DESTROY) { + do_destroy(get_index(op_pool)); + return 0; + } + if (op == OP_CHECK) { + if (optind >= argc) + usage("missing address to check"); + return do_check(get_index(op_pool), argv[optind]); + } + if (op == OP_NEW_ALL) { + do_new_all(); + return 0; + } + if (op == OP_NEW) { + do_new(get_index(op_pool), argc-optind, argv+optind); + return 0; + } + if (op == OP_ADD) { + if (optind >= argc) + usage("missing address to add"); + return do_adddel(get_index(op_pool), + argv[optind], IP_POOL_ADD_ADDR); + } + if (op == OP_DEL) { + if (optind >= argc) + usage("missing address to delete"); + return do_adddel(get_index(op_pool), + argv[optind], IP_POOL_DEL_ADDR); + } + if (op == OP_HIGHEST) { + printf("%d\n", high_nr()); + return 0; + } + usage("no operation specified"); + return 0; +} diff --git a/ippool/libippool.c b/ippool/libippool.c new file mode 100644 index 0000000..7f3ac76 --- /dev/null +++ b/ippool/libippool.c @@ -0,0 +1,72 @@ +/* support functions for ip_pool modules */ + +#include +#include +#include +#include + +#include + +void ip_pool_init(void) +{ +} + +ip_pool_t ip_pool_get_index(char *name) +{ + FILE *fp; + char buf[256]; + + if (isdigit(*name)) + return atoi(name); + fp = fopen(IPPOOL_CONF, "r"); + if (!fp) exit_error(PARAMETER_PROBLEM, + "cannot open %s - no pool names", IPPOOL_CONF); + while (fgets(buf, sizeof(buf), fp)) { + char *p = strtok(buf, " \t\n"); + ip_pool_t index; + + if (!p || *p == '#') continue; + index = atoi(p); + p = strtok(0, " \t\n"); + if (p && 0 == strcmp(p, name)) { + fclose(fp); + return index; + } + } + exit_error(PARAMETER_PROBLEM, + "pool '%s' not found in %s\n", name, IPPOOL_CONF); +} + +char *ip_pool_get_name(char *buf, int size, ip_pool_t index, int numeric) +{ + FILE *fp; + int ok = 0; + + /* make sure we have enough room, at least for a %d */ + if (size < 16) + exit_error(PARAMETER_PROBLEM, + "ip_pool_support:get_name buf too small (%d vs. 16)\n", + size); + if (numeric) + goto do_numeric; + fp = fopen(IPPOOL_CONF, "r"); + if (fp) { + while (fgets(buf, size, fp)) { + char *p = strtok(buf, " \t\n"); + + if (!p || *p == '#') continue; + if (index != atoi(p)) continue; + p = strtok(0, " \t\n"); + if (!p || *p == '#') continue; + memmove(buf, p, strlen(p)+1); + ok = 1; + break; + } + fclose(fp); + } + if (!ok) { +do_numeric: + sprintf(buf, "%d", index); + } + return buf; +} diff --git a/iptables-config b/iptables-config new file mode 100644 index 0000000..80e37fb --- /dev/null +++ b/iptables-config @@ -0,0 +1,37 @@ +# Load additional iptables modules (nat helpers) +# Default: -none- +# Space separated list of nat helpers (e.g. 'ip_nat_ftp ip_nat_irc'), which +# are loaded after the firewall rules are applied. Options for the helpers are +# stored in /etc/modules.conf. +#IPTABLES_MODULES="" + +# Unload modules on restart and stop +# Value: yes|no, default: yes +# This option has to be 'yes' to get to a sane state for a firewall +# restart or stop. Only set to 'no' if there are problems unloading netfilter +# modules. +#IPTABLES_MODULES_UNLOAD="yes" + +# Save current firewall rules on stop. +# Value: yes|no, default: no +# Saves all firewall rules to /etc/sysconfig/iptables if firewall gets stopped +# (e.g. on system shutdown). +#IPTABLES_SAVE_ON_STOP="no" + +# Save current firewall rules on restart. +# Value: yes|no, default: no +# Saves all firewall rules to /etc/sysconfig/iptables if firewall gets +# restarted. +#IPTABLES_SAVE_ON_RESTART="no" + +# Save (and restore) rule and chain counter. +# Value: yes|no, default: no +# Save counters for rules and chains to /etc/sysconfig/iptables if +# 'service iptables save' is called or on stop or restart if SAVE_ON_STOP or +# SAVE_ON_RESTART is enabled. +#IPTABLES_SAVE_COUNTER="no" + +# Numeric status output +# Value: yes|no, default: no +# Print IP addresses and port numbers in numeric format in the status output. +#IPTABLES_STATUS_NUMERIC="no" diff --git a/iptables.8 b/iptables.8 new file mode 100644 index 0000000..b79f1ec --- /dev/null +++ b/iptables.8 @@ -0,0 +1,1072 @@ +.TH IPTABLES 8 "Mar 09, 2002" "" "" +.\" +.\" Man page written by Herve Eychenne (May 1999) +.\" It is based on ipchains page. +.\" TODO: add a word for protocol helpers (FTP, IRC, SNMP-ALG) +.\" +.\" ipchains page by Paul ``Rusty'' Russell March 1997 +.\" Based on the original ipfwadm man page by Jos Vos +.\" +.\" 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., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +iptables \- administration tool for IPv4 packet filtering and NAT +.SH SYNOPSIS +.BR "iptables [-t table] -[AD] " "chain rule-specification [options]" +.br +.BR "iptables [-t table] -I " "chain [rulenum] rule-specification [options]" +.br +.BR "iptables [-t table] -R " "chain rulenum rule-specification [options]" +.br +.BR "iptables [-t table] -D " "chain rulenum [options]" +.br +.BR "iptables [-t table] -[LFZ] " "[chain] [options]" +.br +.BR "iptables [-t table] -N " "chain" +.br +.BR "iptables [-t table] -X " "[chain]" +.br +.BR "iptables [-t table] -P " "chain target [options]" +.br +.BR "iptables [-t table] -E " "old-chain-name new-chain-name" +.SH DESCRIPTION +.B Iptables +is used to set up, maintain, and inspect the tables of IP packet +filter rules in the Linux kernel. Several different tables +may be defined. Each table contains a number of built-in +chains and may also contain user-defined chains. + +Each chain is a list of rules which can match a set of packets. Each +rule specifies what to do with a packet that matches. This is called +a `target', which may be a jump to a user-defined chain in the same +table. + +.SH TARGETS +A firewall rule specifies criteria for a packet, and a target. If the +packet does not match, the next rule in the chain is the examined; if +it does match, then the next rule is specified by the value of the +target, which can be the name of a user-defined chain or one of the +special values +.IR ACCEPT , +.IR DROP , +.IR QUEUE , +or +.IR RETURN . +.PP +.I ACCEPT +means to let the packet through. +.I DROP +means to drop the packet on the floor. +.I QUEUE +means to pass the packet to userspace (if supported by the kernel). +.I RETURN +means stop traversing this chain and resume at the next rule in the +previous (calling) chain. If the end of a built-in chain is reached +or a rule in a built-in chain with target +.I RETURN +is matched, the target specified by the chain policy determines the +fate of the packet. +.SH TABLES +There are currently three independent tables (which tables are present +at any time depends on the kernel configuration options and which +modules are present). +.TP +.BI "-t, --table " "table" +This option specifies the packet matching table which the command +should operate on. If the kernel is configured with automatic module +loading, an attempt will be made to load the appropriate module for +that table if it is not already there. + +The tables are as follows: +.RS +.TP .4i +.BR "filter" : +This is the default table (if no -t option is passed). It contains +the built-in chains +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for packets being routed through the box), and +.B OUTPUT +(for locally-generated packets). +.TP +.BR "nat" : +This table is consulted when a packet that creates a new +connection is encountered. It consists of three built-ins: +.B PREROUTING +(for altering packets as soon as they come in), +.B OUTPUT +(for altering locally-generated packets before routing), and +.B POSTROUTING +(for altering packets as they are about to go out). +.TP +.BR "mangle" : +This table is used for specialized packet alteration. Until kernel +2.4.17 it had two built-in chains: +.B PREROUTING +(for altering incoming packets before routing) and +.B OUTPUT +(for altering locally-generated packets before routing). +Since kernel 2.4.18, three other built-in chains are also supported: +.B INPUT +(for packets coming into the box itself), +.B FORWARD +(for altering packets being routed through the box), and +.B POSTROUTING +(for altering packets as they are about to go out). +.RE +.SH OPTIONS +The options that are recognized by +.B iptables +can be divided into several different groups. +.SS COMMANDS +These options specify the specific action to perform. Only one of them +can be specified on the command line unless otherwise specified +below. For all the long versions of the command and option names, you +need to use only enough letters to ensure that +.B iptables +can differentiate it from all other options. +.TP +.BI "-A, --append " "chain rule-specification" +Append one or more rules to the end of the selected chain. +When the source and/or destination names resolve to more than one +address, a rule will be added for each possible address combination. +.TP +.BI "-D, --delete " "chain rule-specification" +.ns +.TP +.BI "-D, --delete " "chain rulenum" +Delete one or more rules from the selected chain. There are two +versions of this command: the rule can be specified as a number in the +chain (starting at 1 for the first rule) or a rule to match. +.TP +.BR "-I, --insert " "\fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP" +Insert one or more rules in the selected chain as the given rule +number. So, if the rule number is 1, the rule or rules are inserted +at the head of the chain. This is also the default if no rule number +is specified. +.TP +.BI "-R, --replace " "chain rulenum rule-specification" +Replace a rule in the selected chain. If the source and/or +destination names resolve to multiple addresses, the command will +fail. Rules are numbered starting at 1. +.TP +.BR "-L, --list " "[\fIchain\fP]" +List all rules in the selected chain. If no chain is selected, all +chains are listed. As every other iptables command, it applies to the +specified table (filter is the default), so NAT rules get listed by +.nf + iptables -t nat -n -L +.fi +Please note that it is often used with the +.B -n +option, in order to avoid long reverse DNS lookups. +It is legal to specify the +.B -Z +(zero) option as well, in which case the chain(s) will be atomically +listed and zeroed. The exact output is affected by the other +arguments given. The exact rules are suppressed until you use +.nf + iptables -L -v +.fi +.TP +.BR "-F, --flush " "[\fIchain\fP]" +Flush the selected chain (all the chains in the table if none is given). +This is equivalent to deleting all the rules one by one. +.TP +.BR "-Z, --zero " "[\fIchain\fP]" +Zero the packet and byte counters in all chains. It is legal to +specify the +.B "-L, --list" +(list) option as well, to see the counters immediately before they are +cleared. (See above.) +.TP +.BI "-N, --new-chain " "chain" +Create a new user-defined chain by the given name. There must be no +target of that name already. +.TP +.BR "-X, --delete-chain " "[\fIchain\fP]" +Delete the optional user-defined chain specified. There must be no references +to the chain. If there are, you must delete or replace the referring +rules before the chain can be deleted. If no argument is given, it +will attempt to delete every non-builtin chain in the table. +.TP +.BI "-P, --policy " "chain target" +Set the policy for the chain to the given target. See the section +.B TARGETS +for the legal targets. Only built-in (non-user-defined) chains can have +policies, and neither built-in nor user-defined chains can be policy +targets. +.TP +.BI "-E, --rename-chain " "old-chain new-chain" +Rename the user specified chain to the user supplied name. This is +cosmetic, and has no effect on the structure of the table. +.TP +.B -h +Help. +Give a (currently very brief) description of the command syntax. +.SS PARAMETERS +The following parameters make up a rule specification (as used in the +add, delete, insert, replace and append commands). +.TP +.BR "-p, --protocol " "[!] \fIprotocol\fP" +The protocol of the rule or of the packet to check. +The specified protocol can be one of +.IR tcp , +.IR udp , +.IR icmp , +or +.IR all , +or it can be a numeric value, representing one of these protocols or a +different one. A protocol name from /etc/protocols is also allowed. +A "!" argument before the protocol inverts the +test. The number zero is equivalent to +.IR all . +Protocol +.I all +will match with all protocols and is taken as default when this +option is omitted. +.TP +.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]" +Source specification. +.I Address +can be either a network name, a hostname (please note that specifying +any name to be resolved with a remote query such as DNS is a really bad idea), +a network IP address (with /mask), or a plain IP address. +The +.I mask +can be either a network mask or a plain number, +specifying the number of 1's at the left side of the network mask. +Thus, a mask of +.I 24 +is equivalent to +.IR 255.255.255.0 . +A "!" argument before the address specification inverts the sense of +the address. The flag +.B --src +is an alias for this option. +.TP +.BR "-d, --destination " "[!] \fIaddress\fP[/\fImask\fP]" +Destination specification. +See the description of the +.B -s +(source) flag for a detailed description of the syntax. The flag +.B --dst +is an alias for this option. +.TP +.BI "-j, --jump " "target" +This specifies the target of the rule; i.e., what to do if the packet +matches it. The target can be a user-defined chain (other than the +one this rule is in), one of the special builtin targets which decide +the fate of the packet immediately, or an extension (see +.B EXTENSIONS +below). If this +option is omitted in a rule, then matching the rule will have no +effect on the packet's fate, but the counters on the rule will be +incremented. +.TP +.BR "-i, --in-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be received (only for +packets entering the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +.BR "-o, --out-interface " "[!] \fIname\fP" +Name of an interface via which a packet is going to be sent (for packets +entering the +.BR FORWARD , +.B OUTPUT +and +.B POSTROUTING +chains). When the "!" argument is used before the interface name, the +sense is inverted. If the interface name ends in a "+", then any +interface which begins with this name will match. If this option is +omitted, any interface name will match. +.TP +.B "[!] " "-f, --fragment" +This means that the rule only refers to second and further fragments +of fragmented packets. Since there is no way to tell the source or +destination ports of such a packet (or ICMP type), such a packet will +not match any rules which specify them. When the "!" argument +precedes the "-f" flag, the rule will only match head fragments, or +unfragmented packets. +.TP +.BI "-c, --set-counters " "PKTS BYTES" +This enables the administrator to initialize the packet and byte +counters of a rule (during +.B INSERT, +.B APPEND, +.B REPLACE +operations). +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +.B "-v, --verbose" +Verbose output. This option makes the list command show the interface +name, the rule options (if any), and the TOS masks. The packet and +byte counters are also listed, with the suffix 'K', 'M' or 'G' for +1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see +the +.B -x +flag to change this). +For appending, insertion, deletion and replacement, this causes +detailed information on the rule or rules to be printed. +.TP +.B "-n, --numeric" +Numeric output. +IP addresses and port numbers will be printed in numeric format. +By default, the program will try to display them as host names, +network names, or services (whenever applicable). +.TP +.B "-x, --exact" +Expand numbers. +Display the exact value of the packet and byte counters, +instead of only the rounded number in K's (multiples of 1000) +M's (multiples of 1000K) or G's (multiples of 1000M). This option is +only relevant for the +.B -L +command. +.TP +.B "--line-numbers" +When listing rules, add line numbers to the beginning of each rule, +corresponding to that rule's position in the chain. +.TP +.B "--modprobe=command" +When adding or inserting rules into a chain, use +.B command +to load any necessary modules (targets, match extensions, etc). +.SH MATCH EXTENSIONS +iptables can use extended packet matching modules. These are loaded +in two ways: implicitly, when +.B -p +or +.B --protocol +is specified, or with the +.B -m +or +.B --match +options, followed by the matching module name; after these, various +extra command line options become available, depending on the specific +module. You can specify multiple extended match modules in one line, +and you can use the +.B -h +or +.B --help +options after the module has been specified to receive help specific +to that module. + +The following are included in the base package, and most of these can +be preceded by a +.B ! +to invert the sense of the match. +.SS ah +This module matches the SPIs in AH header of IPSec packets. +.TP +.BR "--ahspi " "[!] \fIspi\fP[:\fIspi\fP]" +.SS conntrack +This module, when combined with connection tracking, allows access to +more connection tracking information than the "state" match. +(this module is present only if iptables was compiled under a kernel +supporting this feature) +.TP +.BI "--ctstate " "state" +Where state is a comma separated list of the connection states to +match. Possible states are +.B INVALID +meaning that the packet is associated with no known connection, +.B ESTABLISHED +meaning that the packet is associated with a connection which has seen +packets in both directions, +.B NEW +meaning that the packet has started a new connection, or otherwise +associated with a connection which has not seen packets in both +directions, and +.B RELATED +meaning that the packet is starting a new connection, but is +associated with an existing connection, such as an FTP data transfer, +or an ICMP error. +.B SNAT +A virtual state, matching if the original source address differs from +the reply destination. +.B DNAT +A virtual state, matching if the original destination differs from the +reply source. +.TP +.BI "--ctproto " "proto" +Protocol to match (by number or name) +.TP +.BI "--ctorigsrc " "[!] \fIaddress\fP[/\fImask\fP]" +Match against original source address +.TP +.BI "--ctorigdst " "[!] \fIaddress\fP[/\fImask\fP]" +Match against original destination address +.TP +.BI "--ctreplsrc " "[!] \fIaddress\fP[/\fImask\fP]" +Match against reply source address +.TP +.BI "--ctrepldst " "[!] \fIaddress\fB[/\fImask\fP]" +Match against reply destination address +.TP +.BI "--ctstatus " "[\fINONE|EXPECTED|SEEN_REPLY|ASSURED\fP][,...]" +Match against internal conntrack states +.TP +.BI "--ctexpire " "\fItime\fP[\fI:time\fP]" +Match remaining lifetime in seconds against given value +or range of values (inclusive) +.SS dscp +This module matches the 6 bit DSCP field within the TOS field in the +IP header. DSCP has superseded TOS within the IETF. +.TP +.BI "--dscp " "value" +Match against a numeric (decimal or hex) value [0-32]. +.TP +.BI "--dscp-class " "\fIDiffServ Class\fP" +Match the DiffServ class. This value may be any of the +BE, EF, AFxx or CSx classes. It will then be converted +into it's according numeric value. +.SS esp +This module matches the SPIs in ESP header of IPSec packets. +.TP +.BR "--espspi " "[!] \fIspi\fP[:\fIspi\fP]" +.SS helper +This module matches packets related to a specific conntrack-helper. +.TP +.BI "--helper " "string" +Matches packets related to the specified conntrack-helper. +.RS +.PP +string can be "ftp" for packets related to a ftp-session on default port. +For other ports append -portnr to the value, ie. "ftp-2121". +.PP +Same rules apply for other conntrack-helpers. +.RE +.SS icmp +This extension is loaded if `--protocol icmp' is specified. It +provides the following option: +.TP +.BR "--icmp-type " "[!] \fItypename\fP" +This allows specification of the ICMP type, which can be a numeric +ICMP type, or one of the ICMP type names shown by the command +.nf + iptables -p icmp -h +.fi +.SS length +This module matches the length of a packet against a specific value +or range of values. +.TP +.BR "--length " "\fIlength\fP[:\fIlength\fP]" +.SS limit +This module matches at a limited rate using a token bucket filter. +A rule using this extension will match until this limit is reached +(unless the `!' flag is used). It can be used in combination with the +.B LOG +target to give limited logging, for example. +.TP +.BI "--limit " "rate" +Maximum average matching rate: specified as a number, with an optional +`/second', `/minute', `/hour', or `/day' suffix; the default is +3/hour. +.TP +.BI "--limit-burst " "number" +Maximum initial number of packets to match: this number gets +recharged by one every time the limit specified above is not reached, +up to this number; the default is 5. +.SS mac +.TP +.BR "--mac-source " "[!] \fIaddress\fP" +Match source MAC address. It must be of the form XX:XX:XX:XX:XX:XX. +Note that this only makes sense for packets coming from an Ethernet device +and entering the +.BR PREROUTING , +.B FORWARD +or +.B INPUT +chains. +.SS mark +This module matches the netfilter mark field associated with a packet +(which can be set using the +.B MARK +target below). +.TP +.BR "--mark " "\fIvalue\fP[/\fImask\fP]" +Matches packets with the given unsigned mark value (if a mask is +specified, this is logically ANDed with the mask before the +comparison). +.SS multiport +This module matches a set of source or destination ports. Up to 15 +ports can be specified. It can only be used in conjunction with +.B "-p tcp" +or +.BR "-p udp" . +.TP +.BR "--source-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the source port is one of the given ports. The flag +.B --sports +is a convenient alias for this option. +.TP +.BR "--destination-ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the destination port is one of the given ports. The flag +.B --dports +is a convenient alias for this option. +.TP +.BR "--ports " "\fIport\fP[,\fIport\fP[,\fIport\fP...]]" +Match if the both the source and destination ports are equal to each +other and to one of the given ports. +.SS owner +This module attempts to match various characteristics of the packet +creator, for locally-generated packets. It is only valid in the +.B OUTPUT +chain, and even this some packets (such as ICMP ping responses) may +have no owner, and hence never match. +.TP +.BI "--uid-owner " "userid" +Matches if the packet was created by a process with the given +effective user id. +.TP +.BI "--gid-owner " "groupid" +Matches if the packet was created by a process with the given +effective group id. +.TP +.BI "--pid-owner " "processid" +Matches if the packet was created by a process with the given +process id. +.TP +.BI "--sid-owner " "sessionid" +Matches if the packet was created by a process in the given session +group. +.TP +.BI "--cmd-owner " "name" +Matches if the packet was created by a process with the given command name. +(this option is present only if iptables was compiled under a kernel +supporting this feature) +.SS physdev +This module matches on the bridge port input and output devices enslaved +to a bridge device. This module is a part of the infrastructure that enables +a transparent bridging IP firewall and is only useful for kernel versions +above version 2.5.44. +.TP +.B --physdev-in name +Name of a bridge port via which a packet is received (only for +packets entering the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +chains). If the interface name ends in a "+", then any +interface which begins with this name will match. If the packet didn't arrive +through a bridge device, this packet won't match this option, unless '!' is used. +.TP +.B --physdev-out name +Name of a bridge port via which a packet is going to be sent (for packets +entering the +.BR FORWARD , +.B OUTPUT +and +.B POSTROUTING +chains). If the interface name ends in a "+", then any +interface which begins with this name will match. Note that in the +.BR nat " and " mangle +.B OUTPUT +chains one cannot match on the bridge output port, however one can in the +.B "filter OUTPUT" +chain. If the packet won't leave by a bridge device or it is yet unknown what +the output device will be, then the packet won't match this option, unless +'!' is used. +.TP +.B --physdev-is-in +Matches if the packet has entered through a bridge interface. +.TP +.B --physdev-is-out +Matches if the packet will leave through a bridge interface. +.TP +.B --physdev-is-bridged +Matches if the packet is being bridged and therefore is not being routed. +This is only useful in the FORWARD and POSTROUTING chains. +.SS pkttype +This module matches the link-layer packet type. +.TP +.BI "--pkt-type " "[\fIunicast\fP|\fIbroadcast\fP|\fImulticast\fP]" +.SS state +This module, when combined with connection tracking, allows access to +the connection tracking state for this packet. +.TP +.BI "--state " "state" +Where state is a comma separated list of the connection states to +match. Possible states are +.B INVALID +meaning that the packet could not be identified for some reason which +includes running out of memory and ICMP errors which don't correspond to any +known connection, +.B ESTABLISHED +meaning that the packet is associated with a connection which has seen +packets in both directions, +.B NEW +meaning that the packet has started a new connection, or otherwise +associated with a connection which has not seen packets in both +directions, and +.B RELATED +meaning that the packet is starting a new connection, but is +associated with an existing connection, such as an FTP data transfer, +or an ICMP error. +.SS tcp +These extensions are loaded if `--protocol tcp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. This can either be a service +name or a port number. An inclusive range can also be specified, +using the format +.IR port : port . +If the first port is omitted, "0" is assumed; if the last is omitted, +"65535" is assumed. +If the second port greater then the first they will be swapped. +The flag +.B --sport +is a convenient alias for this option. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. The flag +.B --dport +is a convenient alias for this option. +.TP +.BR "--tcp-flags " "[!] \fImask\fP \fIcomp\fP" +Match when the TCP flags are as specified. The first argument is the +flags which we should examine, written as a comma-separated list, and +the second argument is a comma-separated list of flags which must be +set. Flags are: +.BR "SYN ACK FIN RST URG PSH ALL NONE" . +Hence the command +.nf + iptables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST SYN +.fi +will only match packets with the SYN flag set, and the ACK, FIN and +RST flags unset. +.TP +.B "[!] --syn" +Only match TCP packets with the SYN bit set and the ACK and RST bits +cleared. Such packets are used to request TCP connection initiation; +for example, blocking such packets coming in an interface will prevent +incoming TCP connections, but outgoing TCP connections will be +unaffected. +It is equivalent to \fB--tcp-flags SYN,RST,ACK SYN\fP. +If the "!" flag precedes the "--syn", the sense of the +option is inverted. +.TP +.BR "--tcp-option " "[!] \fInumber\fP" +Match if TCP option set. +.TP +.BR "--mss " "\fIvalue\fP[:\fIvalue\fP]" +Match TCP SYN or SYN/ACK packets with the specified MSS value (or range), +which control the maximum packet size for that connection. +.SS tos +This module matches the 8 bits of Type of Service field in the IP +header (ie. including the precedence bits). +.TP +.BI "--tos " "tos" +The argument is either a standard name, (use +.br + iptables -m tos -h +.br +to see the list), or a numeric value to match. +.SS ttl +This module matches the time to live field in the IP header. +.TP +.BI "--ttl " "ttl" +Matches the given TTL value. +.SS udp +These extensions are loaded if `--protocol udp' is specified. It +provides the following options: +.TP +.BR "--source-port " "[!] \fIport\fP[:\fIport\fP]" +Source port or port range specification. +See the description of the +.B --source-port +option of the TCP extension for details. +.TP +.BR "--destination-port " "[!] \fIport\fP[:\fIport\fP]" +Destination port or port range specification. +See the description of the +.B --destination-port +option of the TCP extension for details. +.SS unclean +This module takes no options, but attempts to match packets which seem +malformed or unusual. This is regarded as experimental. +.SH TARGET EXTENSIONS +iptables can use extended target modules: the following are included +in the standard distribution. +.SS DNAT +This target is only valid in the +.B nat +table, in the +.B PREROUTING +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. It specifies that the destination address of the packet +should be modified (and all future packets in this connection will +also be mangled), and rules should cease being examined. It takes one +type of option: +.TP +.BR "--to-destination " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +which can specify a single new destination IP address, an inclusive +range of IP addresses, and optionally, a port range (which is only +valid if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" ). +If no port range is specified, then the destination port will never be +modified. +.RS +.PP +You can add several --to-destination options. If you specify more +than one destination address, either via an address range or multiple +--to-destination options, a simple round-robin (one after another in +cycle) load balancing takes place between these adresses. +.SS DSCP +This target allows to alter the value of the DSCP bits within the TOS +header of the IPv4 packet. As this manipulates a packet, it can only +be used in the mangle table. +.TP +.BI "--set-dscp " "value" +Set the DSCP field to a numerical value (can be decimal or hex) +.TP +.BI "--set-dscp-class " "class" +Set the DSCP field to a DiffServ class. +.SS ECN +This target allows to selectively work around known ECN blackholes. +It can only be used in the mangle table. +.TP +.BI "--ecn-tcp-remove" +Remove all ECN bits from the TCP header. Of course, it can only be used +in conjunction with +.BR "-p tcp" . +.SS LOG +Turn on kernel logging of matching packets. When this option is set +for a rule, the Linux kernel will print some information on all +matching packets (like most IP header fields) via the kernel log +(where it can be read with +.I dmesg +or +.IR syslogd (8)). +This is a "non-terminating target", i.e. rule traversal continues at +the next rule. So if you want to LOG the packets you refuse, use two +separate rules with the same matching criteria, first using target LOG +then DROP (or REJECT). +.TP +.BI "--log-level " "level" +Level of logging (numeric or see \fIsyslog.conf\fP(5)). +.TP +.BI "--log-prefix " "prefix" +Prefix log messages with the specified prefix; up to 29 letters long, +and useful for distinguishing messages in the logs. +.TP +.B --log-tcp-sequence +Log TCP sequence numbers. This is a security risk if the log is +readable by users. +.TP +.B --log-tcp-options +Log options from the TCP packet header. +.TP +.B --log-ip-options +Log options from the IP packet header. +.SS MARK +This is used to set the netfilter mark value associated with the +packet. It is only valid in the +.B mangle +table. It can for example be used in conjunction with iproute2. +.TP +.BI "--set-mark " "mark" +.SS MASQUERADE +This target is only valid in the +.B nat +table, in the +.B POSTROUTING +chain. It should only be used with dynamically assigned IP (dialup) +connections: if you have a static IP address, you should use the SNAT +target. Masquerading is equivalent to specifying a mapping to the IP +address of the interface the packet is going out, but also has the +effect that connections are +.I forgotten +when the interface goes down. This is the correct behavior when the +next dialup is unlikely to have the same interface address (and hence +any established connections are lost anyway). It takes one option: +.TP +.BR "--to-ports " "\fIport\fP[-\fIport\fP]" +This specifies a range of source ports to use, overriding the default +.B SNAT +source port-selection heuristics (see above). This is only valid +if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" . +.SS MIRROR +This is an experimental demonstration target which inverts the source +and destination fields in the IP header and retransmits the packet. +It is only valid in the +.BR INPUT , +.B FORWARD +and +.B PREROUTING +chains, and user-defined chains which are only called from those +chains. Note that the outgoing packets are +.B NOT +seen by any packet filtering chains, connection tracking or NAT, to +avoid loops and other problems. +.SS REDIRECT +This target is only valid in the +.B nat +table, in the +.B PREROUTING +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. It alters the destination IP address to send the packet to +the machine itself (locally-generated packets are mapped to the +127.0.0.1 address). It takes one option: +.TP +.BR "--to-ports " "\fIport\fP[-\fIport\fP]" +This specifies a destination port or range of ports to use: without +this, the destination port is never altered. This is only valid +if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" . +.SS REJECT +This is used to send back an error packet in response to the matched +packet: otherwise it is equivalent to +.B DROP +so it is a terminating TARGET, ending rule traversal. +This target is only valid in the +.BR INPUT , +.B FORWARD +and +.B OUTPUT +chains, and user-defined chains which are only called from those +chains. The following option controls the nature of the error packet +returned: +.TP +.BI "--reject-with " "type" +The type given can be +.nf +.B " icmp-net-unreachable" +.B " icmp-host-unreachable" +.B " icmp-port-unreachable" +.B " icmp-proto-unreachable" +.B " icmp-net-prohibited" +.B " icmp-host-prohibited or" +.B " icmp-admin-prohibited (*)" +.fi +which return the appropriate ICMP error message (\fBport-unreachable\fP is +the default). The option +.B tcp-reset +can be used on rules which only match the TCP protocol: this causes a +TCP RST packet to be sent back. This is mainly useful for blocking +.I ident +(113/tcp) probes which frequently occur when sending mail to broken mail +hosts (which won't accept your mail otherwise). +.TP +(*) Using icmp-admin-prohibited with kernels that do not support it will result in a plain DROP instead of REJECT +.SS SNAT +This target is only valid in the +.B nat +table, in the +.B POSTROUTING +chain. It specifies that the source address of the packet should be +modified (and all future packets in this connection will also be +mangled), and rules should cease being examined. It takes one type +of option: +.TP +.BR "--to-source " "\fIipaddr\fP[-\fIipaddr\fP][:\fIport\fP-\fIport\fP]" +which can specify a single new source IP address, an inclusive range +of IP addresses, and optionally, a port range (which is only valid if +the rule also specifies +.B "-p tcp" +or +.BR "-p udp" ). +If no port range is specified, then source ports below 512 will be +mapped to other ports below 512: those between 512 and 1023 inclusive +will be mapped to ports below 1024, and other ports will be mapped to +1024 or above. Where possible, no port alteration will occur. +.RS +.PP +You can add several --to-source options. If you specify more +than one source address, either via an address range or multiple +--to-source options, a simple round-robin (one after another in +cycle) takes place between these adresses. +.SS TCPMSS +This target allows to alter the MSS value of TCP SYN packets, to control +the maximum size for that connection (usually limiting it to your +outgoing interface's MTU minus 40). Of course, it can only be used +in conjunction with +.BR "-p tcp" . +.br +This target is used to overcome criminally braindead ISPs or servers +which block ICMP Fragmentation Needed packets. The symptoms of this +problem are that everything works fine from your Linux +firewall/router, but machines behind it can never exchange large +packets: +.PD 0 +.RS 0.1i +.TP 0.3i +1) +Web browsers connect, then hang with no data received. +.TP +2) +Small mail works fine, but large emails hang. +.TP +3) +ssh works fine, but scp hangs after initial handshaking. +.RE +.PD +Workaround: activate this option and add a rule to your firewall +configuration like: +.nf + iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \\ + -j TCPMSS --clamp-mss-to-pmtu +.fi +.TP +.BI "--set-mss " "value" +Explicitly set MSS option to specified value. +.TP +.B "--clamp-mss-to-pmtu" +Automatically clamp MSS value to (path_MTU - 40). +.TP +These options are mutually exclusive. +.SS TOS +This is used to set the 8-bit Type of Service field in the IP header. +It is only valid in the +.B mangle +table. +.TP +.BI "--set-tos " "tos" +You can use a numeric TOS values, or use +.nf + iptables -j TOS -h +.fi +to see the list of valid TOS names. +.SS ULOG +This target provides userspace logging of matching packets. When this +target is set for a rule, the Linux kernel will multicast this packet +through a +.IR netlink +socket. One or more userspace processes may then subscribe to various +multicast groups and receive the packets. +Like LOG, this is a "non-terminating target", i.e. rule traversal +continues at the next rule. +.TP +.BI "--ulog-nlgroup " "nlgroup" +This specifies the netlink group (1-32) to which the packet is sent. +Default value is 1. +.TP +.BI "--ulog-prefix " "prefix" +Prefix log messages with the specified prefix; up to 32 characters +long, and useful for distinguishing messages in the logs. +.TP +.BI "--ulog-cprange " "size" +Number of bytes to be copied to userspace. A value of 0 always copies +the entire packet, regardless of its size. Default is 0. +.TP +.BI "--ulog-qthreshold " "size" +Number of packet to queue inside kernel. Setting this value to, e.g. 10 +accumulates ten packets inside the kernel and transmits them as one +netlink multipart message to userspace. Default is 1 (for backwards +compatibility). +.br +.SH DIAGNOSTICS +Various error messages are printed to standard error. The exit code +is 0 for correct functioning. Errors which appear to be caused by +invalid or abused command line parameters cause an exit code of 2, and +other errors cause an exit code of 1. +.SH BUGS +Bugs? What's this? ;-) +Well... the counters are not reliable on sparc64. +.SH COMPATIBILITY WITH IPCHAINS +This +.B iptables +is very similar to ipchains by Rusty Russell. The main difference is +that the chains +.B INPUT +and +.B OUTPUT +are only traversed for packets coming into the local host and +originating from the local host respectively. Hence every packet only +passes through one of the three chains (except loopback traffic, which +involves both INPUT and OUTPUT chains); previously a forwarded packet +would pass through all three. +.PP +The other main difference is that +.B -i +refers to the input interface; +.B -o +refers to the output interface, and both are available for packets +entering the +.B FORWARD +chain. +.PP The various forms of NAT have been separated out; +.B iptables +is a pure packet filter when using the default `filter' table, with +optional extension modules. This should simplify much of the previous +confusion over the combination of IP masquerading and packet filtering +seen previously. So the following options are handled differently: +.nf + -j MASQ + -M -S + -M -L +.fi +There are several other changes in iptables. +.SH SEE ALSO +.BR iptables-save (8), +.BR iptables-restore (8), +.BR ip6tables (8), +.BR ip6tables-save (8), +.BR ip6tables-restore (8). +.P +The packet-filtering-HOWTO details iptables usage for +packet filtering, the NAT-HOWTO details NAT, +the netfilter-extensions-HOWTO details the extensions that are +not in the standard distribution, +and the netfilter-hacking-HOWTO details the netfilter internals. +.br +See +.BR "http://www.netfilter.org/" . +.SH AUTHORS +Rusty Russell wrote iptables, in early consultation with Michael +Neuling. +.PP +Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic packet +selection framework in iptables, then wrote the mangle table, the owner match, +the mark stuff, and ran around doing cool stuff everywhere. +.PP +James Morris wrote the TOS target, and tos match. +.PP +Jozsef Kadlecsik wrote the REJECT target. +.PP +Harald Welte wrote the ULOG target, TTL, DSCP, ECN matches and targets. +.PP +The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Jozsef Kadlecsik, +James Morris, Harald Welte and Rusty Russell. +.PP +Man page written by Herve Eychenne . +.\" .. and did I mention that we are incredibly cool people? +.\" .. sexy, too .. +.\" .. witty, charming, powerful .. +.\" .. and most of all, modest .. diff --git a/iptables.init b/iptables.init new file mode 100755 index 0000000..5c99246 --- /dev/null +++ b/iptables.init @@ -0,0 +1,320 @@ +#!/bin/sh +# +# iptables Start iptables firewall +# +# chkconfig: 2345 08 92 +# description: Starts, stops and saves iptables firewall +# +# config: /etc/sysconfig/iptables +# config: /etc/sysconfig/iptables-config + +# Source function library. +. /etc/init.d/functions + +IPTABLES=iptables +IPTABLES_DATA=/etc/sysconfig/$IPTABLES +IPTABLES_CONFIG=/etc/sysconfig/${IPTABLES}-config +IPV=${IPTABLES%tables} # ip for ipv4 | ip6 for ipv6 +PROC_IPTABLES_NAMES=/proc/net/${IPV}_tables_names +VAR_SUBSYS_IPTABLES=/var/lock/subsys/$IPTABLES + +if [ ! -x /sbin/$IPTABLES ]; then + echo -n $"/sbin/$IPTABLES does not exist."; warning; echo + exit 0 +fi + +if lsmod 2>/dev/null | grep -q ipchains ; then + echo -n $"ipchains and $IPTABLES can not be used together."; warning; echo + exit 0 +fi + +# Old or new modutils +/sbin/modprobe --version 2>&1 | grep -q module-init-tools \ + && NEW_MODUTILS=1 \ + || NEW_MODUTILS=0 + +# Default firewall configuration: +IPTABLES_MODULES="" +IPTABLES_MODULES_UNLOAD="yes" +IPTABLES_SAVE_ON_STOP="no" +IPTABLES_SAVE_ON_RESTART="no" +IPTABLES_SAVE_COUNTER="no" +IPTABLES_STATUS_NUMERIC="no" + +# Load firewall configuration. +[ -f "$IPTABLES_CONFIG" ] && . "$IPTABLES_CONFIG" + +rmmod_r() { + # Unload module with all referring modules. + # At first all referring modules will be unloaded, then the module itself. + local mod=$1 + local ret=0 + local ref= + + # Get referring modules. + # New modutils have another output format. + [ $NEW_MODUTILS = 1 ] \ + && ref=`lsmod | awk "/^${mod}/ { print \\\$4; }" | tr ',' ' '` \ + || ref=`lsmod | grep ^${mod} | cut -d "[" -s -f 2 | cut -d "]" -s -f 1` + + # recursive call for all referring modules + for i in $ref; do + rmmod_r $i + let ret+=$?; + done + + # Unload module. + # The extra test is for 2.6: The module might have autocleaned, + # after all referring modules are unloaded. + if grep -q "^${mod}" /proc/modules ; then + modprobe -r $mod > /dev/null 2>&1 + let ret+=$?; + fi + + return $ret +} + +flush_n_delete() { + # Flush firewall rules and delete chains. + [ -e "$PROC_IPTABLES_NAMES" ] || return 1 + + # Check if firewall is configured (has tables) + tables=`cat $PROC_IPTABLES_NAMES 2>/dev/null` + [ -z "$tables" ] && return 1 + + echo -n $"Flushing firewall rules: " + ret=0 + # For all tables + for i in $tables; do + # Flush firewall rules. + $IPTABLES -t $i -F; + let ret+=$?; + + # Delete firewall chains. + $IPTABLES -t $i -X; + let ret+=$?; + + # Set counter to zero. + $IPTABLES -t $i -Z; + let ret+=$?; + done + + [ $ret -eq 0 ] && success || failure + echo + return $ret +} + +set_policy() { + # Set policy for configured tables. + policy=$1 + + # Check if iptable module is loaded + [ ! -e "$PROC_IPTABLES_NAMES" ] && return 1 + + # Check if firewall is configured (has tables) + tables=`cat $PROC_IPTABLES_NAMES 2>/dev/null` + [ -z "$tables" ] && return 1 + + echo -n $"Setting chains to policy $policy: " + ret=0 + for i in $tables; do + echo -n "$i " + case "$i" in + filter) + $IPTABLES -t filter -P INPUT $policy \ + && $IPTABLES -t filter -P OUTPUT $policy \ + && $IPTABLES -t filter -P FORWARD $policy \ + || let ret+=1 + ;; + nat) + $IPTABLES -t nat -P PREROUTING $policy \ + && $IPTABLES -t nat -P POSTROUTING $policy \ + && $IPTABLES -t nat -P OUTPUT $policy \ + || let ret+=1 + ;; + mangle) + $IPTABLES -t mangle -P PREROUTING $policy \ + && $IPTABLES -t mangle -P POSTROUTING $policy \ + && $IPTABLES -t mangle -P INPUT $policy \ + && $IPTABLES -t mangle -P OUTPUT $policy \ + && $IPTABLES -t mangle -P FORWARD $policy \ + || let ret+=1 + ;; + *) + let ret+=1 + ;; + esac + done + + [ $ret -eq 0 ] && success || failure + echo + return $ret +} + +start() { + # Do not start if there is no config file. + [ -f "$IPTABLES_DATA" ] || return 1 + + echo -n $"Applying $IPTABLES firewall rules: " + + OPT= + [ "x$IPTABLES_SAVE_COUNTER" = "xyes" ] && OPT="-c" + + $IPTABLES-restore $OPT $IPTABLES_DATA + if [ $? -eq 0 ]; then + success; echo + else + failure; echo; return 1 + fi + + # Load additional modules (helpers) + if [ -n "$IPTABLES_MODULES" ]; then + echo -n $"Loading additional $IPTABLES modules: " + ret=0 + for mod in $IPTABLES_MODULES; do + echo -n "$mod " + modprobe $mod > /dev/null 2>&1 + let ret+=$?; + done + [ $ret -eq 0 ] && success || failure + echo + fi + + touch $VAR_SUBSYS_IPTABLES + return $ret +} + +stop() { + # Do not stop if iptables module is not loaded. + [ -e "$PROC_IPTABLES_NAMES" ] || return 1 + + flush_n_delete + set_policy ACCEPT + + if [ "x$IPTABLES_MODULES_UNLOAD" = "xyes" ]; then + echo -n $"Unloading $IPTABLES modules: " + ret=0 + rmmod_r ${IPV}_tables + let ret+=$?; + rmmod_r ${IPV}_conntrack + let ret+=$?; + [ $ret -eq 0 ] && success || failure + echo + fi + + rm -f $VAR_SUBSYS_IPTABLES + return $ret +} + +save() { + # Check if iptable module is loaded + [ ! -e "$PROC_IPTABLES_NAMES" ] && return 1 + + # Check if firewall is configured (has tables) + tables=`cat $PROC_IPTABLES_NAMES 2>/dev/null` + [ -z "$tables" ] && return 1 + + echo -n $"Saving firewall rules to $IPTABLES_DATA: " + + OPT= + [ "x$IPTABLES_SAVE_COUNTER" = "xyes" ] && OPT="-c" + + ret=0 + TMP_FILE=`/bin/mktemp -q /tmp/$IPTABLES.XXXXXX` \ + && chmod 600 "$TMP_FILE" \ + && $IPTABLES-save $OPT > $TMP_FILE 2>/dev/null \ + && size=`stat -c '%s' $TMP_FILE` && [ $size -gt 0 ] \ + || ret=1 + if [ $ret -eq 0 ]; then + if [ -e $IPTABLES_DATA ]; then + cp -f $IPTABLES_DATA $IPTABLES_DATA.save \ + && chmod 600 $IPTABLES_DATA.save \ + || ret=1 + fi + if [ $ret -eq 0 ]; then + cp -f $TMP_FILE $IPTABLES_DATA \ + && chmod 600 $IPTABLES_DATA \ + || ret=1 + fi + fi + [ $ret -eq 0 ] && success || failure + echo + rm -f $TMP_FILE + return $ret +} + +status() { + # Do not print status if lockfile is missing and iptables modules are not + # loaded. + # Check if iptable module is loaded + if [ ! -f "$VAR_SUBSYS_IPTABLES" ]; then + echo $"Firewall is stopped." + return 1 + fi + + # Check if firewall is configured (has tables) + if [ ! -e "$PROC_IPTABLES_NAMES" ]; then + echo $"Firewall is not configured. " + return 1 + fi + tables=`cat $PROC_IPTABLES_NAMES 2>/dev/null` + if [ -z "$tables" ]; then + echo $"Firewall is not configured. " + return 1 + fi + + NUM= + [ "x$IPTABLES_STATUS_NUMERIC" = "xyes" ] && NUM="-n" + + for table in $tables; do + echo $"Table: $table" + $IPTABLES -t $table --list $NUM && echo + done + + return 0 +} + +restart() { + [ "x$IPTABLES_SAVE_ON_RESTART" = "xyes" ] && save + stop + start +} + +case "$1" in + start) + stop + start + RETVAL=$? + ;; + stop) + [ "x$IPTABLES_SAVE_ON_STOP" = "xyes" ] && save + stop + RETVAL=$? + ;; + restart) + restart + RETVAL=$? + ;; + condrestart) + [ -e "$VAR_SUBSYS_IPTABLES" ] && restart + ;; + status) + status + RETVAL=$? + ;; + panic) + flush_n_delete + set_policy DROP + RETVAL=$? + ;; + save) + save + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|restart|condrestart|status|panic|save}" + exit 1 + ;; +esac + +exit $RETVAL diff --git a/iptables.spec b/iptables.spec new file mode 100644 index 0000000..a6ddcef --- /dev/null +++ b/iptables.spec @@ -0,0 +1,371 @@ +%define name iptables +%define version 1.2.9 +%define release 2.3.1.1.planetlab%{?date:.%{date}} + +Vendor: PlanetLab +Packager: PlanetLab Central +Distribution: PlanetLab 3.0 +URL: http://cvs.planet-lab.org/cvs/iptables + +%define build_devel 1 +%define linux_header 0 + +Name: %{name} +Summary: Tools for managing Linux kernel packet filtering capabilities. +Version: %{version} +Release: %{release} +Source: http://www.netfilter.org/%{name}-%{version}.tar.bz2 +%define SOURCE1 iptables.init +%define SOURCE2 iptables-config +%if %{linux_header} +Source3: netfilter-2.4.20.tar.gz +%endif +Patch2: iptables-1.2.8-nolibnsl.patch +Patch3: iptables-1.2.8-print_type.patch +Patch4: iptables-1.2.9-netlink.patch +Patch5: iptables-1.2.9-selinux.patch +Patch6: iptables-1.2.9-counters.patch +Group: System Environment/Base +#URL: http://www.netfilter.org/ +BuildRoot: %{_tmppath}/%{name}-buildroot +License: GPL +BuildPrereq: /usr/bin/perl +Requires: kernel >= 2.4.20 +Requires(post,postun): chkconfig +Prefix: %{_prefix} + +%package ipv6 +Summary: IPv6 support for iptables. +Group: System Environment/Base +Requires: %{name} = %{version} + +%if %{build_devel} +%package devel +Summary: Development package for iptables. +Group: System Environment/Base +Requires: %{name} = %{version} +%endif + +%description +The iptables utility controls the network packet filtering code in the +Linux kernel. If you need to set up firewalls and/or IP masquerading, +you should install this package. + +%description ipv6 +The iptables package contains IPv6 (the next version of the IP +protocol) support for iptables. Iptables controls the Linux kernel +network packet filtering code, allowing you to set up firewalls and IP +masquerading. + +Install iptables-ipv6 if you need to set up firewalling for your +network and you are using ipv6. + +%if %{build_devel} +%description devel +The iptables utility controls the network packet filtering code in the +Linux kernel. If you need to set up firewalls and/or IP masquerading, +you should install this package. +%endif + +%prep +rm -rf %{buildroot} + +%setup -q + +# Put it to a reasonable place +find . -type f -exec perl -pi -e "s,/usr,%{prefix},g" {} \; + +%build +TOPDIR=`pwd` +OPT="$RPM_OPT_FLAGS -I$TOPDIR/include" +# bootstrap to avoid BuildRequires of kernel-source +for KERNEL_DIR in $RPM_BUILD_DIR/linux-* /lib/modules/`uname -r`/build /usr ; do + if [ -f $KERNEL_DIR/include/linux/version.h ] ; then + break + fi +done +make COPT_FLAGS="$OPT" KERNEL_DIR=/usr LIBDIR=/%{_lib} +make COPT_FLAGS="$OPT" KERNEL_DIR=/usr LIBDIR=/%{_lib} iptables-save iptables-restore +make COPT_FLAGS="$OPT" KERNEL_DIR=/usr LIBDIR=/%{_lib} ip6tables-save ip6tables-restore + +%install +# bootstrap to avoid BuildRequires of kernel-source +for KERNEL_DIR in $RPM_BUILD_DIR/linux-* /lib/modules/`uname -r`/build /usr ; do + if [ -f $KERNEL_DIR/include/linux/version.h ] ; then + break + fi +done +make install DESTDIR=%{buildroot} KERNEL_DIR=/usr BINDIR=/sbin LIBDIR=/%{_lib} MANDIR=%{_mandir} +%if %{build_devel} +make install-devel DESTDIR=%{buildroot} KERNEL_DIR=/usr BINDIR=/sbin LIBDIR=%{_libdir} MANDIR=%{_mandir} +%endif +cp ip{6,}tables-{save,restore} $RPM_BUILD_ROOT/sbin +cp iptables-*.8 $RPM_BUILD_ROOT%{_mandir}/man8 +mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d +install -c -m755 %{SOURCE1} $RPM_BUILD_ROOT/etc/rc.d/init.d/iptables +sed -e 's;iptables;ip6tables;g' -e 's;IPTABLES;IP6TABLES;g' < %{SOURCE1} > ip6tables.init +install -c -m755 ip6tables.init $RPM_BUILD_ROOT/etc/rc.d/init.d/ip6tables +mkdir -p $RPM_BUILD_ROOT/etc/sysconfig +install -c -m755 %{SOURCE2} $RPM_BUILD_ROOT/etc/sysconfig/iptables-config +sed -e 's;iptables;ip6tables;g' -e 's;IPTABLES;IP6TABLES;g' < %{SOURCE2} > ip6tables-config +install -c -m755 ip6tables-config $RPM_BUILD_ROOT/etc/sysconfig/ip6tables-config + +%clean +rm -rf $RPM_BUILD_ROOT + +%post +/sbin/chkconfig --add iptables + +%preun +if [ "$1" = 0 ]; then + /sbin/chkconfig --del iptables +fi + +%post ipv6 +/sbin/chkconfig --add ip6tables + +%preun ipv6 +if [ "$1" = 0 ]; then + /sbin/chkconfig --del ip6tables +fi + +%files +%defattr(-,root,root,0755) +%doc COPYING INSTALL INCOMPATIBILITIES +%config %attr(0755,root,root) /etc/rc.d/init.d/iptables +%config(noreplace) %attr(0600,root,root) /etc/sysconfig/iptables-config +/sbin/iptables* +%{_mandir}/man8/iptables* +%dir /%{_lib}/iptables +/%{_lib}/iptables/libipt* + +%files ipv6 +%defattr(-,root,root,0755) +%config %attr(0755,root,root) /etc/rc.d/init.d/ip6tables +%config(noreplace) %attr(0600,root,root) /etc/sysconfig/ip6tables-config +/sbin/ip6tables* +%{_mandir}/man8/ip6tables* +/%{_lib}/iptables/libip6t* + +%if %{build_devel} +%files devel +%defattr(-,root,root,0755) +%{_includedir}/libipq.h +%{_libdir}/libipq.a +%{_libdir}/libiptc.a +%{_mandir}/man3/* +%endif + +%changelog +* Tue Mar 02 2004 Elliot Lee +- rebuilt + +* Thu Feb 26 2004 Thomas Woerner 1.2.9-2.3 +- fixed iptables-restore -c fault if there are no counters (#116421) + +* Fri Feb 13 2004 Elliot Lee +- rebuilt + +* Sun Jan 25 2004 Dan Walsh 1.2.9-1.2 +- Close File descriptors to prevent SELinux error message + +* Wed Jan 7 2004 Thomas Woerner 1.2.9-1.1 +- rebuild + +* Wed Dec 17 2003 Thomas Woerner 1.2.9-1 +- vew version 1.2.9 +- new config options in ipXtables-config: + IPTABLES_MODULES_UNLOAD +- more documentation in ipXtables-config +- fix for netlink security issue in libipq (devel package) +- print fix for libipt_icmp (#109546) + +* Thu Oct 23 2003 Thomas Woerner 1.2.8-13 +- marked all messages in iptables init script for translation (#107462) +- enabled devel package (#105884, #106101) +- bumped build for fedora for libipt_recent.so (#106002) + +* Tue Sep 23 2003 Thomas Woerner 1.2.8-12.1 +- fixed lost udp port range in ip6tables-save (#104484) +- fixed non numeric multiport port output in ipXtables-savs + +* Mon Sep 22 2003 Florian La Roche 1.2.8-11 +- do not link against -lnsl + +* Wed Sep 17 2003 Thomas Woerner 1.2.8-10 +- made variables in rmmod_r local + +* Tue Jul 22 2003 Thomas Woerner 1.2.8-9 +- fixed permission for init script + +* Sat Jul 19 2003 Thomas Woerner 1.2.8-8 +- fixed save when iptables file is missing and iptables-config permissions + +* Tue Jul 8 2003 Thomas Woerner 1.2.8-7 +- fixes for ip6tables: module unloading, setting policy only for existing + tables + +* Thu Jul 3 2003 Thomas Woerner 1.2.8-6 +- IPTABLES_SAVE_COUNTER defaults to no, now +- install config file in /etc/sysconfig +- exchange unload of ip_tables and ip_conntrack +- fixed start function + +* Wed Jul 2 2003 Thomas Woerner 1.2.8-5 +- new config option IPTABLES_SAVE_ON_RESTART +- init script: new status, save and restart +- fixes #44905, #65389, #80785, #82860, #91040, #91560 and #91374 + +* Mon Jun 30 2003 Thomas Woerner 1.2.8-4 +- new config option IPTABLES_STATUS_NUMERIC +- cleared IPTABLES_MODULES in iptables-config + +* Mon Jun 30 2003 Thomas Woerner 1.2.8-3 +- new init scripts + +* Sat Jun 28 2003 Florian La Roche +- remove check for very old kernel versions in init scripts +- sync up both init scripts and remove some further ugly things +- add some docu into rpm + +* Thu Jun 26 2003 Thomas Woerner 1.2.8-2 +- rebuild + +* Mon Jun 16 2003 Thomas Woerner 1.2.8-1 +- update to 1.2.8 + +* Wed Jan 22 2003 Tim Powers +- rebuilt + +* Mon Jan 13 2003 Bill Nottingham 1.2.7a-1 +- update to 1.2.7a +- add a plethora of bugfixes courtesy Michael Schwendt + +* Fri Dec 13 2002 Elliot Lee 1.2.6a-3 +- Fix multilib + +* Wed Aug 07 2002 Karsten Hopp +- fixed iptables and ip6tables initscript output, based on #70511 +- check return status of all iptables calls, not just the last one + in a 'for' loop. + +* Mon Jul 29 2002 Bernhard Rosenkraenzer 1.2.6a-1 +- 1.2.6a (bugfix release, #69747) + +* Fri Jun 21 2002 Tim Powers +- automated rebuild + +* Thu May 23 2002 Tim Powers +- automated rebuild + +* Mon Mar 4 2002 Bernhard Rosenkraenzer 1.2.5-3 +- Add some fixes from CVS, fixing bug #60465 + +* Tue Feb 12 2002 Bernhard Rosenkraenzer 1.2.5-2 +- Merge ip6tables improvements from Ian Prowell + #59402 +- Update URL (#59354) +- Use /sbin/chkconfig rather than chkconfig in %postun script + +* Fri Jan 11 2002 Bernhard Rosenkraenzer 1.2.5-1 +- 1.2.5 + +* Wed Jan 09 2002 Tim Powers +- automated rebuild + +* Mon Nov 5 2001 Bernhard Rosenkraenzer 1.2.4-2 +- Fix %preun script + +* Tue Oct 30 2001 Bernhard Rosenkraenzer 1.2.4-1 +- Update to 1.2.4 (various fixes, including security fixes; among others: + #42990, #50500, #53325, #54280) +- Fix init script (#31133) + +* Mon Sep 3 2001 Bernhard Rosenkraenzer 1.2.3-1 +- 1.2.3 (5 security fixes, some other fixes) +- Fix updating (#53032) + +* Mon Aug 27 2001 Bernhard Rosenkraenzer 1.2.2-4 +- Fix #50990 +- Add some fixes from current CVS; should fix #52620 + +* Mon Jul 16 2001 Bernhard Rosenkraenzer 1.2.2-3 +- Add some fixes from the current CVS tree; fixes #49154 and some IPv6 + issues + +* Tue Jun 26 2001 Bernhard Rosenkraenzer 1.2.2-2 +- Fix iptables-save reject-with (#45632), Patch from Michael Schwendt + + +* Tue May 8 2001 Bernhard Rosenkraenzer 1.2.2-1 +- 1.2.2 + +* Wed Mar 21 2001 Bernhard Rosenkraenzer +- 1.2.1a, fixes #28412, #31136, #31460, #31133 + +* Thu Mar 1 2001 Bernhard Rosenkraenzer +- Yet another initscript fix (#30173) +- Fix the fixes; they fixed some issues but broke more important + stuff :/ (#30176) + +* Tue Feb 27 2001 Bernhard Rosenkraenzer +- Fix up initscript (#27962) +- Add fixes from CVS to iptables-{restore,save}, fixing #28412 + +* Fri Feb 09 2001 Karsten Hopp +- create /etc/sysconfig/iptables mode 600 (same problem as #24245) + +* Mon Feb 05 2001 Karsten Hopp +- fix bugzilla #25986 (initscript not marked as config file) +- fix bugzilla #25962 (iptables-restore) +- mv chkconfig --del from postun to preun + +* Thu Feb 1 2001 Trond Eivind Glomsrød +- Fix check for ipchains + +* Mon Jan 29 2001 Bernhard Rosenkraenzer +- Some fixes to init scripts + +* Wed Jan 24 2001 Bernhard Rosenkraenzer +- Add some fixes from CVS, fixes among other things Bug #24732 + +* Wed Jan 17 2001 Bernhard Rosenkraenzer +- Add missing man pages, fix up init script (Bug #17676) + +* Mon Jan 15 2001 Bill Nottingham +- add init script + +* Mon Jan 15 2001 Bernhard Rosenkraenzer +- 1.2 +- fix up ipv6 split +- add init script +- Move the plugins from /usr/lib/iptables to /lib/iptables. + This needs to work before /usr is mounted... +- Use -O1 on alpha (compiler bug) + +* Sat Jan 6 2001 Bernhard Rosenkraenzer +- 1.1.2 +- Add IPv6 support (in separate package) + +* Thu Aug 17 2000 Bill Nottingham +- build everywhere + +* Tue Jul 25 2000 Bernhard Rosenkraenzer +- 1.1.1 + +* Thu Jul 13 2000 Prospector +- automatic rebuild + +* Tue Jun 27 2000 Preston Brown +- move iptables to /sbin. +- excludearch alpha for now, not building there because of compiler bug(?) + +* Fri Jun 9 2000 Bill Nottingham +- don't obsolete ipchains either +- update to 1.1.0 + +* Mon Jun 4 2000 Bill Nottingham +- remove explicit kernel requirement + +* Tue May 2 2000 Bernhard Rosenkränzer +- initial package diff --git a/libiptc/.cvsignore b/libiptc/.cvsignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/libiptc/.cvsignore @@ -0,0 +1 @@ +*.d diff --git a/libiptc/libip6tc.c.orig b/libiptc/libip6tc.c.orig new file mode 100644 index 0000000..9a78a5a --- /dev/null +++ b/libiptc/libip6tc.c.orig @@ -0,0 +1,449 @@ +/* Library which manipulates firewall rules. Version 0.1. */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + COPYING for details). */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_CONNTRACK +#define inline +#endif + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include "libiptc/libip6tc.h" + +#define HOOK_PRE_ROUTING NF_IP6_PRE_ROUTING +#define HOOK_LOCAL_IN NF_IP6_LOCAL_IN +#define HOOK_FORWARD NF_IP6_FORWARD +#define HOOK_LOCAL_OUT NF_IP6_LOCAL_OUT +#define HOOK_POST_ROUTING NF_IP6_POST_ROUTING + +#define STRUCT_ENTRY_TARGET struct ip6t_entry_target +#define STRUCT_ENTRY struct ip6t_entry +#define STRUCT_ENTRY_MATCH struct ip6t_entry_match +#define STRUCT_GETINFO struct ip6t_getinfo +#define STRUCT_GET_ENTRIES struct ip6t_get_entries +#define STRUCT_COUNTERS struct ip6t_counters +#define STRUCT_COUNTERS_INFO struct ip6t_counters_info +#define STRUCT_STANDARD_TARGET struct ip6t_standard_target +#define STRUCT_REPLACE struct ip6t_replace + +#define STRUCT_TC_HANDLE struct ip6tc_handle +#define TC_HANDLE_T ip6tc_handle_t + +#define ENTRY_ITERATE IP6T_ENTRY_ITERATE +#define TABLE_MAXNAMELEN IP6T_TABLE_MAXNAMELEN +#define FUNCTION_MAXNAMELEN IP6T_FUNCTION_MAXNAMELEN + +#define GET_TARGET ip6t_get_target + +#define ERROR_TARGET IP6T_ERROR_TARGET +#define NUMHOOKS NF_IP6_NUMHOOKS + +#define IPT_CHAINLABEL ip6t_chainlabel + +#define TC_DUMP_ENTRIES dump_entries6 +#define TC_IS_CHAIN ip6tc_is_chain +#define TC_FIRST_CHAIN ip6tc_first_chain +#define TC_NEXT_CHAIN ip6tc_next_chain +#define TC_FIRST_RULE ip6tc_first_rule +#define TC_NEXT_RULE ip6tc_next_rule +#define TC_GET_TARGET ip6tc_get_target +#define TC_BUILTIN ip6tc_builtin +#define TC_GET_POLICY ip6tc_get_policy +#define TC_INSERT_ENTRY ip6tc_insert_entry +#define TC_REPLACE_ENTRY ip6tc_replace_entry +#define TC_APPEND_ENTRY ip6tc_append_entry +#define TC_DELETE_ENTRY ip6tc_delete_entry +#define TC_DELETE_NUM_ENTRY ip6tc_delete_num_entry +#define TC_CHECK_PACKET ip6tc_check_packet +#define TC_FLUSH_ENTRIES ip6tc_flush_entries +#define TC_ZERO_ENTRIES ip6tc_zero_entries +#define TC_ZERO_COUNTER ip6tc_zero_counter +#define TC_READ_COUNTER ip6tc_read_counter +#define TC_SET_COUNTER ip6tc_set_counter +#define TC_CREATE_CHAIN ip6tc_create_chain +#define TC_GET_REFERENCES ip6tc_get_references +#define TC_DELETE_CHAIN ip6tc_delete_chain +#define TC_RENAME_CHAIN ip6tc_rename_chain +#define TC_SET_POLICY ip6tc_set_policy +#define TC_GET_RAW_SOCKET ip6tc_get_raw_socket +#define TC_INIT ip6tc_init +#define TC_FREE ip6tc_free +#define TC_COMMIT ip6tc_commit +#define TC_STRERROR ip6tc_strerror + +#define TC_AF AF_INET6 +#define TC_IPPROTO IPPROTO_IPV6 + +#define SO_SET_REPLACE IP6T_SO_SET_REPLACE +#define SO_SET_ADD_COUNTERS IP6T_SO_SET_ADD_COUNTERS +#define SO_GET_INFO IP6T_SO_GET_INFO +#define SO_GET_ENTRIES IP6T_SO_GET_ENTRIES +#define SO_GET_VERSION IP6T_SO_GET_VERSION + +#define STANDARD_TARGET IP6T_STANDARD_TARGET +#define LABEL_RETURN IP6TC_LABEL_RETURN +#define LABEL_ACCEPT IP6TC_LABEL_ACCEPT +#define LABEL_DROP IP6TC_LABEL_DROP +#define LABEL_QUEUE IP6TC_LABEL_QUEUE + +#define ALIGN IP6T_ALIGN +#define RETURN IP6T_RETURN + +#include "libiptc.c" + +#define BIT6(a, l) \ + ((ntohl(a->in6_u.u6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1) + +int +ipv6_prefix_length(const struct in6_addr *a) +{ + int l, i; + for (l = 0; l < 128; l++) { + if (BIT6(a, l) == 0) + break; + } + for (i = l + 1; i < 128; i++) { + if (BIT6(a, i) == 1) + return -1; + } + return l; +} + +static int +dump_entry(struct ip6t_entry *e, const ip6tc_handle_t handle) +{ + size_t i; + char buf[40]; + int len; + struct ip6t_entry_target *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + puts("SRC IP: "); + inet_ntop(AF_INET6, &e->ipv6.src, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.smsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.smsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + puts("DST IP: "); + inet_ntop(AF_INET6, &e->ipv6.dst, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.dmsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.dmsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + printf("Interface: `%s'/", e->ipv6.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ipv6.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ipv6.proto); + if (e->ipv6.flags & IP6T_F_TOS) + printf("TOS: %u\n", e->ipv6.tos); + printf("Flags: %02X\n", e->ipv6.flags); + printf("Invflags: %02X\n", e->ipv6.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP6_SRC) printf("IP6_SRC "); + if (e->nfcache & NFC_IP6_DST) printf("IP6_DST "); + if (e->nfcache & NFC_IP6_IF_IN) printf("IP6_IF_IN "); + if (e->nfcache & NFC_IP6_IF_OUT) printf("IP6_IF_OUT "); + if (e->nfcache & NFC_IP6_TOS) printf("IP6_TOS "); + if (e->nfcache & NFC_IP6_PROTO) printf("IP6_PROTO "); + if (e->nfcache & NFC_IP6_OPTIONS) printf("IP6_OPTIONS "); + if (e->nfcache & NFC_IP6_TCPFLAGS) printf("IP6_TCPFLAGS "); + if (e->nfcache & NFC_IP6_SRC_PT) printf("IP6_SRC_PT "); + if (e->nfcache & NFC_IP6_DST_PT) printf("IP6_DST_PT "); + if (e->nfcache & NFC_IP6_PROTO_UNKNOWN) printf("IP6_PROTO_UNKNOWN "); + printf("\n"); + + IP6T_MATCH_ITERATE(e, print_match); + + t = ip6t_get_target(e); + printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size); + if (strcmp(t->u.user.name, IP6T_STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == IP6T_RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.user.name, IP6T_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, + unsigned char *matchmask) +{ + unsigned int i; + STRUCT_ENTRY_TARGET *ta, *tb; + unsigned char *mptr; + + /* Always compare head structures: ignore mask here. */ + if (memcmp(&a->ipv6.src, &b->ipv6.src, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dst, &b->ipv6.dst, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.smsk, &b->ipv6.smsk, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dmsk, &b->ipv6.dmsk, sizeof(struct in6_addr)) + || a->ipv6.proto != b->ipv6.proto + || a->ipv6.tos != b->ipv6.tos + || a->ipv6.flags != b->ipv6.flags + || a->ipv6.invflags != b->ipv6.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ipv6.iniface_mask[i] != b->ipv6.iniface_mask[i]) + return 0; + if ((a->ipv6.iniface[i] & a->ipv6.iniface_mask[i]) + != (b->ipv6.iniface[i] & b->ipv6.iniface_mask[i])) + return 0; + if (a->ipv6.outiface_mask[i] != b->ipv6.outiface_mask[i]) + return 0; + if ((a->ipv6.outiface[i] & a->ipv6.outiface_mask[i]) + != (b->ipv6.outiface[i] & b->ipv6.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + mptr = matchmask + sizeof(STRUCT_ENTRY); + if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr)) + return 0; + + ta = GET_TARGET((STRUCT_ENTRY *)a); + tb = GET_TARGET((STRUCT_ENTRY *)b); + if (ta->u.target_size != tb->u.target_size) + return 0; + if (strcmp(ta->u.user.name, tb->u.user.name) != 0) + return 0; + mptr += sizeof(*ta); + + if (target_different(ta->data, tb->data, + ta->u.target_size - sizeof(*ta), mptr)) + return 0; + + return 1; +} + +/* All zeroes == unconditional rule. */ +static inline int +unconditional(const struct ip6t_ip6 *ipv6) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ipv6); i++) + if (((char *)ipv6)[i]) + break; + + return (i == sizeof(*ipv6)); +} + +#ifdef IPTC_DEBUG +/* Do every conceivable sanity check on the handle */ +static void +do_check(TC_HANDLE_T h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + n = get_chain_end(h, n); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_IN]; + } + + } else if (strcmp(h->info.name, "mangle") == 0) { + /* This code is getting ugly because linux < 2.4.18-pre6 had + * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks + * */ + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + /* Hooks should be first five */ + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + n = get_chain_end(h, n); + } + + if (h->info.valid_hooks & (1 << NF_IP6_FORWARD)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + n = get_chain_end(h, n); + } + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_POST_ROUTING)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + user_offset = h->info.hook_entry[NF_IP6_POST_ROUTING]; + } + } else { + fprintf(stderr, "Unknown table `%s'\n", h->info.name); + abort(); + } + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NUMHOOKS; i++) { + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ipv6)); + assert(e->target_offset == sizeof(*e)); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + printf("target_size=%u, align=%u\n", + t->target.u.target_size, ALIGN(sizeof(*t))); + assert(t->target.u.target_size == ALIGN(sizeof(*t))); + assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t))); + + assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(STRUCT_ENTRY) + +sizeof(STRUCT_STANDARD_TARGET))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(STRUCT_ENTRY) + + sizeof(STRUCT_STANDARD_TARGET)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + +#if 0 + /* Check all the entries. */ + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1)) + ->u.user.name, + ERROR_TARGET) == 0); +#endif +} +#endif /*IPTC_DEBUG*/ diff --git a/libiptc2/foo.diff b/libiptc2/foo.diff new file mode 100644 index 0000000..af69eb5 --- /dev/null +++ b/libiptc2/foo.diff @@ -0,0 +1,391 @@ +--- libiptc.c 2003-06-30 18:26:59.000000000 +0200 ++++ libiptc.c 2003-06-30 18:27:24.000000000 +0200 +@@ -64,19 +61,35 @@ + char error[TABLE_MAXNAMELEN]; + }; + +-struct chain_cache ++struct rule_head + { ++ struct list_head list; ++ ++ struct chain_head *chain; ++ ++ unsigned int size; ++ STRUCT_ENTRY entry[0]; ++} ++ ++struct chain_head ++{ ++ struct list_head list; ++ + char name[TABLE_MAXNAMELEN]; +- /* This is the first rule in chain. */ +- unsigned int start_off; +- /* Last rule in chain */ +- unsigned int end_off; ++ unsigned int hooknum; ++ struct list_head rules; + }; + + STRUCT_TC_HANDLE + { + /* Have changes been made? */ + int changed; ++ ++ struct list_head chains; ++ ++ struct chain_head *chain_iterator_cur; ++ ++#if 0 + /* Size in here reflects original state. */ + STRUCT_GETINFO info; + +@@ -98,6 +111,7 @@ + /* Number in here reflects current state. */ + unsigned int new_number; + STRUCT_GET_ENTRIES entries; ++#endif + }; + + static void +@@ -375,173 +389,25 @@ + } + return 0; + } +- +-static inline int +-add_chain(STRUCT_ENTRY *e, TC_HANDLE_T h, STRUCT_ENTRY **prev) +-{ +- unsigned int builtin; +- +- /* Last entry. End it. */ +- if (entry2offset(h, e) + e->next_offset == h->entries.size) { +- /* This is the ERROR node at end of the table */ +- h->cache_chain_heads[h->cache_num_chains-1].end_off = +- entry2offset(h, *prev); +- return 0; +- } +- +- /* We know this is the start of a new chain if it's an ERROR +- target, or a hook entry point */ +- if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) { +- /* prev was last entry in previous chain */ +- h->cache_chain_heads[h->cache_num_chains-1].end_off +- = entry2offset(h, *prev); +- +- strcpy(h->cache_chain_heads[h->cache_num_chains].name, +- (const char *)GET_TARGET(e)->data); +- h->cache_chain_heads[h->cache_num_chains].start_off +- = entry2offset(h, (void *)e + e->next_offset); +- h->cache_num_chains++; +- } else if ((builtin = is_hook_entry(e, h)) != 0) { +- if (h->cache_num_chains > 0) +- /* prev was last entry in previous chain */ +- h->cache_chain_heads[h->cache_num_chains-1].end_off +- = entry2offset(h, *prev); +- +- strcpy(h->cache_chain_heads[h->cache_num_chains].name, +- h->hooknames[builtin-1]); +- h->cache_chain_heads[h->cache_num_chains].start_off +- = entry2offset(h, (void *)e); +- h->cache_num_chains++; +- } +- +- *prev = e; +- return 0; +-} +- + static int alphasort(const void *a, const void *b) + { + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); + } + +-static int populate_cache(TC_HANDLE_T h) +-{ +- unsigned int i; +- STRUCT_ENTRY *prev; +- +- /* # chains < # rules / 2 + num builtins - 1 */ +- h->cache_chain_heads = malloc((h->new_number / 2 + 4) +- * sizeof(struct chain_cache)); +- if (!h->cache_chain_heads) { +- errno = ENOMEM; +- return 0; +- } +- +- h->cache_num_chains = 0; +- h->cache_num_builtins = 0; +- +- /* Count builtins */ +- for (i = 0; i < NUMHOOKS; i++) { +- if (h->info.valid_hooks & (1 << i)) +- h->cache_num_builtins++; +- } +- +- prev = NULL; +- ENTRY_ITERATE(h->entries.entrytable, h->entries.size, +- add_chain, h, &prev); +- +- qsort(h->cache_chain_heads + h->cache_num_builtins, +- h->cache_num_chains - h->cache_num_builtins, +- sizeof(struct chain_cache), alphasort); +- +- return 1; +-} +- +-static int +-correct_cache(TC_HANDLE_T h, unsigned int offset, int delta) +-{ +- int i; /* needs to be signed because deleting first +- chain can make it drop to -1 */ +- +- if (!delta) +- return 1; +- +- for (i = 0; i < h->cache_num_chains; i++) { +- struct chain_cache *cc = &h->cache_chain_heads[i]; +- +- if (delta < 0) { +- /* take care about deleted chains */ +- if (cc->start_off >= offset+delta +- && cc->end_off <= offset) { +- /* this chain is within the deleted range, +- * let's remove it from the cache */ +- void *start; +- unsigned int size; +- +- h->cache_num_chains--; +- if (i+1 >= h->cache_num_chains) +- continue; +- start = &h->cache_chain_heads[i+1]; +- size = (h->cache_num_chains-i) +- * sizeof(struct chain_cache); +- memmove(cc, start, size); +- +- /* iterate over same index again, since +- * it is now a different chain */ +- i--; +- continue; +- } +- } +- +- if (cc->start_off > offset) +- cc->start_off += delta; +- +- if (cc->end_off >= offset) +- cc->end_off += delta; +- } +- /* HW_FIXME: sorting might be needed, but just in case a new chain was +- * added */ +- +- return 1; +-} +- +-static int +-add_chain_cache(TC_HANDLE_T h, const char *name, unsigned int start_off, +- unsigned int end_off) +-{ +- struct chain_cache *ccs = realloc(h->cache_chain_heads, +- (h->new_number / 2 + 4 + 1) +- * sizeof(struct chain_cache)); +- struct chain_cache *newcc; +- +- if (!ccs) +- return 0; +- +- h->cache_chain_heads = ccs; +- newcc = &h->cache_chain_heads[h->cache_num_chains]; +- h->cache_num_chains++; +- +- strncpy(newcc->name, name, TABLE_MAXNAMELEN-1); +- newcc->start_off = start_off; +- newcc->end_off = end_off; +- +- return 1; +-} +- +-/* Returns cache ptr if found, otherwise NULL. */ +-static struct chain_cache * ++/* Returns chain head if found, otherwise NULL. */ ++static struct chain_head * + find_label(const char *name, TC_HANDLE_T handle) + { +- unsigned int i; ++ struct list_head *pos; + +- if (handle->cache_chain_heads == NULL +- && !populate_cache(handle)) ++ if (!handle->chains) + return NULL; + +- /* FIXME: Linear search through builtins, then binary --RR */ +- for (i = 0; i < handle->cache_num_chains; i++) { +- if (strcmp(handle->cache_chain_heads[i].name, name) == 0) +- return &handle->cache_chain_heads[i]; ++ list_for_each(pos, &handle->chains) { ++ struct chain_head *c = list_entry(pos, struct chain_head, list); ++ if (!strcmp(c->name, name)) ++ return c; + } + + return NULL; +@@ -594,34 +460,30 @@ + const char * + TC_FIRST_CHAIN(TC_HANDLE_T *handle) + { +- if ((*handle)->cache_chain_heads == NULL +- && !populate_cache(*handle)) +- return NULL; ++ (*handle)->chain_iterator_cur = (*handle)->chains; + +- (*handle)->cache_chain_iteration +- = &(*handle)->cache_chain_heads[0]; +- +- return (*handle)->cache_chain_iteration->name; ++ return (*handle)->chains.name; + } + + /* Iterator functions to run through the chains. Returns NULL at end. */ + const char * + TC_NEXT_CHAIN(TC_HANDLE_T *handle) + { +- (*handle)->cache_chain_iteration++; ++ struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); ++ (*handle)->chain_iterator_cur = next; + +- if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads +- == (*handle)->cache_num_chains) ++ if (next == (*handle)->chains) + return NULL; + +- return (*handle)->cache_chain_iteration->name; ++ return next->name; + } + + /* Get first rule in the given chain: NULL for empty chain. */ + const STRUCT_ENTRY * + TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) + { +- struct chain_cache *c; ++ struct chain_head *c; ++ struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { +@@ -630,22 +492,26 @@ + } + + /* Empty chain: single return/policy rule */ +- if (c->start_off == c->end_off) ++ if (list_empty(c->rules)) + return NULL; + +- (*handle)->cache_rule_end = offset2entry(*handle, c->end_off); +- return offset2entry(*handle, c->start_off); ++ r = list_entry(&c->rules.next, struct rule_head, list); ++ (*handle)->rule_iterator_cur = r; ++ ++ return r->entry; + } + + /* Returns NULL when rules run out. */ + const STRUCT_ENTRY * + TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) + { +- if ((void *)prev + prev->next_offset +- == (void *)(*handle)->cache_rule_end) ++ struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); ++ ++ if (r == r->chain) + return NULL; + +- return (void *)prev + prev->next_offset; ++ /* NOTE: prev is without any influence ! */ ++ return r->entry; + } + + #if 0 +@@ -773,7 +639,7 @@ + return target_name(*handle, e); + } + +-static inline int ++static int + correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +@@ -874,16 +740,8 @@ + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + +- newh->cache_chain_heads = (*handle)->cache_chain_heads; +- newh->cache_num_builtins = (*handle)->cache_num_builtins; +- newh->cache_num_chains = (*handle)->cache_num_chains; +- newh->cache_rule_end = (*handle)->cache_rule_end; +- newh->cache_chain_iteration = (*handle)->cache_chain_iteration; +- if (!correct_cache(newh, offset, rules_size)) { +- free(newh); +- return 0; +- } +- ++ if ((*handle)->cache_chain_heads) ++ free((*handle)->cache_chain_heads); + free(*handle); + *handle = newh; + +@@ -942,10 +800,6 @@ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + +- /* Fix the chain cache */ +- if (!correct_cache(*handle, offset, -(int)rules_size)) +- return 0; +- + return set_verdict(offset, -(int)rules_size, handle); + } + +@@ -1449,7 +1303,6 @@ + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } newc; +- unsigned int destination; + + iptc_fn = TC_CREATE_CHAIN; + +@@ -1487,21 +1340,11 @@ + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc.target.verdict = RETURN; + +- destination = index2offset(*handle, (*handle)->new_number -1); +- + /* Add just before terminal entry */ + ret = insert_rules(2, sizeof(newc), &newc.head, +- destination, ++ index2offset(*handle, (*handle)->new_number - 1), + (*handle)->new_number - 1, + 0, handle); +- +- set_changed(*handle); +- +- /* add chain cache info for this chain */ +- add_chain_cache(*handle, chain, +- destination+newc.head.next_offset, +- destination+newc.head.next_offset); +- + return ret; + } + +@@ -1629,11 +1472,6 @@ + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); +- +- /* update chain cache */ +- memset(c->name, 0, sizeof(c->name)); +- strcpy(c->name, newname); +- + set_changed(*handle); + + return 1; diff --git a/libiptc2/libip4tc.c b/libiptc2/libip4tc.c new file mode 100644 index 0000000..e012c08 --- /dev/null +++ b/libiptc2/libip4tc.c @@ -0,0 +1,507 @@ +/* Library which manipulates firewall rules. Version 0.1. */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + COPYING for details). */ + +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_CONNTRACK +#define inline +#endif + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include "libiptc/libiptc.h" + +#define IP_VERSION 4 +#define IP_OFFSET 0x1FFF + +#define HOOK_PRE_ROUTING NF_IP_PRE_ROUTING +#define HOOK_LOCAL_IN NF_IP_LOCAL_IN +#define HOOK_FORWARD NF_IP_FORWARD +#define HOOK_LOCAL_OUT NF_IP_LOCAL_OUT +#define HOOK_POST_ROUTING NF_IP_POST_ROUTING +#ifdef NF_IP_DROPPING +#define HOOK_DROPPING NF_IP_DROPPING +#endif + +#define STRUCT_ENTRY_TARGET struct ipt_entry_target +#define STRUCT_ENTRY struct ipt_entry +#define STRUCT_ENTRY_MATCH struct ipt_entry_match +#define STRUCT_GETINFO struct ipt_getinfo +#define STRUCT_GET_ENTRIES struct ipt_get_entries +#define STRUCT_COUNTERS struct ipt_counters +#define STRUCT_COUNTERS_INFO struct ipt_counters_info +#define STRUCT_STANDARD_TARGET struct ipt_standard_target +#define STRUCT_REPLACE struct ipt_replace + +#define STRUCT_TC_HANDLE struct iptc_handle +#define TC_HANDLE_T iptc_handle_t + +#define ENTRY_ITERATE IPT_ENTRY_ITERATE +#define TABLE_MAXNAMELEN IPT_TABLE_MAXNAMELEN +#define FUNCTION_MAXNAMELEN IPT_FUNCTION_MAXNAMELEN + +#define GET_TARGET ipt_get_target + +#define ERROR_TARGET IPT_ERROR_TARGET +#define NUMHOOKS NF_IP_NUMHOOKS + +#define IPT_CHAINLABEL ipt_chainlabel + +#define TC_DUMP_ENTRIES dump_entries +#define TC_IS_CHAIN iptc_is_chain +#define TC_FIRST_CHAIN iptc_first_chain +#define TC_NEXT_CHAIN iptc_next_chain +#define TC_FIRST_RULE iptc_first_rule +#define TC_NEXT_RULE iptc_next_rule +#define TC_GET_TARGET iptc_get_target +#define TC_BUILTIN iptc_builtin +#define TC_GET_POLICY iptc_get_policy +#define TC_INSERT_ENTRY iptc_insert_entry +#define TC_REPLACE_ENTRY iptc_replace_entry +#define TC_APPEND_ENTRY iptc_append_entry +#define TC_DELETE_ENTRY iptc_delete_entry +#define TC_DELETE_NUM_ENTRY iptc_delete_num_entry +#define TC_CHECK_PACKET iptc_check_packet +#define TC_FLUSH_ENTRIES iptc_flush_entries +#define TC_ZERO_ENTRIES iptc_zero_entries +#define TC_READ_COUNTER iptc_read_counter +#define TC_ZERO_COUNTER iptc_zero_counter +#define TC_SET_COUNTER iptc_set_counter +#define TC_CREATE_CHAIN iptc_create_chain +#define TC_GET_REFERENCES iptc_get_references +#define TC_DELETE_CHAIN iptc_delete_chain +#define TC_RENAME_CHAIN iptc_rename_chain +#define TC_SET_POLICY iptc_set_policy +#define TC_GET_RAW_SOCKET iptc_get_raw_socket +#define TC_INIT iptc_init +#define TC_FREE iptc_free +#define TC_COMMIT iptc_commit +#define TC_STRERROR iptc_strerror + +#define TC_AF AF_INET +#define TC_IPPROTO IPPROTO_IP + +#define SO_SET_REPLACE IPT_SO_SET_REPLACE +#define SO_SET_ADD_COUNTERS IPT_SO_SET_ADD_COUNTERS +#define SO_GET_INFO IPT_SO_GET_INFO +#define SO_GET_ENTRIES IPT_SO_GET_ENTRIES +#define SO_GET_VERSION IPT_SO_GET_VERSION + +#define STANDARD_TARGET IPT_STANDARD_TARGET +#define LABEL_RETURN IPTC_LABEL_RETURN +#define LABEL_ACCEPT IPTC_LABEL_ACCEPT +#define LABEL_DROP IPTC_LABEL_DROP +#define LABEL_QUEUE IPTC_LABEL_QUEUE + +#define ALIGN IPT_ALIGN +#define RETURN IPT_RETURN + +#include "libiptc.c" + +#define IP_PARTS_NATIVE(n) \ +(unsigned int)((n)>>24)&0xFF, \ +(unsigned int)((n)>>16)&0xFF, \ +(unsigned int)((n)>>8)&0xFF, \ +(unsigned int)((n)&0xFF) + +#define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n)) + +int +dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle) +{ + size_t i; + STRUCT_ENTRY_TARGET *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + printf("SRC IP: %u.%u.%u.%u/%u.%u.%u.%u\n", + IP_PARTS(e->ip.src.s_addr),IP_PARTS(e->ip.smsk.s_addr)); + printf("DST IP: %u.%u.%u.%u/%u.%u.%u.%u\n", + IP_PARTS(e->ip.dst.s_addr),IP_PARTS(e->ip.dmsk.s_addr)); + printf("Interface: `%s'/", e->ip.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ip.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ip.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ip.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ip.proto); + printf("Flags: %02X\n", e->ip.flags); + printf("Invflags: %02X\n", e->ip.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP_SRC) printf("IP_SRC "); + if (e->nfcache & NFC_IP_DST) printf("IP_DST "); + if (e->nfcache & NFC_IP_IF_IN) printf("IP_IF_IN "); + if (e->nfcache & NFC_IP_IF_OUT) printf("IP_IF_OUT "); + if (e->nfcache & NFC_IP_TOS) printf("IP_TOS "); + if (e->nfcache & NFC_IP_PROTO) printf("IP_PROTO "); + if (e->nfcache & NFC_IP_OPTIONS) printf("IP_OPTIONS "); + if (e->nfcache & NFC_IP_TCPFLAGS) printf("IP_TCPFLAGS "); + if (e->nfcache & NFC_IP_SRC_PT) printf("IP_SRC_PT "); + if (e->nfcache & NFC_IP_DST_PT) printf("IP_DST_PT "); + if (e->nfcache & NFC_IP_PROTO_UNKNOWN) printf("IP_PROTO_UNKNOWN "); + printf("\n"); + + IPT_MATCH_ITERATE(e, print_match); + + t = GET_TARGET(e); + printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size); + if (strcmp(t->u.user.name, STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == -NF_QUEUE-1 ? "NF_QUEUE" + : pos == RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.user.name, IPT_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, unsigned char *matchmask) +{ + unsigned int i; + STRUCT_ENTRY_TARGET *ta, *tb; + unsigned char *mptr; + + /* Always compare head structures: ignore mask here. */ + if (a->ip.src.s_addr != b->ip.src.s_addr + || a->ip.dst.s_addr != b->ip.dst.s_addr + || a->ip.smsk.s_addr != b->ip.smsk.s_addr + || a->ip.dmsk.s_addr != b->ip.dmsk.s_addr + || a->ip.proto != b->ip.proto + || a->ip.flags != b->ip.flags + || a->ip.invflags != b->ip.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ip.iniface_mask[i] != b->ip.iniface_mask[i]) + return 0; + if ((a->ip.iniface[i] & a->ip.iniface_mask[i]) + != (b->ip.iniface[i] & b->ip.iniface_mask[i])) + return 0; + if (a->ip.outiface_mask[i] != b->ip.outiface_mask[i]) + return 0; + if ((a->ip.outiface[i] & a->ip.outiface_mask[i]) + != (b->ip.outiface[i] & b->ip.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + mptr = matchmask + sizeof(STRUCT_ENTRY); + if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr)) + return 0; + + ta = GET_TARGET((STRUCT_ENTRY *)a); + tb = GET_TARGET((STRUCT_ENTRY *)b); + if (ta->u.target_size != tb->u.target_size) + return 0; + if (strcmp(ta->u.user.name, tb->u.user.name) != 0) + return 0; + + mptr += sizeof(*ta); + if (target_different(ta->data, tb->data, + ta->u.target_size - sizeof(*ta), mptr)) + return 0; + + return 1; +} + +/***************************** DEBUGGING ********************************/ +static inline int +unconditional(const struct ipt_ip *ip) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ip)/sizeof(u_int32_t); i++) + if (((u_int32_t *)ip)[i]) + return 0; + + return 1; +} + +static inline int +check_match(const STRUCT_ENTRY_MATCH *m, unsigned int *off) +{ + assert(m->u.match_size >= sizeof(STRUCT_ENTRY_MATCH)); + assert(ALIGN(m->u.match_size) == m->u.match_size); + + (*off) += m->u.match_size; + return 0; +} + +static inline int +check_entry(const STRUCT_ENTRY *e, unsigned int *i, unsigned int *off, + unsigned int user_offset, int *was_return, + TC_HANDLE_T h) +{ + unsigned int toff; + STRUCT_STANDARD_TARGET *t; + + assert(e->target_offset >= sizeof(STRUCT_ENTRY)); + assert(e->next_offset >= e->target_offset + + sizeof(STRUCT_ENTRY_TARGET)); + toff = sizeof(STRUCT_ENTRY); + IPT_MATCH_ITERATE(e, check_match, &toff); + + assert(toff == e->target_offset); + + t = (STRUCT_STANDARD_TARGET *) + GET_TARGET((STRUCT_ENTRY *)e); + /* next_offset will have to be multiple of entry alignment. */ + assert(e->next_offset == ALIGN(e->next_offset)); + assert(e->target_offset == ALIGN(e->target_offset)); + assert(t->target.u.target_size == ALIGN(t->target.u.target_size)); + assert(!TC_IS_CHAIN(t->target.u.user.name, h)); + + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0) { + assert(t->target.u.target_size + == ALIGN(sizeof(STRUCT_STANDARD_TARGET))); + + assert(t->verdict == -NF_DROP-1 + || t->verdict == -NF_ACCEPT-1 + || t->verdict == RETURN + || t->verdict < (int)h->entries.size); + + if (t->verdict >= 0) { + STRUCT_ENTRY *te = get_entry(h, t->verdict); + int idx; + + idx = entry2index(h, te); + assert(strcmp(GET_TARGET(te)->u.user.name, + IPT_ERROR_TARGET) + != 0); + assert(te != e); + + /* Prior node must be error node, or this node. */ + assert(t->verdict == entry2offset(h, e)+e->next_offset + || strcmp(GET_TARGET(index2entry(h, idx-1)) + ->u.user.name, IPT_ERROR_TARGET) + == 0); + } + + if (t->verdict == RETURN + && unconditional(&e->ip) + && e->target_offset == sizeof(*e)) + *was_return = 1; + else + *was_return = 0; + } else if (strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0) { + assert(t->target.u.target_size + == ALIGN(sizeof(struct ipt_error_target))); + + /* If this is in user area, previous must have been return */ + if (*off > user_offset) + assert(*was_return); + + *was_return = 0; + } + else *was_return = 0; + + if (*off == user_offset) + assert(strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0); + + (*off) += e->next_offset; + (*i)++; + return 0; +} + +#ifdef IPTC_DEBUG +/* Do every conceivable sanity check on the handle */ +static void +do_check(TC_HANDLE_T h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP_LOCAL_IN + | 1 << NF_IP_FORWARD + | 1 << NF_IP_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert((h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_POST_ROUTING + | 1 << NF_IP_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_IN + | 1 << NF_IP_POST_ROUTING + | 1 << NF_IP_LOCAL_OUT))); + + assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n); + n = get_chain_end(h, n); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n); + user_offset = h->info.hook_entry[NF_IP_LOCAL_IN]; + } + + } else if (strcmp(h->info.name, "mangle") == 0) { + /* This code is getting ugly because linux < 2.4.18-pre6 had + * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks + * */ + assert((h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_IN + | 1 << NF_IP_FORWARD + | 1 << NF_IP_LOCAL_OUT + | 1 << NF_IP_POST_ROUTING))); + + /* Hooks should be first five */ + assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n); + n = get_chain_end(h, n); + } + + if (h->info.valid_hooks & (1 << NF_IP_FORWARD)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_FORWARD] == n); + n = get_chain_end(h, n); + } + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP_POST_ROUTING)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n); + user_offset = h->info.hook_entry[NF_IP_POST_ROUTING]; + } + +#ifdef NF_IP_DROPPING + } else if (strcmp(h->info.name, "drop") == 0) { + assert(h->info.valid_hooks == (1 << NF_IP_DROPPING)); + + /* Hook should be first */ + assert(h->info.hook_entry[NF_IP_DROPPING] == 0); + user_offset = 0; +#endif + } else { + fprintf(stderr, "Unknown table `%s'\n", h->info.name); + abort(); + } + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NUMHOOKS; i++) { + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ip)); + assert(e->target_offset == sizeof(*e)); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + assert(t->target.u.target_size == ALIGN(sizeof(*t))); + assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t))); + + assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(STRUCT_ENTRY) + +sizeof(STRUCT_STANDARD_TARGET))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(STRUCT_ENTRY) + + sizeof(STRUCT_STANDARD_TARGET)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + /* Check all the entries. */ + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1)) + ->u.user.name, + ERROR_TARGET) == 0); +} +#endif /*IPTC_DEBUG*/ diff --git a/libiptc2/libip6tc.c b/libiptc2/libip6tc.c new file mode 100644 index 0000000..9a78a5a --- /dev/null +++ b/libiptc2/libip6tc.c @@ -0,0 +1,449 @@ +/* Library which manipulates firewall rules. Version 0.1. */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + COPYING for details). */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_CONNTRACK +#define inline +#endif + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include "libiptc/libip6tc.h" + +#define HOOK_PRE_ROUTING NF_IP6_PRE_ROUTING +#define HOOK_LOCAL_IN NF_IP6_LOCAL_IN +#define HOOK_FORWARD NF_IP6_FORWARD +#define HOOK_LOCAL_OUT NF_IP6_LOCAL_OUT +#define HOOK_POST_ROUTING NF_IP6_POST_ROUTING + +#define STRUCT_ENTRY_TARGET struct ip6t_entry_target +#define STRUCT_ENTRY struct ip6t_entry +#define STRUCT_ENTRY_MATCH struct ip6t_entry_match +#define STRUCT_GETINFO struct ip6t_getinfo +#define STRUCT_GET_ENTRIES struct ip6t_get_entries +#define STRUCT_COUNTERS struct ip6t_counters +#define STRUCT_COUNTERS_INFO struct ip6t_counters_info +#define STRUCT_STANDARD_TARGET struct ip6t_standard_target +#define STRUCT_REPLACE struct ip6t_replace + +#define STRUCT_TC_HANDLE struct ip6tc_handle +#define TC_HANDLE_T ip6tc_handle_t + +#define ENTRY_ITERATE IP6T_ENTRY_ITERATE +#define TABLE_MAXNAMELEN IP6T_TABLE_MAXNAMELEN +#define FUNCTION_MAXNAMELEN IP6T_FUNCTION_MAXNAMELEN + +#define GET_TARGET ip6t_get_target + +#define ERROR_TARGET IP6T_ERROR_TARGET +#define NUMHOOKS NF_IP6_NUMHOOKS + +#define IPT_CHAINLABEL ip6t_chainlabel + +#define TC_DUMP_ENTRIES dump_entries6 +#define TC_IS_CHAIN ip6tc_is_chain +#define TC_FIRST_CHAIN ip6tc_first_chain +#define TC_NEXT_CHAIN ip6tc_next_chain +#define TC_FIRST_RULE ip6tc_first_rule +#define TC_NEXT_RULE ip6tc_next_rule +#define TC_GET_TARGET ip6tc_get_target +#define TC_BUILTIN ip6tc_builtin +#define TC_GET_POLICY ip6tc_get_policy +#define TC_INSERT_ENTRY ip6tc_insert_entry +#define TC_REPLACE_ENTRY ip6tc_replace_entry +#define TC_APPEND_ENTRY ip6tc_append_entry +#define TC_DELETE_ENTRY ip6tc_delete_entry +#define TC_DELETE_NUM_ENTRY ip6tc_delete_num_entry +#define TC_CHECK_PACKET ip6tc_check_packet +#define TC_FLUSH_ENTRIES ip6tc_flush_entries +#define TC_ZERO_ENTRIES ip6tc_zero_entries +#define TC_ZERO_COUNTER ip6tc_zero_counter +#define TC_READ_COUNTER ip6tc_read_counter +#define TC_SET_COUNTER ip6tc_set_counter +#define TC_CREATE_CHAIN ip6tc_create_chain +#define TC_GET_REFERENCES ip6tc_get_references +#define TC_DELETE_CHAIN ip6tc_delete_chain +#define TC_RENAME_CHAIN ip6tc_rename_chain +#define TC_SET_POLICY ip6tc_set_policy +#define TC_GET_RAW_SOCKET ip6tc_get_raw_socket +#define TC_INIT ip6tc_init +#define TC_FREE ip6tc_free +#define TC_COMMIT ip6tc_commit +#define TC_STRERROR ip6tc_strerror + +#define TC_AF AF_INET6 +#define TC_IPPROTO IPPROTO_IPV6 + +#define SO_SET_REPLACE IP6T_SO_SET_REPLACE +#define SO_SET_ADD_COUNTERS IP6T_SO_SET_ADD_COUNTERS +#define SO_GET_INFO IP6T_SO_GET_INFO +#define SO_GET_ENTRIES IP6T_SO_GET_ENTRIES +#define SO_GET_VERSION IP6T_SO_GET_VERSION + +#define STANDARD_TARGET IP6T_STANDARD_TARGET +#define LABEL_RETURN IP6TC_LABEL_RETURN +#define LABEL_ACCEPT IP6TC_LABEL_ACCEPT +#define LABEL_DROP IP6TC_LABEL_DROP +#define LABEL_QUEUE IP6TC_LABEL_QUEUE + +#define ALIGN IP6T_ALIGN +#define RETURN IP6T_RETURN + +#include "libiptc.c" + +#define BIT6(a, l) \ + ((ntohl(a->in6_u.u6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1) + +int +ipv6_prefix_length(const struct in6_addr *a) +{ + int l, i; + for (l = 0; l < 128; l++) { + if (BIT6(a, l) == 0) + break; + } + for (i = l + 1; i < 128; i++) { + if (BIT6(a, i) == 1) + return -1; + } + return l; +} + +static int +dump_entry(struct ip6t_entry *e, const ip6tc_handle_t handle) +{ + size_t i; + char buf[40]; + int len; + struct ip6t_entry_target *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + puts("SRC IP: "); + inet_ntop(AF_INET6, &e->ipv6.src, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.smsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.smsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + puts("DST IP: "); + inet_ntop(AF_INET6, &e->ipv6.dst, buf, sizeof buf); + puts(buf); + putchar('/'); + len = ipv6_prefix_length(&e->ipv6.dmsk); + if (len != -1) + printf("%d", len); + else { + inet_ntop(AF_INET6, &e->ipv6.dmsk, buf, sizeof buf); + puts(buf); + } + putchar('\n'); + + printf("Interface: `%s'/", e->ipv6.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ipv6.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ipv6.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ipv6.proto); + if (e->ipv6.flags & IP6T_F_TOS) + printf("TOS: %u\n", e->ipv6.tos); + printf("Flags: %02X\n", e->ipv6.flags); + printf("Invflags: %02X\n", e->ipv6.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP6_SRC) printf("IP6_SRC "); + if (e->nfcache & NFC_IP6_DST) printf("IP6_DST "); + if (e->nfcache & NFC_IP6_IF_IN) printf("IP6_IF_IN "); + if (e->nfcache & NFC_IP6_IF_OUT) printf("IP6_IF_OUT "); + if (e->nfcache & NFC_IP6_TOS) printf("IP6_TOS "); + if (e->nfcache & NFC_IP6_PROTO) printf("IP6_PROTO "); + if (e->nfcache & NFC_IP6_OPTIONS) printf("IP6_OPTIONS "); + if (e->nfcache & NFC_IP6_TCPFLAGS) printf("IP6_TCPFLAGS "); + if (e->nfcache & NFC_IP6_SRC_PT) printf("IP6_SRC_PT "); + if (e->nfcache & NFC_IP6_DST_PT) printf("IP6_DST_PT "); + if (e->nfcache & NFC_IP6_PROTO_UNKNOWN) printf("IP6_PROTO_UNKNOWN "); + printf("\n"); + + IP6T_MATCH_ITERATE(e, print_match); + + t = ip6t_get_target(e); + printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size); + if (strcmp(t->u.user.name, IP6T_STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == IP6T_RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.user.name, IP6T_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, + unsigned char *matchmask) +{ + unsigned int i; + STRUCT_ENTRY_TARGET *ta, *tb; + unsigned char *mptr; + + /* Always compare head structures: ignore mask here. */ + if (memcmp(&a->ipv6.src, &b->ipv6.src, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dst, &b->ipv6.dst, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.smsk, &b->ipv6.smsk, sizeof(struct in6_addr)) + || memcmp(&a->ipv6.dmsk, &b->ipv6.dmsk, sizeof(struct in6_addr)) + || a->ipv6.proto != b->ipv6.proto + || a->ipv6.tos != b->ipv6.tos + || a->ipv6.flags != b->ipv6.flags + || a->ipv6.invflags != b->ipv6.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ipv6.iniface_mask[i] != b->ipv6.iniface_mask[i]) + return 0; + if ((a->ipv6.iniface[i] & a->ipv6.iniface_mask[i]) + != (b->ipv6.iniface[i] & b->ipv6.iniface_mask[i])) + return 0; + if (a->ipv6.outiface_mask[i] != b->ipv6.outiface_mask[i]) + return 0; + if ((a->ipv6.outiface[i] & a->ipv6.outiface_mask[i]) + != (b->ipv6.outiface[i] & b->ipv6.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + mptr = matchmask + sizeof(STRUCT_ENTRY); + if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr)) + return 0; + + ta = GET_TARGET((STRUCT_ENTRY *)a); + tb = GET_TARGET((STRUCT_ENTRY *)b); + if (ta->u.target_size != tb->u.target_size) + return 0; + if (strcmp(ta->u.user.name, tb->u.user.name) != 0) + return 0; + mptr += sizeof(*ta); + + if (target_different(ta->data, tb->data, + ta->u.target_size - sizeof(*ta), mptr)) + return 0; + + return 1; +} + +/* All zeroes == unconditional rule. */ +static inline int +unconditional(const struct ip6t_ip6 *ipv6) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ipv6); i++) + if (((char *)ipv6)[i]) + break; + + return (i == sizeof(*ipv6)); +} + +#ifdef IPTC_DEBUG +/* Do every conceivable sanity check on the handle */ +static void +do_check(TC_HANDLE_T h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + n = get_chain_end(h, n); + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_IN]; + } + + } else if (strcmp(h->info.name, "mangle") == 0) { + /* This code is getting ugly because linux < 2.4.18-pre6 had + * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks + * */ + assert((h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_OUT)) || + (h->info.valid_hooks + == (1 << NF_IP6_PRE_ROUTING + | 1 << NF_IP6_LOCAL_IN + | 1 << NF_IP6_FORWARD + | 1 << NF_IP6_LOCAL_OUT + | 1 << NF_IP6_POST_ROUTING))); + + /* Hooks should be first five */ + assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + + if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n); + n = get_chain_end(h, n); + } + + if (h->info.valid_hooks & (1 << NF_IP6_FORWARD)) { + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_FORWARD] == n); + n = get_chain_end(h, n); + } + + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n); + user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT]; + + if (h->info.valid_hooks & (1 << NF_IP6_POST_ROUTING)) { + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n); + user_offset = h->info.hook_entry[NF_IP6_POST_ROUTING]; + } + } else { + fprintf(stderr, "Unknown table `%s'\n", h->info.name); + abort(); + } + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NUMHOOKS; i++) { + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ipv6)); + assert(e->target_offset == sizeof(*e)); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + printf("target_size=%u, align=%u\n", + t->target.u.target_size, ALIGN(sizeof(*t))); + assert(t->target.u.target_size == ALIGN(sizeof(*t))); + assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t))); + + assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(STRUCT_ENTRY) + +sizeof(STRUCT_STANDARD_TARGET))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(STRUCT_ENTRY) + + sizeof(STRUCT_STANDARD_TARGET)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + +#if 0 + /* Check all the entries. */ + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1)) + ->u.user.name, + ERROR_TARGET) == 0); +#endif +} +#endif /*IPTC_DEBUG*/ diff --git a/libiptc2/libiptc.c b/libiptc2/libiptc.c new file mode 100644 index 0000000..3c3c76a --- /dev/null +++ b/libiptc2/libiptc.c @@ -0,0 +1,1830 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.41 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte : + * - Reimplementation of chain cache to use offsets instead of entries + * 2003-Jun-23: Harald Welte : + * - speed optimization, sponsored by Astaro AG (http://www.astaro.com/) + * don't rebuild the chain cache after every operation, instead fix it + * up after a ruleset change. + * 2003-Jun-30: Harald Welte : + * - reimplementation from scratch. *sigh*. I hope nobody has to touch + * this code ever again. + */ +#include "linux_listhelp.h" + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct rule_head +{ + struct list_head list; /* list of rules in chain */ + + struct chain_head *chain; /* we're part of this chain */ + + struct chain_head *jumpto; /* target of this rule, in case + it is a jump rule */ + + struct counter_map counter_map; + + unsigned int size; /* size of rule */ + STRUCT_ENTRY *entry_blob; /* pointer to entry in blob */ + STRUCT_ENTRY entry[0]; +}; + +struct chain_head +{ + struct list_head list; + + char name[TABLE_MAXNAMELEN]; + unsigned int hooknum; + struct list_head rules; + struct rule_head *firstrule; /* first (ERROR) rule */ + struct rule_head *lastrule; /* last (RETURN) rule */ +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + + /* linked list of chains in this table */ + struct list_head chains; + + /* current position of first_chain() / next_chain() */ + struct chain_head *chain_iterator_cur; + + /* current position of first_rule() / next_rule() */ + struct rule_head *rule_iterator_cur; + + /* the structure we receive from getsockopt() */ + STRUCT_GETINFO info; + + /* Array of hook names */ + const char **hooknames; +#if 0 + /* Size in here reflects original state. */ + + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; +#endif + STRUCT_GET_ENTRIES entries; +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static struct rule_head *ruleh_alloc(unsigned int size) +{ + struct rule_head *ruleh = malloc(sizeof(*ruleh)+size); + if (!ruleh) + return NULL; + + memset(ruleh, 0, sizeof(*ruleh)+size); + ruleh->size = size; + + return ruleh; +} + +static void ruleh_free(struct rule_head *ruleh) +{ + list_del(&ruleh->list); + free(ruleh); +} + +static struct chain_head *chainh_alloc(TC_HANDLE_T h, const char *name) +{ + struct chain_head *chainh = malloc(sizeof(*chainh)); + if (!chainh) + return NULL; + + memset(chainh, 0, sizeof(*chainh)); + strncpy(chainh->name, name, sizeof(&chainh->name)); + list_append(&chainh->list, &h->chains); + + return chainh; +} + +static void +chainh_clean(struct chain_head *chainh) +{ + /* FIXME */ + struct list_head *cur_item, *item2; + + list_for_each_safe(cur_item, item2, &chainh->rules) { + struct rule_head *ruleh = list_entry(cur_item, + struct rule_head, + list); + ruleh_free(ruleh); + } +} + +static void +chainh_free(struct chain_head *chainh) +{ + chainh_clean(chainh); + list_del(&chainh->list); +} + +static struct chain_head * +chainh_find(TC_HANDLE_T h, const IPT_CHAINLABEL name) +{ + struct list_head *cur; + + list_for_each(cur, &h->chains) { + struct chain_head *ch = list_entry(cur, struct chain_head, + list); + if (!strcmp(name, ch->name)) + return ch; + } + return NULL; +} + +/* Returns chain head if found, otherwise NULL. */ +static struct chain_head * +find_label(const char *name, TC_HANDLE_T handle) +{ + return chainh_find(handle, name); +} + + +/* + * functions that directly operate on the blob + */ + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + +/* needed by entry2index */ +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (char *)GET_TARGET(e)->data; +} + +#if 0 +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +#endif + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_tc_handle(const char *tablename, unsigned int size, + unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + INIT_LIST_HEAD(&h->chains); + + return h; +} + +/* get the name of the chain that we jump to */ +static char * +parse_jumptarget(const STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + STRUCT_ENTRY *jumpto; + int spos, labelidx; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) { + /* called for non-standard target */ + return "__FIXME"; + } + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + return "__FIXME"; + } + + jumpto = get_entry(h, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + /* FIXME: this needs to deal with internal jump targets */ + labelidx = entry2index(h, jumpto) - 1; + return get_errorlabel(h, index2offset(h, labelidx)); +} + +/* parser functions */ + +struct rule_head * +append_entrycopy(const STRUCT_ENTRY *e, struct rule_head *prev) +{ + struct rule_head *ruleh = ruleh_alloc(e->next_offset); + if (!ruleh) + return NULL; + + memcpy(&ruleh->entry, e, e->next_offset); + ruleh->chain = prev->chain; + ruleh->entry_blob = e; + list_append(&ruleh->list, &prev->list); + + return ruleh; +} + +/* have to return 0 on success, bcf ENTRY_ITERATE */ +static inline int +parse_entry(const STRUCT_ENTRY *e, TC_HANDLE_T h, struct chain_head **curchain) +{ + int i; + union tgt_u { + STRUCT_ENTRY_TARGET ent; + STRUCT_STANDARD_TARGET std; + struct ipt_error_target err; + } *tgt; + + struct rule_head *lastrule = list_entry((*curchain)->rules.prev, + struct rule_head, list); + struct rule_head *newrule; + + tgt = (union tgt_u *) GET_TARGET(e); + + if (e->target_offset == sizeof(STRUCT_ENTRY) + && (strcmp(tgt->ent.u.user.name, IPT_STANDARD_TARGET) == 0)) { + /* jump to somewhere else */ + char *targname; + struct chain_head *chainh; + + newrule = append_entrycopy(e, lastrule); + + targname = parse_jumptarget(e, h); + if (!(chainh = find_label(targname, h))) { + chainh = chainh_alloc(h, targname); + } + if (!chainh) { + errno = ENOMEM; + return 1; + } + newrule->jumpto = chainh; + + } else if (e->target_offset == sizeof(STRUCT_ENTRY) + && e->next_offset == sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)) + && !strcmp(tgt->ent.u.user.name, ERROR_TARGET)) { + /* chain head */ + *curchain = chainh_find(h, tgt->err.error); + if (!(*curchain)) { + *curchain = chainh_alloc(h, tgt->err.error); + /* FIXME: error handling */ + } + newrule = append_entrycopy(e, lastrule); + (*curchain)->firstrule = newrule; + + } else if (e->target_offset == sizeof(STRUCT_ENTRY) + && e->next_offset == sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)) + && tgt->std.verdict == RETURN) { + /* chain end */ + newrule = append_entrycopy(e, lastrule); + (*curchain)->lastrule = newrule; + *curchain = NULL; + } else { + /* normal rule */ + newrule = append_entrycopy(e, lastrule); + } + + /* create counter map entry */ + newrule->counter_map.maptype = COUNTER_MAP_NORMAL_MAP; + newrule->counter_map.mappos = entry2index(h, e); + + /* iterate over hook_entries, needed to connect builtin + * chains with hook numbers */ + for (i = 0; i < NUMHOOKS; i++) { + if (!(h->info.valid_hooks & (1 << i))) + continue; + if (h->info.hook_entry[i] == entry2offset(h, e)) { + /* found hook entry point */ + if (*curchain) + (*curchain)->hooknum = i; + } + if (h->info.underflow[i] == entry2offset(h, e)) { + /* found underflow point */ + } + } + + return 0; +} + +static int parse_ruleset(TC_HANDLE_T h) +{ + struct chain_head *curchain; + + /* iterate over ruleset; create linked list of rule_head/chain_head */ + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + parse_entry, h, &curchain)) { + /* some error happened while iterating */ + return 0; + } + + return 1; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_tc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + //h->new_number = h->info.num_entries; + // + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + parse_ruleset(h); + + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + struct list_head *cur_item, *item2; + + close(sockfd); + sockfd = -1; + + /* free all chains */ + list_for_each_safe(cur_item, item2, &(*h)->chains) { + struct chain_head *chead = list_entry(cur_item, + struct chain_head, + list); + chainh_free(chead); + } + + /* FIXME: free all other ressources we might be using */ + + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +#if 0 +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} + + +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} +#endif + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +#if 0 +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} +#endif + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *firsthead = list_entry((*handle)->chains.next, + struct chain_head, list); + (*handle)->chain_iterator_cur = firsthead; + + return firsthead->name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); + (*handle)->chain_iterator_cur = next; + + if (&next->list == &(*handle)->chains) + return NULL; + + return next->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (list_empty(&c->rules)) + return NULL; + + r = list_entry(c->rules.next, struct rule_head, list); + (*handle)->rule_iterator_cur = r; + + return r->entry; +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); + + if (&r->list == &r->chain->rules) + return NULL; + + /* NOTE: prev is without any influence ! */ + return r->entry; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: entry %p not a valid target (%d)\n", + e, spos); + abort(); + } + +#if 0 +// jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + /* FIXME: this needs to deal with internal jump targets */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +#endif + return ""; +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *chainh; + struct rule_head *ruleh; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) + return NULL; + + chainh = find_label(chain, *handle); + if (!chainh) { + errno = ENOENT; + return NULL; + } + + ruleh = chainh->lastrule; + + e = ruleh->entry; + *counters = e->counters; + + return target_name(*handle, e); +} + +#if 0 +static int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} +#endif + + + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = (STRUCT_ENTRY_TARGET *)GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_head *c; + +#if 0 + /* FIXME */ + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); +#endif + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +static struct rule_head * +ruleh_get_n(struct chain_head *chead, int rulenum) +{ + int i = 0; + struct list_head *list; + + + list_for_each(list, &chead->rules) { + struct rule_head *rhead = list_entry(list, struct rule_head, + list); + i++; + if (i == rulenum) + return rhead; + } + return NULL; +} + +/* Insert the entry `e' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *prev; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + prev = ruleh_get_n(c, rulenum-1); + if (!prev) { + errno = E2BIG; + return 0; + } + + if (append_entrycopy(e, prev)) + return 1; + + return 0; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *repl; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + repl = ruleh_get_n(c, rulenum); + if (!repl) { + errno = E2BIG; + return 0; + } + + if (!append_entrycopy(e, repl)) { + errno = ENOMEM; + return 0; + } + + ruleh_free(repl); + return 1; +} + +/* Append entry `e' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *rhead; + + iptc_fn = TC_APPEND_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + rhead = list_entry(c->rules.prev, struct rule_head, list); + if(append_entrycopy(e, rhead)) + return 1; + + return 0; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `origfw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct list_head *cur, *cur2; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + list_for_each_safe(cur, cur2, &c->rules) { + struct rule_head *rhead = list_entry(cur, struct rule_head, + list); + if (is_same(rhead->entry, origfw, matchmask)) { + ruleh_free(rhead); + return 1; + } + } + + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + struct chain_head *chainh; + struct rule_head *rhead; + + iptc_fn = TC_DELETE_NUM_ENTRY; + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + rhead = ruleh_get_n(chainh, rulenum); + if (!rhead) { + errno = E2BIG; + return 0; + } + + ruleh_free(rhead); + + return 1; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + struct list_head *cur, *cur2; + struct chain_head *chainh; + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + list_for_each_safe(cur, cur2, &chainh->rules) { + struct rule_head *ruleh = list_entry(cur, struct rule_head, + list); + /* don't free the entry and policy/return entries */ + if (ruleh != chainh->firstrule && ruleh != chainh->lastrule) + ruleh_free(ruleh); + } + return 1; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct list_head *cur; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + list_for_each(cur, c->rules.next) { + struct rule_head *r = list_entry(cur, struct rule_head, list); + if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP) + r->counter_map.maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *c; + struct rule_head *r; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle) ) + || !(r = ruleh_get_n(c, rulenum))) { + errno = ENOENT; + return NULL; + } + + return &r->entry->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *c; + struct rule_head *r; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle)) + || !(r = ruleh_get_n(c, rulenum))) { + errno = ENOENT; + return 0; + } + + if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP) + r->counter_map.maptype = COUNTER_MAP_ZEROED; + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_head *c; + struct rule_head *r; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle)) + || !(r = ruleh_get_n(c, rulenum))) { + errno = ENOENT; + return 0; + } + + r->counter_map.maptype = COUNTER_MAP_SET; + memcpy(&r->entry->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct chainstart { + STRUCT_ENTRY head; + struct ipt_error_target name; + } *newc1; + struct chainend { + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } *newc2; + struct rule_head *newr1, *newr2; + struct chain_head *chead; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + chead = chainh_alloc(*handle, chain); + if (!chead) { + errno = ENOMEM; + return 0; + } + + newr1 = ruleh_alloc(sizeof(*newc1)); + if (!newr1) { + chainh_free(chead); + return 0; + } + newc1 = (struct chainstart *) newr1->entry; + + newr2 = ruleh_alloc(sizeof(*newc2)); + if (!newr2) { + chainh_free(chead); + ruleh_free(newr1); + return 0; + } + newc2 = (struct chainend *) newr2->entry; + + newc1->head.target_offset = sizeof(STRUCT_ENTRY); + newc1->head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc1->name.t.u.user.name, ERROR_TARGET); + newc1->name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc1->name.error, chain); + + newc2->ret.target_offset = sizeof(STRUCT_ENTRY); + newc2->ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc2->target.target.u.user.name, STANDARD_TARGET); + newc2->target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc2->target.verdict = RETURN; + + list_prepend(&newr1->list, &chead->rules); + chead->firstrule = newr1; + list_append(&newr2->list, &chead->rules); + chead->lastrule = newr2; + + return 1; +} + +#if 0 +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} +#endif + +static unsigned int +count_rules(struct chain_head *chainh) +{ + unsigned int numrules = 0; + struct list_head *cur; + + list_for_each(cur, &chainh->rules) { + numrules++; + } + + if (numrules <=2) + return 0; + else + return numrules-2; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int references; + struct chain_head *chainh; + +#if 0 + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } +#endif + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!(count_rules(chainh) == 0)) { + errno = ENOTEMPTY; + return 0; + } + + chainh_free(chainh); + return 1; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *ruleh; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + ruleh = list_entry(&c->rules.next, struct rule_head, list); + + t = (struct ipt_error_target *) + GET_TARGET(ruleh->entry); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + int ctrindex; + unsigned int hook; + struct chain_head *chainh; + struct rule_head *policyrh; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + if (!(chainh = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + policyrh = chainh->lastrule; + if (policyrh) { + printf("ERROR: Policy for `%s' non-existant", chain); + return 0; + } + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(policyrh->entry); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + policyrh->counter_map.maptype = COUNTER_MAP_SET; + + } else { + policyrh->counter_map.maptype = COUNTER_MAP_NOMAP; + policyrh->counter_map.mappos = 0; + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/libiptc.cvs.c b/libiptc2/libiptc.cvs.c new file mode 100644 index 0000000..3f03593 --- /dev/null +++ b/libiptc2/libiptc.cvs.c @@ -0,0 +1,1920 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.40 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte : + * - Reimplementation of chain cache to use offsets instead of entries + * 2003-Jun-23: Harald Welte : + * - performance optimization, sponsored by Astaro AG (http://www.astaro.com/) + * don't rebuild the chain cache after every operation, instead fix it + * up after a ruleset change. + */ + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/lib/iptables" +#endif + +#ifndef __OPTIMIZE__ +STRUCT_ENTRY_TARGET * +GET_TARGET(STRUCT_ENTRY *e) +{ + return (void *)e + e->target_offset; +} +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct chain_cache +{ + char name[TABLE_MAXNAMELEN]; + /* This is the first rule in chain. */ + unsigned int start_off; + /* Last rule in chain */ + unsigned int end_off; +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + /* Size in here reflects original state. */ + STRUCT_GETINFO info; + + struct counter_map *counter_map; + /* Array of hook names */ + const char **hooknames; + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; + STRUCT_GET_ENTRIES entries; +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +static const char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)GET_TARGET(e)->data; +} + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + h->cache_num_chains = 0; + h->cache_chain_heads = NULL; + h->counter_map = (void *)h + + sizeof(STRUCT_TC_HANDLE) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + + return h; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + h->new_number = h->info.num_entries; + for (i = 0; i < h->info.num_entries; i++) + h->counter_map[i] + = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i}); + + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + close(sockfd); + sockfd = -1; + if ((*h)->cache_chain_heads) + free((*h)->cache_chain_heads); + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} + +static inline int +add_chain(STRUCT_ENTRY *e, TC_HANDLE_T h, STRUCT_ENTRY **prev) +{ + unsigned int builtin; + + /* Last entry. End it. */ + if (entry2offset(h, e) + e->next_offset == h->entries.size) { + /* This is the ERROR node at end of the table */ + h->cache_chain_heads[h->cache_num_chains-1].end_off = + entry2offset(h, *prev); + return 0; + } + + /* We know this is the start of a new chain if it's an ERROR + target, or a hook entry point */ + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) { + /* prev was last entry in previous chain */ + h->cache_chain_heads[h->cache_num_chains-1].end_off + = entry2offset(h, *prev); + + strcpy(h->cache_chain_heads[h->cache_num_chains].name, + (const char *)GET_TARGET(e)->data); + h->cache_chain_heads[h->cache_num_chains].start_off + = entry2offset(h, (void *)e + e->next_offset); + h->cache_num_chains++; + } else if ((builtin = is_hook_entry(e, h)) != 0) { + if (h->cache_num_chains > 0) + /* prev was last entry in previous chain */ + h->cache_chain_heads[h->cache_num_chains-1].end_off + = entry2offset(h, *prev); + + strcpy(h->cache_chain_heads[h->cache_num_chains].name, + h->hooknames[builtin-1]); + h->cache_chain_heads[h->cache_num_chains].start_off + = entry2offset(h, (void *)e); + h->cache_num_chains++; + } + + *prev = e; + return 0; +} + +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} + +static int populate_cache(TC_HANDLE_T h) +{ + unsigned int i; + STRUCT_ENTRY *prev; + + /* # chains < # rules / 2 + num builtins - 1 */ + h->cache_chain_heads = malloc((h->new_number / 2 + 4) + * sizeof(struct chain_cache)); + if (!h->cache_chain_heads) { + errno = ENOMEM; + return 0; + } + + h->cache_num_chains = 0; + h->cache_num_builtins = 0; + + /* Count builtins */ + for (i = 0; i < NUMHOOKS; i++) { + if (h->info.valid_hooks & (1 << i)) + h->cache_num_builtins++; + } + + prev = NULL; + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + add_chain, h, &prev); + + qsort(h->cache_chain_heads + h->cache_num_builtins, + h->cache_num_chains - h->cache_num_builtins, + sizeof(struct chain_cache), alphasort); + + return 1; +} + +static int +correct_cache(TC_HANDLE_T h, unsigned int offset, int delta) +{ + int i; /* needs to be signed because deleting first + chain can make it drop to -1 */ + + if (!delta) + return 1; + + for (i = 0; i < h->cache_num_chains; i++) { + struct chain_cache *cc = &h->cache_chain_heads[i]; + + if (delta < 0) { + /* take care about deleted chains */ + if (cc->start_off >= offset+delta + && cc->end_off <= offset) { + /* this chain is within the deleted range, + * let's remove it from the cache */ + void *start; + unsigned int size; + + h->cache_num_chains--; + if (i+1 >= h->cache_num_chains) + continue; + start = &h->cache_chain_heads[i+1]; + size = (h->cache_num_chains-i) + * sizeof(struct chain_cache); + memmove(cc, start, size); + + /* iterate over same index again, since + * it is now a different chain */ + i--; + continue; + } + } + + if (cc->start_off > offset) + cc->start_off += delta; + + if (cc->end_off >= offset) + cc->end_off += delta; + } + /* HW_FIXME: sorting might be needed, but just in case a new chain was + * added */ + + return 1; +} + +static int +add_chain_cache(TC_HANDLE_T h, const char *name, unsigned int start_off, + unsigned int end_off) +{ + struct chain_cache *ccs = realloc(h->cache_chain_heads, + (h->new_number / 2 + 4 + 1) + * sizeof(struct chain_cache)); + struct chain_cache *newcc; + + if (!ccs) + return 0; + + h->cache_chain_heads = ccs; + newcc = &h->cache_chain_heads[h->cache_num_chains]; + h->cache_num_chains++; + + strncpy(newcc->name, name, TABLE_MAXNAMELEN-1); + newcc->start_off = start_off; + newcc->end_off = end_off; + + return 1; +} + +/* Returns cache ptr if found, otherwise NULL. */ +static struct chain_cache * +find_label(const char *name, TC_HANDLE_T handle) +{ + unsigned int i; + + if (handle->cache_chain_heads == NULL + && !populate_cache(handle)) + return NULL; + + /* FIXME: Linear search through builtins, then binary --RR */ + for (i = 0; i < handle->cache_num_chains; i++) { + if (strcmp(handle->cache_chain_heads[i].name, name) == 0) + return &handle->cache_chain_heads[i]; + } + + return NULL; +} + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + if ((*handle)->cache_chain_heads == NULL + && !populate_cache(*handle)) + return NULL; + + (*handle)->cache_chain_iteration + = &(*handle)->cache_chain_heads[0]; + + return (*handle)->cache_chain_iteration->name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + (*handle)->cache_chain_iteration++; + + if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads + == (*handle)->cache_num_chains) + return NULL; + + return (*handle)->cache_chain_iteration->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (c->start_off == c->end_off) + return NULL; + + (*handle)->cache_rule_end = offset2entry(*handle, c->end_off); + return offset2entry(*handle, c->start_off); +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + if ((void *)prev + prev->next_offset + == (void *)(*handle)->cache_rule_end) + return NULL; + + return (void *)prev + prev->next_offset; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + unsigned int labelidx; + STRUCT_ENTRY *jumpto; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: off %lu/%u not a valid target (%i)\n", + entry2offset(handle, e), handle->entries.size, + spos); + abort(); + } + + jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int start; + STRUCT_ENTRY *e; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook != 0) + start = (*handle)->info.hook_entry[hook-1]; + else + return NULL; + + e = get_entry(*handle, get_chain_end(*handle, start)); + *counters = e->counters; + + return target_name(*handle, e); +} + +static inline int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} + +/* If prepend is set, then we are prepending to a chain: if the + * insertion position is an entry point, keep the entry point. */ +static int +insert_rules(unsigned int num_rules, unsigned int rules_size, + const STRUCT_ENTRY *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + TC_HANDLE_T *handle) +{ + TC_HANDLE_T newh; + STRUCT_GETINFO newinfo; + unsigned int i; + + if (offset >= (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + newinfo = (*handle)->info; + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* Entry points to START of chain, so keep same if + inserting on at that point. */ + if ((*handle)->info.hook_entry[i] > offset) + newinfo.hook_entry[i] += rules_size; + + /* Underflow always points to END of chain (policy), + so if something is inserted at same point, it + should be advanced. */ + if ((*handle)->info.underflow[i] >= offset) + newinfo.underflow[i] += rules_size; + } + + newh = alloc_handle((*handle)->info.name, + (*handle)->entries.size + rules_size, + (*handle)->new_number + num_rules); + if (!newh) + return 0; + newh->info = newinfo; + + /* Copy pre... */ + memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset); + /* ... Insert new ... */ + memcpy((char *)newh->entries.entrytable + offset, insert, rules_size); + /* ... copy post */ + memcpy((char *)newh->entries.entrytable + offset + rules_size, + (char *)(*handle)->entries.entrytable + offset, + (*handle)->entries.size - offset); + + /* Move counter map. */ + /* Copy pre... */ + memcpy(newh->counter_map, (*handle)->counter_map, + sizeof(struct counter_map) * num_rules_offset); + /* ... copy post */ + memcpy(newh->counter_map + num_rules_offset + num_rules, + (*handle)->counter_map + num_rules_offset, + sizeof(struct counter_map) * ((*handle)->new_number + - num_rules_offset)); + /* Set intermediates to no counter copy */ + for (i = 0; i < num_rules; i++) + newh->counter_map[num_rules_offset+i] + = ((struct counter_map){ COUNTER_MAP_SET, 0 }); + + newh->new_number = (*handle)->new_number + num_rules; + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + + newh->cache_chain_heads = (*handle)->cache_chain_heads; + newh->cache_num_builtins = (*handle)->cache_num_builtins; + newh->cache_num_chains = (*handle)->cache_num_chains; + newh->cache_rule_end = (*handle)->cache_rule_end; + newh->cache_chain_iteration = (*handle)->cache_chain_iteration; + if (!correct_cache(newh, offset, rules_size)) { + free(newh); + return 0; + } + + free(*handle); + *handle = newh; + + return set_verdict(offset, rules_size, handle); +} + +static int +delete_rules(unsigned int num_rules, unsigned int rules_size, + unsigned int offset, unsigned int num_rules_offset, + TC_HANDLE_T *handle) +{ + unsigned int i; + + if (offset + rules_size > (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* In practice, we never delete up to a hook entry, + since the built-in chains are always first, + so these two are never equal */ + if ((*handle)->info.hook_entry[i] >= offset + rules_size) + (*handle)->info.hook_entry[i] -= rules_size; + else if ((*handle)->info.hook_entry[i] > offset) { + fprintf(stderr, "ERROR: Deleting entry %u %u %u\n", + i, (*handle)->info.hook_entry[i], offset); + abort(); + } + + /* Underflow points to policy (terminal) rule in + built-in, so sequality is valid here (when deleting + the last rule). */ + if ((*handle)->info.underflow[i] >= offset + rules_size) + (*handle)->info.underflow[i] -= rules_size; + else if ((*handle)->info.underflow[i] > offset) { + fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n", + i, (*handle)->info.underflow[i], offset); + abort(); + } + } + + /* Move the rules down. */ + memmove((char *)(*handle)->entries.entrytable + offset, + (char *)(*handle)->entries.entrytable + offset + rules_size, + (*handle)->entries.size - (offset + rules_size)); + + /* Move the counter map down. */ + memmove(&(*handle)->counter_map[num_rules_offset], + &(*handle)->counter_map[num_rules_offset + num_rules], + sizeof(struct counter_map) + * ((*handle)->new_number - (num_rules + num_rules_offset))); + + /* Fix numbers */ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + + /* Fix the chain cache */ + if (!correct_cache(*handle, offset, -(int)rules_size)) + return 0; + + return set_verdict(offset, -(int)rules_size, handle); +} + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_cache *c; + + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +/* Insert the entry `fw' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp > offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + offset = index2offset(*handle, chainindex + rulenum); + + /* Mapping target actually alters entry, but that's + transparent to the caller. */ + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp >= offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + offset = index2offset(*handle, chainindex + rulenum); + /* Replace = delete and insert. */ + if (!delete_rules(1, get_entry(*handle, offset)->next_offset, + offset, chainindex + rulenum, handle)) + return 0; + + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + STRUCT_ENTRY_TARGET old; + int ret; + + iptc_fn = TC_APPEND_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!map_target(*handle, (STRUCT_ENTRY *)e, + c->end_off, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, c->end_off, + offset2index(*handle, c->end_off), 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `fw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + unsigned int offset; + struct chain_cache *c; + STRUCT_ENTRY *e, *fw; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + + for (offset = c->start_off; offset < c->end_off; + offset += e->next_offset) { + STRUCT_ENTRY_TARGET discard; + + memcpy(fw, origfw, origfw->next_offset); + + /* FIXME: handle this in is_same --RR */ + if (!map_target(*handle, fw, offset, &discard)) { + free(fw); + return 0; + } + e = get_entry(*handle, offset); + +#if 0 + printf("Deleting:\n"); + dump_entry(newe); +#endif + if (is_same(e, fw, matchmask)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int index; + int ret; + STRUCT_ENTRY *e; + struct chain_cache *c; + + iptc_fn = TC_DELETE_NUM_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + index = offset2index(*handle, c->start_off) + rulenum; + + if (index >= offset2index(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, index); + if (e == NULL) { + errno = EINVAL; + return 0; + } + + ret = delete_rules(1, e->next_offset, entry2offset(*handle, e), + index, handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int startindex, endindex; + STRUCT_ENTRY *startentry, *endentry; + struct chain_cache *c; + int ret; + + iptc_fn = TC_FLUSH_ENTRIES; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + startindex = offset2index(*handle, c->start_off); + endindex = offset2index(*handle, c->end_off); + startentry = offset2entry(*handle, c->start_off); + endentry = offset2entry(*handle, c->end_off); + + ret = delete_rules(endindex - startindex, + (char *)endentry - (char *)startentry, + c->start_off, startindex, + handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int i, end; + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + i = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return NULL; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return NULL; + } + + e = index2entry(*handle, chainindex + rulenum); + + return &e->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + if ((*handle)->counter_map[chainindex + rulenum].maptype + == COUNTER_MAP_NORMAL_MAP) { + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_ZEROED; + } + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_SET; + + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct { + STRUCT_ENTRY head; + struct ipt_error_target name; + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } newc; + unsigned int destination; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + memset(&newc, 0, sizeof(newc)); + newc.head.target_offset = sizeof(STRUCT_ENTRY); + newc.head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.t.u.user.name, ERROR_TARGET); + newc.name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.error, chain); + + newc.ret.target_offset = sizeof(STRUCT_ENTRY); + newc.ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc.target.target.u.user.name, STANDARD_TARGET); + newc.target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc.target.verdict = RETURN; + + destination = index2offset(*handle, (*handle)->new_number -1); + + /* Add just before terminal entry */ + ret = insert_rules(2, sizeof(newc), &newc.head, + destination, + (*handle)->new_number - 1, + 0, handle); + + set_changed(*handle); + + /* add chain cache info for this chain */ + add_chain_cache(*handle, chain, + destination+newc.head.next_offset, + destination+newc.head.next_offset); + + return ret; +} + +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int labelidx, labeloff; + unsigned int references; + struct chain_cache *c; + int ret; + STRUCT_ENTRY *start; + + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (c->start_off != c->end_off) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + start = offset2entry(*handle, c->start_off); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + start->next_offset, + labeloff, labelidx, handle); + return ret; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + unsigned int labeloff, labelidx; + struct chain_cache *c; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + GET_TARGET(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + + /* update chain cache */ + memset(c->name, 0, sizeof(c->name)); + strcpy(c->name, newname); + + set_changed(*handle); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int hook; + unsigned int policyoff, ctrindex; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]); + if (policyoff != (*handle)->info.underflow[hook]) { + printf("ERROR: Policy for `%s' offset %u != underflow %u\n", + chain, policyoff, (*handle)->info.underflow[hook]); + return 0; + } + + e = get_entry(*handle, policyoff); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + (*handle)->counter_map[ctrindex].maptype + = COUNTER_MAP_SET; + + } else { + (*handle)->counter_map[ctrindex] + = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/libiptc2.c b/libiptc2/libiptc2.c new file mode 100644 index 0000000..a7d7915 --- /dev/null +++ b/libiptc2/libiptc2.c @@ -0,0 +1,1758 @@ +/* Library which manipulates firewall rules. Version $Revision: 1.39 $ */ + +/* Architecture of firewall rules is as follows: + * + * Chains go INPUT, FORWARD, OUTPUT then user chains. + * Each user chain starts with an ERROR node. + * Every chain ends with an unconditional jump: a RETURN for user chains, + * and a POLICY for built-ins. + */ + +/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See + * COPYING for details). + * (C) 2000-2003 by the Netfilter Core Team + * + * 2003-Jun-20: Harald Welte target_offset; +} +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [HOOK_PRE_ROUTING] "PREROUTING", + [HOOK_LOCAL_IN] "INPUT", + [HOOK_FORWARD] "FORWARD", + [HOOK_LOCAL_OUT] "OUTPUT", + [HOOK_POST_ROUTING] "POSTROUTING", +#ifdef HOOK_DROPPING + [HOOK_DROPPING] "DROPPING" +#endif +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED, + COUNTER_MAP_SET + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + STRUCT_ENTRY_TARGET t; + char error[TABLE_MAXNAMELEN]; +}; + +struct rule_head +{ + struct list_head list; + + struct chain_head *chain; + + unsigned int size; + STRUCT_ENTRY entry[0]; +} + +struct chain_head +{ + struct list_head list; + + char name[TABLE_MAXNAMELEN]; + unsigned int hooknum; + struct list_head rules; +}; + +STRUCT_TC_HANDLE +{ + /* Have changes been made? */ + int changed; + + struct list_head chains; + + struct chain_head *chain_iterator_cur; + +#if 0 + /* Size in here reflects original state. */ + STRUCT_GETINFO info; + + struct counter_map *counter_map; + /* Array of hook names */ + const char **hooknames; + + /* Cached position of chain heads (NULL = no cache). */ + unsigned int cache_num_chains; + unsigned int cache_num_builtins; + struct chain_cache *cache_chain_heads; + + /* Chain iterator: current chain cache entry. */ + struct chain_cache *cache_chain_iteration; + + /* Rule iterator: terminal rule */ + STRUCT_ENTRY *cache_rule_end; + + /* Number in here reflects current state. */ + unsigned int new_number; + STRUCT_GET_ENTRIES entries; +#endif +}; + +static void +set_changed(TC_HANDLE_T h) +{ + h->changed = 1; +} + +#ifdef IPTC_DEBUG +static void do_check(TC_HANDLE_T h, unsigned int line); +#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) +#else +#define CHECK(h) +#endif + +static inline int +get_number(const STRUCT_ENTRY *i, + const STRUCT_ENTRY *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) +{ + unsigned int pos = 0; + + if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (char *)seek - (char *)h->entries.entrytable); + abort(); + } + return pos; +} + +static inline int +get_entry_n(STRUCT_ENTRY *i, + unsigned int number, + unsigned int *pos, + STRUCT_ENTRY **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static STRUCT_ENTRY * +index2entry(TC_HANDLE_T h, unsigned int index) +{ + unsigned int pos = 0; + STRUCT_ENTRY *ret = NULL; + + ENTRY_ITERATE(h->entries.entrytable, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline STRUCT_ENTRY * +get_entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); +} + +static inline unsigned long +entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) +{ + return (void *)e - (void *)h->entries.entrytable; +} + +static inline unsigned long +index2offset(TC_HANDLE_T h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static inline STRUCT_ENTRY * +offset2entry(TC_HANDLE_T h, unsigned int offset) +{ + return (STRUCT_ENTRY *) ((void *)h->entries.entrytable+offset); +} + +static inline unsigned int +offset2index(const TC_HANDLE_T h, unsigned int offset) +{ + return entry2index(h, offset2entry(h, offset)); +} + + +static const char * +get_errorlabel(TC_HANDLE_T h, unsigned int offset) +{ + STRUCT_ENTRY *e; + + e = get_entry(h, offset); + if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)GET_TARGET(e)->data; +} + +/* Allocate handle of given size */ +static TC_HANDLE_T +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + TC_HANDLE_T h; + + len = sizeof(STRUCT_TC_HANDLE) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + h->cache_num_chains = 0; + h->cache_chain_heads = NULL; + h->counter_map = (void *)h + + sizeof(STRUCT_TC_HANDLE) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + + return h; +} + +TC_HANDLE_T +TC_INIT(const char *tablename) +{ + TC_HANDLE_T h; + STRUCT_GETINFO info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = TC_INIT; + + if (sockfd != -1) { + close(sockfd); + sockfd = -1; + } + + if (strlen(tablename) >= TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + + sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + + strcpy(info.name, tablename); + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_handle(info.name, info.size, info.num_entries)) + == NULL) { + close(sockfd); + sockfd = -1; + return NULL; + } + +/* Too hard --RR */ +#if 0 + sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); + dynlib = dlopen(pathname, RTLD_NOW); + if (!dynlib) { + errno = ENOENT; + return NULL; + } + h->hooknames = dlsym(dynlib, "hooknames"); + if (!h->hooknames) { + errno = ENOENT; + return NULL; + } +#else + h->hooknames = hooknames; +#endif + + /* Initialize current state */ + h->info = info; + h->new_number = h->info.num_entries; + for (i = 0; i < h->info.num_entries; i++) + h->counter_map[i] + = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i}); + + h->entries.size = h->info.size; + + tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; + + if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + close(sockfd); + sockfd = -1; + free(h); + return NULL; + } + + CHECK(h); + return h; +} + +void +TC_FREE(TC_HANDLE_T *h) +{ + close(sockfd); + sockfd = -1; + if ((*h)->cache_chain_heads) + free((*h)->cache_chain_heads); + free(*h); + *h = NULL; +} + +static inline int +print_match(const STRUCT_ENTRY_MATCH *m) +{ + printf("Match name: `%s'\n", m->u.user.name); + return 0; +} + +static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); + +void +TC_DUMP_ENTRIES(const TC_HANDLE_T handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + IPTABLES_VERSION, + handle->new_number, handle->entries.size); + printf("Table `%s'\n", handle->info.name); + printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.hook_entry[HOOK_PRE_ROUTING], + handle->info.hook_entry[HOOK_LOCAL_IN], + handle->info.hook_entry[HOOK_FORWARD], + handle->info.hook_entry[HOOK_LOCAL_OUT], + handle->info.hook_entry[HOOK_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[HOOK_PRE_ROUTING], + handle->info.underflow[HOOK_LOCAL_IN], + handle->info.underflow[HOOK_FORWARD], + handle->info.underflow[HOOK_LOCAL_OUT], + handle->info.underflow[HOOK_POST_ROUTING]); + + ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, + dump_entry, handle); +} + +/* Returns 0 if not hook entry, else hooknumber + 1 */ +static inline unsigned int +is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((h->info.valid_hooks & (1 << i)) + && get_entry(h, h->info.hook_entry[i]) == e) + return i+1; + } + return 0; +} +static int alphasort(const void *a, const void *b) +{ + return strcmp(((struct chain_cache *)a)->name, + ((struct chain_cache *)b)->name); +} + +/* Returns chain head if found, otherwise NULL. */ +static struct chain_head * +find_label(const char *name, TC_HANDLE_T handle) +{ + struct list_head *pos; + + if (!handle->chains) + return NULL; + + list_for_each(pos, &handle->chains) { + struct chain_head *c = list_entry(pos, struct chain_head, list); + if (!strcmp(c->name, name)) + return c; + } + + return NULL; +} + +/* Does this chain exist? */ +int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) +{ + return find_label(chain, handle) != NULL; +} + +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const TC_HANDLE_T handle, unsigned int start) +{ + unsigned int last_off, off; + STRUCT_ENTRY *e; + + last_off = start; + e = get_entry(handle, start); + + /* Terminate when we meet a error label or a hook entry. */ + for (off = start + e->next_offset; + off < handle->entries.size; + last_off = off, off += e->next_offset) { + STRUCT_ENTRY_TARGET *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && off == handle->info.hook_entry[i]) + return last_off; + } + + /* We hit a user chain label */ + t = GET_TARGET(e); + if (strcmp(t->u.user.name, ERROR_TARGET) == 0) + return last_off; + } + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", + handle->entries.size, off); + abort(); +} + +/* Iterator functions to run through the chains. */ +const char * +TC_FIRST_CHAIN(TC_HANDLE_T *handle) +{ + (*handle)->chain_iterator_cur = (*handle)->chains; + + return (*handle)->chains.name; +} + +/* Iterator functions to run through the chains. Returns NULL at end. */ +const char * +TC_NEXT_CHAIN(TC_HANDLE_T *handle) +{ + struct chain_head *next = list_entry(&(*handle)->chain_iterator_cur->list.next, struct chain_head, list); + (*handle)->chain_iterator_cur = next; + + if (next == (*handle)->chains) + return NULL; + + return next->name; +} + +/* Get first rule in the given chain: NULL for empty chain. */ +const STRUCT_ENTRY * +TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) +{ + struct chain_head *c; + struct rule_head *r; + + c = find_label(chain, *handle); + if (!c) { + errno = ENOENT; + return NULL; + } + + /* Empty chain: single return/policy rule */ + if (list_empty(c->rules)) + return NULL; + + r = list_entry(&c->rules.next, struct rule_head, list); + (*handle)->rule_iterator_cur = r; + + return r->entry; +} + +/* Returns NULL when rules run out. */ +const STRUCT_ENTRY * +TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) +{ + struct rule_head *r = list_entry((*handle)->rule_iterator_cur->list.next, struct rule_head, list); + + if (r == r->chain) + return NULL; + + /* NOTE: prev is without any influence ! */ + return r->entry; +} + +#if 0 +/* How many rules in this chain? */ +unsigned int +TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) +{ + unsigned int off = 0; + STRUCT_ENTRY *start, *end; + + CHECK(*handle); + if (!find_label(&off, chain, *handle)) { + errno = ENOENT; + return (unsigned int)-1; + } + + start = get_entry(*handle, off); + end = get_entry(*handle, get_chain_end(*handle, off)); + + return entry2index(*handle, end) - entry2index(*handle, start); +} + +/* Get n'th rule in this chain. */ +const STRUCT_ENTRY *TC_GET_RULE(const char *chain, + unsigned int n, + TC_HANDLE_T *handle) +{ + unsigned int pos = 0, chainindex; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + + return index2entry(*handle, chainindex + n); +} +#endif + +static const char * +target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) +{ + int spos; + unsigned int labelidx; + STRUCT_ENTRY *jumpto; + + /* To avoid const warnings */ + STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) + return GET_TARGET(e)->u.user.name; + + /* Standard target: evaluate */ + spos = *(int *)GET_TARGET(e)->data; + if (spos < 0) { + if (spos == RETURN) + return LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return LABEL_DROP; + else if (spos == -NF_QUEUE-1) + return LABEL_QUEUE; + + fprintf(stderr, "ERROR: off %lu/%u not a valid target (%i)\n", + entry2offset(handle, e), handle->entries.size, + spos); + abort(); + } + + jumpto = get_entry(handle, spos); + + /* Fall through rule */ + if (jumpto == (void *)e + e->next_offset) + return ""; + + /* Must point to head of a chain: ie. after error rule */ + labelidx = entry2index(handle, jumpto) - 1; + return get_errorlabel(handle, index2offset(handle, labelidx)); +} + +/* Returns a pointer to the target name of this position. */ +const char *TC_GET_TARGET(const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) +{ + unsigned int i; + + for (i = 0; i < NUMHOOKS; i++) { + if ((handle->info.valid_hooks & (1 << i)) + && handle->hooknames[i] + && strcmp(handle->hooknames[i], chain) == 0) + return i+1; + } + return 0; +} + +/* Get the policy of a given built-in chain */ +const char * +TC_GET_POLICY(const char *chain, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int start; + STRUCT_ENTRY *e; + int hook; + + hook = TC_BUILTIN(chain, *handle); + if (hook != 0) + start = (*handle)->info.hook_entry[hook-1]; + else + return NULL; + + e = get_entry(*handle, get_chain_end(*handle, start)); + *counters = e->counters; + + return target_name(*handle, e); +} + +static int +correct_verdict(STRUCT_ENTRY *e, + char *base, + unsigned int offset, int delta_offset) +{ + STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); + unsigned int curr = (char *)e - base; + + /* Trap: insert of fall-through rule. Don't change fall-through + verdict to jump-over-next-rule. */ + if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 + && t->verdict > (int)offset + && !(curr == offset && + t->verdict == curr + e->next_offset)) { + t->verdict += delta_offset; + } + + return 0; +} + +/* Adjusts standard verdict jump positions after an insertion/deletion. */ +static int +set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) +{ + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + correct_verdict, (char *)(*handle)->entries.entrytable, + offset, delta_offset); + + set_changed(*handle); + return 1; +} + +/* If prepend is set, then we are prepending to a chain: if the + * insertion position is an entry point, keep the entry point. */ +static int +insert_rules(unsigned int num_rules, unsigned int rules_size, + const STRUCT_ENTRY *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + TC_HANDLE_T *handle) +{ + TC_HANDLE_T newh; + STRUCT_GETINFO newinfo; + unsigned int i; + + if (offset >= (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + newinfo = (*handle)->info; + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* Entry points to START of chain, so keep same if + inserting on at that point. */ + if ((*handle)->info.hook_entry[i] > offset) + newinfo.hook_entry[i] += rules_size; + + /* Underflow always points to END of chain (policy), + so if something is inserted at same point, it + should be advanced. */ + if ((*handle)->info.underflow[i] >= offset) + newinfo.underflow[i] += rules_size; + } + + newh = alloc_handle((*handle)->info.name, + (*handle)->entries.size + rules_size, + (*handle)->new_number + num_rules); + if (!newh) + return 0; + newh->info = newinfo; + + /* Copy pre... */ + memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset); + /* ... Insert new ... */ + memcpy((char *)newh->entries.entrytable + offset, insert, rules_size); + /* ... copy post */ + memcpy((char *)newh->entries.entrytable + offset + rules_size, + (char *)(*handle)->entries.entrytable + offset, + (*handle)->entries.size - offset); + + /* Move counter map. */ + /* Copy pre... */ + memcpy(newh->counter_map, (*handle)->counter_map, + sizeof(struct counter_map) * num_rules_offset); + /* ... copy post */ + memcpy(newh->counter_map + num_rules_offset + num_rules, + (*handle)->counter_map + num_rules_offset, + sizeof(struct counter_map) * ((*handle)->new_number + - num_rules_offset)); + /* Set intermediates to no counter copy */ + for (i = 0; i < num_rules; i++) + newh->counter_map[num_rules_offset+i] + = ((struct counter_map){ COUNTER_MAP_SET, 0 }); + + newh->new_number = (*handle)->new_number + num_rules; + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + + if ((*handle)->cache_chain_heads) + free((*handle)->cache_chain_heads); + free(*handle); + *handle = newh; + + return set_verdict(offset, rules_size, handle); +} + +static int +delete_rules(unsigned int num_rules, unsigned int rules_size, + unsigned int offset, unsigned int num_rules_offset, + TC_HANDLE_T *handle) +{ + unsigned int i; + + if (offset + rules_size > (*handle)->entries.size) { + errno = EINVAL; + return 0; + } + + /* Fix up entry points. */ + for (i = 0; i < NUMHOOKS; i++) { + /* In practice, we never delete up to a hook entry, + since the built-in chains are always first, + so these two are never equal */ + if ((*handle)->info.hook_entry[i] >= offset + rules_size) + (*handle)->info.hook_entry[i] -= rules_size; + else if ((*handle)->info.hook_entry[i] > offset) { + fprintf(stderr, "ERROR: Deleting entry %u %u %u\n", + i, (*handle)->info.hook_entry[i], offset); + abort(); + } + + /* Underflow points to policy (terminal) rule in + built-in, so sequality is valid here (when deleting + the last rule). */ + if ((*handle)->info.underflow[i] >= offset + rules_size) + (*handle)->info.underflow[i] -= rules_size; + else if ((*handle)->info.underflow[i] > offset) { + fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n", + i, (*handle)->info.underflow[i], offset); + abort(); + } + } + + /* Move the rules down. */ + memmove((char *)(*handle)->entries.entrytable + offset, + (char *)(*handle)->entries.entrytable + offset + rules_size, + (*handle)->entries.size - (offset + rules_size)); + + /* Move the counter map down. */ + memmove(&(*handle)->counter_map[num_rules_offset], + &(*handle)->counter_map[num_rules_offset + num_rules], + sizeof(struct counter_map) + * ((*handle)->new_number - (num_rules + num_rules_offset))); + + /* Fix numbers */ + (*handle)->new_number -= num_rules; + (*handle)->entries.size -= rules_size; + + return set_verdict(offset, -(int)rules_size, handle); +} + +static int +standard_map(STRUCT_ENTRY *e, int verdict) +{ + STRUCT_STANDARD_TARGET *t; + + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->target.u.target_size + != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); + strcpy(t->target.u.user.name, STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const TC_HANDLE_T handle, + STRUCT_ENTRY *e, + unsigned int offset, + STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *old = *t; + + /* Maybe it's empty (=> fall through) */ + if (strcmp(t->u.user.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.user.name, LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) + return standard_map(e, RETURN); + else if (TC_BUILTIN(t->u.user.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + struct chain_cache *c; + + c = find_label(t->u.user.name, handle); + if (c) + return standard_map(e, c->start_off); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.user.name + strlen(t->u.user.name), + 0, + FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + return 1; +} + +static void +unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) +{ + STRUCT_ENTRY_TARGET *t = GET_TARGET(e); + + /* Save old target (except data, which we don't change, except for + standard case, where we don't care). */ + *t = *old; +} + +/* Insert the entry `fw' in chain `chain' into position `rulenum'. */ +int +TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_INSERT_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp > offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + offset = index2offset(*handle, chainindex + rulenum); + + /* Mapping target actually alters entry, but that's + transparent to the caller. */ + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int chainindex, offset; + STRUCT_ENTRY_TARGET old; + struct chain_cache *c; + STRUCT_ENTRY *tmp; + int ret; + + iptc_fn = TC_REPLACE_ENTRY; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + + tmp = index2entry(*handle, chainindex + rulenum); + if (!tmp || tmp >= offset2entry(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + offset = index2offset(*handle, chainindex + rulenum); + /* Replace = delete and insert. */ + if (!delete_rules(1, get_entry(*handle, offset)->next_offset, + offset, chainindex + rulenum, handle)) + return 0; + + if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *e, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + STRUCT_ENTRY_TARGET old; + int ret; + + iptc_fn = TC_APPEND_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (!map_target(*handle, (STRUCT_ENTRY *)e, + c->end_off, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, c->end_off, + offset2index(*handle, c->end_off), 0, handle); + unmap_target((STRUCT_ENTRY *)e, &old); + return ret; +} + +static inline int +match_different(const STRUCT_ENTRY_MATCH *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const STRUCT_ENTRY_MATCH *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static inline int +target_different(const unsigned char *a_targdata, + const unsigned char *b_targdata, + unsigned int tdatasize, + const unsigned char *mask) +{ + unsigned int i; + for (i = 0; i < tdatasize; i++) + if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) + return 1; + + return 0; +} + +static int +is_same(const STRUCT_ENTRY *a, + const STRUCT_ENTRY *b, + unsigned char *matchmask); + +/* Delete the first rule in `chain' which matches `fw'. */ +int +TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, + const STRUCT_ENTRY *origfw, + unsigned char *matchmask, + TC_HANDLE_T *handle) +{ + unsigned int offset; + struct chain_cache *c; + STRUCT_ENTRY *e, *fw; + + iptc_fn = TC_DELETE_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + + for (offset = c->start_off; offset < c->end_off; + offset += e->next_offset) { + STRUCT_ENTRY_TARGET discard; + + memcpy(fw, origfw, origfw->next_offset); + + /* FIXME: handle this in is_same --RR */ + if (!map_target(*handle, fw, offset, &discard)) { + free(fw); + return 0; + } + e = get_entry(*handle, offset); + +#if 0 + printf("Deleting:\n"); + dump_entry(newe); +#endif + if (is_same(e, fw, matchmask)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + unsigned int index; + int ret; + STRUCT_ENTRY *e; + struct chain_cache *c; + + iptc_fn = TC_DELETE_NUM_ENTRY; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + index = offset2index(*handle, c->start_off) + rulenum; + + if (index >= offset2index(*handle, c->end_off)) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, index); + if (e == NULL) { + errno = EINVAL; + return 0; + } + + ret = delete_rules(1, e->next_offset, entry2offset(*handle, e), + index, handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +TC_CHECK_PACKET(const IPT_CHAINLABEL chain, + STRUCT_ENTRY *entry, + TC_HANDLE_T *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int startindex, endindex; + STRUCT_ENTRY *startentry, *endentry; + struct chain_cache *c; + int ret; + + iptc_fn = TC_FLUSH_ENTRIES; + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + startindex = offset2index(*handle, c->start_off); + endindex = offset2index(*handle, c->end_off); + startentry = offset2entry(*handle, c->start_off); + endentry = offset2entry(*handle, c->end_off); + + ret = delete_rules(endindex - startindex, + (char *)endentry - (char *)startentry, + c->start_off, startindex, + handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int i, end; + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + i = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + set_changed(*handle); + + return 1; +} + +STRUCT_COUNTERS * +TC_READ_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_READ_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return NULL; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return NULL; + } + + e = index2entry(*handle, chainindex + rulenum); + + return &e->counters; +} + +int +TC_ZERO_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_ZERO_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + if ((*handle)->counter_map[chainindex + rulenum].maptype + == COUNTER_MAP_NORMAL_MAP) { + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_ZEROED; + } + + set_changed(*handle); + + return 1; +} + +int +TC_SET_COUNTER(const IPT_CHAINLABEL chain, + unsigned int rulenum, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + STRUCT_ENTRY *e; + struct chain_cache *c; + unsigned int chainindex, end; + + iptc_fn = TC_SET_COUNTER; + CHECK(*handle); + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + chainindex = offset2index(*handle, c->start_off); + end = offset2index(*handle, c->end_off); + + if (chainindex + rulenum > end) { + errno = E2BIG; + return 0; + } + + e = index2entry(*handle, chainindex + rulenum); + + (*handle)->counter_map[chainindex + rulenum].maptype + = COUNTER_MAP_SET; + + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + set_changed(*handle); + + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + int ret; + struct { + STRUCT_ENTRY head; + struct ipt_error_target name; + STRUCT_ENTRY ret; + STRUCT_STANDARD_TARGET target; + } newc; + + iptc_fn = TC_CREATE_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(chain, *handle) + || strcmp(chain, LABEL_DROP) == 0 + || strcmp(chain, LABEL_ACCEPT) == 0 + || strcmp(chain, LABEL_QUEUE) == 0 + || strcmp(chain, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + memset(&newc, 0, sizeof(newc)); + newc.head.target_offset = sizeof(STRUCT_ENTRY); + newc.head.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.t.u.user.name, ERROR_TARGET); + newc.name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); + strcpy(newc.name.error, chain); + + newc.ret.target_offset = sizeof(STRUCT_ENTRY); + newc.ret.next_offset + = sizeof(STRUCT_ENTRY) + + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + strcpy(newc.target.target.u.user.name, STANDARD_TARGET); + newc.target.target.u.target_size + = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); + newc.target.verdict = RETURN; + + /* Add just before terminal entry */ + ret = insert_rules(2, sizeof(newc), &newc.head, + index2offset(*handle, (*handle)->new_number - 1), + (*handle)->new_number - 1, + 0, handle); + return ret; +} + +static int +count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) +{ + STRUCT_STANDARD_TARGET *t; + + if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, + TC_HANDLE_T *handle) +{ + struct chain_cache *c; + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + *ref = 0; + ENTRY_ITERATE((*handle)->entries.entrytable, + (*handle)->entries.size, + count_ref, c->start_off, ref); + return 1; +} + +/* Deletes a chain. */ +int +TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) +{ + unsigned int labelidx, labeloff; + unsigned int references; + struct chain_cache *c; + int ret; + STRUCT_ENTRY *start; + + if (!TC_GET_REFERENCES(&references, chain, handle)) + return 0; + + iptc_fn = TC_DELETE_CHAIN; + + if (TC_BUILTIN(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!(c = find_label(chain, *handle))) { + errno = ENOENT; + return 0; + } + + if (c->start_off != c->end_off) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + start = offset2entry(*handle, c->start_off); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + start->next_offset, + labeloff, labelidx, handle); + return ret; +} + +/* Renames a chain. */ +int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, + const IPT_CHAINLABEL newname, + TC_HANDLE_T *handle) +{ + unsigned int labeloff, labelidx; + struct chain_cache *c; + struct ipt_error_target *t; + + iptc_fn = TC_RENAME_CHAIN; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(newname, *handle) + || strcmp(newname, LABEL_DROP) == 0 + || strcmp(newname, LABEL_ACCEPT) == 0 + || strcmp(newname, LABEL_QUEUE) == 0 + || strcmp(newname, LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!(c = find_label(oldname, *handle)) + || TC_BUILTIN(oldname, *handle)) { + errno = ENOENT; + return 0; + } + + if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { + errno = EINVAL; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = offset2index(*handle, c->start_off) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + GET_TARGET(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + set_changed(*handle); + + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +TC_SET_POLICY(const IPT_CHAINLABEL chain, + const IPT_CHAINLABEL policy, + STRUCT_COUNTERS *counters, + TC_HANDLE_T *handle) +{ + unsigned int hook; + unsigned int policyoff, ctrindex; + STRUCT_ENTRY *e; + STRUCT_STANDARD_TARGET *t; + + iptc_fn = TC_SET_POLICY; + /* Figure out which chain. */ + hook = TC_BUILTIN(chain, *handle); + if (hook == 0) { + errno = ENOENT; + return 0; + } else + hook--; + + policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]); + if (policyoff != (*handle)->info.underflow[hook]) { + printf("ERROR: Policy for `%s' offset %u != underflow %u\n", + chain, policyoff, (*handle)->info.underflow[hook]); + return 0; + } + + e = get_entry(*handle, policyoff); + t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); + + if (strcmp(policy, LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + + ctrindex = entry2index(*handle, e); + + if (counters) { + /* set byte and packet counters */ + memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS)); + + (*handle)->counter_map[ctrindex].maptype + = COUNTER_MAP_SET; + + } else { + (*handle)->counter_map[ctrindex] + = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); + } + + set_changed(*handle); + + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `TC_COMMIT': + libiptc.c:833: fixed or forbidden register was spilled. + This may be due to a compiler bug or to impossible asm + statements or clauses. +*/ +static void +subtract_counters(STRUCT_COUNTERS *answer, + const STRUCT_COUNTERS *a, + const STRUCT_COUNTERS *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +TC_COMMIT(TC_HANDLE_T *handle) +{ + /* Replace, then map back the counters. */ + STRUCT_REPLACE *repl; + STRUCT_COUNTERS_INFO *newcounters; + unsigned int i; + size_t counterlen; + + CHECK(*handle); + + counterlen = sizeof(STRUCT_COUNTERS_INFO) + + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; + +#if 0 + TC_DUMP_ENTRIES(*handle); +#endif + + /* Don't commit if nothing changed. */ + if (!(*handle)->changed) + goto finished; + + repl = malloc(sizeof(*repl) + (*handle)->entries.size); + if (!repl) { + errno = ENOMEM; + return 0; + } + + /* These are the old counters we will get from kernel */ + repl->counters = malloc(sizeof(STRUCT_COUNTERS) + * (*handle)->info.num_entries); + if (!repl->counters) { + free(repl); + errno = ENOMEM; + return 0; + } + + /* These are the counters we're going to put back, later. */ + newcounters = malloc(counterlen); + if (!newcounters) { + free(repl->counters); + free(repl); + errno = ENOMEM; + return 0; + } + + strcpy(repl->name, (*handle)->info.name); + repl->num_entries = (*handle)->new_number; + repl->size = (*handle)->entries.size; + memcpy(repl->hook_entry, (*handle)->info.hook_entry, + sizeof(repl->hook_entry)); + memcpy(repl->underflow, (*handle)->info.underflow, + sizeof(repl->underflow)); + repl->num_counters = (*handle)->info.num_entries; + repl->valid_hooks = (*handle)->info.valid_hooks; + memcpy(repl->entries, (*handle)->entries.entrytable, + (*handle)->entries.size); + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, + sizeof(*repl) + (*handle)->entries.size) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + /* Put counters back. */ + strcpy(newcounters->name, (*handle)->info.name); + newcounters->num_counters = (*handle)->new_number; + for (i = 0; i < (*handle)->new_number; i++) { + unsigned int mappos = (*handle)->counter_map[i].mappos; + switch ((*handle)->counter_map[i].maptype) { + case COUNTER_MAP_NOMAP: + newcounters->counters[i] + = ((STRUCT_COUNTERS){ 0, 0 }); + break; + + case COUNTER_MAP_NORMAL_MAP: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: X + Y + Z. + * => Add in X + Y + * => Add in replacement read. + */ + newcounters->counters[i] = repl->counters[mappos]; + break; + + case COUNTER_MAP_ZEROED: + /* Original read: X. + * Atomic read on replacement: X + Y. + * Currently in kernel: Z. + * Want in kernel: Y + Z. + * => Add in Y. + * => Add in (replacement read - original read). + */ + subtract_counters(&newcounters->counters[i], + &repl->counters[mappos], + &index2entry(*handle, i)->counters); + break; + + case COUNTER_MAP_SET: + /* Want to set counter (iptables-restore) */ + + memcpy(&newcounters->counters[i], + &index2entry(*handle, i)->counters, + sizeof(STRUCT_COUNTERS)); + + break; + } + } + +#ifdef KERNEL_64_USERSPACE_32 + { + /* Kernel will think that pointer should be 64-bits, and get + padding. So we accomodate here (assumption: alignment of + `counters' is on 64-bit boundary). */ + u_int64_t *kernptr = (u_int64_t *)&newcounters->counters; + if ((unsigned long)&newcounters->counters % 8 != 0) { + fprintf(stderr, + "counters alignment incorrect! Mail rusty!\n"); + abort(); + } + *kernptr = newcounters->counters; + } +#endif /* KERNEL_64_USERSPACE_32 */ + + if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + TC_FREE(handle); + return 1; +} + +/* Get raw socket. */ +int +TC_GET_RAW_SOCKET() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +TC_STRERROR(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { TC_INIT, EPERM, "Permission denied (you must be root)" }, + { TC_INIT, EINVAL, "Module is wrong version" }, + { TC_INIT, ENOENT, + "Table does not exist (do you need to insmod?)" }, + { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, + { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, + { TC_DELETE_CHAIN, EMLINK, + "Can't delete chain with references left" }, + { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, + { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, + { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, + { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, + { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, + { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, + { TC_INSERT_ENTRY, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { TC_CHECK_PACKET, EINVAL, + "Bad arguments (does that interface exist?)" }, + { TC_CHECK_PACKET, ENOSYS, + "Checking will most likely never get implemented" }, + /* ENOENT for DELETE probably means no matching rule */ + { TC_DELETE_ENTRY, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { TC_SET_POLICY, ENOENT, + "Bad built-in chain name" }, + { TC_SET_POLICY, EINVAL, + "Bad policy name" }, + + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == iptc_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/libiptc2/linux_list.h b/libiptc2/linux_list.h new file mode 100644 index 0000000..41de482 --- /dev/null +++ b/libiptc2/linux_list.h @@ -0,0 +1,161 @@ +#ifndef _LINUXLIST_H +#define _LINUXLIST_H + + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#endif diff --git a/libiptc2/linux_listhelp.h b/libiptc2/linux_listhelp.h new file mode 100644 index 0000000..466d921 --- /dev/null +++ b/libiptc2/linux_listhelp.h @@ -0,0 +1,99 @@ +#ifndef _LINUX_LISTHELP_H +#define _LINUX_LISTHELP_H +#include +#include +#include "linux_list.h" + +/* Header to do more comprehensive job than linux/list.h; assume list + is first entry in structure. */ + +/* Return pointer to first true entry, if any, or NULL. A macro + required to allow inlining of cmpfn. */ +#define LIST_FIND(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i = (head); \ + \ + do { \ + __i = __i->next; \ + if (__i == (head)) { \ + __i = NULL; \ + break; \ + } \ + } while (!cmpfn((const type)__i , ## args)); \ + (type)__i; \ +}) + +#define LIST_FIND_W(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i = (head); \ + \ + do { \ + __i = __i->next; \ + if (__i == (head)) { \ + __i = NULL; \ + break; \ + } \ + } while (!cmpfn((type)__i , ## args)); \ + (type)__i; \ +}) + +static inline int +__list_cmp_same(const void *p1, const void *p2) { return p1 == p2; } + +/* Is this entry in the list? */ +static inline int +list_inlist(struct list_head *head, const void *entry) +{ + return LIST_FIND(head, __list_cmp_same, void *, entry) != NULL; +} + +/* Delete from list. */ +#define LIST_DELETE(head, oldentry) list_del((struct list_head *)oldentry) + +/* Append. */ +static inline void +list_append(struct list_head *head, void *new) +{ + list_add((new), (head)->prev); +} + +/* Prepend. */ +static inline void +list_prepend(struct list_head *head, void *new) +{ + list_add(new, head); +} + +/* Insert according to ordering function; insert before first true. */ +#define LIST_INSERT(head, new, cmpfn) \ +do { \ + struct list_head *__i; \ + for (__i = (head)->next; \ + !cmpfn((new), (typeof (new))__i) && __i != (head); \ + __i = __i->next); \ + list_add((struct list_head *)(new), __i->prev); \ +} while(0) + +/* If the field after the list_head is a nul-terminated string, you + can use these functions. */ +static inline int __list_cmp_name(const void *i, const char *name) +{ + return strcmp(name, i+sizeof(struct list_head)) == 0; +} + +/* Returns false if same name already in list, otherwise does insert. */ +static inline int +list_named_insert(struct list_head *head, void *new) +{ + if (LIST_FIND(head, __list_cmp_name, void *, + new + sizeof(struct list_head))) + return 0; + list_prepend(head, new); + return 1; +} + +/* Find this named element in the list. */ +#define list_named_find(head, name) \ +LIST_FIND(head, __list_cmp_name, void *, name) + +#endif /*_LISTHELP_H*/ -- 2.43.0