I have also included a brief description (when available) taken from Linux netfilter Hacking HOWTO within each function explanation.
Usage: Takes a snapshot of the rules.
Prototype: iptc_handle_t iptc_init(const char *tablename)
Description: This function must be called as initiator before any other function can be called.
Have a look at this section of code in file iptables-save.c for how to invoke this function:
h = iptc_init(tablename); if (!h) exit_error(OTHER_PROBLEM, "Can't initialize: %s\n",iptc_strerror(errno)); |
Usage: Translates error numbers into more human-readable form.
Prototype: const char *iptc_strerror(int err)
Usage: Iterator functions to run through the chains.
Prototype: const char *iptc_first_chain(iptc_handle_t *handle)
Description: This function returns the first chain name in the table.
Usage: Iterator functions to run through the chains.
Prototype: const char *iptc_next_chain(iptc_handle_t *handle)
Description: This function returns the next chain name in the table; NULL means no more chains.
Returns: Char pointer to the name of the chain.
We can create Program #1 to exercise our understanding of these previous four functions:
/* * How to use libiptc- program #1 * /usr/local/src/p1.c */ #include <getopt.h> #include <sys/errno.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <time.h> #include "libiptc/libiptc.h" #include "iptables.h" int main(void) { iptc_handle_t h; const char *chain = NULL; const char *tablename = "filter"; program_name = "p1"; program_version = NETFILTER_VERSION; h = iptc_init(tablename); if ( !h ) { printf("Error initializing: %s\n", iptc_strerror(errno)); exit(errno); } for (chain = iptc_first_chain(&h); chain; chain = iptc_next_chain(&h)) { printf("%s\n", chain); } exit(0); } /* main */ |
Write this program and save it as p1.c in /usr/local/src.
Now write this "bash" script to simplify the compiling process:
#!/bin/bash gcc -Wall -Wunused -DNETFILTER_VERSION=\"1.2.6\" -rdynamic -o $1 $1.c \ /usr/local/lib/iptables.o /usr/local/lib/libiptc.a -ldl |
Save it as ipt-cc and do not forget to chmod 0700 ipt-cc.
bash# ./ipt-cc p1 |
bash# ./p1 |
INPUT FORWARD OUTPUT |
These are the three built-in iptables chains.
Now create some new chains using iptables and run your program again:
bash# iptables -N chain_1 bash# iptables -N chain_2 bash# ./p1 |
INPUT FORWARD OUTPUT chain_1 chain_2 |
Error initializing: Table does not exist (do you need to insmod?) |
iptables informs you that myfilter does not exist as a table.
Usage: Check if a chain exists.
Prototype: int iptc_is_chain(const char *chain, const iptc_handle_t handle)
Usage: Is this a built-in chain?
Prototype: int iptc_builtin(const char *chain, const iptc_handle_t handle)
Description: This function is used to check if a given chain name is a built-in chain or not.
Usage: Get first rule in the given chain.
Prototype: const struct ipt_entry *iptc_first_rule(const char *chain, iptc_handle_t *handle)
Usage: Get a pointer to the target name of this entry.
Prototype: const char *iptc_get_target(const struct ipt_entry *e, iptc_handle_t *handle)
Returns: Returns a char pointer to the target name. See Description above for more information.
/* Internet address. */ struct in_addr { __u32 s_addr; }; /* Yes, Virginia, you have to zero the padding. */ struct ipt_ip { /* Source and destination IP addr */ struct in_addr src, dst; /* Mask for src and dest IP addr */ struct in_addr smsk, dmsk; char iniface[IFNAMSIZ], outiface[IFNAMSIZ]; unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ]; /* Protocol, 0 = ANY */ u_int16_t proto; /* Flags word */ u_int8_t flags; /* Inverse flags */ u_int8_t invflags; }; struct ipt_counters { u_int64_t pcnt, bcnt; /* Packet and byte counters */ }; /* This structure defines each of the firewall rules. Consists of 3 parts which are 1) general IP header stuff 2) match specific stuff 3) the target to perform if the rule matches */ struct ipt_entry { struct ipt_ip ip; /* Mark with fields that we care about. */ unsigned int nfcache; /* Size of ipt_entry + matches */ u_int16_t target_offset; /* Size of ipt_entry + matches + target */ u_int16_t next_offset; /* Back pointer */ unsigned int comefrom; /* Packet and byte counters. */ struct ipt_counters counters; /* The matches (if any), then the target. */ unsigned char elems[0]; }; |
An ipt_entry structure contains:
Here is another sample program Program #2 written with a lot of help from Russell-Welte:
/* * How to use libiptc- program #2 * /usr/local/src/p1.c */ #include <getopt.h> #include <sys/errno.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <time.h> #include "libiptc/libiptc.h" #include "iptables.h" /* Here begins some of the code taken from iptables-save.c **************** */ #define IP_PARTS_NATIVE(n) \ (unsigned int)((n)>>24)&0xFF, \ (unsigned int)((n)>>16)&0xFF, \ (unsigned int)((n)>>8)&0xFF, \ (unsigned int)((n)&0xFF) #define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n)) /* 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 { /* we can access iface[i-1] here, because * a few lines above we make sure that mask[0] != 0 */ if (iface[i-1] != '\0') printf("+"); break; } } printf(" "); } /* These are hardcoded backups in iptables.c, so they are safe */ struct pprot { char *name; u_int8_t num; }; /* FIXME: why don't we use /etc/protocols ? */ static const struct pprot chain_protos[] = { { "tcp", IPPROTO_TCP }, { "udp", IPPROTO_UDP }, { "icmp", IPPROTO_ICMP }, { "esp", IPPROTO_ESP }, { "ah", IPPROTO_AH }, }; 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 print_match(const struct ipt_entry_match *e, const struct ipt_ip *ip) { struct iptables_match *match = find_match(e->u.user.name, TRY_LOAD); if (match) { printf("-m %s ", e->u.user.name); /* some matches don't provide a save function */ if (match->save) match->save(ip, e); } else { if (e->u.match_size) { fprintf(stderr, "Can't find library for match `%s'\n", e->u.user.name); exit(1); } } return 0; } /* print a given ip including mask if neccessary */ static void print_ip(char *prefix, u_int32_t ip, u_int32_t mask, int invert) { if (!mask && !ip) return; printf("%s %s%u.%u.%u.%u", prefix, invert ? "! " : "", IP_PARTS(ip)); if (mask != 0xffffffff) printf("/%u.%u.%u.%u ", IP_PARTS(mask)); else printf(" "); } /* 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, iptc_handle_t *h, const char *chain, int counters) { struct ipt_entry_target *t; const char *target_name; /* print counters */ if (counters) printf("[%llu:%llu] ", e->counters.pcnt, e->counters.bcnt); /* print chain name */ printf("-A %s ", chain); /* Print IP part. */ print_ip("-s", e->ip.src.s_addr,e->ip.smsk.s_addr, e->ip.invflags & IPT_INV_SRCIP); print_ip("-d", e->ip.dst.s_addr, e->ip.dmsk.s_addr, e->ip.invflags & IPT_INV_DSTIP); 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 ? "! " : ""); /* Print matchinfo part */ if (e->target_offset) { IPT_MATCH_ITERATE(e, print_match, &e->ip); } /* Print target name */ target_name = iptc_get_target(e, h); if (target_name && (*target_name != '\0')) printf("-j %s ", target_name); /* Print targinfo part */ t = ipt_get_target((struct ipt_entry *)e); if (t->u.user.name[0]) { struct iptables_target *target = find_target(t->u.user.name, TRY_LOAD); if (!target) { fprintf(stderr, "Can't find library for target `%s'\n", t->u.user.name); exit(1); } if (target->save) target->save(&e->ip, t); else { /* If the target size is greater than ipt_entry_target * there is something to be saved, we just don't know * how to print it */ if (t->u.target_size != sizeof(struct ipt_entry_target)) { fprintf(stderr, "Target `%s' is missing " "save function\n", t->u.user.name); exit(1); } } } printf("\n"); } /* Here ends some of the code taken from iptables-save.c ****************** */ int main(void) { iptc_handle_t h; const struct ipt_entry *e; const char *chain = NULL; const char *tablename = "filter"; const int counters = 1; program_name = "p2"; program_version = NETFILTER_VERSION; /* initialize */ h = iptc_init(tablename); if ( !h ) { printf("Error initializing: %s\n", iptc_strerror(errno)); exit(errno); } /* print chains and their rules */ for (chain = iptc_first_chain(&h); chain; chain = iptc_next_chain(&h)) { printf("%s\n", chain); for (e = iptc_first_rule(chain, &h); e; e = iptc_next_rule(e, &h)) { print_rule(e, &h, chain, counters); } } exit(0); } /* main */ |
In main we iterate through each chain and for each one we iterate through each rule printing it.
The arguments of print_rule are:
e = pointer to an ipt_entry structure containing information about the rule.
h = pointer to an iptc_handle_t structure returned by iptc_init.
OK, compile and run program p2:
bash# ./ipt-cc p2 bash# ./p2 |
INPUT FORWARD OUTPUT chain_1 chain_2 |
Now modify the environment using iptables to add some rules:
bash# iptables -A INPUT -p tcp -i eth0 -s ! 192.168.1.1 --dport 20 -j ACCEPT bash# iptables -A chain_1 -p udp -o eth1 -s 192.168.2.0/24 --sport 33 -j DROP |
Now if you run again p2 you will get:
INPUT [0:0] -A INPUT -s ! 192.168.1.1 -i eth0 -p tcp -m tcp --dport 20 -j ACCEPT FORWARD OUTPUT chain_1 [0:0] -A chain_1 -s 192.168.2.0/255.255.255.0 -o eth1 -p udp -m udp --sport 33 -j DROP chain_2 |
Usage: Get the policy of a given built-in chain.
Returns: Returns a char pointer to the policy name.
Using pieces of programs 1 and 2 we can write program #3:
/* * How to use libiptc- program #3 * /usr/local/src/p3.c */ #include <getopt.h> #include <sys/errno.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <time.h> #include "libiptc/libiptc.h" #include "iptables.h" int main(void) { iptc_handle_t h; const char *chain = NULL; const char *policy = NULL; const char *tablename = "filter"; struct ipt_counters counters; program_name = "p3"; program_version = NETFILTER_VERSION; /* initialize */ h = iptc_init(tablename); if ( !h ) { printf("Error initializing: %s\n", iptc_strerror(errno)); exit(errno); } /* print built-in chains, their policies and counters */ printf("BUILT-IN POLICY PKTS-BYTES\n"); printf("-----------------------------\n"); for (chain = iptc_first_chain(&h); chain; chain = iptc_next_chain(&h)) { if ( !iptc_builtin(chain, h) ) continue; if ( (policy = iptc_get_policy(chain, &counters, &h)) ) printf("%-10s %-10s [%llu:%llu]\n", chain, policy, counters.pcnt, counters.bcnt); } exit(0); } /* main */ |
OK, compile and run program p3:
bash# ./ipt-cc p3 bash# ./p3 |
You will get something like this:
BUILT-IN POLICY PKTS-BYTES ---------------------------- INPUT ACCEPT [0:0] FORWARD ACCEPT [0:0] OUTPUT ACCEPT [0:0] |