From 5c306289b8a78a060a12eba5cd86e0efc3533da0 Mon Sep 17 00:00:00 2001 From: marc Date: Mon, 20 Mar 2000 06:03:29 +0000 Subject: reorganized tree after kernel merge --- COPYING | 339 ++++++ Makefile | 64 ++ Rules.make | 56 + extensions/Makefile | 14 + extensions/libipt_DNAT.c | 244 +++++ extensions/libipt_LOG.c | 260 +++++ extensions/libipt_MARK.c | 120 +++ extensions/libipt_MASQUERADE.c | 166 +++ extensions/libipt_REDIRECT.c | 166 +++ extensions/libipt_REJECT.c | 159 +++ extensions/libipt_SNAT.c | 244 +++++ extensions/libipt_TOS.c | 173 +++ extensions/libipt_icmp.c | 295 +++++ extensions/libipt_limit.c | 196 ++++ extensions/libipt_mac.c | 144 +++ extensions/libipt_mark.c | 128 +++ extensions/libipt_multiport.c | 262 +++++ extensions/libipt_owner.c | 219 ++++ extensions/libipt_standard.c | 67 ++ extensions/libipt_state.c | 162 +++ extensions/libipt_tcp.c | 439 ++++++++ extensions/libipt_tos.c | 171 +++ extensions/libipt_udp.c | 252 +++++ extensions/libipt_unclean.c | 66 ++ include/iptables.h | 122 +++ include/libipq/libipq.h | 77 ++ include/libiptc/ipt_kernel_headers.h | 22 + include/libiptc/libiptc.h | 131 +++ iptables-restore.c | 154 +++ iptables-save.c | 260 +++++ iptables-standalone.c | 50 + iptables.8 | 708 ++++++++++++ iptables.c | 1963 ++++++++++++++++++++++++++++++++++ libipq/IPQ.notes.txt | 118 ++ libipq/Makefile | 11 + libipq/libipq.c | 310 ++++++ libiptc/Makefile | 16 + libiptc/libiptc.c | 1828 +++++++++++++++++++++++++++++++ 38 files changed, 10176 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 Rules.make create mode 100644 extensions/Makefile create mode 100644 extensions/libipt_DNAT.c create mode 100644 extensions/libipt_LOG.c create mode 100644 extensions/libipt_MARK.c create mode 100644 extensions/libipt_MASQUERADE.c create mode 100644 extensions/libipt_REDIRECT.c create mode 100644 extensions/libipt_REJECT.c create mode 100644 extensions/libipt_SNAT.c create mode 100644 extensions/libipt_TOS.c create mode 100644 extensions/libipt_icmp.c create mode 100644 extensions/libipt_limit.c create mode 100644 extensions/libipt_mac.c create mode 100644 extensions/libipt_mark.c create mode 100644 extensions/libipt_multiport.c create mode 100644 extensions/libipt_owner.c create mode 100644 extensions/libipt_standard.c create mode 100644 extensions/libipt_state.c create mode 100644 extensions/libipt_tcp.c create mode 100644 extensions/libipt_tos.c create mode 100644 extensions/libipt_udp.c create mode 100644 extensions/libipt_unclean.c create mode 100644 include/iptables.h create mode 100644 include/libipq/libipq.h create mode 100644 include/libiptc/ipt_kernel_headers.h create mode 100644 include/libiptc/libiptc.h create mode 100644 iptables-restore.c create mode 100644 iptables-save.c create mode 100644 iptables-standalone.c create mode 100644 iptables.8 create mode 100644 iptables.c create mode 100644 libipq/IPQ.notes.txt create mode 100644 libipq/Makefile create mode 100644 libipq/libipq.c create mode 100644 libiptc/Makefile create mode 100644 libiptc/libiptc.c diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6090e4f --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +# Standard part of Makefile for topdir. +TOPLEVEL_INCLUDED=YES + +ifndef KERNEL_DIR +KERNEL_DIR=/usr/src/linux +endif +NETFILTER_VERSION:=1.0.0alpha + +LIBDIR:=/usr/local/lib +BINDIR:=/usr/local/bin +MANDIR:=/usr/local/man + +COPT_FLAGS:=-O +CFLAGS:=$(COPT_FLAGS) -Wall -Wunused -Iinclude/ -I$(KERNEL_DIR)/include -DNETFILTER_VERSION=\"$(NETFILTER_VERSION)\" -g + +DEPFILES := $(SHARED_LIBS:%.so=%.d) +SH_CFLAGS:=$(CFLAGS) -fPIC +DEPFILES := $(SHARED_LIBS:%.so=%.d) + +EXTRAS+=iptables iptables.o #iptables-save iptables-restore +EXTRA_INSTALLS+=$(DESTDIR)$(BINDIR)/iptables $(DESTDIR)$(MANDIR)/man8/iptables.8 #$(DESTDIR)$(BINDIR)/iptables-save $(DESTDIR)$(BINDIR)/iptables-restore + +ifndef IPT_LIBDIR +IPT_LIBDIR:=$(LIBDIR)/iptables +endif + +default: all + +iptables.o: iptables.c + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" -c -o $@ $< + +iptables: iptables-standalone.c iptables.o libiptc/libiptc.a + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" -rdynamic -o $@ $^ -ldl + +$(DESTDIR)$(BINDIR)/iptables: iptables + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +iptables-save: iptables-save.c iptables.o libiptc/libiptc.a + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" -rdynamic -o $@ $^ -ldl + +$(DESTDIR)$(BINDIR)/iptables-save: iptables-save + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +iptables-restore: iptables-restore.c iptables.o libiptc/libiptc.a + $(CC) $(CFLAGS) -DIPT_LIB_DIR=\"$(IPT_LIBDIR)\" -rdynamic -o $@ $^ -ldl + +$(DESTDIR)$(BINDIR)/iptables-restore: iptables-restore + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +$(DESTDIR)$(MANDIR)/man8/iptables.8: iptables.8 + @[ -d $(DESTDIR)$(MANDIR)/man8 ] || mkdir -p $(DESTDIR)$(MANDIR)/man8 + cp $< $@ + +EXTRA_DEPENDS+=iptables-standalone.d iptables.d + +iptables-standalone.d iptables.d: %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | sed -e 's@^.*\.o:@$*.d $*.o:@' > $@ + +# $(wildcard) fails wierdly with make v.3.78.1. +include $(shell echo */Makefile) +include Rules.make diff --git a/Rules.make b/Rules.make new file mode 100644 index 0000000..b90866d --- /dev/null +++ b/Rules.make @@ -0,0 +1,56 @@ +#! /usr/bin/make + +all: $(SHARED_LIBS) $(EXTRAS) + +clean: $(EXTRA_CLEANS) + rm -f $(SHARED_LIBS) $(EXTRAS) $(SHARED_LIBS:%.so=%_sh.o) + +install: all $(EXTRA_INSTALLS) + +TAGS: + @rm -f $@ + find . -name '*.[ch]' | xargs etags -a + +dep: $(DEPFILES) $(EXTRA_DEPENDS) + @echo Dependencies will be generated on next make. + @rm -f $(DEPFILES) $(EXTRA_DEPENDS) .makefirst + +$(SHARED_LIBS:%.so=%.d): %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | \ + sed -e 's@^.*\.o:@$*.d $*.o:@' > $@ + +$(SHARED_LIBS): %.so : %_sh.o + $(LD) -shared -o $@ $< + +%_sh.o : %.c + $(CC) $(SH_CFLAGS) -o $@ -c $< + +distrib: nowhitespace distclean delrelease /home/public/netfilter/netfilter-$(NETFILTER_VERSION).tar.bz2 #diff md5sums + +delrelease: + rm -f /home/public/netfilter/netfilter-$(NETFILTER_VERSION).tar.bz2 + +distclean: clean + @rm -f TAGS `find . -name '*~' -o -name '*.[do]' -o -name '*.rej'` .makefirst + +nowhitespace: + @if grep -n '[ ]$$' `find . -name 'Makefile' -o -name '*.[ch]'`; then exit 1; else exit 0; fi + +/home/public/netfilter/netfilter-$(NETFILTER_VERSION).tar.bz2: + cd .. && ln -sfn netfilter netfilter-$(NETFILTER_VERSION) && tar cvf - --exclude install-kernel --exclude transfer --exclude netfilter-$(NETFILTER_VERSION)/bugs --exclude CVS --exclude .depend --exclude netfilter-$(NETFILTER_VERSION)/./NAT/userspace/.depend --exclude netfilter-$(NETFILTER_VERSION)/linux-netfilter netfilter-$(NETFILTER_VERSION)/. | bzip2 -9 > $@ && rm netfilter-$(NETFILTER_VERSION) + +diff: /home/public/netfilter/netfilter-$(NETFILTER_VERSION).tar.bz2 + @mkdir /tmp/diffdir + @cd /tmp/diffdir && tar xfI /home/public/netfilter/netfilter-$(NETFILTER_VERSION).tar.bz2 + @set -e; cd /tmp/diffdir; tar xfI /home/public/netfilter/netfilter-$(OLD_NETFILTER_VERSION).tar.bz2; echo Creating patch-netfilter-$(OLD_NETFILTER_VERSION)-$(NETFILTER_VERSION).bz2; diff -urN netfilter-$(OLD_NETFILTER_VERSION) netfilter-$(NETFILTER_VERSION) | bzip2 -9 > /home/public/netfilter/patch-netfilter-$(OLD_NETFILTER_VERSION)-$(NETFILTER_VERSION).bz2 + @rm -rf /tmp/diffdir + +md5sums: + cd /home/public/netfilter/ && md5sum patch-netfilter-*-$(NETFILTER_VERSION).bz2 netfilter-$(NETFILTER_VERSION).tar.bz2 + +.makefirst: + @echo Making dependencies: please wait... + @touch .makefirst + +-include $(DEPFILES) $(EXTRA_DEPENDS) +-include .makefirst diff --git a/extensions/Makefile b/extensions/Makefile new file mode 100644 index 0000000..8e0dec1 --- /dev/null +++ b/extensions/Makefile @@ -0,0 +1,14 @@ +#! /usr/bin/make + +PF_EXT_SLIB:=tcp udp icmp mac limit standard REJECT LOG unclean state multiport tos TOS mark MARK owner SNAT DNAT MASQUERADE REDIRECT +SHARED_LIBS+=$(foreach T,$(PF_EXT_SLIB),extensions/libipt_$(T).so) +EXTRA_INSTALLS+=$(foreach T, $(PF_EXT_SLIB), $(DESTDIR)$(LIBDIR)/iptables/libipt_$(T).so) + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(SHARED_LIBS) +endif + +$(DESTDIR)$(LIBDIR)/iptables/libipt_%.so: extensions/libipt_%.so + @[ -d $(DESTDIR)$(LIBDIR)/iptables ] || mkdir -p $(DESTDIR)$(LIBDIR)/iptables + cp $< $@ diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c new file mode 100644 index 0000000..e3c3722 --- /dev/null +++ b/extensions/libipt_DNAT.c @@ -0,0 +1,244 @@ +/* Shared library add-on to iptables to add destination-NAT support. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Dest NAT data consists of a multi-range, indicating where to map + to. */ +struct ipt_natinfo +{ + struct ipt_entry_target t; + struct ip_nat_multi_range mr; +}; + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"DNAT v%s options:\n" +" --to-destination [-][:port-port]\n" +" Address to map destination to.\n" +" (You can use this more than once)\n\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "to-destination", 1, 0, '1' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +static struct ipt_natinfo * +append_range(struct ipt_natinfo *info, const struct ip_nat_range *range) +{ + unsigned int size; + + /* One rangesize already in struct ipt_natinfo */ + size = IPT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range)); + + info = realloc(info, size); + if (!info) + exit_error(OTHER_PROBLEM, "Out of memory\n"); + + info->t.target_size = size; + info->mr.range[info->mr.rangesize] = *range; + info->mr.rangesize++; + + return info; +} + +/* Ranges expected in network order. */ +static struct ipt_entry_target * +parse_to(char *arg, int portok, struct ipt_natinfo *info) +{ + struct ip_nat_range range; + char *colon, *dash; + struct in_addr *ip; + + memset(&range, 0, sizeof(range)); + colon = strchr(arg, ':'); + + if (colon) { + int port; + + if (!portok) + exit_error(PARAMETER_PROBLEM, + "Need TCP or UDP with port specification"); + + range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED; + + port = atoi(colon+1); + if (port == 0 || port > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", colon+1); + + dash = strchr(colon, '-'); + if (!dash) { + range.min.tcp.port + = range.max.tcp.port + = htons(port); + } else { + int maxport; + + maxport = atoi(dash + 1); + if (maxport == 0 || maxport > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", dash+1); + if (maxport < port) + /* People are stupid. */ + exit_error(PARAMETER_PROBLEM, + "Port range `%s' funky\n", colon+1); + range.min.tcp.port = htons(port); + range.max.tcp.port = htons(maxport); + } + /* Starts with a colon? No IP info...*/ + if (colon == arg) + return &(append_range(info, &range)->t); + *colon = '\0'; + } + + range.flags |= IP_NAT_RANGE_MAP_IPS; + dash = strchr(arg, '-'); + if (colon && dash && dash > colon) + dash = NULL; + + if (dash) + *dash = '\0'; + + ip = dotted_to_addr(arg); + if (!ip) + exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n", + arg); + range.min_ip = ip->s_addr; + if (dash) { + ip = dotted_to_addr(dash+1); + if (!ip) + exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n", + dash+1); + range.max_ip = ip->s_addr; + } else + range.max_ip = range.min_ip; + + return &(append_range(info, &range)->t); +} + +/* 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_natinfo *info = (void *)*target; + int portok; + + if (entry->ip.proto == IPPROTO_TCP + || entry->ip.proto == IPPROTO_UDP) + portok = 1; + else + portok = 0; + + switch (c) { + case '1': + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --to-destination"); + + *target = parse_to(optarg, portok, info); + *flags = 1; + return 1; + + default: + return 0; + } +} + +/* Final check; must have specfied --to-source. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "You must specify --to-destination"); +} + +static void print_range(const struct ip_nat_range *r) +{ + if (r->flags & IP_NAT_RANGE_MAP_IPS) { + struct in_addr a; + + a.s_addr = r->min_ip; + printf("%s", addr_to_dotted(&a)); + if (r->max_ip != r->min_ip) { + a.s_addr = r->max_ip; + printf("-%s", addr_to_dotted(&a)); + } + } + if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { + printf(":"); + printf("%hu", ntohs(r->min.tcp.port)); + if (r->max.tcp.port != r->min.tcp.port) + printf("-%hu", ntohs(r->max.tcp.port)); + } +} + +/* Prints out the targinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + struct ipt_natinfo *info = (void *)target; + unsigned int i = 0; + + printf("to:"); + for (i = 0; i < info->mr.rangesize; i++) { + print_range(&info->mr.range[i]); + printf(" "); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + struct ipt_natinfo *info = (void *)target; + unsigned int i = 0; + + for (i = 0; i < info->mr.rangesize; i++) { + printf("--to-destination "); + print_range(&info->mr.range[i]); + printf(" "); + } +} + +struct iptables_target dnat += { NULL, + "DNAT", + NETFILTER_VERSION, + sizeof(struct ip_nat_multi_range), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&dnat); +} diff --git a/extensions/libipt_LOG.c b/extensions/libipt_LOG.c new file mode 100644 index 0000000..cab5739 --- /dev/null +++ b/extensions/libipt_LOG.c @@ -0,0 +1,260 @@ +/* Shared library add-on to iptables to add LOG support. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_DEFAULT_LEVEL LOG_WARNING + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"LOG v%s options:\n" +" --log-level level Level of logging (numeric or see syslog.conf)\n" +" --log-prefix prefix Prefix log messages with this prefix.\n\n" +" --log-tcp-sequence Log TCP sequence numbers.\n\n" +" --log-tcp-options Log TCP options.\n\n" +" --log-ip-options Log IP options.\n\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "log-level", 1, 0, '!' }, + { "log-prefix", 1, 0, '#' }, + { "log-tcp-sequence", 0, 0, '1' }, + { "log-tcp-options", 0, 0, '2' }, + { "log-ip-options", 0, 0, '3' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ + struct ipt_log_info *loginfo = (struct ipt_log_info *)t->data; + + loginfo->level = LOG_DEFAULT_LEVEL; + + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +struct ipt_log_names { + const char *name; + unsigned int level; +}; + +static struct ipt_log_names ipt_log_names[] += { { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "debug", LOG_DEBUG }, + { "emerg", LOG_EMERG }, + { "error", LOG_ERR }, /* DEPRECATED */ + { "info", LOG_INFO }, + { "notice", LOG_NOTICE }, + { "panic", LOG_EMERG }, /* DEPRECATED */ + { "warning", LOG_WARNING } +}; + +static u_int8_t +parse_level(const char *level) +{ + int lev; + + lev = string_to_number(level, 0, 7); + if (lev == -1) { + unsigned int i = 0; + + for (i = 0; + i < sizeof(ipt_log_names) / sizeof(struct ipt_log_names); + i++) { + if (strncasecmp(level, ipt_log_names[i].name, + strlen(level)) == 0) { + if (lev != -1) + exit_error(PARAMETER_PROBLEM, + "log-level `%s' ambiguous", + level); + lev = ipt_log_names[i].level; + } + } + + if (lev == -1) + exit_error(PARAMETER_PROBLEM, + "log-level `%s' unknown", level); + } + + return (u_int8_t)lev; +} + +#define IPT_LOG_OPT_LEVEL 0x01 +#define IPT_LOG_OPT_PREFIX 0x02 +#define IPT_LOG_OPT_TCPSEQ 0x04 +#define IPT_LOG_OPT_TCPOPT 0x08 +#define IPT_LOG_OPT_IPOPT 0x10 + +/* 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_log_info *loginfo = (struct ipt_log_info *)(*target)->data; + + switch (c) { + case '!': + if (*flags & IPT_LOG_OPT_LEVEL) + exit_error(PARAMETER_PROBLEM, + "Can't specify --log-level twice"); + + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --log-level"); + + loginfo->level = parse_level(optarg); + *flags |= IPT_LOG_OPT_LEVEL; + break; + + case '#': + if (*flags & IPT_LOG_OPT_PREFIX) + exit_error(PARAMETER_PROBLEM, + "Can't specify --log-prefix twice"); + + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --log-prefix"); + + if (strlen(optarg) > sizeof(loginfo->prefix) - 1) + exit_error(PARAMETER_PROBLEM, + "Maximum prefix length %u for --log-prefix", + sizeof(loginfo->prefix) - 1); + + strcpy(loginfo->prefix, optarg); + *flags |= IPT_LOG_OPT_PREFIX; + break; + + case '1': + if (*flags & IPT_LOG_OPT_TCPSEQ) + exit_error(PARAMETER_PROBLEM, + "Can't specify --log-tcp-sequence " + "twice"); + + loginfo->logflags |= IPT_LOG_TCPSEQ; + *flags |= IPT_LOG_OPT_TCPSEQ; + break; + + case '2': + if (*flags & IPT_LOG_OPT_TCPOPT) + exit_error(PARAMETER_PROBLEM, + "Can't specify --log-tcp-options twice"); + + loginfo->logflags |= IPT_LOG_TCPOPT; + *flags |= IPT_LOG_OPT_TCPOPT; + break; + + case '3': + if (*flags & IPT_LOG_OPT_IPOPT) + exit_error(PARAMETER_PROBLEM, + "Can't specify --log-ip-options twice"); + + loginfo->logflags |= IPT_LOG_IPOPT; + *flags |= IPT_LOG_OPT_IPOPT; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; nothing. */ +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) +{ + const struct ipt_log_info *loginfo + = (const struct ipt_log_info *)target->data; + unsigned int i = 0; + + printf("LOG "); + if (numeric) + printf("flags %u level %u ", + loginfo->logflags, loginfo->level); + else { + for (i = 0; + i < sizeof(ipt_log_names) / sizeof(struct ipt_log_names); + i++) { + if (loginfo->level == ipt_log_names[i].level) { + printf("level %s ", ipt_log_names[i].name); + break; + } + } + if (i == sizeof(ipt_log_names) / sizeof(struct ipt_log_names)) + printf("UNKNOWN level %u ", loginfo->level); + if (loginfo->logflags & IPT_LOG_TCPSEQ) + printf("tcp-sequence "); + if (loginfo->logflags & IPT_LOG_TCPOPT) + printf("tcp-options "); + if (loginfo->logflags & IPT_LOG_IPOPT) + printf("ip-options "); + if (loginfo->logflags & ~(IPT_LOG_MASK)) + printf("unknown-flags "); + } + + if (strcmp(loginfo->prefix, "") != 0) + printf("prefix `%s' ", loginfo->prefix); +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + const struct ipt_log_info *loginfo + = (const struct ipt_log_info *)target->data; + + if (strcmp(loginfo->prefix, "") != 0) + printf("--log-prefix %s ", loginfo->prefix); + + if (loginfo->level != LOG_DEFAULT_LEVEL) + printf("--log-level %u ", loginfo->level); + + if (loginfo->logflags & IPT_LOG_TCPSEQ) + printf("--log-tcp-sequence "); + if (loginfo->logflags & IPT_LOG_TCPOPT) + printf("--log-tcp-options "); + if (loginfo->logflags & IPT_LOG_IPOPT) + printf("--log-ip-options "); +} + +struct iptables_target log += { NULL, + "LOG", + NETFILTER_VERSION, + sizeof(struct ipt_log_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&log); +} diff --git a/extensions/libipt_MARK.c b/extensions/libipt_MARK.c new file mode 100644 index 0000000..ef5a60d --- /dev/null +++ b/extensions/libipt_MARK.c @@ -0,0 +1,120 @@ +/* Shared library add-on to iptables to add MARK target support. */ +#include +#include +#include +#include + +#include +#include +#include + +struct markinfo { + struct ipt_entry_target t; + struct ipt_mark_target_info mark; +}; + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"MARK target v%s options:\n" +" --set-mark value Set nfmark value\n" +"\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "set-mark", 1, 0, '1' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ +} + +/* 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_mark_target_info *markinfo + = (struct ipt_mark_target_info *)(*target)->data; + + switch (c) { + char *end; + case '1': + markinfo->mark = strtoul(optarg, &end, 0); + if (*end != '\0' || end == optarg) + exit_error(PARAMETER_PROBLEM, "Bad MARK value `%s'", optarg); + if (*flags) + exit_error(PARAMETER_PROBLEM, + "MARK target: Can't specify --set-mark twice"); + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "MARK target: Parameter --set-mark is required"); +} + +static void +print_mark(unsigned long mark, int numeric) +{ + printf("0x%lx ", mark); +} + +/* Prints out the targinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + const struct ipt_mark_target_info *markinfo = + (const struct ipt_mark_target_info *)target->data; + printf("MARK set "); + print_mark(markinfo->mark, 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) +{ + const struct ipt_mark_target_info *markinfo = + (const struct ipt_mark_target_info *)target->data; + + printf("--set-mark 0x%lx ", markinfo->mark); +} + +struct iptables_target mark += { NULL, + "MARK", + NETFILTER_VERSION, + sizeof(struct ipt_mark_target_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&mark); +} diff --git a/extensions/libipt_MASQUERADE.c b/extensions/libipt_MASQUERADE.c new file mode 100644 index 0000000..a1151bb --- /dev/null +++ b/extensions/libipt_MASQUERADE.c @@ -0,0 +1,166 @@ +/* Shared library add-on to iptables to add masquerade support. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"MASQUERADE v%s options:\n" +" --to-ports [-]\n" +" Port (range) to map to.\n\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "to-ports", 1, 0, '1' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ + struct ip_nat_multi_range *mr = (struct ip_nat_multi_range *)t->data; + + /* Actually, it's 0, but it's ignored at the moment. */ + mr->rangesize = 1; + + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +/* Parses ports */ +static void +parse_ports(const char *arg, struct ip_nat_multi_range *mr) +{ + const char *dash; + int port; + + mr->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED; + + port = atoi(arg); + if (port == 0 || port > 65535) + exit_error(PARAMETER_PROBLEM, "Port `%s' not valid\n", arg); + + dash = strchr(arg, '-'); + if (!dash) { + mr->range[0].min.tcp.port + = mr->range[0].max.tcp.port + = port; + } else { + int maxport; + + maxport = atoi(dash + 1); + if (maxport == 0 || maxport > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", dash+1); + if (maxport < port) + /* People are stupid. Present reader excepted. */ + exit_error(PARAMETER_PROBLEM, + "Port range `%s' funky\n", arg); + mr->range[0].min.tcp.port = port; + mr->range[0].max.tcp.port = maxport; + } +} + +/* 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) +{ + int portok; + struct ip_nat_multi_range *mr + = (struct ip_nat_multi_range *)(*target)->data; + + if (entry->ip.proto == IPPROTO_TCP + || entry->ip.proto == IPPROTO_UDP) + portok = 1; + else + portok = 0; + + switch (c) { + case '1': + if (!portok) + exit_error(PARAMETER_PROBLEM, + "Need TCP or UDP with port specification"); + + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --to-ports"); + + parse_ports(optarg, mr); + return 1; + + default: + return 0; + } +} + +/* 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) +{ + struct ip_nat_multi_range *mr + = (struct ip_nat_multi_range *)target->data; + struct ip_nat_range *r = &mr->range[0]; + + printf("MASQUERADE "); + if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { + printf("%hu", r->min.tcp.port); + if (r->max.tcp.port != r->min.tcp.port) + printf("-%hu", r->max.tcp.port); + printf(" "); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + struct ip_nat_multi_range *mr + = (struct ip_nat_multi_range *)target->data; + struct ip_nat_range *r = &mr->range[0]; + + if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { + printf("%hu", r->min.tcp.port); + if (r->max.tcp.port != r->min.tcp.port) + printf("-%hu", r->max.tcp.port); + printf(" "); + } +} + +struct iptables_target masq += { NULL, + "MASQUERADE", + NETFILTER_VERSION, + sizeof(struct ip_nat_multi_range), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&masq); +} diff --git a/extensions/libipt_REDIRECT.c b/extensions/libipt_REDIRECT.c new file mode 100644 index 0000000..aaafaaf --- /dev/null +++ b/extensions/libipt_REDIRECT.c @@ -0,0 +1,166 @@ +/* Shared library add-on to iptables to add redirect support. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"REDIRECT v%s options:\n" +" --to-ports [-]\n" +" Port (range) to map to.\n\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "to-ports", 1, 0, '1' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ + struct ip_nat_multi_range *mr = (struct ip_nat_multi_range *)t->data; + + /* Actually, it's 0, but it's ignored at the moment. */ + mr->rangesize = 1; + + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +/* Parses ports */ +static void +parse_ports(const char *arg, struct ip_nat_multi_range *mr) +{ + const char *dash; + int port; + + mr->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED; + + port = atoi(arg); + if (port == 0 || port > 65535) + exit_error(PARAMETER_PROBLEM, "Port `%s' not valid\n", arg); + + dash = strchr(arg, '-'); + if (!dash) { + mr->range[0].min.tcp.port + = mr->range[0].max.tcp.port + = port; + } else { + int maxport; + + maxport = atoi(dash + 1); + if (maxport == 0 || maxport > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", dash+1); + if (maxport < port) + /* People are stupid. */ + exit_error(PARAMETER_PROBLEM, + "Port range `%s' funky\n", arg); + mr->range[0].min.tcp.port = port; + mr->range[0].max.tcp.port = maxport; + } +} + +/* 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 ip_nat_multi_range *mr + = (struct ip_nat_multi_range *)(*target)->data; + int portok; + + if (entry->ip.proto == IPPROTO_TCP + || entry->ip.proto == IPPROTO_UDP) + portok = 1; + else + portok = 0; + + switch (c) { + case '1': + if (!portok) + exit_error(PARAMETER_PROBLEM, + "Need TCP or UDP with port specification"); + + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --to-ports"); + + parse_ports(optarg, mr); + return 1; + + default: + return 0; + } +} + +/* 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) +{ + struct ip_nat_multi_range *mr + = (struct ip_nat_multi_range *)target->data; + struct ip_nat_range *r = &mr->range[0]; + + printf("REDIRECT "); + if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { + printf("%hu", r->min.tcp.port); + if (r->max.tcp.port != r->min.tcp.port) + printf("-%hu", r->max.tcp.port); + printf(" "); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + struct ip_nat_multi_range *mr + = (struct ip_nat_multi_range *)target->data; + struct ip_nat_range *r = &mr->range[0]; + + if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { + printf("%hu", r->min.tcp.port); + if (r->max.tcp.port != r->min.tcp.port) + printf("-%hu", r->max.tcp.port); + printf(" "); + } +} + +struct iptables_target redir += { NULL, + "REDIRECT", + NETFILTER_VERSION, + sizeof(struct ip_nat_multi_range), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&redir); +} diff --git a/extensions/libipt_REJECT.c b/extensions/libipt_REJECT.c new file mode 100644 index 0000000..e336587 --- /dev/null +++ b/extensions/libipt_REJECT.c @@ -0,0 +1,159 @@ +/* Shared library add-on to iptables to add customized REJECT support. + * + * (C) 2000 Jozsef Kadlecsik + */ +#include +#include +#include +#include +#include +#include +#include + +struct reject_names { + const char *name; + const char *alias; + enum ipt_reject_with with; + const char *desc; +}; + +static const struct reject_names reject_table[] = { + {"icmp-net-unreachable", "net-unreach", + IPT_ICMP_NET_UNREACHABLE, "ICMP network unreachable"}, + {"icmp-host-unreachable", "host-unreach", + IPT_ICMP_HOST_UNREACHABLE, "ICMP host unreachable"}, + {"icmp-port-unreachable", "port-unreach", + IPT_ICMP_PORT_UNREACHABLE, "ICMP port unreachable (default)"}, + {"icmp-proto-unreachable", "proto-unreach", + IPT_ICMP_PROT_UNREACHABLE, "ICMP protocol unreachable"}, + {"tcp-reset", "rst", + IPT_TCP_RESET, "for TCP only: faked TCP RST"}, + {"echo-reply", "echoreply", + IPT_ICMP_ECHOREPLY, "for ICMP echo only: faked ICMP echo reply"}, +}; + +static void +print_reject_types() +{ + unsigned int i; + + printf("Valid reject types:\n"); + + for (i = 0; i < sizeof(reject_table)/sizeof(struct reject_names); i++) { + printf(" %-25s\t%s\n", reject_table[i].name, reject_table[i].desc); + printf(" %-25s\talias\n", reject_table[i].alias); + } + printf("\n"); +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"REJECT options:\n" +"--reject-with type drop input packet and send back\n" +" a reply packet according to type:\n"); + + print_reject_types(); +} + +static struct option opts[] = { + { "reject-with", 1, 0, '1' }, + { 0 } +}; + +/* Allocate and initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ + struct ipt_reject_info *reject = (struct ipt_reject_info *)t->data; + + /* default */ + reject->with = IPT_ICMP_PORT_UNREACHABLE; + + /* 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_reject_info *reject = (struct ipt_reject_info *)(*target)->data; + unsigned int limit = sizeof(reject_table)/sizeof(struct reject_names); + unsigned int i; + + switch(c) { + case '1': + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --reject-with"); + for (i = 0; i < limit; i++) { + if ((strncasecmp(reject_table[i].name, optarg, strlen(optarg)) == 0) + || (strncasecmp(reject_table[i].alias, optarg, strlen(optarg)) == 0)) { + reject->with = reject_table[i].with; + return 1; + } + } + exit_error(PARAMETER_PROBLEM, "unknown reject type `%s'",optarg); + default: + /* Fall through */ + } + return 0; +} + +/* Final check; nothing. */ +static void final_check(unsigned int flags) +{ +} + +/* Prints out ipt_reject_info. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + const struct ipt_reject_info *reject + = (const struct ipt_reject_info *)target->data; + unsigned int i; + + for (i = 0; i < sizeof(reject_table)/sizeof(struct reject_names); i++) { + if (reject_table[i].with == reject->with) + break; + } + printf("reject-with %s ", reject_table[i].name); +} + +/* Saves ipt_reject in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + const struct ipt_reject_info *reject + = (const struct ipt_reject_info *)target->data; + + printf("--reject-with %s ", reject_table[reject->with].name); +} + +struct iptables_target reject += { NULL, + "REJECT", + NETFILTER_VERSION, + sizeof(struct ipt_reject_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&reject); +} diff --git a/extensions/libipt_SNAT.c b/extensions/libipt_SNAT.c new file mode 100644 index 0000000..86ba39b --- /dev/null +++ b/extensions/libipt_SNAT.c @@ -0,0 +1,244 @@ +/* Shared library add-on to iptables to add source-NAT support. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Source NAT data consists of a multi-range, indicating where to map + to. */ +struct ipt_natinfo +{ + struct ipt_entry_target t; + struct ip_nat_multi_range mr; +}; + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"SNAT v%s options:\n" +" --to-source [-][:port-port]\n" +" Address to map source to.\n" +" (You can use this more than once)\n\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "to-source", 1, 0, '1' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +static struct ipt_natinfo * +append_range(struct ipt_natinfo *info, const struct ip_nat_range *range) +{ + unsigned int size; + + /* One rangesize already in struct ipt_natinfo */ + size = IPT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range)); + + info = realloc(info, size); + if (!info) + exit_error(OTHER_PROBLEM, "Out of memory\n"); + + info->t.target_size = size; + info->mr.range[info->mr.rangesize] = *range; + info->mr.rangesize++; + + return info; +} + +/* Ranges expected in network order. */ +static struct ipt_entry_target * +parse_to(char *arg, int portok, struct ipt_natinfo *info) +{ + struct ip_nat_range range; + char *colon, *dash; + struct in_addr *ip; + + memset(&range, 0, sizeof(range)); + colon = strchr(arg, ':'); + + if (colon) { + int port; + + if (!portok) + exit_error(PARAMETER_PROBLEM, + "Need TCP or UDP with port specification"); + + range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED; + + port = atoi(colon+1); + if (port == 0 || port > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", colon+1); + + dash = strchr(colon, '-'); + if (!dash) { + range.min.tcp.port + = range.max.tcp.port + = htons(port); + } else { + int maxport; + + maxport = atoi(dash + 1); + if (maxport == 0 || maxport > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", dash+1); + if (maxport < port) + /* People are stupid. */ + exit_error(PARAMETER_PROBLEM, + "Port range `%s' funky\n", colon+1); + range.min.tcp.port = htons(port); + range.max.tcp.port = htons(maxport); + } + /* Starts with a colon? No IP info...*/ + if (colon == arg) + return &(append_range(info, &range)->t); + *colon = '\0'; + } + + range.flags |= IP_NAT_RANGE_MAP_IPS; + dash = strchr(arg, '-'); + if (colon && dash && dash > colon) + dash = NULL; + + if (dash) + *dash = '\0'; + + ip = dotted_to_addr(arg); + if (!ip) + exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n", + arg); + range.min_ip = ip->s_addr; + if (dash) { + ip = dotted_to_addr(dash+1); + if (!ip) + exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n", + dash+1); + range.max_ip = ip->s_addr; + } else + range.max_ip = range.min_ip; + + return &(append_range(info, &range)->t); +} + +/* 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_natinfo *info = (void *)*target; + int portok; + + if (entry->ip.proto == IPPROTO_TCP + || entry->ip.proto == IPPROTO_UDP) + portok = 1; + else + portok = 0; + + switch (c) { + case '1': + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --to-source"); + + *target = parse_to(optarg, portok, info); + *flags = 1; + return 1; + + default: + return 0; + } +} + +/* Final check; must have specfied --to-source. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "You must specify --to-source"); +} + +static void print_range(const struct ip_nat_range *r) +{ + if (r->flags & IP_NAT_RANGE_MAP_IPS) { + struct in_addr a; + + a.s_addr = r->min_ip; + printf("%s", addr_to_dotted(&a)); + if (r->max_ip != r->min_ip) { + a.s_addr = r->max_ip; + printf("-%s", addr_to_dotted(&a)); + } + } + if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) { + printf(":"); + printf("%hu", ntohs(r->min.tcp.port)); + if (r->max.tcp.port != r->min.tcp.port) + printf("-%hu", ntohs(r->max.tcp.port)); + } +} + +/* Prints out the targinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + struct ipt_natinfo *info = (void *)target; + unsigned int i = 0; + + printf("to:"); + for (i = 0; i < info->mr.rangesize; i++) { + print_range(&info->mr.range[i]); + printf(" "); + } +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + struct ipt_natinfo *info = (void *)target; + unsigned int i = 0; + + for (i = 0; i < info->mr.rangesize; i++) { + printf("--to-source "); + print_range(&info->mr.range[i]); + printf(" "); + } +} + +struct iptables_target snat += { NULL, + "SNAT", + NETFILTER_VERSION, + sizeof(struct ip_nat_multi_range), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&snat); +} diff --git a/extensions/libipt_TOS.c b/extensions/libipt_TOS.c new file mode 100644 index 0000000..4a8e91b --- /dev/null +++ b/extensions/libipt_TOS.c @@ -0,0 +1,173 @@ +/* Shared library add-on to iptables to add TOS target support. */ +#include +#include +#include +#include + +#include +#include +#include + +struct tosinfo { + struct ipt_entry_target t; + struct ipt_tos_target_info tos; +}; + +/* TOS names and values. */ +struct TOS_value +{ + unsigned char TOS; + const char *name; +} TOS_values[] = { + { IPTOS_LOWDELAY, "Minimize-Delay" }, + { IPTOS_THROUGHPUT, "Maximize-Throughput" }, + { IPTOS_RELIABILITY, "Maximize-Reliability" }, + { IPTOS_MINCOST, "Minimize-Cost" }, + { IPTOS_NORMALSVC, "Normal-Service" }, +}; + +/* Function which prints out usage message. */ +static void +help(void) +{ + unsigned int i; + + printf( +"TOS target v%s options:\n" +" --set-tos value Set Type of Service field to one of the\n" +" following numeric or descriptive values:\n", +NETFILTER_VERSION); + + for (i = 0; i < sizeof(TOS_values)/sizeof(struct TOS_value);i++) + printf(" %s %u (0x%02x)\n", + TOS_values[i].name, + TOS_values[i].TOS, + TOS_values[i].TOS); + fputc('\n', stdout); +} + +static struct option opts[] = { + { "set-tos", 1, 0, '1' }, + { 0 } +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ +} + +static void +parse_tos(const unsigned char *s, struct ipt_tos_target_info *info) +{ + unsigned int i; + int tos = string_to_number(s, 0, 255); + + if (tos != -1) { + if (tos == IPTOS_LOWDELAY + || tos == IPTOS_THROUGHPUT + || tos == IPTOS_RELIABILITY + || tos == IPTOS_MINCOST + || tos == IPTOS_NORMALSVC) { + info->tos = (u_int8_t )tos; + return; + } + } else { + for (i = 0; itos = TOS_values[i].TOS; + return; + } + } + exit_error(PARAMETER_PROBLEM, "Bad TOS value `%s'", s); +} + +/* 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_tos_target_info *tosinfo + = (struct ipt_tos_target_info *)(*target)->data; + + switch (c) { + case '1': + if (*flags) + exit_error(PARAMETER_PROBLEM, + "TOS target: Cant specify --set-tos twice"); + parse_tos(optarg, tosinfo); + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "TOS target: Parameter --set-tos is required"); +} + +static void +print_tos(u_int8_t tos, int numeric) +{ + unsigned int i; + + if (!numeric) { + for (i = 0; idata; + printf("TOS set "); + print_tos(tosinfo->tos, 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) +{ + const struct ipt_tos_target_info *tosinfo = + (const struct ipt_tos_target_info *)target->data; + + printf("--set-tos 0x%02x ", tosinfo->tos); +} + +struct iptables_target tos += { NULL, + "TOS", + NETFILTER_VERSION, + sizeof(struct ipt_tos_target_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_target(&tos); +} diff --git a/extensions/libipt_icmp.c b/extensions/libipt_icmp.c new file mode 100644 index 0000000..65857ce --- /dev/null +++ b/extensions/libipt_icmp.c @@ -0,0 +1,295 @@ +/* Shared library add-on to iptables to add ICMP support. */ +#include +#include +#include +#include +#include +#include +#include + +struct icmp_names { + const char *name; + u_int8_t type; + u_int8_t code_min, code_max; +}; + +static const struct icmp_names icmp_codes[] = { + { "echo-reply", 0, 0, 0xFF }, + /* Alias */ { "pong", 0, 0, 0xFF }, + + { "destination-unreachable", 3, 0, 0xFF }, + { "network-unreachable", 3, 0, 0 }, + { "host-unreachable", 3, 1, 1 }, + { "protocol-unreachable", 3, 2, 2 }, + { "port-unreachable", 3, 3, 3 }, + { "fragmentation-needed", 3, 4, 4 }, + { "source-route-failed", 3, 5, 5 }, + { "network-unknown", 3, 6, 6 }, + { "host-unknown", 3, 7, 7 }, + { "network-prohibited", 3, 9, 9 }, + { "host-prohibited", 3, 10, 10 }, + { "TOS-network-unreachable", 3, 11, 11 }, + { "TOS-host-unreachable", 3, 12, 12 }, + { "communication-prohibited", 3, 13, 13 }, + { "host-precedence-violation", 3, 14, 14 }, + { "precedence-cutoff", 3, 15, 15 }, + + { "source-quench", 4, 0, 0xFF }, + + { "redirect", 5, 0, 0xFF }, + { "network-redirect", 5, 0, 0 }, + { "host-redirect", 5, 1, 1 }, + { "TOS-network-redirect", 5, 2, 2 }, + { "TOS-host-redirect", 5, 3, 3 }, + + { "echo-request", 8, 0, 0xFF }, + /* Alias */ { "ping", 8, 0, 0xFF }, + + { "router-advertisement", 9, 0, 0xFF }, + + { "router-solicitation", 10, 0, 0xFF }, + + { "time-exceeded", 11, 0, 0xFF }, + /* Alias */ { "ttl-exceeded", 11, 0, 0xFF }, + { "ttl-zero-during-transit", 11, 0, 0 }, + { "ttl-zero-during-reassembly", 11, 1, 1 }, + + { "parameter-problem", 12, 0, 0xFF }, + { "ip-header-bad", 12, 0, 0 }, + { "required-option-missing", 12, 1, 1 }, + + { "timestamp-request", 13, 0, 0xFF }, + + { "timestamp-reply", 14, 0, 0xFF }, + + { "address-mask-request", 17, 0, 0xFF }, + + { "address-mask-reply", 18, 0, 0xFF } +}; + +static void +print_icmptypes() +{ + unsigned int i; + printf("Valid ICMP Types:"); + + for (i = 0; i < sizeof(icmp_codes)/sizeof(struct icmp_names); i++) { + if (i && icmp_codes[i].type == icmp_codes[i-1].type) { + if (icmp_codes[i].code_min == icmp_codes[i-1].code_min + && (icmp_codes[i].code_max + == icmp_codes[i-1].code_max)) + printf(" (%s)", icmp_codes[i].name); + else + printf("\n %s", icmp_codes[i].name); + } + else + printf("\n%s", icmp_codes[i].name); + } + printf("\n"); +} + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"ICMP v%s options:\n" +" --icmp-type [!] typename match icmp type\n" +" (or numeric type or type/code)\n" +"\n", NETFILTER_VERSION); + print_icmptypes(); +} + +static struct option opts[] = { + { "icmp-type", 1, 0, '1' }, + {0} +}; + +static unsigned int +parse_icmp(const char *icmptype, u_int8_t *type, u_int8_t code[]) +{ + unsigned int limit = sizeof(icmp_codes)/sizeof(struct icmp_names); + unsigned int match = limit; + unsigned int i; + + for (i = 0; i < limit; i++) { + if (strncasecmp(icmp_codes[i].name, icmptype, strlen(icmptype)) + == 0) { + if (match != limit) + exit_error(PARAMETER_PROBLEM, + "Ambiguous ICMP type `%s':" + " `%s' or `%s'?", + icmptype, + icmp_codes[match].name, + icmp_codes[i].name); + match = i; + } + } + + if (match != limit) { + *type = icmp_codes[match].type; + code[0] = icmp_codes[match].code_min; + code[1] = icmp_codes[match].code_max; + } else { + char *slash; + char buffer[strlen(icmptype) + 1]; + int number; + + strcpy(buffer, icmptype); + slash = strchr(buffer, '/'); + + if (slash) + *slash = '\0'; + + number = string_to_number(buffer, 0, 255); + if (number == -1) + exit_error(PARAMETER_PROBLEM, + "Invalid ICMP type `%s'\n", buffer); + *type = number; + if (slash) { + number = string_to_number(slash+1, 0, 255); + if (number == -1) + exit_error(PARAMETER_PROBLEM, + "Invalid ICMP code `%s'\n", + slash+1); + code[0] = code[1] = number; + } else { + code[0] = 0; + code[1] = 0xFF; + } + } + + if (code[0] == 0 && code[1] == 0xFF) + return NFC_IP_SRC_PT; + else return NFC_IP_SRC_PT | NFC_IP_DST_PT; +} + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + struct ipt_icmp *icmpinfo = (struct ipt_icmp *)m->data; + + icmpinfo->code[1] = 0xFF; +} + +/* 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_icmp *icmpinfo = (struct ipt_icmp *)(*match)->data; + + switch (c) { + case '1': + if (check_inverse(optarg, &invert)) + optind++; + *nfcache |= parse_icmp(argv[optind-1], + &icmpinfo->type, + icmpinfo->code); + if (invert) + icmpinfo->invflags |= IPT_ICMP_INV; + break; + + default: + return 0; + } + + return 1; +} + +static void print_icmptype(u_int8_t type, + u_int8_t code_min, u_int8_t code_max, + int invert, + int numeric) +{ + if (!numeric) { + unsigned int i; + + for (i = 0; + i < sizeof(icmp_codes)/sizeof(struct icmp_names); + i++) { + if (icmp_codes[i].type == type + && icmp_codes[i].code_min == code_min + && icmp_codes[i].code_max == code_max) + break; + } + + if (i != sizeof(icmp_codes)/sizeof(struct icmp_names)) { + printf("%s%s ", + invert ? "!" : "", + icmp_codes[i].name); + return; + } + } + + if (invert) + printf("!"); + + printf("type %u", type); + if (code_min == 0 && code_max == 0xFF) + printf(" "); + else if (code_min == code_max) + printf(" code %u ", code_min); + else + printf(" codes %u-%u ", code_min, code_max); +} + +/* Prints out the union ipt_matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + const struct ipt_icmp *icmp = (struct ipt_icmp *)match->data; + + printf("icmp "); + print_icmptype(icmp->type, icmp->code[0], icmp->code[1], + icmp->invflags & IPT_ICMP_INV, + numeric); + + if (icmp->invflags & ~IPT_ICMP_INV) + printf("Unknown invflags: 0x%X ", + icmp->invflags & ~IPT_ICMP_INV); +} + +/* Saves the match in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + const struct ipt_icmp *icmp = (struct ipt_icmp *)match->data; + + if (icmp->invflags & IPT_ICMP_INV) + printf("! "); + + printf("--icmp-type %u", icmp->type); + if (icmp->code[0] != 0 || icmp->code[1] != 0xFF) + printf("/%u", icmp->code[0]); + printf(" "); +} + +/* Final check; we don't care. */ +static void final_check(unsigned int flags) +{ +} + +struct iptables_match icmp += { NULL, + "icmp", + NETFILTER_VERSION, + sizeof(struct ipt_icmp), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&icmp); +} diff --git a/extensions/libipt_limit.c b/extensions/libipt_limit.c new file mode 100644 index 0000000..aed6305 --- /dev/null +++ b/extensions/libipt_limit.c @@ -0,0 +1,196 @@ +/* Shared library add-on to iptables to add limit support. + * + * Jérôme de Vivie + * Hervé Eychenne + */ +#include +#include +#include +#include +#include +#include +#include + +#define IPT_LIMIT_AVG "3/hour" +#define IPT_LIMIT_BURST 5 + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"limit v%s options:\n" +"--limit avg max average match rate: default "IPT_LIMIT_AVG"\n" +" [Packets per second unless followed by \n" +" /sec /minute /hour /day postfixes]\n" +"--limit-burst number number to match in a burst, default %u\n" +"\n", NETFILTER_VERSION, IPT_LIMIT_BURST); +} + +static struct option opts[] = { + { "limit", 1, 0, '%' }, + { "limit-burst", 1, 0, '$' }, + { 0 } +}; + +static +int parse_rate(const char *rate, u_int32_t *val) +{ + const char *delim; + u_int32_t r; + u_int32_t mult = 1; /* Seconds by default. */ + + delim = strchr(rate, '/'); + if (delim) { + if (strlen(delim+1) == 0) + return 0; + + if (strncasecmp(delim+1, "second", strlen(delim+1)) == 0) + mult = 1; + else if (strncasecmp(delim+1, "minute", strlen(delim+1)) == 0) + mult = 60; + else if (strncasecmp(delim+1, "hour", strlen(delim+1)) == 0) + mult = 60*60; + else if (strncasecmp(delim+1, "day", strlen(delim+1)) == 0) + mult = 24*60*60; + else + return 0; + } + r = atoi(rate); + if (!r) + return 0; + + /* This would get mapped to infinite (1/day is minimum they + can specify, so we're ok at that end). */ + if (r / mult > IPT_LIMIT_SCALE) + exit_error(PARAMETER_PROBLEM, "Rate too fast `%s'\n", rate); + + *val = IPT_LIMIT_SCALE * mult / r; + return 1; +} + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + struct ipt_rateinfo *r = (struct ipt_rateinfo *)m->data; + + parse_rate(IPT_LIMIT_AVG, &r->avg); + r->burst = IPT_LIMIT_BURST; + + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +/* FIXME: handle overflow: + if (r->avg*r->burst/r->burst != r->avg) + exit_error(PARAMETER_PROBLEM, + "Sorry: burst too large for that avg rate.\n"); +*/ + +/* 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_rateinfo *r = (struct ipt_rateinfo *)(*match)->data; + int num; + + switch(c) { + case '%': + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --limit"); + if (!parse_rate(optarg, &r->avg)) + exit_error(PARAMETER_PROBLEM, + "bad rate `%s'", optarg); + break; + + case '$': + if (check_inverse(optarg, &invert)) + exit_error(PARAMETER_PROBLEM, + "Unexpected `!' after --limit-burst"); + + num = string_to_number(optarg, 0, 10000); + if (num <= 0) + exit_error(PARAMETER_PROBLEM, + "bad --limit-burst `%s'", optarg); + r->burst = num; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; nothing. */ +static void final_check(unsigned int flags) +{ +} + +static struct rates +{ + const char *name; + u_int32_t mult; +} rates[] = { { "day", IPT_LIMIT_SCALE*24*60*60 }, + { "hour", IPT_LIMIT_SCALE*60*60 }, + { "min", IPT_LIMIT_SCALE*60 }, + { "sec", IPT_LIMIT_SCALE } }; + +static void print_rate(u_int32_t period) +{ + unsigned int i; + + for (i = 1; i < sizeof(rates)/sizeof(struct rates); i++) { + if (period > rates[i].mult + || rates[i].mult % period != 0) + break; + } + + printf("%u/%s ", rates[i-1].mult / period, rates[i-1].name); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + struct ipt_rateinfo *r = (struct ipt_rateinfo *)match->data; + printf("limit: avg "); print_rate(r->avg); + printf("burst %u ", r->burst); +} + +/* FIXME: Make minimalist: only print rate if not default --RR */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + struct ipt_rateinfo *r = (struct ipt_rateinfo *)match->data; + + printf("--limit "); print_rate(r->avg); + if (r->burst != IPT_LIMIT_BURST) + printf("--limit-burst %u ", r->burst); +} + +struct iptables_match limit += { NULL, + "limit", + NETFILTER_VERSION, + sizeof(struct ipt_rateinfo), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&limit); +} diff --git a/extensions/libipt_mac.c b/extensions/libipt_mac.c new file mode 100644 index 0000000..36d8969 --- /dev/null +++ b/extensions/libipt_mac.c @@ -0,0 +1,144 @@ +/* Shared library add-on to iptables to add MAC address support. */ +#include +#include +#include +#include +#include +#if defined(__GLIBC__) && __GLIBC__ == 2 +#include +#else +#include +#endif +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"MAC v%s options:\n" +" --mac-source [!] XX:XX:XX:XX:XX:XX\n" +" Match source MAC address\n" +"\n", NETFILTER_VERSION); +} + +static struct option opts[] = { + { "mac-source", 1, 0, '1' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +static void +parse_mac(const char *mac, struct ipt_mac_info *info) +{ + unsigned int i = 0; + + if (strlen(mac) != ETH_ALEN*3-1) + exit_error(PARAMETER_PROBLEM, "Bad mac address `%s'", mac); + + for (i = 0; i < ETH_ALEN; i++) { + long number; + char *end; + + number = strtol(mac + i*3, &end, 16); + + if (end == mac + i*3 + 2 + && number >= 0 + && number <= 255) + info->srcaddr[i] = number; + else + exit_error(PARAMETER_PROBLEM, + "Bad mac address `%s'", mac); + } +} + +/* 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_mac_info *macinfo = (struct ipt_mac_info *)(*match)->data; + + switch (c) { + case '1': + if (check_inverse(optarg, &invert)) + optind++; + parse_mac(argv[optind-1], macinfo); + if (invert) + macinfo->invert = 1; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +static void print_mac(unsigned char macaddress[ETH_ALEN], int invert) +{ + unsigned int i; + + printf("%s%02X", invert ? "!" : "", macaddress[0]); + for (i = 1; i < ETH_ALEN; i++) + printf(":%02X", macaddress[i]); + printf(" "); +} + +/* Final check; must have specified --mac. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "You must specify `--mac-source'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + printf("MAC "); + print_mac(((struct ipt_mac_info *)match->data)->srcaddr, + ((struct ipt_mac_info *)match->data)->invert); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + printf("--mac "); + print_mac(((struct ipt_mac_info *)match->data)->srcaddr, + ((struct ipt_mac_info *)match->data)->invert); +} + +struct iptables_match mac += { NULL, + "mac", + NETFILTER_VERSION, + sizeof(struct ipt_mac_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&mac); +} diff --git a/extensions/libipt_mark.c b/extensions/libipt_mark.c new file mode 100644 index 0000000..318cd94 --- /dev/null +++ b/extensions/libipt_mark.c @@ -0,0 +1,128 @@ +/* Shared library add-on to iptables to add NFMARK matching support. */ +#include +#include +#include +#include +#include + +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"MARK match v%s options:\n" +"[!] --mark value[/mask] Match nfmark value with optional mask\n" +"\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "mark", 1, 0, '1' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + /* 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, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_mark_info *markinfo = (struct ipt_mark_info *)(*match)->data; + + switch (c) { + char *end; + case '1': + if (check_inverse(optarg, &invert)) + optind++; + markinfo->mark = strtoul(optarg, &end, 0); + if (*end == '/') { + markinfo->mask = strtoul(end+1, &end, 0); + } else + markinfo->mask = 0xffffffff; + if (*end != '\0' || end == optarg) + exit_error(PARAMETER_PROBLEM, "Bad MARK value `%s'", optarg); + if (invert) + markinfo->invert = 1; + *flags = 1; + break; + + default: + return 0; + } + return 1; +} + +static void +print_mark(unsigned long mark, unsigned long mask, int invert, int numeric) +{ + if (invert) + fputc('!', stdout); + + if(mask != 0xffffffff) + printf("0x%lx/0x%lx ", mark, mask); + else + printf("0x%lx ", mark); +} + +/* Final check; must have specified --mark. */ +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "MARK match: You must specify `--mark'"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + printf("MARK match "); + print_mark(((struct ipt_mark_info *)match->data)->mark, + ((struct ipt_mark_info *)match->data)->mask, + ((struct ipt_mark_info *)match->data)->invert, numeric); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + printf("--mark "); + print_mark(((struct ipt_mark_info *)match->data)->mark, + ((struct ipt_mark_info *)match->data)->mask, + ((struct ipt_mark_info *)match->data)->invert, 0); +} + +struct iptables_match mark += { NULL, + "mark", + NETFILTER_VERSION, + sizeof(struct ipt_mark_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&mark); +} diff --git a/extensions/libipt_multiport.c b/extensions/libipt_multiport.c new file mode 100644 index 0000000..727f95f --- /dev/null +++ b/extensions/libipt_multiport.c @@ -0,0 +1,262 @@ +/* Shared library add-on to iptables to add multiple TCP port support. */ +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"multiport v%s options:\n" +" --source-ports port[,port,port...]\n" +" --sports ...\n" +" match source port(s)\n" +" --destination-ports port[,port,port...]\n" +" --dports ...\n" +" match destination port(s)\n" +" --ports port[,port,port]\n" +" match both source and destination port(s)\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "source-ports", 1, 0, '1' }, + { "sports", 1, 0, '1' }, /* synonym */ + { "destination-ports", 1, 0, '2' }, + { "dports", 1, 0, '2' }, /* synonym */ + { "ports", 1, 0, '3' }, + {0} +}; + +static int +service_to_port(const char *name, const char *proto) +{ + struct servent *service; + + if ((service = getservbyname(name, proto)) != NULL) + return ntohs((unsigned short) service->s_port); + + return -1; +} + +static u_int16_t +parse_port(const char *port, const char *proto) +{ + int portnum; + if ((portnum = string_to_number(port, 0, 65535)) != -1 || + (portnum = service_to_port(port, proto)) != -1) + return (u_int16_t)portnum; + + exit_error(PARAMETER_PROBLEM, + "invalid port/service `%s' specified", port); +} + +static unsigned int +parse_multi_ports(const char *portstring, u_int16_t *ports, const char *proto) +{ + char *buffer, *cp, *next; + unsigned int i; + + buffer = strdup(portstring); + if (!buffer) exit_error(OTHER_PROBLEM, "strdup failed"); + + for (cp=buffer, i=0; cp && iip.proto == IPPROTO_TCP) + return "tcp"; + else if (entry->ip.proto == IPPROTO_UDP) + return "udp"; + else if (!entry->ip.proto) + exit_error(PARAMETER_PROBLEM, + "multiport needs `-p tcp' or `-p udp'"); + else + exit_error(PARAMETER_PROBLEM, + "multiport only works with TCP or UDP"); +} + +/* 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) +{ + const char *proto; + struct ipt_multiport *multiinfo + = (struct ipt_multiport *)(*match)->data; + + switch (c) { + case '1': + proto = check_proto(entry); + multiinfo->count = parse_multi_ports(argv[optind-1], + multiinfo->ports, proto); + multiinfo->flags = IPT_MULTIPORT_SOURCE; + *nfcache |= NFC_IP_SRC_PT; + break; + + case '2': + proto = check_proto(entry); + multiinfo->count = parse_multi_ports(argv[optind-1], + multiinfo->ports, proto); + multiinfo->flags = IPT_MULTIPORT_DESTINATION; + *nfcache |= NFC_IP_DST_PT; + break; + + case '3': + proto = check_proto(entry); + multiinfo->count = parse_multi_ports(argv[optind-1], + multiinfo->ports, proto); + multiinfo->flags = IPT_MULTIPORT_EITHER; + *nfcache |= NFC_IP_SRC_PT | NFC_IP_DST_PT; + break; + + default: + return 0; + } + + if (*flags) + exit_error(PARAMETER_PROBLEM, + "multiport can only have one option"); + *flags = 1; + return 1; +} + +/* Final check; must specify something. */ +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, "multiport expection an option"); +} + +static char * +port_to_service(int port, u_int8_t proto) +{ + struct servent *service; + + if ((service = getservbyport(htons(port), + proto == IPPROTO_TCP ? "tcp" : "udp"))) + return service->s_name; + + return NULL; +} + +static void +print_port(u_int16_t port, u_int8_t protocol, int numeric) +{ + char *service; + + if (numeric || (service = port_to_service(port, protocol)) == NULL) + printf("%u", port); + else + printf("%s", service); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + const struct ipt_multiport *multiinfo + = (const struct ipt_multiport *)match->data; + unsigned int i; + + printf("multiport "); + + switch (multiinfo->flags) { + case IPT_MULTIPORT_SOURCE: + printf("sports "); + break; + + case IPT_MULTIPORT_DESTINATION: + printf("dports "); + break; + + case IPT_MULTIPORT_EITHER: + printf("ports "); + break; + + default: + printf("ERROR "); + break; + } + + for (i=0; i < multiinfo->count; i++) { + printf("%s", i ? "," : ""); + print_port(multiinfo->ports[i], ip->proto, numeric); + } + printf(" "); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + const struct ipt_multiport *multiinfo + = (const struct ipt_multiport *)match->data; + unsigned int i; + + switch (multiinfo->flags) { + case IPT_MULTIPORT_SOURCE: + printf("--sports "); + break; + + case IPT_MULTIPORT_DESTINATION: + printf("--dports "); + break; + + case IPT_MULTIPORT_EITHER: + printf("--ports "); + break; + } + + for (i=0; i < multiinfo->count; i++) { + printf("%s", i ? "," : ""); + print_port(multiinfo->ports[i], ip->proto, 0); + } + printf(" "); +} + +struct iptables_match multiport += { NULL, + "multiport", + NETFILTER_VERSION, + sizeof(struct ipt_multiport), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void +_init(void) +{ + register_match(&multiport); +} diff --git a/extensions/libipt_owner.c b/extensions/libipt_owner.c new file mode 100644 index 0000000..2931102 --- /dev/null +++ b/extensions/libipt_owner.c @@ -0,0 +1,219 @@ +/* Shared library add-on to iptables to add OWNER matching support. */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"OWNER match v%s options:\n" +"[!] --uid-owner userid Match local uid\n" +"[!] --gid-owner groupid Match local gid\n" +"[!] --pid-owner processid Match local pid\n" +"[!] --sid-owner sessionid Match local sid\n" +"\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "uid-owner", 1, 0, '1' }, + { "gid-owner", 1, 0, '2' }, + { "pid-owner", 1, 0, '3' }, + { "sid-owner", 1, 0, '4' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + /* 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, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + struct ipt_owner_info *ownerinfo = (struct ipt_owner_info *)(*match)->data; + + switch (c) { + char *end; + struct passwd *pwd; + struct group *grp; + case '1': + if (check_inverse(optarg, &invert)) + optind++; + + if ((pwd = getpwnam(optarg))) + ownerinfo->uid = pwd->pw_uid; + else { + ownerinfo->uid = strtoul(optarg, &end, 0); + if (*end != '\0' || end == optarg) + exit_error(PARAMETER_PROBLEM, "Bad OWNER UID value `%s'", optarg); + } + if (invert) + ownerinfo->invert |= IPT_OWNER_UID; + ownerinfo->match |= IPT_OWNER_UID; + *flags = 1; + break; + + case '2': + if (check_inverse(optarg, &invert)) + optind++; + if ((grp = getgrnam(optarg))) + ownerinfo->gid = grp->gr_gid; + else { + ownerinfo->gid = strtoul(optarg, &end, 0); + if (*end != '\0' || end == optarg) + exit_error(PARAMETER_PROBLEM, "Bad OWNER GID value `%s'", optarg); + } + if (invert) + ownerinfo->invert |= IPT_OWNER_GID; + ownerinfo->match |= IPT_OWNER_GID; + *flags = 1; + break; + + case '3': + if (check_inverse(optarg, &invert)) + optind++; + ownerinfo->pid = strtoul(optarg, &end, 0); + if (*end != '\0' || end == optarg) + exit_error(PARAMETER_PROBLEM, "Bad OWNER PID value `%s'", optarg); + if (invert) + ownerinfo->invert |= IPT_OWNER_PID; + ownerinfo->match |= IPT_OWNER_PID; + *flags = 1; + break; + + case '4': + if (check_inverse(optarg, &invert)) + optind++; + ownerinfo->sid = strtoul(optarg, &end, 0); + if (*end != '\0' || end == optarg) + exit_error(PARAMETER_PROBLEM, "Bad OWNER SID value `%s'", optarg); + if (invert) + ownerinfo->invert |= IPT_OWNER_SID; + ownerinfo->match |= IPT_OWNER_SID; + *flags = 1; + break; + + default: + return 0; + } + return 1; +} + +static void +print_item(struct ipt_owner_info *info, u_int8_t flag, int numeric, char *label) +{ + if(info->match & flag) { + + printf(label); + + if (info->invert & flag) + fputc('!', stdout); + + switch(info->match & flag) { + case IPT_OWNER_UID: + if(!numeric) { + struct passwd *pwd = getpwuid(info->uid); + + if(pwd && pwd->pw_name) { + printf("%s ", pwd->pw_name); + break; + } + /* FALLTHROUGH */ + } + printf("%u ", info->uid); + break; + case IPT_OWNER_GID: + if(!numeric) { + struct group *grp = getgrgid(info->gid); + + if(grp && grp->gr_name) { + printf("%s ", grp->gr_name); + break; + } + /* FALLTHROUGH */ + } + printf("%u ", info->gid); + break; + case IPT_OWNER_PID: + printf("%u ", info->pid); + break; + case IPT_OWNER_SID: + printf("%u ", info->sid); + break; + default: + break; + } + } +} + +/* Final check; must have specified --own. */ +static void +final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "OWNER match: You must specify one or more options"); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + struct ipt_owner_info *info = (struct ipt_owner_info *)match->data; + + print_item(info, IPT_OWNER_UID, numeric, "OWNER UID match "); + print_item(info, IPT_OWNER_GID, numeric, "OWNER GID match "); + print_item(info, IPT_OWNER_PID, numeric, "OWNER PID match "); + print_item(info, IPT_OWNER_SID, numeric, "OWNER SID match "); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + struct ipt_owner_info *info = (struct ipt_owner_info *)match->data; + + print_item(info, IPT_OWNER_UID, 0, "--uid-owner "); + print_item(info, IPT_OWNER_GID, 0, "--gid-owner "); + print_item(info, IPT_OWNER_PID, 0, "--pid-owner "); + print_item(info, IPT_OWNER_SID, 0, "--sid-owner "); +} + +struct iptables_match owner += { NULL, + "owner", + NETFILTER_VERSION, + sizeof(struct ipt_owner_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&owner); +} diff --git a/extensions/libipt_standard.c b/extensions/libipt_standard.c new file mode 100644 index 0000000..9a746b2 --- /dev/null +++ b/extensions/libipt_standard.c @@ -0,0 +1,67 @@ +/* Shared library add-on to iptables for standard target support. */ +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"Standard v%s options:\n" +"(If target is DROP, ACCEPT, RETURN or nothing)\n", NETFILTER_VERSION); +} + +static struct option opts[] = { + {0} +}; + +/* Initialize the target. */ +static void +init(struct ipt_entry_target *t, unsigned int *nfcache) +{ +} + +/* 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) +{ + return 0; +} + +/* Final check; don't care. */ +static void final_check(unsigned int flags) +{ +} + +/* Saves the targinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ +} + +struct iptables_target standard += { NULL, + "standard", + NETFILTER_VERSION, + sizeof(int), + &help, + &init, + &parse, + &final_check, + NULL, /* print */ + &save, + opts +}; + +void _init(void) +{ + register_target(&standard); +} diff --git a/extensions/libipt_state.c b/extensions/libipt_state.c new file mode 100644 index 0000000..19751d7 --- /dev/null +++ b/extensions/libipt_state.c @@ -0,0 +1,162 @@ +/* Shared library add-on to iptables to add state tracking support. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"state v%s options:\n" +" [!] --state [INVALID|ESTABLISHED|NEW|RELATED][,...]\n" +" State(s) to match\n" +"\n", NETFILTER_VERSION); +} + +static struct option opts[] = { + { "state", 1, 0, '1' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + /* Can't cache this */ + *nfcache |= NFC_UNKNOWN; +} + +static int +parse_state(const char *state, size_t strlen, struct ipt_state_info *sinfo) +{ + if (strncasecmp(state, "INVALID", strlen) == 0) + sinfo->statemask |= IPT_STATE_INVALID; + else if (strncasecmp(state, "NEW", strlen) == 0) + sinfo->statemask |= IPT_STATE_BIT(IP_CT_NEW); + else if (strncasecmp(state, "ESTABLISHED", strlen) == 0) + sinfo->statemask |= IPT_STATE_BIT(IP_CT_ESTABLISHED); + else if (strncasecmp(state, "RELATED", strlen) == 0) + sinfo->statemask |= IPT_STATE_BIT(IP_CT_RELATED); + else + return 0; + return 1; +} + +static void +parse_states(const char *arg, struct ipt_state_info *sinfo) +{ + const char *comma; + + while ((comma = strchr(arg, ',')) != NULL) { + if (comma == arg || !parse_state(arg, comma-arg, sinfo)) + exit_error(PARAMETER_PROBLEM, "Bad state `%s'", arg); + arg = comma+1; + } + + if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo)) + exit_error(PARAMETER_PROBLEM, "Bad state `%s'", arg); +} + +/* 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_state_info *sinfo = (struct ipt_state_info *)(*match)->data; + + switch (c) { + case '1': + if (check_inverse(optarg, &invert)) + optind++; + + parse_states(argv[optind-1], sinfo); + if (invert) + sinfo->statemask = ~sinfo->statemask; + *flags = 1; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; must have specified --state. */ +static void final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, "You must specify `--state'"); +} + +static void print_state(unsigned int statemask) +{ + const char *sep = ""; + + if (statemask & IPT_STATE_INVALID) { + printf("%sINVALID", sep); + sep = ","; + } + if (statemask & IPT_STATE_BIT(IP_CT_NEW)) { + printf("%sNEW", sep); + sep = ","; + } + if (statemask & IPT_STATE_BIT(IP_CT_RELATED)) { + printf("%sRELATED", sep); + sep = ","; + } + if (statemask & IPT_STATE_BIT(IP_CT_ESTABLISHED)) { + printf("%sESTABLISHED", sep); + sep = ","; + } + printf(" "); +} + +/* Prints out the matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, + int numeric) +{ + struct ipt_state_info *sinfo = (struct ipt_state_info *)match->data; + + printf("state "); + print_state(sinfo->statemask); +} + +/* Saves the matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + struct ipt_state_info *sinfo = (struct ipt_state_info *)match->data; + + printf("--state "); + print_state(sinfo->statemask); +} + +struct iptables_match state += { NULL, + "state", + NETFILTER_VERSION, + sizeof(struct ipt_state_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&state); +} diff --git a/extensions/libipt_tcp.c b/extensions/libipt_tcp.c new file mode 100644 index 0000000..94285a0 --- /dev/null +++ b/extensions/libipt_tcp.c @@ -0,0 +1,439 @@ +/* Shared library add-on to iptables to add TCP support. */ +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"TCP v%s options:\n" +" --tcp-flags [!] mask comp match when TCP flags & mask == comp\n" +" (Flags: SYN ACK FIN RST URG PSH ALL NONE)\n" +"[!] --syn match when only SYN flag set\n" +" (equivalent to --tcp-flags SYN,RST,ACK SYN)\n" +" --source-port [!] port[:port]\n" +" --sport ...\n" +" match source port(s)\n" +" --destination-port [!] port[:port]\n" +" --dport ...\n" +" match destination port(s)\n" +" --tcp-option [!] number match if TCP option set\n\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "source-port", 1, 0, '1' }, + { "sport", 1, 0, '1' }, /* synonym */ + { "destination-port", 1, 0, '2' }, + { "dport", 1, 0, '2' }, /* synonym */ + { "syn", 0, 0, '3' }, + { "tcp-flags", 1, 0, '4' }, + { "tcp-option", 1, 0, '5' }, + {0} +}; + +static int +service_to_port(const char *name) +{ + struct servent *service; + + if ((service = getservbyname(name, "tcp")) != NULL) + return ntohs((unsigned short) service->s_port); + + return -1; +} + +static u_int16_t +parse_tcp_port(const char *port) +{ + int portnum; + + if ((portnum = string_to_number(port, 0, 65535)) != -1 || + (portnum = service_to_port(port)) != -1) + return (u_int16_t)portnum; + + exit_error(PARAMETER_PROBLEM, + "invalid TCP port/service `%s' specified", port); +} + +static void +parse_tcp_ports(const char *portstring, u_int16_t *ports) +{ + char *buffer; + char *cp; + + buffer = strdup(portstring); + if ((cp = strchr(buffer, ':')) == NULL) + ports[0] = ports[1] = parse_tcp_port(buffer); + else { + *cp = '\0'; + cp++; + + ports[0] = buffer[0] ? parse_tcp_port(buffer) : 0; + ports[1] = cp[0] ? parse_tcp_port(cp) : 0xFFFF; + } + free(buffer); +} + +struct tcp_flag_names { + const char *name; + unsigned int flag; +}; + +static struct tcp_flag_names tcp_flag_names[] += { { "FIN", 0x01 }, + { "SYN", 0x02 }, + { "RST", 0x04 }, + { "PSH", 0x08 }, + { "ACK", 0x10 }, + { "URG", 0x20 }, + { "ALL", 0x3F }, + { "NONE", 0 }, +}; + +static unsigned int +parse_tcp_flag(const char *flags) +{ + unsigned int ret = 0; + char *ptr; + char *buffer; + + buffer = strdup(flags); + + for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) { + unsigned int i; + for (i = 0; + i < sizeof(tcp_flag_names)/sizeof(struct tcp_flag_names); + i++) { + if (strcasecmp(tcp_flag_names[i].name, ptr) == 0) { + ret |= tcp_flag_names[i].flag; + break; + } + } + if (i == sizeof(tcp_flag_names)/sizeof(struct tcp_flag_names)) + exit_error(PARAMETER_PROBLEM, + "Unknown TCP flag `%s'", buffer); + } + + free(buffer); + return ret; +} + +static void +parse_tcp_flags(struct ipt_tcp *tcpinfo, + const char *mask, + const char *cmp, + int invert) +{ + tcpinfo->flg_mask = parse_tcp_flag(mask); + tcpinfo->flg_cmp = parse_tcp_flag(cmp); + + if (invert) + tcpinfo->invflags |= IPT_TCP_INV_FLAGS; +} + +static void +parse_tcp_option(const char *option, u_int8_t *result) +{ + int ret; + + ret = string_to_number(option, 1, 255); + if (ret == -1) + exit_error(PARAMETER_PROBLEM, "Bad TCP option `%s'", option); + + *result = (u_int8_t)ret; +} + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + struct ipt_tcp *tcpinfo = (struct ipt_tcp *)m->data; + + tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF; +} + +#define TCP_SRC_PORTS 0x01 +#define TCP_DST_PORTS 0x02 +#define TCP_FLAGS 0x04 +#define TCP_OPTION 0x08 + +/* 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_tcp *tcpinfo = (struct ipt_tcp *)(*match)->data; + + switch (c) { + case '1': + if (*flags & TCP_SRC_PORTS) + exit_error(PARAMETER_PROBLEM, + "Only one `--source-port' allowed"); + if (check_inverse(optarg, &invert)) + optind++; + parse_tcp_ports(argv[optind-1], tcpinfo->spts); + if (invert) + tcpinfo->invflags |= IPT_TCP_INV_SRCPT; + *flags |= TCP_SRC_PORTS; + *nfcache |= NFC_IP_SRC_PT; + break; + + case '2': + if (*flags & TCP_DST_PORTS) + exit_error(PARAMETER_PROBLEM, + "Only one `--destination-port' allowed"); + if (check_inverse(optarg, &invert)) + optind++; + parse_tcp_ports(argv[optind-1], tcpinfo->dpts); + if (invert) + tcpinfo->invflags |= IPT_TCP_INV_DSTPT; + *flags |= TCP_DST_PORTS; + *nfcache |= NFC_IP_DST_PT; + break; + + case '3': + if (*flags & TCP_FLAGS) + exit_error(PARAMETER_PROBLEM, + "Only one of `--syn' or `--tcp-flags' " + " allowed"); + parse_tcp_flags(tcpinfo, "SYN,RST,ACK", "SYN", invert); + *flags |= TCP_FLAGS; + *nfcache |= NFC_IP_TCPFLAGS; + break; + + case '4': + if (*flags & TCP_FLAGS) + exit_error(PARAMETER_PROBLEM, + "Only one of `--syn' or `--tcp-flags' " + " allowed"); + if (check_inverse(optarg, &invert)) + optind++; + + if (!argv[optind] + || argv[optind][0] == '-' || argv[optind][0] == '!') + exit_error(PARAMETER_PROBLEM, + "--tcp-flags requires two args."); + + parse_tcp_flags(tcpinfo, optarg, argv[optind++], invert); + *flags |= TCP_FLAGS; + *nfcache |= NFC_IP_TCPFLAGS; + break; + + case '5': + if (*flags & TCP_OPTION) + exit_error(PARAMETER_PROBLEM, + "Only one `--tcp-option' allowed"); + if (check_inverse(optarg, &invert)) + optind++; + parse_tcp_option(argv[optind-1], &tcpinfo->option); + if (invert) + tcpinfo->invflags |= IPT_TCP_INV_OPTION; + *flags |= TCP_OPTION; + *nfcache |= NFC_IP_PROTO_UNKNOWN; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; we don't care. */ +static void +final_check(unsigned int flags) +{ +} + +static char * +port_to_service(int port) +{ + struct servent *service; + + if ((service = getservbyport(htons(port), "tcp"))) + return service->s_name; + + return NULL; +} + +static void +print_port(u_int16_t port, int numeric) +{ + char *service; + + if (numeric || (service = port_to_service(port)) == NULL) + printf("%u", port); + else + printf("%s", service); +} + +static void +print_ports(const char *name, u_int16_t min, u_int16_t max, + int invert, int numeric) +{ + const char *inv = invert ? "!" : ""; + + if (min != 0 || max != 0xFFFF || invert) { + printf("%s", name); + if (min == max) { + printf(":%s", inv); + print_port(min, numeric); + } else { + printf("s:%s", inv); + print_port(min, numeric); + printf(":"); + print_port(max, numeric); + } + printf(" "); + } +} + +static void +print_option(u_int8_t option, int invert, int numeric) +{ + if (option || invert) + printf("option=%s%u ", invert ? "!" : "", option); +} + +static void +print_tcpf(u_int8_t flags) +{ + int sole_flag = 1; + + do { + unsigned int i; + + /* Terminates because last flag is 0 */ + for (i = 0; !(flags & tcp_flag_names[i].flag); i++); + + if (!sole_flag) + printf(","); + printf("%s", tcp_flag_names[i].name); + sole_flag = 0; + + flags &= ~tcp_flag_names[i].flag; + } while (flags); +} + +static void +print_flags(u_int8_t mask, u_int8_t cmp, int invert, int numeric) +{ + if (mask || invert) { + printf("flags:%s", invert ? "!" : ""); + if (numeric) + printf("0x02%X/0x02%X ", mask, cmp); + else { + print_tcpf(cmp); + printf("/"); + print_tcpf(mask); + printf(" "); + } + } +} + +/* Prints out the union ipt_matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, int numeric) +{ + const struct ipt_tcp *tcp = (struct ipt_tcp *)match->data; + + printf("tcp "); + print_ports("spt", tcp->spts[0], tcp->spts[1], + tcp->invflags & IPT_TCP_INV_SRCPT, + numeric); + print_ports("dpt", tcp->dpts[0], tcp->dpts[1], + tcp->invflags & IPT_TCP_INV_DSTPT, + numeric); + print_option(tcp->option, + tcp->invflags & IPT_TCP_INV_OPTION, + numeric); + print_flags(tcp->flg_mask, tcp->flg_cmp, + tcp->invflags & IPT_TCP_INV_FLAGS, + numeric); + if (tcp->invflags & ~IPT_TCP_INV_MASK) + printf("Unknown invflags: 0x%X ", + tcp->invflags & ~IPT_TCP_INV_MASK); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + const struct ipt_tcp *tcpinfo = (struct ipt_tcp *)match->data; + + if (tcpinfo->spts[0] != 0 + && tcpinfo->spts[1] != 0xFFFF) { + if (tcpinfo->invflags & IPT_TCP_INV_SRCPT) + printf("! "); + if (tcpinfo->spts[0] + != tcpinfo->spts[1]) + printf("--sport %u-%u ", + ntohs(tcpinfo->spts[0]), + ntohs(tcpinfo->spts[1])); + else + printf("--sport %u ", + ntohs(tcpinfo->spts[0])); + } + + if (tcpinfo->dpts[0] != 0 + && tcpinfo->dpts[1] != 0xFFFF) { + if (tcpinfo->invflags & IPT_TCP_INV_DSTPT) + printf("! "); + if (tcpinfo->dpts[0] + != tcpinfo->dpts[1]) + printf("--dport %u-%u ", + ntohs(tcpinfo->dpts[0]), + ntohs(tcpinfo->dpts[1])); + else + printf("--dport %u ", + ntohs(tcpinfo->dpts[0])); + } + + if (tcpinfo->option + || (tcpinfo->invflags & IPT_TCP_INV_OPTION)) { + if (tcpinfo->invflags & IPT_TCP_INV_OPTION) + printf("! "); + printf("--tcp-option %u ", tcpinfo->option); + } + + if (tcpinfo->flg_mask + || (tcpinfo->invflags & IPT_TCP_INV_FLAGS)) { + if (tcpinfo->invflags & IPT_TCP_INV_FLAGS) + printf("! "); + + print_tcpf(tcpinfo->flg_cmp); + if (tcpinfo->flg_mask != 0xFF) { + printf("/"); + print_tcpf(tcpinfo->flg_mask); + } + } +} + +struct iptables_match tcp += { NULL, + "tcp", + NETFILTER_VERSION, + sizeof(struct ipt_tcp), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts }; + +void +_init(void) +{ + register_match(&tcp); +} diff --git a/extensions/libipt_tos.c b/extensions/libipt_tos.c new file mode 100644 index 0000000..eb62081 --- /dev/null +++ b/extensions/libipt_tos.c @@ -0,0 +1,171 @@ +/* Shared library add-on to iptables to add TOS matching support. */ +#include +#include +#include +#include +#include + +#include +#include + +/* TOS names and values. */ +struct TOS_value +{ + unsigned char TOS; + const char *name; +} TOS_values[] = { + { IPTOS_LOWDELAY, "Minimize-Delay" }, + { IPTOS_THROUGHPUT, "Maximize-Throughput" }, + { IPTOS_RELIABILITY, "Maximize-Reliability" }, + { IPTOS_MINCOST, "Minimize-Cost" }, + { IPTOS_NORMALSVC, "Normal-Service" }, +}; + +/* Function which prints out usage message. */ +static void +help(void) +{ + unsigned int i; + + printf( +"TOS match v%s options:\n" +"[!] --tos value Match Type of Service field from one of the\n" +" following numeric or descriptive values:\n", +NETFILTER_VERSION); + + for (i = 0; i < sizeof(TOS_values)/sizeof(struct TOS_value);i++) + printf(" %s %u (0x%02x)\n", + TOS_values[i].name, + TOS_values[i].TOS, + TOS_values[i].TOS); + fputc('\n', stdout); +} + +static struct option opts[] = { + { "tos", 1, 0, '1' }, + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + *nfcache |= NFC_IP_TOS; +} + +static void +parse_tos(const unsigned char *s, struct ipt_tos_info *info) +{ + unsigned int i; + int tos = string_to_number(s, 0, 255); + + if (tos != -1) { + if (tos == IPTOS_LOWDELAY + || tos == IPTOS_THROUGHPUT + || tos == IPTOS_RELIABILITY + || tos == IPTOS_MINCOST + || tos == IPTOS_NORMALSVC) { + info->tos = (u_int8_t )tos; + return; + } + } else { + for (i = 0; itos = TOS_values[i].TOS; + return; + } + } + exit_error(PARAMETER_PROBLEM, "Bad TOS value `%s'", s); +} + +/* 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_tos_info *tosinfo = (struct ipt_tos_info *)(*match)->data; + + switch (c) { + case '1': + if (check_inverse(optarg, &invert)) + optind++; + parse_tos(argv[optind-1], tosinfo); + if (invert) + tosinfo->invert = 1; + *flags = 1; + break; + + default: + return 0; + } + return 1; +} + +static void +print_tos(u_int8_t tos, int invert, int numeric) +{ + unsigned int i; + + if (invert) + fputc('!', stdout); + + if (!numeric) { + for (i = 0; idata)->tos, + ((struct ipt_tos_info *)match->data)->invert, numeric); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void +save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + printf("--tos "); + print_tos(((struct ipt_tos_info *)match->data)->tos, + ((struct ipt_tos_info *)match->data)->invert, 0); +} + +struct iptables_match tos += { NULL, + "tos", + NETFILTER_VERSION, + sizeof(struct ipt_tos_info), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void _init(void) +{ + register_match(&tos); +} diff --git a/extensions/libipt_udp.c b/extensions/libipt_udp.c new file mode 100644 index 0000000..e359357 --- /dev/null +++ b/extensions/libipt_udp.c @@ -0,0 +1,252 @@ +/* Shared library add-on to iptables to add UDP support. */ +#include +#include +#include +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"UDP v%s options:\n" +" --source-port [!] port[:port]\n" +" --sport ...\n" +" match source port(s)\n" +" --destination-port [!] port[:port]\n" +" --dport ...\n" +" match destination port(s)\n", +NETFILTER_VERSION); +} + +static struct option opts[] = { + { "source-port", 1, 0, '1' }, + { "sport", 1, 0, '1' }, /* synonym */ + { "destination-port", 1, 0, '2' }, + { "dport", 1, 0, '2' }, /* synonym */ + {0} +}; + +static int +service_to_port(const char *name) +{ + struct servent *service; + + if ((service = getservbyname(name, "udp")) != NULL) + return ntohs((unsigned short) service->s_port); + + return -1; +} + +static u_int16_t +parse_udp_port(const char *port) +{ + int portnum; + + if ((portnum = string_to_number(port, 0, 65535)) != -1 || + (portnum = service_to_port(port)) != -1) + return (u_int16_t)portnum; + + exit_error(PARAMETER_PROBLEM, + "invalid UDP port/service `%s' specified", port); + } + +static void +parse_udp_ports(const char *portstring, u_int16_t *ports) +{ + char *buffer; + char *cp; + + buffer = strdup(portstring); + if ((cp = strchr(buffer, ':')) == NULL) + ports[0] = ports[1] = parse_udp_port(buffer); + else { + *cp = '\0'; + cp++; + + ports[0] = buffer[0] ? parse_udp_port(buffer) : 0; + ports[1] = cp[0] ? parse_udp_port(cp) : 0xFFFF; + } + free(buffer); +} + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + struct ipt_udp *udpinfo = (struct ipt_udp *)m->data; + + udpinfo->spts[1] = udpinfo->dpts[1] = 0xFFFF; +} + +#define UDP_SRC_PORTS 0x01 +#define UDP_DST_PORTS 0x02 + +/* 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_udp *udpinfo = (struct ipt_udp *)(*match)->data; + + switch (c) { + case '1': + if (*flags & UDP_SRC_PORTS) + exit_error(PARAMETER_PROBLEM, + "Only one `--source-port' allowed"); + if (check_inverse(optarg, &invert)) + optind++; + parse_udp_ports(argv[optind-1], udpinfo->spts); + if (invert) + udpinfo->invflags |= IPT_UDP_INV_SRCPT; + *flags |= UDP_SRC_PORTS; + *nfcache |= NFC_IP_SRC_PT; + break; + + case '2': + if (*flags & UDP_DST_PORTS) + exit_error(PARAMETER_PROBLEM, + "Only one `--destination-port' allowed"); + if (check_inverse(optarg, &invert)) + optind++; + parse_udp_ports(argv[optind-1], udpinfo->dpts); + if (invert) + udpinfo->invflags |= IPT_UDP_INV_DSTPT; + *flags |= UDP_DST_PORTS; + *nfcache |= NFC_IP_DST_PT; + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; we don't care. */ +static void +final_check(unsigned int flags) +{ +} + +static char * +port_to_service(int port) +{ + struct servent *service; + + if ((service = getservbyport(htons(port), "udp"))) + return service->s_name; + + return NULL; +} + +static void +print_port(u_int16_t port, int numeric) +{ + char *service; + + if (numeric || (service = port_to_service(port)) == NULL) + printf("%u", port); + else + printf("%s", service); +} + +static void +print_ports(const char *name, u_int16_t min, u_int16_t max, + int invert, int numeric) +{ + const char *inv = invert ? "!" : ""; + + if (min != 0 || max != 0xFFFF || invert) { + printf("%s", name); + if (min == max) { + printf(":%s", inv); + print_port(min, numeric); + } else { + printf("s:%s", inv); + print_port(min, numeric); + printf(":"); + print_port(max, numeric); + } + printf(" "); + } +} + +/* Prints out the union ipt_matchinfo. */ +static void +print(const struct ipt_ip *ip, + const struct ipt_entry_match *match, int numeric) +{ + const struct ipt_udp *udp = (struct ipt_udp *)match->data; + + printf("udp "); + print_ports("spt", udp->spts[0], udp->spts[1], + udp->invflags & IPT_UDP_INV_SRCPT, + numeric); + print_ports("dpt", udp->dpts[0], udp->dpts[1], + udp->invflags & IPT_UDP_INV_DSTPT, + numeric); + if (udp->invflags & ~IPT_UDP_INV_MASK) + printf("Unknown invflags: 0x%X ", + udp->invflags & ~IPT_UDP_INV_MASK); +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ + const struct ipt_udp *udpinfo = (struct ipt_udp *)match->data; + + if (udpinfo->spts[0] != 0 + && udpinfo->spts[1] != 0xFFFF) { + if (udpinfo->invflags & IPT_UDP_INV_SRCPT) + printf("! "); + if (udpinfo->spts[0] + != udpinfo->spts[1]) + printf("--sport %u-%u ", + ntohs(udpinfo->spts[0]), + ntohs(udpinfo->spts[1])); + else + printf("--sport %u ", + ntohs(udpinfo->spts[0])); + } + + if (udpinfo->dpts[0] != 0 + && udpinfo->dpts[1] != 0xFFFF) { + if (udpinfo->invflags & IPT_UDP_INV_DSTPT) + printf("! "); + if (udpinfo->dpts[0] + != udpinfo->dpts[1]) + printf("--dport %u-%u ", + ntohs(udpinfo->dpts[0]), + ntohs(udpinfo->dpts[1])); + else + printf("--dport %u ", + ntohs(udpinfo->dpts[0])); + } +} + +struct iptables_match udp += { NULL, + "udp", + NETFILTER_VERSION, + sizeof(struct ipt_udp), + &help, + &init, + &parse, + &final_check, + &print, + &save, + opts +}; + +void +_init(void) +{ + register_match(&udp); +} diff --git a/extensions/libipt_unclean.c b/extensions/libipt_unclean.c new file mode 100644 index 0000000..50f62bd --- /dev/null +++ b/extensions/libipt_unclean.c @@ -0,0 +1,66 @@ +/* Shared library add-on to iptables for unclean. */ +#include +#include +#include +#include + +/* Function which prints out usage message. */ +static void +help(void) +{ + printf( +"unclean v%s takes no options\n" +"\n", NETFILTER_VERSION); +} + +static struct option opts[] = { + {0} +}; + +/* Initialize the match. */ +static void +init(struct ipt_entry_match *m, unsigned int *nfcache) +{ + /* 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, + unsigned int *nfcache, + struct ipt_entry_match **match) +{ + return 0; +} + +/* Final check; must have specified --mac. */ +static void final_check(unsigned int flags) +{ +} + +/* Saves the union ipt_matchinfo in parsable form to stdout. */ +static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) +{ +} + +struct iptables_match unclean += { NULL, + "unclean", + NETFILTER_VERSION, + 0, + &help, + &init, + &parse, + &final_check, + NULL, /* print */ + &save, + opts +}; + +void _init(void) +{ + register_match(&unclean); +} diff --git a/include/iptables.h b/include/iptables.h new file mode 100644 index 0000000..9b1a4a1 --- /dev/null +++ b/include/iptables.h @@ -0,0 +1,122 @@ +#ifndef _IPTABLES_USER_H +#define _IPTABLES_USER_H + +#include "libiptc/libiptc.h" + +/* Include file for additions: new matches and targets. */ +struct iptables_match +{ + struct iptables_match *next; + + ipt_chainlabel name; + + const char *version; + + /* Size of match data. */ + size_t size; + + /* Function which prints out usage message. */ + void (*help)(void); + + /* Initialize the match. */ + void (*init)(struct ipt_entry_match *m, unsigned int *nfcache); + + /* Function which parses command options; returns true if it + ate an option */ + int (*parse)(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + unsigned int *nfcache, + struct ipt_entry_match **match); + + /* Final check; exit if not ok. */ + void (*final_check)(unsigned int flags); + + /* Prints out the match iff non-NULL: put space at end */ + void (*print)(const struct ipt_ip *ip, + const struct ipt_entry_match *match, int numeric); + + /* Saves the union ipt_matchinfo in parsable form to stdout. */ + void (*save)(const struct ipt_ip *ip, + const struct ipt_entry_match *match); + + /* Pointer to list of extra command-line options */ + struct option *extra_opts; + + /* Ignore these men behind the curtain: */ + unsigned int option_offset; + struct ipt_entry_match *m; + unsigned int mflags; +}; + +struct iptables_target +{ + struct iptables_target *next; + + ipt_chainlabel name; + + const char *version; + + /* Size of target data. */ + size_t size; + + /* Function which prints out usage message. */ + void (*help)(void); + + /* Initialize the target. */ + void (*init)(struct ipt_entry_target *t, unsigned int *nfcache); + + /* Function which parses command options; returns true if it + ate an option */ + int (*parse)(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target); + + /* Final check; exit if not ok. */ + void (*final_check)(unsigned int flags); + + /* Prints out the target iff non-NULL: put space at end */ + void (*print)(const struct ipt_ip *ip, + const struct ipt_entry_target *target, int numeric); + + /* Saves the targinfo in parsable form to stdout. */ + void (*save)(const struct ipt_ip *ip, + const struct ipt_entry_target *target); + + /* Pointer to list of extra command-line options */ + struct option *extra_opts; + + /* Ignore these men behind the curtain: */ + unsigned int option_offset; + struct ipt_entry_target *t; + unsigned int tflags; +}; + +/* Your shared library should call one of these. */ +extern void register_match(struct iptables_match *me); +extern void register_target(struct iptables_target *me); + +/* Functions we share */ +enum exittype { + OTHER_PROBLEM = 1, + PARAMETER_PROBLEM, + VERSION_PROBLEM +}; +extern void exit_printhelp() __attribute__((noreturn)); +extern void exit_tryhelp(int) __attribute__((noreturn)); +int check_inverse(const char option[], int *invert); +extern int string_to_number(const char *, int, int); +void exit_error(enum exittype, char *, ...)__attribute__((noreturn, + format(printf,2,3))); +extern char *addr_to_dotted(const struct in_addr *addrp); +struct in_addr *dotted_to_addr(const char *dotted); +extern const char *program_name, *program_version; + +extern int do_command(int argc, char *argv[], char **table, + iptc_handle_t *handle); +/* Keeping track of external matches and targets: linked lists. */ +extern struct iptables_match *iptables_matches; +extern struct iptables_target *iptables_targets; + +extern struct iptables_target *find_target(const char *name, int tryload); +extern struct iptables_match *find_match(const char *name, int tryload); +#endif /*_IPTABLES_USER_H*/ diff --git a/include/libipq/libipq.h b/include/libipq/libipq.h new file mode 100644 index 0000000..bc51ec7 --- /dev/null +++ b/include/libipq/libipq.h @@ -0,0 +1,77 @@ +/* + * libipq.h + * + * IPQ library for userspace. + * + * 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. + * + */ +#ifndef _LIBIPQ_H +#define _LIBIPQ_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef DEBUG_LIBIPQ +#include +#define LDEBUG(x...) fprintf(stderr, ## x) +#else +#define LDEBUG(x...) +#endif /* DEBUG_LIBIPQ */ + +/* FIXME: glibc sucks */ +#ifndef MSG_TRUNC +#define MSG_TRUNC 0x20 +#endif + +struct ipq_handle +{ + int fd; + u_int8_t blocking; + struct sockaddr_nl local; + struct sockaddr_nl peer; +}; + +struct ipq_handle *ipq_create_handle(u_int32_t flags); + +int ipq_destroy_handle(struct ipq_handle *h); + +ssize_t ipq_read(const struct ipq_handle *h, + unsigned char *buf, size_t len, int timeout); + +int ipq_set_mode(const struct ipq_handle *h, u_int8_t mode, size_t len); + +ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf); + +int ipq_message_type(const unsigned char *buf); + +int ipq_get_msgerr(const unsigned char *buf); + +int ipq_set_verdict(const struct ipq_handle *h, + unsigned long id, + unsigned int verdict, + size_t data_len, + unsigned char *buf); + +int ipq_ctl(const struct ipq_handle *h, int request, ...); + +void ipq_perror(const char *s); + +#endif /* _LIBIPQ_H */ + diff --git a/include/libiptc/ipt_kernel_headers.h b/include/libiptc/ipt_kernel_headers.h new file mode 100644 index 0000000..0fd848e --- /dev/null +++ b/include/libiptc/ipt_kernel_headers.h @@ -0,0 +1,22 @@ +/* This is the userspace/kernel interface for Generic IP Chains, + required for libc6. */ +#ifndef _FWCHAINS_KERNEL_HEADERS_H +#define _FWCHAINS_KERNEL_HEADERS_H + +#if defined(__GLIBC__) && __GLIBC__ == 2 +#include +#include +#include +#include +#include +#include +#else /* libc5 */ +#include +#include +#include +#include +#include +#include +#include +#endif +#endif diff --git a/include/libiptc/libiptc.h b/include/libiptc/libiptc.h new file mode 100644 index 0000000..4a964e0 --- /dev/null +++ b/include/libiptc/libiptc.h @@ -0,0 +1,131 @@ +#ifndef _LIBIPTC_H +#define _LIBIPTC_H +/* Library which manipulates filtering rules. */ + +#include +#include + +#ifndef IPT_MIN_ALIGN +#define IPT_MIN_ALIGN (__alignof__(struct ipt_entry_match)) +#endif +#define IPT_ALIGN(s) (((s) + (IPT_MIN_ALIGN-1)) & ~(IPT_MIN_ALIGN-1)) + +typedef char ipt_chainlabel[32]; + +#define IPTC_LABEL_ACCEPT "ACCEPT" +#define IPTC_LABEL_DROP "DROP" +#define IPTC_LABEL_QUEUE "QUEUE" +#define IPTC_LABEL_RETURN "RETURN" + +/* Transparent handle type. */ +typedef struct iptc_handle *iptc_handle_t; + +/* Does this chain exist? */ +int iptc_is_chain(const char *chain, const iptc_handle_t handle); + +/* Take a snapshot of the rules. Returns NULL on error. */ +iptc_handle_t iptc_init(const char *tablename); + +/* Iterator functions to run through the chains; prev = NULL means + first chain. Returns NULL at end. */ +const char *iptc_next_chain(const char *prev, iptc_handle_t *handle); + +/* How many rules in this chain? */ +unsigned int iptc_num_rules(const char *chain, iptc_handle_t *handle); + +/* Get n'th rule in this chain. */ +const struct ipt_entry *iptc_get_rule(const char *chain, + unsigned int n, + iptc_handle_t *handle); + +/* Returns a pointer to the target name of this position. */ +const char *iptc_get_target(const char *chain, + unsigned int n, + iptc_handle_t *handle); + +/* Is this a built-in chain? */ +int iptc_builtin(const char *chain, const iptc_handle_t handle); + +/* Get the policy of a given built-in chain */ +const char *iptc_get_policy(const char *chain, + struct ipt_counters *counter, + iptc_handle_t *handle); + +/* These functions return TRUE for OK or 0 and set errno. If errno == + 0, it means there was a version error (ie. upgrade libiptc). */ +/* Rule numbers start at 1 for the first rule. */ + +/* Insert the entry `e' in chain `chain' into position `rulenum'. */ +int iptc_insert_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + unsigned int rulenum, + iptc_handle_t *handle); + +/* Atomically replace rule `rulenum' in `chain' with `e'. */ +int iptc_replace_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + unsigned int rulenum, + iptc_handle_t *handle); + +/* Append entry `e' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int iptc_append_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + iptc_handle_t *handle); + +/* Delete the first rule in `chain' which matches `e'. */ +int iptc_delete_entry(const ipt_chainlabel chain, + const struct ipt_entry *origfw, + iptc_handle_t *handle); + +/* Delete the rule in position `rulenum' in `chain'. */ +int iptc_delete_num_entry(const ipt_chainlabel chain, + unsigned int rulenum, + iptc_handle_t *handle); + +/* Check the packet `e' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char *iptc_check_packet(const ipt_chainlabel chain, + struct ipt_entry *entry, + iptc_handle_t *handle); + +/* Flushes the entries in the given chain (ie. empties chain). */ +int iptc_flush_entries(const ipt_chainlabel chain, + iptc_handle_t *handle); + +/* Zeroes the counters in a chain. */ +int iptc_zero_entries(const ipt_chainlabel chain, + iptc_handle_t *handle); + +/* Creates a new chain. */ +int iptc_create_chain(const ipt_chainlabel chain, + iptc_handle_t *handle); + +/* Deletes a chain. */ +int iptc_delete_chain(const ipt_chainlabel chain, + iptc_handle_t *handle); + +/* Renames a chain. */ +int iptc_rename_chain(const ipt_chainlabel oldname, + const ipt_chainlabel newname, + iptc_handle_t *handle); + +/* Sets the policy on a built-in chain. */ +int iptc_set_policy(const ipt_chainlabel chain, + const ipt_chainlabel policy, + iptc_handle_t *handle); + +/* Get the number of references to this chain */ +int iptc_get_references(unsigned int *ref, + const ipt_chainlabel chain, + iptc_handle_t *handle); + +/* Makes the actual changes. */ +int iptc_commit(iptc_handle_t *handle); + +/* Get raw socket. */ +int iptc_get_raw_socket(); + +/* Translates errno numbers into more human-readable form than strerror. */ +const char *iptc_strerror(int err); +#endif /* _LIBIPTC_H */ diff --git a/iptables-restore.c b/iptables-restore.c new file mode 100644 index 0000000..9b4ece2 --- /dev/null +++ b/iptables-restore.c @@ -0,0 +1,154 @@ +/* Code to restore the iptables state, from file by iptables-save. */ +#include +#include +#include +#include +#include "packet-filter/userspace/iptables.h" +#include "packet-filter/userspace/libiptc/libiptc.h" + +/* Keeping track of external matches and targets. */ +static struct option options[] = { + { "binary", 1, 0, 'b' }, + { "counters", 1, 0, 'c' }, + { "verbose", 1, 0, 'v' }, + { "help", 1, 0, 'h' }, + { 0 } +}; + +static void print_usage(const char *name, const char *version) __attribute__((noreturn)); + +static void print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-h]\n", name); + exit(1); +} + +static int clean_slate(iptc_handle_t *handle) +{ + /* Skip over builtins. */ + const char *i, *last = IPTC_LABEL_OUTPUT; + + /* Be careful iterating: it isn't safe during delete. */ + /* Re-iterate after each delete successful */ + while ((i = iptc_next_chain(last, handle)) != NULL) { + if (!iptc_flush_entries(i, handle) + || !iptc_delete_chain(i, handle)) + return 0; + } + return 1; +} + +int main(int argc, char *argv[]) +{ + iptc_handle_t handle; + char buffer[10240]; + int counters = 0, binary = 0, verbose = 0; + unsigned int line = 0; + int c; + const char *chain; + FILE *in; + + program_name = "iptables-restore"; + program_version = NETFILTER_VERSION; + + /* Don't use getopt here; it would interfere 8-(. */ + if (optind == argc - 1) { + in = fopen(argv[optind], "r"); + if (!in) { + fprintf(stderr, "Can't open %s: %s", argv[optind], + strerror(errno)); + exit(1); + } + } + else if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline"); + exit(1); + } + else in = stdin; + + handle = iptc_init(); + if (!handle) + exit_error(VERSION_PROBLEM, + "can't initialize iptables-restore: %s", + iptc_strerror(errno)); + + if (!clean_slate(&handle)) + exit_error(OTHER_PROBLEM, "Deleting old chains: %s", + iptc_strerror(errno)); + + /* Grab standard input. */ + while (fgets(buffer, sizeof(buffer), in)) { + int ret; + + line++; + if (buffer[0] == '\n') continue; + else if (buffer[0] == '#') { + if (verbose) fputs(buffer, stdout); + continue; + } else if (strcmp(buffer, "COMMIT\n") == 0) + ret = iptc_commit(&handle); + else if (buffer[0] == ':') { + /* New chain. */ + char *chain, *policy; + + /* FIXME: Don't ignore counters. */ + chain = strtok(buffer+1, " \t\n"); + if (!chain) { + exit_error(PARAMETER_PROBLEM, + "%s: line %u chain name invalid\n", + program_name, line); + exit(1); + } + policy = strtok(NULL, " \t\n"); + if (!policy) { + exit_error(PARAMETER_PROBLEM, + "%s: line %u policy invalid\n", + program_name, line); + exit(1); + } + if (strcmp(policy, "-") != 0 + && !iptc_set_policy(chain, policy, &handle)) + exit_error(OTHER_PROBLEM, + "Can't set policy `%s'" + " on `%s' line %u: %s\n", + chain, policy, line, + iptc_strerror(errno)); + } else { + char *newargv[1024]; + int i; + char *ptr = buffer; + + /* FIXME: Don't ignore counters. */ + if (buffer[0] == '[') { + ptr = strchr(buffer, ']'); + if (!ptr) + exit_error(PARAMETER_PROBLEM, + "Bad line %u: need ]\n", + line); + } + + /* strtok: a function only a coder could love */ + newargv[0] = argv[0]; + for (i = 1; i < sizeof(newargv)/sizeof(char *); i++) { + if (!(newargv[i] = strtok(ptr, " \t\n"))) + break; + ptr = NULL; + } + if (i == sizeof(newargv)/sizeof(char *)) { + fprintf(stderr, + "%s: line %u too many arguments\n", + program_name, line); + exit(1); + } + + ret = do_command(i, newargv, &handle); + } + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + program_name, line); + exit(1); + } + } + + return 0; +} diff --git a/iptables-save.c b/iptables-save.c new file mode 100644 index 0000000..40e9d6a --- /dev/null +++ b/iptables-save.c @@ -0,0 +1,260 @@ +/* Code to save the iptables state, in human readable-form. */ +#include +#include +#include +#include +#include +#include "packet-match/userspace/libiptc/libiptc.h" +#include "packet-match/userspace/iptables.h" + +/* Keeping track of external matches and targets. */ +static struct option options[] = { + { "binary", 1, 0, 'b' }, + { "counters", 1, 0, 'c' }, + { "dump", 1, 0, 'd' }, + { 0 } +}; + +#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)) + +/* This assumes that mask is contiguous, and byte-bounded. */ +static void +print_iface(char letter, const char *iface, const unsigned char *mask, + int invert) +{ + unsigned int i; + + if (mask[0] == 0) + return; + + printf("-%c %s", letter, invert ? "! " : ""); + + for (i = 0; i < IFNAMSIZ; i++) { + if (mask[i] != 0) { + if (iface[i] != '\0') + printf("%c", iface[i]); + } else { + if (iface[i] != '\0') + printf("+"); + break; + } + } +} + +/* These are hardcoded backups in iptables.c, so they are safe */ +struct pprot { + char *name; + u_int8_t num; +}; + +static const struct pprot chain_protos[] = { + { "tcp", IPPROTO_TCP }, + { "udp", IPPROTO_UDP }, + { "icmp", IPPROTO_ICMP }, +}; + +static void print_proto(u_int16_t proto, int invert) +{ + if (proto) { + unsigned int i; + const char *invertstr = invert ? "! " : ""; + + for (i = 0; i < sizeof(chain_protos)/sizeof(struct pprot); i++) + if (chain_protos[i].num == proto) { + printf("-p %s%s ", + invertstr, chain_protos[i].name); + return; + } + + printf("-p %s%u ", invertstr, proto); + } +} + +static int non_zero(const void *ptr, size_t size) +{ + unsigned int i; + + for (i = 0; i < size; i++) + if (((char *)ptr)[i]) + return 0; + + return 1; +} + +/* We want this to be readable, so only print out neccessary fields. + * Because that's the kind of world I want to live in. */ +static void print_rule(const struct ipt_entry *e, int counters) +{ + if (counters) + printf("[%llu,%llu] ", e->counters.pcnt, e->counters.bcnt); + + /* Print IP part. */ + if (e->ip.smsk.s_addr) + printf("-s %s%u.%u.%u.%u/%u.%u.%u.%u ", + e->ip.invflags & IPT_INV_SRCIP ? "! " : "", + IP_PARTS(e->ip.src.s_addr), + IP_PARTS(e->ip.smsk.s_addr)); + if (e->ip.dmsk.s_addr) + printf("-d %s%u.%u.%u.%u/%u.%u.%u.%u ", + e->ip.invflags & IPT_INV_SRCIP ? "! " : "", + IP_PARTS(e->ip.dst.s_addr), + IP_PARTS(e->ip.dmsk.s_addr)); + + print_iface('i', e->ip.iniface, e->ip.iniface_mask, + e->ip.invflags & IPT_INV_VIA_IN); + print_iface('o', e->ip.outiface, e->ip.outiface_mask, + e->ip.invflags & IPT_INV_VIA_OUT); + print_proto(e->ip.proto, e->ip.invflags & IPT_INV_PROTO); + + if (e->ip.flags & IPT_F_FRAG) + printf("%s-f ", + e->ip.invflags & IPT_INV_FRAG ? "! " : ""); + + if (e->ip.flags & IPT_F_TOS) + printf("-t %s0x%02X ", + e->ip.invflags & IPT_INV_TOS ? "! " : "", + e->ip.tos); + + /* Print matchinfo part */ + if (e->match_name[0]) { + struct iptables_match *match = find_match(e->match_name, 1); + + if (match) + match->save(e); + else { + /* If some bits are non-zero, it implies we *need* + to understand it */ + if (non_zero(&e->matchinfo, sizeof(e->matchinfo))) { + fprintf(stderr, + "Can't find library for match `%s'\n", + e->match_name); + exit(1); + } + } + } + + /* Print targinfo part */ + if (e->target_name[0]) { + struct iptables_target *target + = find_target(e->target_name, 1); + + if (target) + target->save(e); + else { + /* If some bits are non-zero, it implies we *need* + to understand it */ + if (non_zero(&e->targinfo, sizeof(e->targinfo))) { + fprintf(stderr, + "Can't find library for target `%s'\n", + e->target_name); + exit(1); + } + } + } + printf("\n"); +} + +/* Debugging prototype. */ +extern void dump_entries(iptc_handle_t handle); + +/* Format: + * :Chain name POLICY packets bytes + * rule + */ +int main(int argc, char *argv[]) +{ + iptc_handle_t h; + const char *chain = NULL; + int c; + int binary = 0, counters = 0; + + program_name = "iptables-save"; + program_version = NETFILTER_VERSION; + + while ((c = getopt_long(argc, argv, "bc", options, NULL)) != -1) { + switch (c) { + case 'b': + binary = 1; + break; + + case 'c': + counters = 1; + break; + + case 'd': + /* Debugging dump. */ + h = iptc_init(); + if (!h) + exit_error(OTHER_PROBLEM, "iptc_init: %s\n", + iptc_strerror(errno)); + dump_entries(h); + exit(0); + } + } + + if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline"); + exit(1); + } + + h = iptc_init(); + if (!h) { + fprintf(stderr, "Can't initialize: %s\n", + iptc_strerror(errno)); + exit(1); + } + + if (!binary) { + time_t now = time(NULL); + + printf("# Generated by iptables-save v%s on %s", + NETFILTER_VERSION, ctime(&now)); + + /* Dump out chain names */ + while ((chain = iptc_next_chain(chain, &h)) != NULL) { + printf(":%s ", chain); + if (iptc_builtin(chain, &h)) { + struct ipt_counters count; + printf("%s ", + iptc_get_policy(chain, &count, &h)); + printf("%llu %llu\n", count.pcnt, count.bcnt); + } else { + printf("- 0 0\n"); + } + } + + /* Dump out rules */ + while ((chain = iptc_next_chain(chain, &h)) != NULL) { + unsigned int i; + + for (i = 0; i < iptc_num_rules(chain, &h); i++) { + const struct ipt_entry *e + = iptc_get_rule(chain, i, &h); + + if (!e) { + fprintf(stderr, + "Can't read rule %u of chain %s: %s\n", + i, chain, iptc_strerror(errno)); + exit(1); + } + print_rule(e, counters); + } + } + + now = time(NULL); + printf("COMMIT\n"); + printf("# Completed on %s", ctime(&now)); + } else { + /* Binary, huh? OK. */ + fprintf(stderr, "Binary NYI\n"); + exit(1); + } + + return 0; +} diff --git a/iptables-standalone.c b/iptables-standalone.c new file mode 100644 index 0000000..dfbc0fa --- /dev/null +++ b/iptables-standalone.c @@ -0,0 +1,50 @@ +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * Based on the ipchains code by Paul Russell and Michael Neuling + * + * iptables -- IP firewall administration for kernels with + * firewall table (aimed for the 2.3 kernels) + * + * See the accompanying manual page iptables(8) for information + * about proper usage of this program. + * + * 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. + */ + +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + int ret; + char *table = "filter"; + iptc_handle_t handle; + + program_name = "iptables"; + program_version = NETFILTER_VERSION; + + ret = do_command(argc, argv, &table, &handle); + if (ret) + ret = iptc_commit(&handle); + + if (!ret) + fprintf(stderr, "iptables: %s\n", + iptc_strerror(errno)); + + exit(!ret); +} diff --git a/iptables.8 b/iptables.8 new file mode 100644 index 0000000..a4e44a3 --- /dev/null +++ b/iptables.8 @@ -0,0 +1,708 @@ +.TH IPTABLES 8 "Mar 20, 2000" "" "" +.\" +.\" Man page written 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 (see README) +.\" +.\" 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 \- IP packet filter administration +.SH SYNOPSIS +.BR "iptables -[ADC] " "chain rule-specification [options]" +.br +.BR "iptables -[RI] " "chain rulenum rule-specification [options]" +.br +.BR "iptables -D " "chain rulenum [options]" +.br +.BR "iptables -[LFZ] " "[chain] [options]" +.br +.BR "iptables -[NX] " "chain" +.br +.BR "iptables -P " "chain target [options]" +.br +.BR "iptables -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. There are several different tables +which may be defined, and each table contains a number of built-in +chains, and may 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 which 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. +.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 current three tables (which tables are present at any time +depends on the kernel configuration options and which modules are +present). +.TP +.B "-t, --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: +.BR "filter" +This is the default table, and contains the built-in chains INPUT (for +packets coming into the box itself), FORWARD (for packets being routed +through the box), and OUTPUT (for locally-generated packets). +.BR "nat" +This table is consulted when a packet which is creates a new +connection is encountered. It consists of three built-ins: PREROUTING +(for altering packets as soon as they come in), OUTPUT (for altering +locally-generated packets before routing), and POSTROUTING (for +altering packets as they are about to go out). +.BR "mangle" +This table is used for specialized packet alteration. It has two +built-in chains: PREROUTING (for altering incoming packets before +routing) and OUTPUT (for altering locally-generated packets before +routing). +.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 +only need to use enough letters to ensure that +.B iptables +can differentiate it from all other options. +.TP +.BR "-A, --append" +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 +.BR "-D, --delete" +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 "-R, --replace" +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 +.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 +.B "-L, --list" +List all rules in the selected chain. If no chain is selected, all +chains are listed. 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 effected by the other +arguments given. +.TP +.B "-F, --flush" +Flush the selected chain. This is equivalent to deleting all the +rules one by one. +.TP +.B "-Z, --zero" +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 +.B "-N, --new-chain" +Create a new user-defined chain of the given name. There must be no +target of that name already. +.TP +.B "-X, --delete-chain" +Delete the specified user-defined chain. 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. +.TP +.B "-P, --policy" +Set the policy for the chain to the given target. See the section +.TP +.B "-E, --rename-chain" +Rename the user specified chain to the user supplied name; this is +cosmetic, and has no effect on the structure of the table. +.B TARGETS +for the legal targets. Only non-userdefined chains can have policies, +and neither built-in nor user-defined chains can be policy targets. +.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, replace, append and check commands). +.TP +.BR "-p, --proto " "[!] \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. Also a protocol name from /etc/protocols is 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. +.I All +may not be used in in combination with the check command. +.TP +.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]" +Source specification. +.I Address +can be either a hostname, a network name, 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 a convenient 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; ie. what to do if the packet +matches it. The target can be a user-defined chain (not 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]" +Optional name of an interface via which a packet is received (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, the string "+" is assumed, which will match with any +interface name. +.TP +.BR "-o, --out-interface " "[!] [\fIname\fP]" +Optional 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, the string "+" is assumed, which will match with any +interface name. +.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 sense is inverted. +.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 +address, 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 relevent 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. +.SH MATCH EXTENSIONS +iptables can use extended packet matching modules. The following are +included in the base package, and most of these can be preceeded by a +.B ! +to invert the sense of the match. +.SS tcp +These extensions are loaded if `--protocol tcp' is specified, and no +other match is specified. It provides the following options: +.TP +.BR "--source-port " "[!] [\fIport[:port]\fP] or [\fIport[-port]]\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 +or +.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 an alias for this option. +.TP +.BR "--destination-port " "[!] [\fIport[:port]\fP] or [\fIport[-port]]\fP" +Destination port or port range specification. The flag +.B --dport +is an 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 +.br + ipchains -p tcp --tcp-flags SYN,ACK,FIN,RST SYN +.br +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 FIN 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, and no +other match is specified. It provides the following options: +.TP +.BR "--source-port " "[!] [\fIport[:port]\fP] or [\fIport[-port]]\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[:port]\fP] or [\fIport[-port]]\fP" +Destination port or port range specification. +See the description of the +.B --destination-port +option of the TCP extension for details. +.SS icmp +This extension is loaded if `--protocol icmp' is specified, and no +other match 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 +.br + iptables -p icmp -h +.br +.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 entering the +.BR PREROUTING , +or +.B INPUT +chains from an ethernet device. +.SS limit +This module matches at a limited rate using a token bucket filter: it +can be used in combination with the LOG target to give limited +logging. A rule using this extension will match until this limit is +reached (unless the `!' flag is used). +.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" +The 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-port" " [\fIport[,port]\fP]" +Match if the source port is one of the given ports. +.TP +.BR "--destination-port" " [\fIport[,port]\fP]" +Match if the destination port is one of the given ports. +.TP +.BR "--port" " [\fIport[,port]\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 +.BI "--mark " "value[/mask]" +Matches packets with the given unsigned mark value (if a mask is +specified, this is logically ANDed with the mark 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 +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. +.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 +iptables 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 IP header fields) via +.IR printk (). +.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 14 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. +.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 +.BR DROP . +This target is only valid in the +.BR INPUT , +.B FORWARD +and +.B OUTPUT +chains. Several options control the nature of the error packet +returned: +.TP +.BI "--reject-with" "type" +The type given can be +.BR icmp-net-unreachable , +.BR icmp-host-unreachable , +.BR icmp-port-unreachable or +.BR icmp-proto-unreachable +which return the appropriate ICMP error message (net-unreachable is +the default). The following special types are also allowed: +.B tcp-reset +is only valid if the rule also specifies +.BR "-p tcp" , +and generates a TCP reset packet in response. This is generally not a +good idea (modern stacks should deal with ICMPs on TCP connection +initiation attempts). +.B echo-reply +can only be used for rules which specify an ICMP ping packet, and +generates a ping reply. +.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 OUTPUT +chains. +.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 +.BI "--to-source" "[-][:port-port]" +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 1024 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. 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 +.BI "--to-destination" "[-][:port-port]" +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 behaviour 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 +.BI "--to-ports" "[-]" +This specifies a range of source ports to use, overriding the default +.B SNAT +source port-selection heuristics (see above). This is only valid with +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. 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 +.BI "--to-ports" "[-]" +This specifies a destination port or range or ports to use: without +this, the destination port is never altered. This is only valid with +if the rule also specifies +.B "-p tcp" +or +.BR "-p udp" ). +.TP +.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 +Check is not implemented (yet). +.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; 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 iptables. +.SH SEE ALSO +The iptables-HOWTO, which details more iptables usage, and the +netfilter-hacking-HOWTO which details the internals. +.SH AUTHOR +Rusty Russell wrote iptables, in early consultation with Michael +Neuling. +.PP +Marc Boucher wrote 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 +The Netfilter Core Team is: Marc Boucher, Rusty Russell. +.\" .. and did I mention that we are incredibly cool people? diff --git a/iptables.c b/iptables.c new file mode 100644 index 0000000..e7110ea --- /dev/null +++ b/iptables.c @@ -0,0 +1,1963 @@ +/* Code to take an iptables-style command line and do it. */ + +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/local/lib/iptables" +#endif + +#define FMT_NUMERIC 0x0001 +#define FMT_NOCOUNTS 0x0002 +#define FMT_KILOMEGAGIGA 0x0004 +#define FMT_OPTIONS 0x0008 +#define FMT_NOTABLE 0x0010 +#define FMT_NOTARGET 0x0020 +#define FMT_VIA 0x0040 +#define FMT_NONEWLINE 0x0080 +#define FMT_LINENUMBERS 0x0100 + +#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \ + | FMT_NUMERIC | FMT_NOTABLE) +#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab)) + + +#define CMD_NONE 0x0000U +#define CMD_INSERT 0x0001U +#define CMD_DELETE 0x0002U +#define CMD_DELETE_NUM 0x0004U +#define CMD_REPLACE 0x0008U +#define CMD_APPEND 0x0010U +#define CMD_LIST 0x0020U +#define CMD_FLUSH 0x0040U +#define CMD_ZERO 0x0080U +#define CMD_NEW_CHAIN 0x0100U +#define CMD_DELETE_CHAIN 0x0200U +#define CMD_SET_POLICY 0x0400U +#define CMD_CHECK 0x0800U +#define CMD_RENAME_CHAIN 0x1000U +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'C', 'E' }; + +#define OPTION_OFFSET 256 + +#define OPT_NONE 0x00000U +#define OPT_NUMERIC 0x00001U +#define OPT_SOURCE 0x00002U +#define OPT_DESTINATION 0x00004U +#define OPT_PROTOCOL 0x00008U +#define OPT_JUMP 0x00010U +#define OPT_VERBOSE 0x00020U +#define OPT_EXPANDED 0x00040U +#define OPT_VIANAMEIN 0x00080U +#define OPT_VIANAMEOUT 0x00100U +#define OPT_FRAGMENT 0x00200U +#define OPT_LINENUMBERS 0x00400U +#define NUMBER_OF_OPT 11 +static const char optflags[NUMBER_OF_OPT] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', 'f', '3'}; + +static struct option original_opts[] = { + { "append", 1, 0, 'A' }, + { "delete", 1, 0, 'D' }, + { "insert", 1, 0, 'I' }, + { "replace", 1, 0, 'R' }, + { "list", 2, 0, 'L' }, + { "flush", 2, 0, 'F' }, + { "zero", 2, 0, 'Z' }, + { "check", 1, 0, 'C' }, + { "new-chain", 1, 0, 'N' }, + { "delete-chain", 2, 0, 'X' }, + { "rename-chain", 2, 0, 'E' }, + { "policy", 1, 0, 'P' }, + { "source", 1, 0, 's' }, + { "destination", 1, 0, 'd' }, + { "src", 1, 0, 's' }, /* synonym */ + { "dst", 1, 0, 'd' }, /* synonym */ + { "proto", 1, 0, 'p' }, + { "in-interface", 1, 0, 'i' }, + { "jump", 1, 0, 'j' }, + { "table", 1, 0, 't' }, + { "match", 1, 0, 'm' }, + { "numeric", 0, 0, 'n' }, + { "out-interface", 1, 0, 'o' }, + { "verbose", 0, 0, 'v' }, + { "exact", 0, 0, 'x' }, + { "fragments", 0, 0, 'f' }, + { "version", 0, 0, 'V' }, + { "help", 2, 0, 'h' }, + { "line-numbers", 0, 0, '0' }, + { 0 } +}; + +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* -n -s -d -p -j -v -x -i -o -f --line */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ',' ','x'}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x','x',' '}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x','x'}, +/*CHECK*/ {'x','+','+','+','x',' ','x','+','+',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x'} +}; + +static int inverse_for_options[NUMBER_OF_OPT] = +{ +/* -n */ 0, +/* -s */ IPT_INV_SRCIP, +/* -d */ IPT_INV_DSTIP, +/* -p */ IPT_INV_PROTO, +/* -j */ 0, +/* -v */ 0, +/* -x */ 0, +/* -i */ IPT_INV_VIA_IN, +/* -o */ IPT_INV_VIA_OUT, +/* -f */ IPT_INV_FRAG, +/*--line*/ 0 +}; + +const char *program_version; +const char *program_name; + +/* Keeping track of external matches and targets: linked lists, but + unless we're listing (-L), they have only 0 or 1 entry. */ +struct iptables_match *iptables_matches = NULL; +struct iptables_target *iptables_targets = NULL; + +/* Extra debugging from libiptc */ +extern void dump_entries(const iptc_handle_t handle); + +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + char *name; + u_int8_t num; +}; + +static const struct pprot chain_protos[] = { + { "tcp", IPPROTO_TCP }, + { "udp", IPPROTO_UDP }, + { "icmp", IPPROTO_ICMP }, + { "all", 0 }, +}; + +static char * +proto_to_name(u_int8_t proto) +{ + unsigned int i; + + if (proto) { + struct protoent *pent = getprotobynumber(proto); + if (pent) + return pent->p_name; + } + + for (i = 0; i < sizeof(chain_protos)/sizeof(struct pprot); i++) + if (chain_protos[i].num == proto) + return chain_protos[i].name; + + return NULL; +} + +struct in_addr * +dotted_to_addr(const char *dotted) +{ + static struct in_addr addr; + unsigned char *addrp; + char *p, *q; + int onebyte, i; + char buf[20]; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + addrp = (unsigned char *) &(addr.s_addr); + + p = buf; + for (i = 0; i < 3; i++) { + if ((q = strchr(p, '.')) == NULL) + return (struct in_addr *) NULL; + + *q = '\0'; + if ((onebyte = string_to_number(p, 0, 255)) == -1) + return (struct in_addr *) NULL; + + addrp[i] = (unsigned char) onebyte; + p = q + 1; + } + + /* we've checked 3 bytes, now we check the last one */ + if ((onebyte = string_to_number(p, 0, 255)) == -1) + return (struct in_addr *) NULL; + + addrp[3] = (unsigned char) onebyte; + + return &addr; +} + +static struct in_addr * +network_to_addr(const char *name) +{ + struct netent *net; + static struct in_addr addr; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return (struct in_addr *) NULL; + addr.s_addr = htonl((unsigned long) net->n_net); + return &addr; + } + + return (struct in_addr *) NULL; +} + +static void +inaddrcpy(struct in_addr *dst, struct in_addr *src) +{ + /* memcpy(dst, src, sizeof(struct in_addr)); */ + dst->s_addr = src->s_addr; +} + +void +exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", program_name, program_version); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + if (status == PARAMETER_PROBLEM) + exit_tryhelp(status); + if (status == VERSION_PROBLEM) + fprintf(stderr, + "Perhaps iptables or your kernel needs to be upgraded.\n"); + exit(status); +} + +void +exit_tryhelp(int status) +{ + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + program_name, program_name ); + exit(status); +} + +void +exit_printhelp(void) +{ + printf("%s v%s\n\n" +"Usage: %s -[ADC] chain rule-specification [options]\n" +" %s -[RI] chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LFZ] [chain] [options]\n" +" %s -[NX] chain\n" +" %s -E old-chain-name new-chain-name\n" +" %s -P chain target [options]\n" +" %s -h (print this help information)\n\n", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name, + program_name, program_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\n" +" --delete -D chain Delete matching rule from chain\n" +" --delete -D chain rulenum\n" +" Delete rule rulenum (1 = first) from chain\n" +" --insert -I chain [rulenum]\n" +" Insert in chain as rulenum (default 1=first)\n" +" --replace -R chain rulenum\n" +" Replace rule rulenum (1 = first) in chain\n" +" --list -L [chain] List the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain] Zero counters in chain or all chains\n" +" --check -C chain Test this packet on chain\n" +" --new -N chain Create a new user-defined chain\n" +" --delete-chain\n" +" -X [chain] Delete a user-defined chain\n" +" --policy -P chain target\n" +" Change policy on chain to target\n" +" --rename-chain\n" +" -E old-chain new-chain\n" +" Change chain name, (moving any references)\n" + +"Options:\n" +" --proto -p [!] proto protocol: by number or name, eg. `tcp'\n" +" --source -s [!] address[/mask]\n" +" source specification\n" +" --destination -d [!] address[/mask]\n" +" destination specification\n" +" --in-interface -i [!] input name[+]\n" +" network interface name ([+] for wildcard)\n" +" --jump -j target\n" +" target for rule\n" +" --numeric -n numeric output of addresses and ports\n" +" --out-interface -o [!] output name[+]\n" +" network interface name ([+] for wildcard)\n" +" --table -t table table to manipulate (default: `filter')\n" +" --verbose -v verbose mode\n" +" --exact -x expand numbers (display exact values)\n" +"[!] --fragment -f match second or further fragments only\n" +"[!] --version -V print package version.\n"); + + /* Print out any special helps. */ + if (iptables_targets) { + printf("\n"); + iptables_targets->help(); + } + if (iptables_matches) { + printf("\n"); + iptables_matches->help(); + } + + exit(0); +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1< 1; option >>= 1, ptr++); + + return *ptr; +} + +static char +cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void +add_command(int *cmd, const int newcmd, const int othercmds, int invert) +{ + if (invert) + exit_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + exit_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +int +check_inverse(const char option[], int *invert) +{ + if (option && strcmp(option, "!") == 0) { + if (*invert) + exit_error(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + + *invert = TRUE; + return TRUE; + } + return FALSE; +} + +static void * +fw_calloc(size_t count, size_t size) +{ + void *p; + + if ((p = calloc(count, size)) == NULL) { + perror("iptables: calloc failed"); + exit(1); + } + return p; +} + +static void * +fw_malloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) { + perror("iptables: malloc failed"); + exit(1); + } + return p; +} + +static struct in_addr * +host_to_addr(const char *name, unsigned int *naddr) +{ + struct hostent *host; + struct in_addr *addr; + unsigned int i; + + *naddr = 0; + if ((host = gethostbyname(name)) != NULL) { + if (host->h_addrtype != AF_INET || + host->h_length != sizeof(struct in_addr)) + return (struct in_addr *) NULL; + + while (host->h_addr_list[*naddr] != (char *) NULL) + (*naddr)++; + addr = fw_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + inaddrcpy(&(addr[i]), + (struct in_addr *) host->h_addr_list[i]); + return addr; + } + + return (struct in_addr *) NULL; +} + +static char * +addr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), AF_INET)) != NULL) + return (char *) host->h_name; + + return (char *) NULL; +} + +/* + * All functions starting with "parse" should succeed, otherwise + * the program fails. + * Most routines return pointers to static data that may change + * between calls to the same or other routines with a few exceptions: + * "host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask" + * return global static data. +*/ + +static struct in_addr * +parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in_addr *addrp, *addrptmp; + + if ((addrptmp = dotted_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = fw_malloc(sizeof(struct in_addr)); + inaddrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + exit_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in_addr * +parse_mask(char *mask) +{ + static struct in_addr maskaddr; + struct in_addr *addrp; + int bits; + + if (mask == NULL) { + /* no mask at all defaults to 32 bits */ + maskaddr.s_addr = 0xFFFFFFFF; + return &maskaddr; + } + if ((addrp = dotted_to_addr(mask)) != NULL) + /* dotted_to_addr already returns a network byte order addr */ + return addrp; + if ((bits = string_to_number(mask, 0, 32)) == -1) + exit_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits)); + return &maskaddr; + } + + maskaddr.s_addr = 0L; + return &maskaddr; +} + +static void +parse_hostnetworkmask(const char *name, struct in_addr **addrpp, + struct in_addr *maskp, unsigned int *naddrs) +{ + struct in_addr *addrp; + char buf[256]; + char *p; + int i, j, k, n; + + strncpy(buf, name, sizeof(buf) - 1); + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_mask(p + 1); + } else + addrp = parse_mask(NULL); + inaddrcpy(maskp, addrp); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (maskp->s_addr == 0L) + strcpy(buf, "0.0.0.0"); + + addrp = *addrpp = parse_hostnetwork(buf, naddrs); + n = *naddrs; + for (i = 0, j = 0; i < n; i++) { + addrp[j++].s_addr &= maskp->s_addr; + for (k = 0; k < j - 1; k++) { + if (addrp[k].s_addr == addrp[j - 1].s_addr) { + (*naddrs)--; + j--; + break; + } + } + } +} + +struct iptables_match * +find_match(const char *name, int tryload) +{ + struct iptables_match *ptr; + + for (ptr = iptables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + + if (!ptr && tryload) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + dlopen(path, RTLD_NOW); + return find_match(name, 0); + } + + return ptr; +} + +static u_int16_t +parse_protocol(const char *s) +{ + int proto = string_to_number(s, 0, 65535); + + if (proto == -1) { + struct protoent *pent; + + if ((pent = getprotobyname(s))) + proto = pent->p_proto; + else { + unsigned int i; + for (i = 0; + i < sizeof(chain_protos)/sizeof(struct pprot); + i++) { + if (strcmp(s, chain_protos[i].name) == 0) { + proto = chain_protos[i].num; + break; + } + } + if (i == sizeof(chain_protos)/sizeof(struct pprot)) + exit_error(PARAMETER_PROBLEM, + "unknown protocol `%s' specified", + s); + } + } + + return (u_int16_t)proto; +} + +static void +parse_interface(const char *arg, char *vianame, unsigned char *mask) +{ + int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + exit_error(PARAMETER_PROBLEM, + "interface name `%s' must be shorter than IFNAMSIZ" + " (%i)", arg, IFNAMSIZ-1); + + strcpy(vianame, arg); + if (vialen == 0) + memset(mask, 0, IFNAMSIZ); + else if (vianame[vialen - 1] == '+') { + memset(mask, 0xFF, vialen - 1); + memset(mask + vialen - 1, 0, IFNAMSIZ - vialen + 1); + /* Remove `+' */ + vianame[vialen - 1] = '\0'; + } else { + /* Include nul-terminator in match */ + memset(mask, 0xFF, vialen + 1); + memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1); + } + for (i = 0; vianame[i]; i++) { + if (!isalnum(vianame[i])) { + printf("Warning: wierd character in interface" + " `%s' (No aliases, :, ! or *).\n", + vianame); + break; + } + } +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + int rulenum = string_to_number(rule, 1, INT_MAX); + + if (rulenum == -1) + exit_error(PARAMETER_PROBLEM, + "Invalid rule number `%s'", rule); + + return rulenum; +} + +static const char * +parse_target(const char *targetname) +{ + const char *ptr; + + if (strlen(targetname) < 1) + exit_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname)+1 > sizeof(ipt_chainlabel)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%i chars max)", + targetname, sizeof(ipt_chainlabel)-1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + exit_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static char * +addr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +char * +addr_to_dotted(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep; + + bytep = (const unsigned char *) &(addrp->s_addr); + sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} +static char * +addr_to_anyname(const struct in_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL || + (name = addr_to_network(addr)) != NULL) + return name; + + return addr_to_dotted(addr); +} + +static char * +mask_to_dotted(const struct in_addr *mask) +{ + int i; + static char buf[20]; + u_int32_t maskaddr, bits; + + maskaddr = ntohl(mask->s_addr); + + if (maskaddr == 0xFFFFFFFFL) + /* we don't want to see "/32" */ + return ""; + + i = 32; + bits = 0xFFFFFFFEL; + while (--i >= 0 && maskaddr != bits) + bits <<= 1; + if (i >= 0) + sprintf(buf, "/%d", i); + else + /* mask was not a decent combination of 1's and 0's */ + sprintf(buf, "/%s", addr_to_dotted(mask)); + + return buf; +} + +int +string_to_number(const char *s, int min, int max) +{ + int number; + char *end; + + /* Handle hex, octal, etc. */ + number = (int)strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (min <= number && number <= max) + return number; + } + return -1; +} + +static void +set_option(unsigned int *options, unsigned int option, u_int8_t *invflg, + int invert) +{ + if (*options & option) + exit_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; + + if (invert) { + unsigned int i; + for (i = 0; 1 << i != option; i++); + + if (!inverse_for_options[i]) + exit_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +struct iptables_target * +find_target(const char *name, int tryload) +{ + struct iptables_target *ptr; + + /* Standard target? */ + if (strcmp(name, "") == 0 + || strcmp(name, IPTC_LABEL_ACCEPT) == 0 + || strcmp(name, IPTC_LABEL_DROP) == 0 + || strcmp(name, IPTC_LABEL_QUEUE) == 0 + || strcmp(name, IPTC_LABEL_RETURN) == 0) + name = "standard"; + + for (ptr = iptables_targets; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) + break; + } + + if (!ptr && tryload) { + char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so") + + strlen(name)]; + sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name); + dlopen(path, RTLD_NOW); + return find_target(name, 0); + } + + return ptr; +} + +static struct option * +merge_options(struct option *oldopts, struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +void +register_match(struct iptables_match *me) +{ + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_match(me->name, 0)) { + fprintf(stderr, "%s: match `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + /* Prepend to list. */ + me->next = iptables_matches; + iptables_matches = me; + me->m = NULL; + me->mflags = 0; + + opts = merge_options(opts, me->extra_opts, &me->option_offset); +} + +void +register_target(struct iptables_target *me) +{ + if (strcmp(me->version, program_version) != 0) { + fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n", + program_name, me->name, me->version, program_version); + exit(1); + } + + if (find_target(me->name, 0)) { + fprintf(stderr, "%s: target `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + /* Prepend to list. */ + me->next = iptables_targets; + iptables_targets = me; + me->t = NULL; + me->tflags = 0; + + opts = merge_options(opts, me->extra_opts, &me->option_offset); +} + +static void +print_header(unsigned int format, const char *chain, iptc_handle_t *handle) +{ + struct ipt_counters counters; + const char *pol = iptc_get_policy(chain, &counters, handle); + printf("Chain %s", chain); + if (pol) { + printf(" (policy %s", pol); + if (!(format & FMT_NOCOUNTS)) + printf(" %llu packets, %llu bytes", + counters.pcnt, counters.bcnt); + printf(")\n"); + } else { + unsigned int refs; + iptc_get_references(&refs, chain, handle); + printf(" (%u references)\n", refs); + } + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4s ", "%s "), "num"); + if (!(format & FMT_NOCOUNTS)) { + if (format & FMT_KILOMEGAGIGA) { + printf(FMT("%5s ","%s "), "pkts"); + printf(FMT("%5s ","%s "), "bytes"); + } else { + printf(FMT("%8s ","%s "), "pkts"); + printf(FMT("%10s ","%s "), "bytes"); + } + } + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ","%s "), "target"); + fputs(" prot ", stdout); + if (format & FMT_OPTIONS) + fputs("opt", stdout); + if (format & FMT_VIA) { + printf(FMT(" %-6s ","%s "), "in"); + printf(FMT("%-6s ","%s "), "out"); + } + printf(FMT(" %-19s ","%s "), "source"); + printf(FMT(" %-19s "," %s "), "destination"); + printf("\n"); +} + +static void +print_num(u_int64_t number, unsigned int format) +{ + if (format & FMT_KILOMEGAGIGA) { + if (number > 99999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + printf(FMT("%4lluG ","%lluG "),number); + } + else printf(FMT("%4lluM ","%lluM "), number); + } else + printf(FMT("%4lluK ","%lluK "), number); + } else + printf(FMT("%5llu ","%llu "), number); + } else + printf(FMT("%8llu ","%llu "), number); +} + +static int +print_match(const struct ipt_entry_match *m, + const struct ipt_ip *ip, + int numeric) +{ + struct iptables_match *match = find_match(m->u.name, 1); + + if (match) { + if (match->print) + match->print(ip, m, numeric); + } else { + if (m->u.name[0]) + printf("UNKNOWN match `%s' ", m->u.name); + } + /* Don't stop iterating. */ + return 0; +} + +/* e is called `fw' here for hysterical raisins */ +static void +print_firewall(const struct ipt_entry *fw, + const char *targname, + unsigned int num, + unsigned int format, + const iptc_handle_t handle) +{ + struct iptables_target *target = NULL; + const struct ipt_entry_target *t; + u_int8_t flags; + char buf[BUFSIZ]; + + /* User creates a chain called "REJECT": this overrides the + `REJECT' target module. Keep feeding them rope until the + revolution... Bwahahahahah */ + if (!iptc_is_chain(targname, handle)) + target = find_target(targname, 1); + else + target = find_target(IPT_STANDARD_TARGET, 1); + + t = ipt_get_target((struct ipt_entry *)fw); + flags = fw->ip.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num+1); + + if (!(format & FMT_NOCOUNTS)) { + print_num(fw->counters.pcnt, format); + print_num(fw->counters.bcnt, format); + } + + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ", "%s "), targname); + + fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout); + { + char *pname = proto_to_name(fw->ip.proto); + if (pname) + printf(FMT("%-5s", "%s "), pname); + else + printf(FMT("%-5hu", "%hu "), fw->ip.proto); + } + + if (format & FMT_OPTIONS) { + if (format & FMT_NOTABLE) + fputs("opt ", stdout); + fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); + fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); + fputc(' ', stdout); + } + + if (format & FMT_VIA) { + char iface[IFNAMSIZ+2]; + + if (fw->ip.invflags & IPT_INV_VIA_IN) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (fw->ip.iniface[0] != '\0') { + strcat(iface, fw->ip.iniface); + /* If it doesn't compare the nul-term, it's a + wildcard. */ + if (fw->ip.iniface_mask[strlen(fw->ip.iniface)] == 0) + strcat(iface, "+"); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT(" %-6s ","in %s "), iface); + + if (fw->ip.invflags & IPT_INV_VIA_OUT) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (fw->ip.outiface[0] != '\0') { + strcat(iface, fw->ip.outiface); + /* If it doesn't compare the nul-term, it's a + wildcard. */ + if (fw->ip.outiface_mask[strlen(fw->ip.outiface)] == 0) + strcat(iface, "+"); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT("%-6s ","out %s "), iface); + } + + fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); + if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","%s "), "anywhere"); + else { + if (format & FMT_NUMERIC) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src))); + strcat(buf, mask_to_dotted(&(fw->ip.smsk))); + printf(FMT("%-19s ","%s "), buf); + } + + fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout); + if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s","-> %s"), "anywhere"); + else { + if (format & FMT_NUMERIC) + sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst))); + strcat(buf, mask_to_dotted(&(fw->ip.dmsk))); + printf(FMT("%-19s","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + + IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC); + + if (target) { + if (target->print) + /* Print the target information. */ + target->print(&fw->ip, t, format & FMT_NUMERIC); + } else if (t->target_size != sizeof(*t)) + printf("[%u bytes of unknown target data] ", + t->target_size - sizeof(*t)); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void +print_firewall_line(const struct ipt_entry *fw, + const iptc_handle_t h) +{ + struct ipt_entry_target *t; + + t = ipt_get_target((struct ipt_entry *)fw); + print_firewall(fw, t->u.name, 0, FMT_PRINT_RULE, h); +} + +static int +append_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + ret &= iptc_append_entry(chain, fw, handle); + } + } + + return ret; +} + +static int +replace_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int rulenum, + const struct in_addr *saddr, + const struct in_addr *daddr, + int verbose, + iptc_handle_t *handle) +{ + fw->ip.src.s_addr = saddr->s_addr; + fw->ip.dst.s_addr = daddr->s_addr; + + if (verbose) + print_firewall_line(fw, *handle); + return iptc_replace_entry(chain, fw, rulenum, handle); +} + +static int +insert_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int rulenum, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + ret &= iptc_insert_entry(chain, fw, rulenum, handle); + } + } + + return ret; +} + +static int +delete_entry(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + ret &= iptc_delete_entry(chain, fw, handle); + } + } + return ret; +} + +static int +check_packet(const ipt_chainlabel chain, + struct ipt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + int verbose, + iptc_handle_t *handle) +{ + int ret = 1; + unsigned int i, j; + const char *msg; + + for (i = 0; i < nsaddrs; i++) { + fw->ip.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->ip.dst.s_addr = daddrs[j].s_addr; + if (verbose) + print_firewall_line(fw, *handle); + msg = iptc_check_packet(chain, fw, handle); + if (!msg) ret = 0; + else printf("%s\n", msg); + } + } + + return ret; +} + +static int +for_each_chain(int (*fn)(const ipt_chainlabel, int, iptc_handle_t *), + int verbose, iptc_handle_t *handle) +{ + int ret = 1; + const char *chain = NULL; + + while ((chain = iptc_next_chain(chain, handle))) { + ret &= fn(chain, verbose, handle); + } + + return ret; +} + +static int +flush_entries(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(flush_entries, verbose, handle); + + if (verbose) + fprintf(stdout, "Flushing chain `%s'\n", chain); + return iptc_flush_entries(chain, handle); + } + +static int +zero_entries(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) + return for_each_chain(zero_entries, verbose, handle); + + if (verbose) + fprintf(stdout, "Zeroing chain `%s'\n", chain); + return iptc_zero_entries(chain, handle); +} + +static int +delete_chain(const ipt_chainlabel chain, int verbose, + iptc_handle_t *handle) +{ + if (!chain) { + const char *i, *last = NULL; + int ret = 1; + + /* Iterate over built-ins */ + for (i = iptc_next_chain(NULL, handle); + i && iptc_builtin(i, *handle); + i = iptc_next_chain(i, handle)) + last = i; + + /* No user-defined chains? */ + if (!i) + return ret; + + /* Be careful iterating: it isn't safe during delete. */ + /* Re-iterate after each delete successful */ + while ((i = iptc_next_chain(last, handle)) != NULL) { + /* Skip over builtins. */ + if (!delete_chain(i, verbose, handle)) { + /* Delete failed; start next + iteration from here */ + last = i; + ret = 0; + } + } + return ret; + } + + if (verbose) + fprintf(stdout, "Deleting chain `%s'\n", chain); + return iptc_delete_chain(chain, handle); +} + +static int +list_entries(const ipt_chainlabel chain, int verbose, int numeric, + int expanded, int linenumbers, iptc_handle_t *handle) +{ + int found = 0; + unsigned int i, format; + const char *this = NULL; + + format = FMT_OPTIONS; + if (!verbose) + format |= FMT_NOCOUNTS; + else + format |= FMT_VIA; + + if (numeric) + format |= FMT_NUMERIC; + + if (!expanded) + format |= FMT_KILOMEGAGIGA; + + if (linenumbers) + format |= FMT_LINENUMBERS; + + + while ((this = iptc_next_chain(this, handle)) != NULL) { + if (chain && strcmp(chain, this) != 0) + continue; + + if (found) printf("\n"); + + print_header(format, this, handle); + for (i = 0; i < iptc_num_rules(this, handle); i++) + print_firewall(iptc_get_rule(this, i, handle), + iptc_get_target(this, i, handle), + i, + format, + *handle); + found = 1; + } + + errno = ENOENT; + return found; +} + +static struct ipt_entry * +generate_entry(const struct ipt_entry *fw, + struct iptables_match *matches, + struct ipt_entry_target *target) +{ + unsigned int size; + struct iptables_match *m; + struct ipt_entry *e; + + size = sizeof(struct ipt_entry); + for (m = matches; m; m = m->next) + size += m->m->match_size; + + e = fw_malloc(size + target->target_size); + *e = *fw; + e->target_offset = size; + e->next_offset = size + target->target_size; + + size = 0; + for (m = matches; m; m = m->next) { + memcpy(e->elems + size, m->m, m->m->match_size); + size += m->m->match_size; + } + memcpy(e->elems + size, target, target->target_size); + + return e; +} + +int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) +{ + struct ipt_entry fw, *e = NULL; + int invert = 0; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *daddrs = NULL; + + int c, verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, options = 0, command = 0; + int ret = 1; + struct iptables_match *m; + struct iptables_target *target = NULL; + const char *jumpto = ""; + char *protocol = NULL; + + memset(&fw, 0, sizeof(fw)); + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + while ((c = getopt_long(argc, argv, + "-A:C:D:R:I:L::F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:x", + opts, NULL)) != -1) { + switch (c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_DELETE_NUM; + } + break; + + case 'C': + add_command(&command, CMD_CHECK, CMD_NONE, + invert); + chain = optarg; + break; + + case 'R': + add_command(&command, CMD_REPLACE, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else rulenum = 1; + break; + + case 'L': + add_command(&command, CMD_LIST, CMD_ZERO, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'Z': + add_command(&command, CMD_ZERO, CMD_LIST, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'N': + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'E': + add_command(&command, CMD_RENAME_CHAIN, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + break; + + case 'P': + add_command(&command, CMD_SET_POLICY, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a chain and a policy", + cmd2char(CMD_SET_POLICY)); + break; + + case 'h': + if (!optarg) + optarg = argv[optind]; + + exit_printhelp(); + + /* + * Option selection + */ + case 'p': + if (check_inverse(optarg, &invert)) + optind++; + set_option(&options, OPT_PROTOCOL, &fw.ip.invflags, + invert); + + /* Canonicalize into lower case */ + for (protocol = argv[optind-1]; *protocol; protocol++) + *protocol = tolower(*protocol); + + protocol = argv[optind-1]; + fw.ip.proto = parse_protocol(protocol); + + if (fw.ip.proto == 0 + && (fw.ip.invflags & IPT_INV_PROTO)) + exit_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + fw.nfcache |= NFC_IP_PROTO; + break; + + case 's': + if (check_inverse(optarg, &invert)) + optind++; + set_option(&options, OPT_SOURCE, &fw.ip.invflags, + invert); + shostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_SRC; + break; + + case 'd': + if (check_inverse(optarg, &invert)) + optind++; + set_option(&options, OPT_DESTINATION, &fw.ip.invflags, + invert); + dhostnetworkmask = argv[optind-1]; + fw.nfcache |= NFC_IP_DST; + break; + + case 'j': + set_option(&options, OPT_JUMP, &fw.ip.invflags, + invert); + jumpto = parse_target(optarg); + target = find_target(jumpto, 1); + + if (target) { + size_t size = sizeof(struct ipt_entry_target) + + IPT_ALIGN(target->size); + + target->t = fw_calloc(size, 1); + target->t->target_size = size; + strcpy(target->t->u.name, jumpto); + target->init(target->t, &fw.nfcache); + } + break; + + + case 'i': + if (check_inverse(optarg, &invert)) + optind++; + set_option(&options, OPT_VIANAMEIN, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.iniface, + fw.ip.iniface_mask); + fw.nfcache |= NFC_IP_IF_IN; + break; + + case 'o': + if (check_inverse(optarg, &invert)) + optind++; + set_option(&options, OPT_VIANAMEOUT, &fw.ip.invflags, + invert); + parse_interface(argv[optind-1], + fw.ip.outiface, + fw.ip.outiface_mask); + fw.nfcache |= NFC_IP_IF_OUT; + break; + + case 'f': + set_option(&options, OPT_FRAGMENT, &fw.ip.invflags, + invert); + fw.ip.flags |= IPT_F_FRAG; + fw.nfcache |= NFC_IP_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&options, OPT_VERBOSE, + &fw.ip.invflags, invert); + verbose++; + break; + + case 'm': + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = find_match(optarg, 1); + if (!m) + exit_error(PARAMETER_PROBLEM, + "Couldn't load match `%s'", optarg); + else { + size_t size = sizeof(struct ipt_entry_match) + + IPT_ALIGN(m->size); + m->m = fw_calloc(size, 1); + m->m->match_size = size; + strcpy(m->m->u.name, optarg); + m->init(m->m, &fw.nfcache); + } + break; + + case 'n': + set_option(&options, OPT_NUMERIC, &fw.ip.invflags, + invert); + break; + + case 't': + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = argv[optind-1]; + break; + + case 'x': + set_option(&options, OPT_EXPANDED, &fw.ip.invflags, + invert); + break; + + case 'V': + if (invert) + printf("Not %s ;-)\n", program_version); + else + printf("%s v%s\n", + program_name, program_version); + exit(0); + + case '0': + set_option(&options, OPT_LINENUMBERS, &fw.ip.invflags, + invert); + break; + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (invert) + exit_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + invert = TRUE; + optarg[0] = '\0'; + continue; + } + exit_tryhelp(2); + + default: + /* FIXME: This scheme doesn't allow two of the same + matches --RR */ + if (!target + || !(target->parse(c - target->option_offset, + argv, invert, + &target->tflags, + &fw, &target->t))) { + for (m = iptables_matches; m; m = m->next) { + if (m->parse(c - m->option_offset, + argv, invert, + &m->mflags, + &fw, + &fw.nfcache, + &m->m)) + break; + } + + /* If you listen carefully, you can + acually hear this code suck. */ + if (!iptables_matches + && protocol + && (m = find_match(protocol, 1))) { + /* Try loading protocol */ + size_t size = sizeof(struct ipt_entry_match) + + IPT_ALIGN(m->size); + + m->m = fw_calloc(size, 1); + m->m->match_size = size; + strcpy(m->m->u.name, protocol); + m->init(m->m, &fw.nfcache); + + optind--; + continue; + } + if (!m) + exit_error(PARAMETER_PROBLEM, + "Unknown arg `%s'", + argv[optind-1]); + } + } + invert = FALSE; + } + + for (m = iptables_matches; m; m = m->next) + m->final_check(m->mflags); + if (target) + target->final_check(target->tflags); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + exit_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + exit_error(PARAMETER_PROBLEM, "no command specified"); + if (invert) + exit_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) { + if (!(options & OPT_DESTINATION)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(options & OPT_SOURCE)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + parse_hostnetworkmask(shostnetworkmask, &saddrs, + &(fw.ip.smsk), &nsaddrs); + + if (dhostnetworkmask) + parse_hostnetworkmask(dhostnetworkmask, &daddrs, + &(fw.ip.dmsk), &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + exit_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_CHECK && fw.ip.invflags != 0) + exit_error(PARAMETER_PROBLEM, "! not allowed with -%c", + cmd2char(CMD_CHECK)); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + exit_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, options); + + if (chain && strlen(chain) > IPT_FUNCTION_MAXNAMELEN) + exit_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %i chars)", + chain, IPT_FUNCTION_MAXNAMELEN); + + *handle = iptc_init(*table); + if (!*handle) + exit_error(VERSION_PROBLEM, + "can't initialize iptables table `%s': %s", + *table, iptc_strerror(errno)); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_INSERT + || command == CMD_REPLACE) { + /* -o not valid with incoming packets. */ + if (options & OPT_VIANAMEOUT) + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + exit_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEOUT), + chain); + } + + /* -i not valid with outgoing packets */ + if (options & OPT_VIANAMEIN) + if (strcmp(chain, "POSTROUTING") == 0 + || strcmp(chain, "OUTPUT") == 0) { + exit_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (target && iptc_is_chain(jumpto, *handle)) { + printf("Warning: using chain %s, not extension\n", + jumpto); + + target = NULL; + } + + /* If they didn't specify a target, or it's a chain + name, use standard. */ + if (!target + && (strlen(jumpto) == 0 + || iptc_is_chain(jumpto, *handle))) { + size_t size; + target = find_target(IPT_STANDARD_TARGET, 1); + + if (!target) + exit_error(OTHER_PROBLEM, + "Can't find standard target\n"); + + size = sizeof(struct ipt_entry_target) + + IPT_ALIGN(target->size); + target->t = fw_calloc(size, 1); + target->t->target_size = size; + strcpy(target->t->u.name, jumpto); + target->init(target->t, &fw.nfcache); + } + + if (!target) { + struct ipt_entry_target unknown_target; + + /* Don't know it. Must be extension with no + options? */ + unknown_target.target_size = sizeof(unknown_target); + strcpy(unknown_target.u.name, jumpto); + + e = generate_entry(&fw, iptables_matches, + &unknown_target); + } else { + e = generate_entry(&fw, iptables_matches, target->t); + } + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_CHECK: + ret = check_packet(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, handle); + break; + case CMD_DELETE: + ret = delete_entry(chain, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_DELETE_NUM: + ret = iptc_delete_num_entry(chain, rulenum - 1, handle); + break; + case CMD_REPLACE: + ret = replace_entry(chain, e, rulenum - 1, + saddrs, daddrs, options&OPT_VERBOSE, + handle); + break; + case CMD_INSERT: + ret = insert_entry(chain, e, rulenum - 1, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, + handle); + break; + case CMD_LIST: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + break; + case CMD_FLUSH: + ret = flush_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_ZERO: + ret = zero_entries(chain, options&OPT_VERBOSE, handle); + break; + case CMD_LIST|CMD_ZERO: + ret = list_entries(chain, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + options&OPT_EXPANDED, + options&OPT_LINENUMBERS, + handle); + if (ret) + ret = zero_entries(chain, + options&OPT_VERBOSE, handle); + break; + case CMD_NEW_CHAIN: + ret = iptc_create_chain(chain, handle); + break; + case CMD_DELETE_CHAIN: + ret = delete_chain(chain, options&OPT_VERBOSE, handle); + break; + case CMD_RENAME_CHAIN: + ret = iptc_rename_chain(chain, newname, handle); + break; + case CMD_SET_POLICY: + ret = iptc_set_policy(chain, policy, handle); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + + if (verbose > 1) + dump_entries(*handle); + + return ret; +} diff --git a/libipq/IPQ.notes.txt b/libipq/IPQ.notes.txt new file mode 100644 index 0000000..a2547fa --- /dev/null +++ b/libipq/IPQ.notes.txt @@ -0,0 +1,118 @@ +------------------------------------------------------------------------------------ +IPv4 Queuing Documentation +------------------------------------------------------------------------------------ + +Note: this file is temporary until the documentation is complete. + +Upgrade information: + * If upgrading from the queue device (v0.90.4 or below), you will need to + delete the old shared library, usually found in + /usr/local/lib/iptables/libipt_QUEUE.so + +TODO List: + * Non-blocking i/o for userspace api + * Buffered verdicts + * Reschedule processing if userspace busy + * Better session reliability + * Testsuite scripts, fix/improve tools + * Documentation + * Multiple queues per protocol? + * Performance analysis + * Userspace language bindings + + +Overview: +The following diagram is a conceptual view of how the queue operates: + + +---------+ + | QUEUE | + +---------+ + | | + | +---+ | --> dequeue() --> nf_reinject() [stack] + | | V | | + | +---+ | + | | + | +---+ | + | | W | | + | +---+ | + | | + | +---+ | + | | V | | + | +---+ | + | | + | +---+ | + | | V | | <-- set_verdict() [user] + | +---+ | + | | + | +---+ | + | | W | | + | +---+ | + | | + | +---+ | + | | N | | --> notify_user() [user] + | +---+ | + | | + +---------+ <-- set_mode() [user] + ^ + | + enqueue() + ^ + | + nf_queue() [stack] + + +The queue is processed via a kernel thread, which is woken up upon enqueue() +set_mode() and set_verdict(). + +As the queue is modal, and netlink is connectionless, a reasonable amount of +state needs to be maintained. + +Packet states: +N = new packet (default initial state) +W = user notfied, waiting for verdict +V = verdict set (usually by user) + +Queue states (settable by user): +* HOLD (default initial state) +enqueue packets +do not notify user +do not accept verdicts +do not dequeue packets + +* NORMAL +enqueue packets +notify user of new packets (may copy entire packet) +accept verdicts from user (may include modified packet) +dequeue packets + +* FLUSH (returns to HOLD when queue is empty, unless terminating) +do not enqueue packets +do not not notify user +set verdicts on all packets to NF_DROP +dequeue all packets for dropping + +Note that for HOLD & NORMAL queue states, new packets are dropped if the +queue is full. + +Known bugs: +- Userspace app gets unknown message from kernel if it sends an invalid + message type (should get an NLMSG_ERROR). + +Documentation notes: +libipq: +- Queue is held after flush completes, user must either start copying + or shutdown or the queue will fill up. + +- If you get a IPQ_ERR_RTRUNC message, your local receive + buffer is probably too small. Netlink has no way of detecting + this, and thinks the message was delivered (technically, it was, + to your *socket* receive buffer though). Thus you need to respond + with an NF_DROP for the packet and use a bigger buffer. + +- If you modify a packet, you must recalculate checksums as + appropriate before sending it back. + +- The code wont stop you from doing this, but try not to set NF_QUEUE + verdict on packets. + + \ No newline at end of file diff --git a/libipq/Makefile b/libipq/Makefile new file mode 100644 index 0000000..954e5e0 --- /dev/null +++ b/libipq/Makefile @@ -0,0 +1,11 @@ +#! /usr/bin/make + +EXTRAS+=libipq/libipq.a +#CFLAGS+=-DDEBUG_LIBIPTQ + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(SHARED_LIBS) $(EXTRAS) +else +libipq/libipq.a: libipq/libipq.a(libipq/libipq.o) +endif diff --git a/libipq/libipq.c b/libipq/libipq.c new file mode 100644 index 0000000..06e4a02 --- /dev/null +++ b/libipq/libipq.c @@ -0,0 +1,310 @@ +/* + * libipq.c + * + * IPQ userspace library. + * + * Please note that this library is still developmental, and there may + * be some API changes. + * + * 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. + * + */ + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * + * Private interface + * + ****************************************************************************/ + +enum { + IPQ_ERR_NONE = 0, + IPQ_ERR_IMPL, + IPQ_ERR_HANDLE, + IPQ_ERR_SOCKET, + IPQ_ERR_BIND, + IPQ_ERR_BUFFER, + IPQ_ERR_RECV, + IPQ_ERR_NLEOF, + IPQ_ERR_ADDRLEN, + IPQ_ERR_STRUNC, + IPQ_ERR_RTRUNC, + IPQ_ERR_NLRECV, + IPQ_ERR_SEND, + IPQ_ERR_SUPP, + IPQ_ERR_RECVBUF +}; +#define IPQ_MAXERR IPQ_ERR_RECVBUF + +struct ipq_errmap_t { + int errcode; + char *message; +} ipq_errmap[] = { + { IPQ_ERR_NONE, "Unknown error" }, + { IPQ_ERR_IMPL, "Implementation error" }, + { IPQ_ERR_HANDLE, "Unable to create netlink handle" }, + { IPQ_ERR_SOCKET, "Unable to create netlink socket" }, + { IPQ_ERR_BIND, "Unable to bind netlink socket" }, + { IPQ_ERR_BUFFER, "Unable to allocate buffer" }, + { IPQ_ERR_RECV, "Failed to receive netlink message" }, + { IPQ_ERR_NLEOF, "Received EOF on netlink socket" }, + { IPQ_ERR_ADDRLEN, "Invalid peer address length" }, + { IPQ_ERR_STRUNC, "Sent message truncated" }, + { IPQ_ERR_RTRUNC, "Received message truncated" }, + { IPQ_ERR_NLRECV, "Received error from netlink" }, + { IPQ_ERR_SEND, "Failed to send netlink message" }, + { IPQ_ERR_SUPP, "Operation not supported" }, + { IPQ_ERR_RECVBUF, "Receive buffer size invalid" } +}; + +static int ipq_errno = IPQ_ERR_NONE; + +static ssize_t ipq_netlink_sendto(const struct ipq_handle *h, + const void *msg, size_t len); + +static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h, + unsigned char *buf, size_t len); + +static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h, + const struct msghdr *msg, + unsigned int flags); + +static char *ipq_strerror(int errcode); + +static ssize_t ipq_netlink_sendto(const struct ipq_handle *h, + const void *msg, size_t len) +{ + int status = sendto(h->fd, msg, len, 0, + (struct sockaddr *)&h->peer, sizeof(h->peer)); + if (status < 0) + ipq_errno = IPQ_ERR_SEND; + return status; +} + +static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h, + const struct msghdr *msg, + unsigned int flags) +{ + int status = sendmsg(h->fd, msg, flags); + if (status < 0) + ipq_errno = IPQ_ERR_SEND; + return status; +} + +static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h, + unsigned char *buf, size_t len) +{ + int addrlen, status; + struct nlmsghdr *nlh; + + if (len < sizeof(struct nlmsgerr)) { + ipq_errno = IPQ_ERR_RECVBUF; + return -1; + } + addrlen = sizeof(h->peer); + status = recvfrom(h->fd, buf, len, 0, + (struct sockaddr *)&h->peer, &addrlen); + if (status < 0) { + ipq_errno = IPQ_ERR_RECV; + return status; + } + if (addrlen != sizeof(h->peer)) { + ipq_errno = IPQ_ERR_RECV; + return -1; + } + if (status == 0) { + ipq_errno = IPQ_ERR_NLEOF; + return -1; + } + nlh = (struct nlmsghdr *)buf; + if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > status) { + ipq_errno = IPQ_ERR_RTRUNC; + return -1; + } + return status; +} + +static char *ipq_strerror(int errcode) +{ + if (errcode < 0 || errcode > IPQ_MAXERR) + errcode = IPQ_ERR_IMPL; + return ipq_errmap[errcode].message; +} + +/**************************************************************************** + * + * Public interface + * + ****************************************************************************/ + +/* + * Create and initialise an ipq handle. + * FIXME: implement flags. + */ +struct ipq_handle *ipq_create_handle(u_int32_t flags) +{ + int status; + struct ipq_handle *h; + + h = (struct ipq_handle *)malloc(sizeof(struct ipq_handle)); + if (h == NULL) { + ipq_errno = IPQ_ERR_HANDLE; + return NULL; + } + memset(h, 0, sizeof(struct ipq_handle)); + h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL); + if (h->fd == -1) { + ipq_errno = IPQ_ERR_SOCKET; + close(h->fd); + free(h); + return NULL; + } + memset(&h->local, 0, sizeof(struct sockaddr_nl)); + h->local.nl_family = AF_NETLINK; + h->local.nl_pid = getpid(); + h->local.nl_groups = 0; + status = bind(h->fd, (struct sockaddr *)&h->local, sizeof(h->local)); + if (status == -1) { + ipq_errno = IPQ_ERR_BIND; + close(h->fd); + free(h); + return NULL; + } + memset(&h->peer, 0, sizeof(struct sockaddr_nl)); + h->peer.nl_family = AF_NETLINK; + h->peer.nl_pid = 0; + h->peer.nl_groups = 0; + return h; +} + +/* + * No error condition is checked here at this stage, but it may happen + * if/when reliable messaging is implemented. + */ +int ipq_destroy_handle(struct ipq_handle *h) +{ + if (h) { + close(h->fd); + free(h); + } + return 0; +} + +int ipq_set_mode(const struct ipq_handle *h, + u_int8_t mode, size_t range) +{ + struct { + struct nlmsghdr nlh; + ipq_peer_msg_t pm; + } req; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req)); + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_type = IPQM_MODE; + req.nlh.nlmsg_pid = h->local.nl_pid; + req.pm.msg.mode.value = mode; + req.pm.msg.mode.range = range; + return ipq_netlink_sendto(h, (void *)&req, req.nlh.nlmsg_len); +} + +/* Note: timeout is not yet implemented */ +ssize_t ipq_read(const struct ipq_handle *h, + unsigned char *buf, size_t len, int timeout) +{ + return ipq_netlink_recvfrom(h, buf, len); +} + +int ipq_message_type(const unsigned char *buf) +{ + return ((struct nlmsghdr*)buf)->nlmsg_type; +} + +int ipq_get_msgerr(const unsigned char *buf) +{ + struct nlmsghdr *h = (struct nlmsghdr *)buf; + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + return -err->error; +} + +ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf) +{ + return NLMSG_DATA((struct nlmsghdr *)(buf)); +} + +int ipq_set_verdict(const struct ipq_handle *h, + unsigned long id, + unsigned int verdict, + size_t data_len, + unsigned char *buf) +{ + unsigned char nvecs; + size_t tlen; + struct nlmsghdr nlh; + ipq_peer_msg_t pm; + struct iovec iov[3]; + struct msghdr msg; + + memset(&nlh, 0, sizeof(nlh)); + nlh.nlmsg_flags = NLM_F_REQUEST; + nlh.nlmsg_type = IPQM_VERDICT; + nlh.nlmsg_pid = h->local.nl_pid; + memset(&pm, 0, sizeof(pm)); + pm.msg.verdict.value = verdict; + pm.msg.verdict.id = id; + pm.msg.verdict.data_len = data_len; + iov[0].iov_base = &nlh; + iov[0].iov_len = sizeof(nlh); + iov[1].iov_base = ± + iov[1].iov_len = sizeof(pm); + tlen = sizeof(nlh) + sizeof(pm); + nvecs = 2; + if (data_len && buf) { + iov[2].iov_base = buf; + iov[2].iov_len = data_len; + tlen += data_len; + nvecs++; + } + msg.msg_name = (void *)&h->peer; + msg.msg_namelen = sizeof(h->peer); + msg.msg_iov = iov; + msg.msg_iovlen = nvecs; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + nlh.nlmsg_len = tlen; + return ipq_netlink_sendmsg(h, &msg, 0); +} + +/* Not implemented yet */ +int ipq_ctl(const struct ipq_handle *h, int request, ...) +{ + return 1; +} + +void ipq_perror(const char *s) +{ + if (s) + fputs(s, stderr); + else + fputs("ERROR", stderr); + if (ipq_errno) + fprintf(stderr, ": %s", ipq_strerror(ipq_errno)); + if (errno) + fprintf(stderr, ": %s", strerror(errno)); + fputc('\n', stderr); +} diff --git a/libiptc/Makefile b/libiptc/Makefile new file mode 100644 index 0000000..e1bc7be --- /dev/null +++ b/libiptc/Makefile @@ -0,0 +1,16 @@ +#! /usr/bin/make + +EXTRAS+=libiptc/libiptc.a + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(KERN_TARGETS) $(SHARED_LIBS) $(EXTRAS) + +else +EXTRA_DEPENDS+=libiptc/libiptc.d + +libiptc/libiptc.a: libiptc/libiptc.a(libiptc/libiptc.o) + +libiptc/libiptc.d: %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | sed -e 's@^.*\.o:@$*.d $*.a:@' > $@ +endif diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c new file mode 100644 index 0000000..9109703 --- /dev/null +++ b/libiptc/libiptc.c @@ -0,0 +1,1828 @@ +/* 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 + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include + +#define IP_VERSION 4 +#define IP_OFFSET 0x1FFF + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/local/lib/iptables" +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +static const char *hooknames[] += { [NF_IP_PRE_ROUTING] "PREROUTING", + [NF_IP_LOCAL_IN] "INPUT", + [NF_IP_FORWARD] "FORWARD", + [NF_IP_LOCAL_OUT] "OUTPUT", + [NF_IP_POST_ROUTING] "POSTROUTING" +}; + +struct counter_map +{ + enum { + COUNTER_MAP_NOMAP, + COUNTER_MAP_NORMAL_MAP, + COUNTER_MAP_ZEROED + } maptype; + unsigned int mappos; +}; + +/* Convenience structures */ +struct ipt_error_target +{ + struct ipt_entry_target t; + char error[IPT_TABLE_MAXNAMELEN]; +}; + +struct iptc_handle +{ + /* Have changes been made? */ + int changed; + /* Size in here reflects original state. */ + struct ipt_getinfo info; + + struct counter_map *counter_map; + /* Array of hook names */ + const char **hooknames; + + /* Number in here reflects current state. */ + unsigned int new_number; + struct ipt_get_entries entries; +}; + +static void do_check(iptc_handle_t h, unsigned int line); +#define CHECK(h) do_check((h), __LINE__) + +static inline int +get_number(const struct ipt_entry *i, + const struct ipt_entry *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const iptc_handle_t h, const struct ipt_entry *seek) +{ + unsigned int pos = 0; + + if (IPT_ENTRY_ITERATE(h->entries.entries, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (unsigned char *)seek - h->entries.entries); + abort(); + } + return pos; +} + +static inline int +get_entry_n(struct ipt_entry *i, + unsigned int number, + unsigned int *pos, + struct ipt_entry **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static struct ipt_entry * +index2entry(iptc_handle_t h, unsigned int index) +{ + unsigned int pos = 0; + struct ipt_entry *ret = NULL; + + IPT_ENTRY_ITERATE(h->entries.entries, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline struct ipt_entry * +get_entry(iptc_handle_t h, unsigned int offset) +{ + return (struct ipt_entry *)(h->entries.entries + offset); +} + +static inline unsigned long +entry2offset(const iptc_handle_t h, const struct ipt_entry *e) +{ + return (unsigned char *)e - h->entries.entries; +} + +static unsigned long +index2offset(iptc_handle_t h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static const char * +get_errorlabel(iptc_handle_t h, unsigned int offset) +{ + struct ipt_entry *e; + + e = get_entry(h, offset); + if (strcmp(ipt_get_target(e)->u.name, IPT_ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)ipt_get_target(e)->data; +} + +/* Allocate handle of given size */ +static iptc_handle_t +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + iptc_handle_t h; + + len = sizeof(struct iptc_handle) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + h->counter_map = (void *)h + + sizeof(struct iptc_handle) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + + return h; +} + +iptc_handle_t +iptc_init(const char *tablename) +{ + iptc_handle_t h; + struct ipt_getinfo info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = iptc_init; + + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + if (strlen(tablename) >= IPT_TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + strcpy(info.name, tablename); + if (getsockopt(sockfd, IPPROTO_IP, IPT_SO_GET_INFO, &info, &s) < 0) + return NULL; + + if ((h = alloc_handle(info.name, info.size, info.num_entries)) + == NULL) + 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 ipt_get_entries) + h->info.size; + + if (getsockopt(sockfd, IPPROTO_IP, IPT_SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + free(h); + return NULL; + } + + CHECK(h); + return h; +} + +#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)) + +static inline int +print_match(const struct ipt_entry_match *m) +{ + printf("Match name: `%s'\n", m->u.name); + return 0; +} + +int +dump_entry(struct ipt_entry *e, const iptc_handle_t handle) +{ + size_t i; + struct ipt_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 = ipt_get_target(e); + printf("Target name: `%s' [%u]\n", t->u.name, t->target_size); + if (strcmp(t->u.name, IPT_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 == IPT_RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.name, IPT_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +void +dump_entries(const iptc_handle_t handle) +{ + CHECK(handle); + + printf("libiptc v%s. %u entries, %u bytes.\n", + NETFILTER_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[NF_IP_PRE_ROUTING], + handle->info.hook_entry[NF_IP_LOCAL_IN], + handle->info.hook_entry[NF_IP_FORWARD], + handle->info.hook_entry[NF_IP_LOCAL_OUT], + handle->info.hook_entry[NF_IP_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[NF_IP_PRE_ROUTING], + handle->info.underflow[NF_IP_LOCAL_IN], + handle->info.underflow[NF_IP_FORWARD], + handle->info.underflow[NF_IP_LOCAL_OUT], + handle->info.underflow[NF_IP_POST_ROUTING]); + + IPT_ENTRY_ITERATE(handle->entries.entries, handle->entries.size, + dump_entry, handle); +} + +static inline int +find_user_label(struct ipt_entry *e, unsigned int *off, const char *name) +{ + /* Increment first: they want offset of entry AFTER label */ + (*off) += e->next_offset; + + if (strcmp(ipt_get_target(e)->u.name, IPT_ERROR_TARGET) == 0 + && strcmp(ipt_get_target(e)->data, name) == 0) + return 1; + + return 0; +} + +/* Returns offset of label. */ +static int +find_label(unsigned int *off, + const char *name, + const iptc_handle_t handle) +{ + unsigned int i; + + /* Builtin chain name? */ + i = iptc_builtin(name, handle); + if (i != 0) { + *off = handle->info.hook_entry[i-1]; + return 1; + } + + /* User chain name? */ + *off = 0; + if (IPT_ENTRY_ITERATE(handle->entries.entries, handle->entries.size, + find_user_label, off, name) != 0) { + /* last error node doesn't count */ + if (*off != handle->entries.size) + return 1; + } + + return 0; +} + +/* Does this chain exist? */ +int iptc_is_chain(const char *chain, const iptc_handle_t handle) +{ + unsigned int dummy; + + /* avoid infinite recursion */ +#if 0 + CHECK(handle); +#endif + + return find_label(&dummy, chain, handle); +} + +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const iptc_handle_t handle, unsigned int start) +{ + unsigned int last_off, off; + struct ipt_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 ipt_entry_target *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NF_IP_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 = ipt_get_target(e); + if (strcmp(t->u.name, IPT_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; prev = NULL means + first chain. Returns NULL at end. */ +const char * +iptc_next_chain(const char *prev, iptc_handle_t *handle) +{ + unsigned int pos; + unsigned int i; + struct ipt_entry *e; + + CHECK(*handle); + if (!prev) + pos = 0; + else { + if (!find_label(&pos, prev, *handle)) { + errno = ENOENT; + return NULL; + } + pos = get_chain_end(*handle, pos); + /* Next entry. */ + e = get_entry(*handle, pos); + pos += e->next_offset; + } + e = get_entry(*handle, pos); + + /* Return names of entry points if it is one. */ + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + if (((*handle)->info.valid_hooks & (1 << i)) + && pos == (*handle)->info.hook_entry[i]) + return (*handle)->hooknames[i]; + } + /* If this is the last element, iteration finished */ + if (pos + e->next_offset == (*handle)->entries.size) + return NULL; + + if (strcmp(ipt_get_target(e)->u.name, IPT_ERROR_TARGET) != 0) { + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: position %u/%u not an error label\n", + pos, (*handle)->entries.size); + abort(); + } + + return (const char *)ipt_get_target(e)->data; +} + +/* How many rules in this chain? */ +unsigned int +iptc_num_rules(const char *chain, iptc_handle_t *handle) +{ + unsigned int off = 0; + struct ipt_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 ipt_entry *iptc_get_rule(const char *chain, + unsigned int n, + iptc_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); +} + +static const char *target_name(iptc_handle_t handle, struct ipt_entry *e) +{ + int spos; + unsigned int labelidx; + struct ipt_entry *jumpto; + + if (strcmp(ipt_get_target(e)->u.name, IPT_STANDARD_TARGET) != 0) + return ipt_get_target(e)->u.name; + + /* Standard target: evaluate */ + spos = *(int *)ipt_get_target(e)->data; + if (spos < 0) { + if (spos == IPT_RETURN) + return IPTC_LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return IPTC_LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return IPTC_LABEL_DROP; + else if (spos == -NF_ACCEPT-1) + return IPTC_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 *iptc_get_target(const char *chain, + unsigned int n, + iptc_handle_t *handle) +{ + unsigned int pos = 0, chainindex; + struct ipt_entry *e; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + e = index2entry(*handle, chainindex + n); + + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +iptc_builtin(const char *chain, const iptc_handle_t handle) +{ + unsigned int i; + + for (i = 0; i < NF_IP_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 * +iptc_get_policy(const char *chain, + struct ipt_counters *counters, + iptc_handle_t *handle) +{ + unsigned int start; + struct ipt_entry *e; + int hook; + + CHECK(*handle); + hook = iptc_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 ipt_entry *e, + unsigned char *base, + unsigned int offset, int delta_offset) +{ + struct ipt_standard_target *t = (void *)ipt_get_target(e); + unsigned int curr = (unsigned 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.name, IPT_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, iptc_handle_t *handle) +{ + IPT_ENTRY_ITERATE((*handle)->entries.entries, + (*handle)->entries.size, + correct_verdict, (*handle)->entries.entries, + offset, delta_offset); + + (*handle)->changed = 1; + 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 ipt_entry *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + iptc_handle_t *handle) +{ + iptc_handle_t newh; + struct ipt_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 < NF_IP_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)->info.size + rules_size, + (*handle)->info.num_entries + num_rules); + if (!newh) + return 0; + newh->info = newinfo; + + /* Copy pre... */ + memcpy(newh->entries.entries, (*handle)->entries.entries, offset); + /* ... Insert new ... */ + memcpy(newh->entries.entries + offset, insert, rules_size); + /* ... copy post */ + memcpy(newh->entries.entries + offset + rules_size, + (*handle)->entries.entries + 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_NOMAP, 0 }); + + newh->new_number = (*handle)->new_number + num_rules; + newh->entries.size = (*handle)->entries.size + rules_size; + newh->hooknames = (*handle)->hooknames; + + 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, + iptc_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 < NF_IP_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((*handle)->entries.entries + offset, + (*handle)->entries.entries + 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 ipt_entry *e, int verdict) +{ + struct ipt_standard_target *t; + + t = (struct ipt_standard_target *)ipt_get_target(e); + + if (t->target.target_size != IPT_ALIGN(sizeof(struct ipt_standard_target))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.name, 0, IPT_FUNCTION_MAXNAMELEN); + strcpy(t->target.u.name, IPT_STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const iptc_handle_t handle, + struct ipt_entry *e, + unsigned int offset, + struct ipt_entry_target *old) +{ + struct ipt_entry_target *t = ipt_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.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.name, IPTC_LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.name, IPTC_LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.name, IPTC_LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.name, IPTC_LABEL_RETURN) == 0) + return standard_map(e, IPT_RETURN); + else if (iptc_builtin(t->u.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + unsigned int exists; + + if (find_label(&exists, t->u.name, handle)) + return standard_map(e, exists); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.name + strlen(t->u.name), + 0, + IPT_FUNCTION_MAXNAMELEN - strlen(t->u.name)); + return 1; +} + +static void +unmap_target(struct ipt_entry *e, struct ipt_entry_target *old) +{ + struct ipt_entry_target *t = ipt_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 +iptc_insert_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + unsigned int rulenum, + iptc_handle_t *handle) +{ + unsigned int chainoff, chainindex, offset; + struct ipt_entry_target old; + int ret; + + CHECK(*handle); + iptc_fn = iptc_insert_entry; + if (!find_label(&chainoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + chainindex = entry2index(*handle, get_entry(*handle, chainoff)); + + if (index2entry(*handle, chainindex + rulenum) + > get_entry(*handle, get_chain_end(*handle, chainoff))) { + 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 ipt_entry *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((struct ipt_entry *)e, &old); + CHECK(*handle); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +iptc_replace_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + unsigned int rulenum, + iptc_handle_t *handle) +{ + unsigned int chainoff, chainindex, offset; + struct ipt_entry_target old; + int ret; + + CHECK(*handle); + iptc_fn = iptc_replace_entry; + + if (!find_label(&chainoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + chainindex = entry2index(*handle, get_entry(*handle, chainoff)); + + if (index2entry(*handle, chainindex + rulenum) + >= get_entry(*handle, get_chain_end(*handle, chainoff))) { + 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 ipt_entry *)e, offset, &old)) + return 0; + CHECK(*handle); + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((struct ipt_entry *)e, &old); + CHECK(*handle); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +iptc_append_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + iptc_handle_t *handle) +{ + unsigned int startoff, endoff; + struct ipt_entry_target old; + int ret; + + CHECK(*handle); + iptc_fn = iptc_append_entry; + if (!find_label(&startoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + endoff = get_chain_end(*handle, startoff); + if (!map_target(*handle, (struct ipt_entry *)e, endoff, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, endoff, + entry2index(*handle, get_entry(*handle, endoff)), + 0, handle); + unmap_target((struct ipt_entry *)e, &old); + CHECK(*handle); + return ret; +} + +static inline int +match_different(const struct ipt_entry_match *a, + const char *a_elems, + const char *b_elems) +{ + const struct ipt_entry_match *b; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + (a_elems - (char *)a); + + if (a->match_size != b->match_size) + return 1; + + if (strcmp(a->u.name, b->u.name) != 0) + return 1; + + /* FIXME: If kernel modifies these (eg. RATE), then we'll + never match --RR */ + if (memcmp(a->data, b->data, a->match_size - sizeof(*a)) != 0) + return 1; + + return 0; +} + +static inline int +is_same(const struct ipt_entry *a, const struct ipt_entry *b) +{ + unsigned int i; + struct ipt_entry_target *ta, *tb; + + 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.smsk.s_addr != b->ip.smsk.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; + + if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems)) + return 0; + + ta = ipt_get_target((struct ipt_entry *)a); + tb = ipt_get_target((struct ipt_entry *)b); + if (ta->target_size != tb->target_size) + return 0; + if (strcmp(ta->u.name, tb->u.name) != 0) + return 0; + /* FIXME: If kernel modifies these, then we never match --RR */ + if (memcmp(ta->data, tb->data, ta->target_size - sizeof(*ta)) != 0) + return 0; + + return 1; +} + +/* Delete the first rule in `chain' which matches `fw'. */ +int +iptc_delete_entry(const ipt_chainlabel chain, + const struct ipt_entry *origfw, + iptc_handle_t *handle) +{ + unsigned int offset, lastoff; + struct ipt_entry *e, *fw; + + CHECK(*handle); + iptc_fn = iptc_delete_entry; + if (!find_label(&offset, chain, *handle)) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + lastoff = get_chain_end(*handle, offset); + + for (; offset < lastoff; offset += e->next_offset) { + struct ipt_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)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + CHECK(*handle); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +iptc_delete_num_entry(const ipt_chainlabel chain, + unsigned int rulenum, + iptc_handle_t *handle) +{ + unsigned int chainstart; + unsigned int index; + int ret; + struct ipt_entry *e; + + CHECK(*handle); + iptc_fn = iptc_delete_num_entry; + if (!find_label(&chainstart, chain, *handle)) { + errno = ENOENT; + return 0; + } + + index = entry2index(*handle, get_entry(*handle, chainstart)) + + rulenum; + + if (index + >= entry2index(*handle, + get_entry(*handle, + get_chain_end(*handle, chainstart)))) { + 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); + CHECK(*handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +iptc_check_packet(const ipt_chainlabel chain, + struct ipt_entry *entry, + iptc_handle_t *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +iptc_flush_entries(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int startoff, endoff, startindex, endindex; + int ret; + + CHECK(*handle); + iptc_fn = iptc_flush_entries; + if (!find_label(&startoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + endoff = get_chain_end(*handle, startoff); + startindex = entry2index(*handle, get_entry(*handle, startoff)); + endindex = entry2index(*handle, get_entry(*handle, endoff)); + + ret = delete_rules(endindex - startindex, + endoff - startoff, startoff, startindex, + handle); + CHECK(*handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +iptc_zero_entries(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int i, end; + + CHECK(*handle); + if (!find_label(&i, chain, *handle)) { + errno = ENOENT; + return 0; + } + end = get_chain_end(*handle, i); + + i = entry2index(*handle, get_entry(*handle, i)); + end = entry2index(*handle, get_entry(*handle, end)); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + (*handle)->changed = 1; + + CHECK(*handle); + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +iptc_create_chain(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int pos; + int ret; + struct { + struct ipt_entry head; + struct ipt_error_target name; + struct ipt_entry ret; + struct ipt_standard_target target; + } newc; + + CHECK(*handle); + iptc_fn = iptc_create_chain; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(&pos, chain, *handle) + || strcmp(chain, IPTC_LABEL_DROP) == 0 + || strcmp(chain, IPTC_LABEL_ACCEPT) == 0 + || strcmp(chain, IPTC_LABEL_QUEUE) == 0 + || strcmp(chain, IPTC_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 ipt_entry); + newc.head.next_offset + = sizeof(struct ipt_entry) + sizeof(struct ipt_error_target); + strcpy(newc.name.t.u.name, IPT_ERROR_TARGET); + newc.name.t.target_size = sizeof(struct ipt_error_target); + strcpy(newc.name.error, chain); + + newc.ret.target_offset = sizeof(struct ipt_entry); + newc.ret.next_offset + = sizeof(struct ipt_entry)+sizeof(struct ipt_standard_target); + strcpy(newc.target.target.u.name, IPT_STANDARD_TARGET); + newc.target.target.target_size = sizeof(struct ipt_standard_target); + newc.target.verdict = IPT_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); + CHECK(*handle); + return ret; +} + +static int +count_ref(struct ipt_entry *e, unsigned int offset, unsigned int *ref) +{ + struct ipt_standard_target *t; + + if (strcmp(ipt_get_target(e)->u.name, IPT_STANDARD_TARGET) == 0) { + t = (struct ipt_standard_target *)ipt_get_target(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +iptc_get_references(unsigned int *ref, const ipt_chainlabel chain, + iptc_handle_t *handle) +{ + unsigned int offset; + + CHECK(*handle); + if (!find_label(&offset, chain, *handle)) { + errno = ENOENT; + return 0; + } + + *ref = 0; + IPT_ENTRY_ITERATE((*handle)->entries.entries, + (*handle)->entries.size, + count_ref, offset, ref); + return 1; +} + +/* Deletes a chain. */ +int +iptc_delete_chain(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int chainoff, labelidx, labeloff; + unsigned int references; + struct ipt_entry *e; + int ret; + + CHECK(*handle); + if (!iptc_get_references(&references, chain, handle)) + return 0; + + iptc_fn = iptc_delete_chain; + + if (iptc_builtin(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!find_label(&chainoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + e = get_entry(*handle, chainoff); + if (get_chain_end(*handle, chainoff) != chainoff) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = entry2index(*handle, e) - 1; + labeloff = index2offset(*handle, labelidx); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + e->next_offset, + labeloff, labelidx, handle); + CHECK(*handle); + return ret; +} + +/* Renames a chain. */ +int iptc_rename_chain(const ipt_chainlabel oldname, + const ipt_chainlabel newname, + iptc_handle_t *handle) +{ + unsigned int chainoff, labeloff, labelidx; + struct ipt_error_target *t; + + CHECK(*handle); + iptc_fn = iptc_rename_chain; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT + RETURN. */ + if (find_label(&chainoff, newname, *handle) + || strcmp(newname, IPTC_LABEL_DROP) == 0 + || strcmp(newname, IPTC_LABEL_ACCEPT) == 0 + || strcmp(newname, IPTC_LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!find_label(&chainoff, oldname, *handle) + || iptc_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 = entry2index(*handle, get_entry(*handle, chainoff)) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + ipt_get_target(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + (*handle)->changed = 1; + + CHECK(*handle); + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +iptc_set_policy(const ipt_chainlabel chain, + const ipt_chainlabel policy, + iptc_handle_t *handle) +{ + unsigned int hook; + unsigned int policyoff; + struct ipt_entry *e; + struct ipt_standard_target *t; + + CHECK(*handle); + /* Figure out which chain. */ + hook = iptc_builtin(chain, *handle); + if (hook == 0) { + errno = EINVAL; + 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 ipt_standard_target *)ipt_get_target(e); + + if (strcmp(policy, IPTC_LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, IPTC_LABEL_DROP) == 0) + t->verdict = -NF_DROP - 1; + else { + errno = EINVAL; + return 0; + } + (*handle)->counter_map[entry2index(*handle, e)] + = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); + (*handle)->changed = 1; + + CHECK(*handle); + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `iptc_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 ipt_counters *answer, + const struct ipt_counters *a, + const struct ipt_counters *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +iptc_commit(iptc_handle_t *handle) +{ + /* Replace, then map back the counters. */ + struct ipt_replace *repl; + struct ipt_counters_info *newcounters; + unsigned int i; + size_t counterlen + = sizeof(struct ipt_counters_info) + + sizeof(struct ipt_counters) * (*handle)->new_number; + + CHECK(*handle); +#if 0 + 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 ipt_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.entries, + (*handle)->entries.size); + + if (setsockopt(sockfd, IPPROTO_IP, IPT_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 ipt_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; + } + } + + if (setsockopt(sockfd, IPPROTO_IP, IPT_SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + free(*handle); + *handle = NULL; + return 1; +} + +/* Get raw socket. */ +int +iptc_get_raw_socket() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +iptc_strerror(int err) +{ + unsigned int i; + struct table_struct { + void *fn; + int err; + const char *message; + } table [] = + { { 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" }, + { iptc_init, EPERM, "Permission denied (you must be root)" }, + { iptc_init, EINVAL, "Module is wrong version" }, + { iptc_delete_chain, ENOTEMPTY, "Chain is not empty" }, + { iptc_delete_chain, EINVAL, "Can't delete built-in chain" }, + { iptc_delete_chain, EMLINK, + "Can't delete chain with references left" }, + { iptc_create_chain, EEXIST, "Chain already exists" }, + { iptc_insert_entry, E2BIG, "Index of insertion too big" }, + { iptc_replace_entry, E2BIG, "Index of replacement too big" }, + { iptc_delete_num_entry, E2BIG, "Index of deletion too big" }, + { iptc_insert_entry, ELOOP, "Loop found in table" }, + { iptc_insert_entry, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { iptc_check_packet, EINVAL, + "bad arguments (does that interface exist?)" }, + /* ENOENT for DELETE probably means no matching rule */ + { iptc_delete_entry, ENOENT, + "bad rule (does a matching rule exist in that chain?)" }, + { NULL, ENOENT, "No extended 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); +} + +/***************************** 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 ipt_entry_match *m, unsigned int *off) +{ + assert(m->match_size >= sizeof(struct ipt_entry_match)); + + (*off) += m->match_size; + return 0; +} + +static inline int +check_entry(const struct ipt_entry *e, unsigned int *i, unsigned int *off, + unsigned int user_offset, int *was_return, + iptc_handle_t h) +{ + unsigned int toff; + struct ipt_standard_target *t; + + assert(e->target_offset >= sizeof(struct ipt_entry)); + assert(e->next_offset >= e->target_offset + + sizeof(struct ipt_entry_target)); + toff = sizeof(struct ipt_entry); + IPT_MATCH_ITERATE(e, check_match, &toff); + + assert(toff == e->target_offset); + + t = (struct ipt_standard_target *) + ipt_get_target((struct ipt_entry *)e); + assert(t->target.target_size == e->next_offset - e->target_offset); + assert(!iptc_is_chain(t->target.u.name, h)); + + if (strcmp(t->target.u.name, IPT_STANDARD_TARGET) == 0) { + assert(t->target.target_size + == IPT_ALIGN(sizeof(struct ipt_standard_target))); + + assert(t->verdict == -NF_DROP-1 + || t->verdict == -NF_ACCEPT-1 + || t->verdict == IPT_RETURN + || t->verdict < (int)h->entries.size); + + if (t->verdict >= 0) { + struct ipt_entry *te = get_entry(h, t->verdict); + int idx; + + idx = entry2index(h, te); + assert(strcmp(ipt_get_target(te)->u.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(ipt_get_target(index2entry(h, idx-1)) + ->u.name, IPT_ERROR_TARGET) + == 0); + } + + if (t->verdict == IPT_RETURN + && unconditional(&e->ip) + && e->target_offset == sizeof(*e)) + *was_return = 1; + else + *was_return = 0; + } else if (strcmp(t->target.u.name, IPT_ERROR_TARGET) == 0) { + assert(t->target.target_size + == IPT_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.name, IPT_ERROR_TARGET) == 0); + + (*off) += e->next_offset; + (*i)++; + return 0; +} + +/* Do every conceivable sanity check on the handle */ +static void +do_check(iptc_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)); + + 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]; + } else if (strcmp(h->info.name, "mangle") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_OUT)); + + /* Hooks should be first three */ + 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_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + } else + 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 < NF_IP_NUMHOOKS; i++) { + struct ipt_entry *e; + struct ipt_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)); + assert(e->next_offset == sizeof(*e) + sizeof(*t)); + t = (struct ipt_standard_target *)ipt_get_target(e); + + assert(strcmp(t->target.u.name, IPT_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 ipt_entry) + +sizeof(struct ipt_standard_target))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(struct ipt_entry) + + sizeof(struct ipt_standard_target)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + /* Check all the entries. */ + IPT_ENTRY_ITERATE(h->entries.entries, 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(ipt_get_target(index2entry(h, h->new_number-1))->u.name, + IPT_ERROR_TARGET) == 0); +} -- cgit v1.2.3