11. Functions to query libiptc
This section explains which functions allow you to query libiptc. We will use the header file of libiptc, file usr/local/include/libiptc/libiptc.h, containing prototypes of each function as a reference to develop our explanation.
I have also included a brief description (when available) taken from Linux netfilter Hacking HOWTO within each function explanation.
11.1. iptc_init
Name: iptc_init
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.
Parameters: tablename is the name of the table we need to query and/or modify; this could be filter, mangle, nat, etc.
Returns: Pointer to a structure of type iptc_handle_t that must be used as main parameter for the rest of functions we will call from libiptc. iptc_init returns the pointer to the structure or NULL if it fails. If this happens you can invoke iptc_strerror to get information about the error. See below.
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)); |
11.2. iptc_strerror
Name: iptc_strerror
Usage: Translates error numbers into more human-readable form.
Prototype: const char *iptc_strerror(int err)
Description: This function returns a more meaningful explanation of a failure code in the iptc library. If a function fails, it will always set errno. This value can be passed to iptc_strerror() to yield an error message.
Parameters: err is an integer indicating the error number.
Returns: Char pointer containing the error description.
11.3. iptc_first_chain
Name: iptc_first_chain
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.
Parameters: Pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Char pointer to the name of the chain.
11.4. iptc_next_chain
Name: iptc_next_chain
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.
Parameters: Pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Char pointer to the name of the chain.
These two previous functions allow to us to iterate through the chains of the table getting the name of each of the chains; iptc_first_chain returns the name of the first chain of the table; iptc_next_chain returns the name of next chains and NULL when the function reaches the end.
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.
Now compile your p1 program:
bash# ./ipt-cc p1 |
And run it:
bash# ./p1 |
You will get:
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 |
You will get:
INPUT FORWARD OUTPUT chain_1 chain_2 |
Try to generate an error initializing tablename to myfilter instead of filter. When you compile and execute your program again, you will get:
Error initializing: Table does not exist (do you need to insmod?) |
iptables informs you that myfilter does not exist as a table.
11.5. iptc_is_chain
Name: iptc_is_chain
Usage: Check if a chain exists.
Prototype: int iptc_is_chain(const char *chain, const iptc_handle_t handle)
Description: This function checks to see if the chain described in the parameter chain exists in the table.
Parameters: chain is a char pointer containing the name of the chain we want to check to. handle is a pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: integer value 1 (true) if the chain exists; integer value 0 (false) if the chain does not exist.
11.6. iptc_builtin
Name: iptc_builtin
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.
Parameters: chain is a char pointer containing the name of the chain we want to check to. handle is a pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Returns integer value 1 (true) if the given chain name is the name of a builtin chain; returns integer value 0 (false) is not.
11.7. iptc_first_rule
Name: iptc_first_rule
Usage: Get first rule in the given chain.
Prototype: const struct ipt_entry *iptc_first_rule(const char *chain, iptc_handle_t *handle)
Description: This function returns a pointer to the first rule in the given chain name; NULL for an empty chain.
Parameters: chain is a char pointer containing the name of the chain we want to get the rules to. handle is a pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Returns a pointer to an ipt_entry structure containing information about the first rule of the chain. See below for an explanation of this structure.
11.8. iptc_next_rule
Name: iptc_next_rule
Usage: Get the next rule in the given chain.
Prototype: const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev, iptc_handle_t *handle)
Description: This function returns a pointer to the next rule in the given chain name; NULL means the end of the chain.
Parameters: prev is a pointer to a structure of type ipt_entry that must be obtained first by a previous call to the function iptc_first_rule. In order to get the second and subsequent rules you have to pass a pointer to the structure containing the information about the previous rule of the chain. handle is a pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Returns a pointer to an ipt_entry structure containing information about the next rule of the chain. See below for an explanation of this structure.
11.9. iptc_get_target
Name: iptc_get_target
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)
Description: This function gets the target of the given rule. If it is an extended target, the name of that target is returned. If it is a jump to another chain, the name of that chain is returned. If it is a verdict (eg. DROP), that name is returned. If it has no target (an accounting-style rule), then the empty string is returned. Note that this function should be used instead of using the value of the verdict field of the ipt_entry structure directly, as it offers the above further interpretations of the standard verdict.
Parameters: e is a pointer to a structure of type ipt_entry that must be obtained first by a previous call to the function iptc_first_rule or the function iptc_next_rule. handle is a pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Returns a char pointer to the target name. See Description above for more information.
Now it is time to explain the ipt_entry structure; these pieces of code are taken from iptables package sources:
/* 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:
An ipt_ip structure containing (for the rule) the source address and netmask (ip.src.s_addr, ip.smsk.s_addr), the destination address and netmask (ip.dst.s_addr, ip.dmsk.s_addr), the protocol (ip.proto), a flags field (invflags) to check for inverse (!) selections (eg. ! 192.168.2.0/24, ! eth0, ! tcp, etc), the input interface (iniface), the output interface (outiface), the input (iniface_mask) and output (outiface_mask) interface masks and the flags field to check for fragmented packets.
An ipt_counters structure containing the packet (pcnt) and byte (bcnt) counter of the rule. This information is important for bandwidth measurement.
target_offset that is used to get the target information of the rule.
Unknown fields: nfcache, comefrom, elems, next_offset. If someone can give me a feedback about these fields I would be grateful.
A simple way to work with all this information is to borrow some functions from iptables-save.c by Paul Russell and Harald Welte.
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 */ |
The function print_rule borrowed from iptables-save.c prints the information about a rule into a readable form using:
print_ip to print the addresses,
print_iface to print the interfaces,
print_proto to print the protocols,
iptc_get_target to get and print the targets (using save).
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.
chain = name of the chain.
counters = 0: do not print counters; 1: print them.
OK, compile and run program p2:
bash# ./ipt-cc p2 bash# ./p2 |
You will get:
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 |
We have now rules printed for INPUT and chain_1 chains. The numbers in the brackets at left are packet and byte counters respectively.
11.10. iptc_get_policy
Name: iptc_get_policy
Usage: Get the policy of a given built-in chain.
Prototype: const char *iptc_get_policy(const char *chain, struct ipt_counters *counter, iptc_handle_t *handle)
Description: This function gets the policy of a built-in chain, and fills in the counters argument with the hit statistics on that policy.
Parameters: You have to pass as arguments the name of the built-in chain you want to get the policy to, a pointer to an ipt_counters structure to be filled by the function and the iptc_handle_t structure identifying the table we are working to. The ipt_counters structure was explained in previous section; do not forget that iptc_handle_t must be obtained by a previous call to the function iptc_init.
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] |
11.11. iptc_read_counter
Name: iptc_read_counter
Usage: Read counters of a rule in a chain.
Prototype: struct ipt_counters *iptc_read_counter(const ipt_chainlabel chain, unsigned int rulenum, iptc_handle_t *handle);
Description: This function read and returns packet and byte counters of the entry rule in chain chain positioned at rulenum. Counters are returned in a pointer to a type structure ipt_counters. Rule numbers start at 1 for the first rule.
Parameters: chain is a char pointer to the name of the chain to be readed; rulenum is an integer value defined the position in the chain of rules of the rule which counters will be read. handle is a pointer to a structure of type iptc_handle_t that was obtained by a previous call to iptc_init.
Returns: Returns a pointer to an ipt_counters structure containing the byte and packet counters readed.