Skip to content

Instantly share code, notes, and snippets.

@mnunberg
Created June 25, 2012 22:44
Show Gist options
  • Save mnunberg/2991921 to your computer and use it in GitHub Desktop.
Save mnunberg/2991921 to your computer and use it in GitHub Desktop.
IPTC Notes
This document outlines some of my experiences with IPTC, both for my own
reference, and for anyone else wishing to use this library.
"libiptc" is an internal library to "iptables". It is available as a
package on some distros, and is compiled with the "iptables" tarball as
well. Depending in "libiptc" should be a fairly simple process.
"libiptc" itself only provides the fairly high level functions used to
actually insert/remove/update/append a rule, but does not actually
provide parameters used to actually construct rules. You need to do this
yourself, figuring out which targets and matches to apply.
GENERAL CONCEPTS
Handles
"libiptc" operates on a "struct xtc_handle*" object, which you allocate
by calling "iptc_init("table name")". This handle is supposed to be
persistent and represents a socket to the netfilter subsystem.
Initializing the handle actually gets a snapshot of the current iptables
ruleset.
struct xtc_handle *h_nat = iptc_init("nat");
struct xtc_handle *h_filter = iptc_init("filter");
/* Of course, these functions can return NULL */
Entries
Entries represent actual rules in the chain. Entries are variable length
structures with a header represented via a "struct ipt_entry".
The header structure is defined as follows (with my comments included)
The definition is from "<linux/netfilter_ipv4/ip_tables.h">
struct ipt_entry {
/** Basic matching information (see below) */
struct ipt_ip ip;
/**
* Don't know what this.. but it seems to only be
* touched by the kernel. So ignore this
*/
unsigned int nfcache;
/**
* This is where the targets begin. The structure of a
* single entry looks like this:
* [ [ HEADER ] [ MATCH_1..MATCH_n ] [ TARGET ] ]
* This field represents the offset (from the beginning of the
* entry) to the first target.
*/
__u16 target_offset;
/**
* This is the entire size of the entry. Matches and target
* included
*/
__u16 next_offset;
/* Back pointer */
/**
* (I don't see anything in the iptables source using it,
* so I'm going to guess it's only relevant in the kernel
* too)
*/
unsigned int comefrom;
/* Packet and byte counters. */
struct xt_counters counters;
/* The matches (if any), then the target. */
unsigned char elems[0];
};
The matches begin right after the header. A match is defined as its own
header structure, specifically, a "struct xt_entry_match",
struct xt_entry_match {
union {
struct {
/** This is what you care about: */
/**
* The match size. This includes the header and the
* body
*/
__u16 match_size;
/**
* This is the name of the match. This is what goes after
* the '-m' in e.g. iptables -m comment
*/
char name[XT_EXTENSION_MAXNAMELEN];
/** Not needed */
__u8 revision;
} user;
struct {
/** Ignore this. Kernel stuff */
__u16 match_size;
/* Used inside the kernel */
struct xt_match *match;
} kernel;
/**
* Total length.
* If you observe closely, this odd duplication of the 'match_size'
* is actually aliased as this is the first field in *all* members
* of the union. Therefore, &m->u.user.match_size == &m->u.match_size
*/
__u16 match_size;
} u;
/**
* This is the 'body' of the match
*/
unsigned char data[0];
};
For targets, there is a "struct xt_entry_target" defined in the same
header file and has the same semantics.
Basic Match (in header)
While you can use the extended 'match' structures, for simple purposes,
you can use the included "struct ipt_ip" within the "struct ipt_entry"
for simple matches
The structure is defined (in "<linux/netfilter_ipv4/ip_tables.h"> as
follows:
struct ipt_ip {
/**
* Source and destination IP addr.
* e.g. -s 1.2.3.4 -d 4.3.2.1
*/
struct in_addr src, dst;
/* Mask for src and dest IP addr */
struct in_addr smsk, dmsk;
/**
* Input and output interfaces, i.e. -i eth0, -o eth1.
* I'm not sure what the mask is, but it might be handy
* for interface wildcard matching?
* /
char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
/**
* I'm not sure what goes here, it might be an NFPROTO_* constant
* or an IPPROTO_* constant
*/
/* Protocol, 0 = ANY */
__u16 proto;
/**
* See the header for these
*/
/* Flags word */
__u8 flags;
/* Inverse flags */
__u8 invflags;
};
Structure Bodies
While we've just defined the headers of those structures, the actual
bodies are defined in numerous other header files. This makes more sense
when you take into consideration that most interaction with iptables is
actually dealing with *extensions*. Therefore to configure a "state"
match or an "SNAT" target, you must manually load and deal with the
structures defined in those headers.
Structure bodies and headers can be manipulated easily by defining
wrapper structures for them. Thus, for the "comment" match, we can do
#include <linux/netfilter/xt_comment.h>
struct comment_match {
/* Header */
struct xt_entry_match m;
/* Body */
struct xt_comment_info c;
};
/* ... */
struct ipt_entry *ent;
struct comment_match *m_comment;
char *buf;
int length = XT_ALIGN(sizeof(*ent))
+ XT_ALIGN(sizeof(*m_comment));
buf = calloc(1, length);
ent = (void*)buf;
m_comment = (void*)( buf + XT_ALIGN(sizeof(*ent)) );
/* Set the size of the entire match */
m_comment->h.u.match_size = XT_ALIGN(sizeof(*ent));
/**
* The 'comment' match body defines a single field of its own,
* which is a char[255] and is called 'comment'
*/
strcpy(m->c.comment, "This is a comment");
The same principle applies for all other matches and target types.
Discovering how to use these structures can be simple or complex
depending on the extension. It is helpful to check out the iptables
source code and browse in its "extension" directory.
Generally extensions will have header files in the kernel defining their
structures, and a C source file within the iptables source tree which
contains the relevant userspace structure manipulation routines.
While these routines are not exported symbols, they are fairly easy to
read and show the possibly 'canonical' usage case for these structures.
The naming of the source file can either be "libxt_foo.c" or
"libipt_foo.c" depending on the nature of the "foo" match or target.
Doing
iptables-1.4.14/extensions $ ls *foo*
should yield the relevant source file.
NAT Targets
For the NAT targets, the definitions have changed between kernels and so
have the structure names and header files. The following snippet should
take care of these changes for you.
#include <linux/version.h>
#include <linux/netfilter/nf_conntrack_common.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
#include <linux/netfilter_ipv4/ip_nat.h>
#define nf_nat_multi_range ip_nat_multi_range
#else
#include <linux/netfilter_ipv4/nf_nat.h>
#endif
NAT targets are rather hairy to deal with, and the
documentation/comments within the source code is confusing at best.
struct nat_target {
/* Target header */
struct xt_entry_target t;
/**
* Body.
*
* The nf_nat.h header says that this structure is
* 'deprecated' and "shouldn't be used in modern code",
* however, the actual libpt_SNAT.c uses this structure
* in the latest iptables release, and it seems to have a
* different format from the 'newer' (?) format (which I
* cannot find)
*/
struct nf_nat_multi_range n;
};
/* ... */
struct nat_target *t_snat;
/** make t_snat point to valid memory, per above */
strcpy(t_snat->t.u.user.name, "SNAT");
t_snat->t.u.target_size = IPT_ALIGN(sizeof(*t_snat));
/**
* Definition of the header requires the 'rangesize' to be 1
*/
t_snat->n.rangesize = 1;
/**
* With NAT targets, we are allowed to map a single IP to a range.
* If we only do a 1:1 NAT, the min and max range are the same
*/
t_snat->n.range->max_ip = t_snat->n.range->min_ip = ctx->inaddr;
/**
* This flag must be set. There are other flags as well,
* (in <linux/netfilter_ipv4/nf_nat.h> but I'm not sure
* of their meaning)
*/
t_snat->n.range->flags |= IP_NAT_RANGE_MAP_IPS;
Operations
So now that I've discussed the actual structures, it's time to modify
the ruleset.
int status = iptc_append_entry("POSTROUTING", ent, h_nat);
/** Status is true on sucess */
if (!status) {
fprintf(stderr,
"Couldn't add new rule: %s\n",
/**
* If errno is 0, then it's a version mismatch
* with the headers and your kernel
*/
(errno) ? iptc_strerror(errno) : "Version Mismatch");
}
int ii;
for (ii = 0; ii < 20; ii++) {
status = iptc_commit(h_nat);
if (status) {
/* OK */
break;
}
if (errno != EAGAIN) {
fprintf(stderr, "Couldn't commit rules: %s\n",
iptc_strerror(errno));
break;
}
/**
* EAGAIN needs special handling. Specifically,
* we can get this when other updates are being made
* to iptables (updates are asynchronous).
*/
usleep(5000);
}
Deletion
The deletion function is prototyped as: /* Delete the first rule in
`chain' which matches `e', subject to matchmask (array of length ==
origfw) */
int iptc_delete_entry(const xt_chainlabel chain,
const struct ipt_entry *origfw,
unsigned char *matchmask,
struct xtc_handle *handle);
Interesting to note is the "matchmask" argument, the use of which I'm
not sure, but apparently it needs to be the same size as the entry
buffer. Its contents don't seem to matter.
See this thread
<http://marc.info/?l=netfilter-devel&m=99705529928050&w=3>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment