一、背景需求
近期编写tcp透明代理时,考虑了透明方式下需要使用 iptables 进行 DNAT/SNAT 规则下发,在程序中可以生成命令后通过 system 调用执行;
那么,iptables 是否有提供外部接口来代替上述这种 system() 执行命令的方式?答案是可以使用libiptc库进行编程实现。
二、相关知识
2.1 Linux/Netfilter
《Linux网络安全技术与实现》提到,Linux系统的防火墙功能主要为Netfilter模块进行实现,从功能上又细分了四大表:Filter、Nat、Mangle、Raw;
1)*Filter表*:数据包过滤功能;
2)*Nat表*:网络地址转换功能,IP分享器(一对一、一对多、多对多)功能;
3)Mangle表:修改经过防火墙的数据包功能;
4)Raw表:负责加快数据包穿越防火墙的速度,用于提高性能;
2.2 Netfilter/iptables
上述简单阐述了Netfilter在Linux内核防火墙的地位,而Netfilter工作在系统内核,规则是存在于内存中的,那么管理人员如何管理防火墙的规则呢?
这样的规则管理工具就是 iptables / ip6tables,比如下发一个规则到 Netfilter规则数据库中:
iptables -t filter -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
三、实例
3.1 准备工作
内核版本:linux-3.10.25_x64
源码版本:iptables-1.4.14(https://www.netfilter.org/projects/iptables/files/iptables-1.4.14.tar.bz2)
工具准备:gcc、automake、libtools(编译时需要把本机的libtools 放到 iptables 源码目录下)
3.2 接口说明
1) 获取当前防火墙规则快照 iptc_init
Name: iptc_init
Usage: Takes a snapshot of the rules.
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.
2) 追加防火墙规则 iptc_append_entry
Name: iptc_append_entry
Usage: Append a new rule in a chain.
Description: This function append a rule defined in structure type ipt_entry in chain chain (equivalent to insert with rulenum = length of chain).
Parameters: chain is a char pointer to the name of the chain to be modified;
e is a pointer to a structure of type ipt_entry that contains information about the rule to be appended.
The programmer must fill the fields of this structure with values required to define his or her rule before passing the pointer as parameter to the function.
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 successful; returns integer value 0 (false) if fails.
In this case errno is set to the error number generated. Use iptc_strerror to get a meaningful information about the problem.
If errno == 0, it means there was a version error (ie. upgrade libiptc).
3) 匹配删除防火墙规则 iptc_delete_entry
Name: iptc_delete_entry
Usage: Delete the first rule in chain matched.
Description: This function delete the entry rule in chain chain matches rules. Rule numbers start at 1 for the first rule.
Parameters: chain is a char pointer to the name of the chain to be modified;
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 successful;
returns integer value 0 (false) if fails. In this case errno is set to the error number generated.
Use iptc_strerror to get a meaningful information about the problem.
If errno == 0, it means there was a version error (ie. upgrade libiptc).
3.3 编程实例
实例这块以SNAT、DNAT为例
1)生成规则 “iptables -p tcp -s src -d dst -j (SNAT|DNAT) --to to”,代码比较繁琐,需要设置好 ipt_entry 结构体各个成员值;
struct ipt_entry *api_iptc_entry_get(struct sockaddr_in src,
struct sockaddr_in dst, struct sockaddr_in nto, const char *option)
{
struct ipt_entry *fw = NULL;
struct ipt_entry_match *match = NULL;
struct ipt_tcp *tcpinfo = NULL;
struct ipt_entry_target *target = NULL;
struct nf_nat_multi_range *mr = NULL;
u32 size1 = XT_ALIGN(sizeof(struct ipt_entry));
u32 size2 = XT_ALIGN(sizeof(struct ipt_entry_match) + sizeof(struct ipt_tcp));
u32 size3 = XT_ALIGN(sizeof(struct ipt_entry_target) + sizeof(struct nf_nat_multi_range));
if ( !option ) {
LOGW("NULL\n");
return NULL;
}
fw = calloc(1, size1 + size2 + size3);
if ( !fw ) {
LOGE("Malloc failure");
return NULL;
}
/* Offsets to the other bits */
fw->target_offset = size1 + size2;
fw->next_offset = size1 + size2 + size3;
/* Set up packet matching rules */
if ( (fw->ip.src.s_addr = src.sin_addr.s_addr) == INADDR_ANY ) {
fw->ip.smsk.s_addr = 0;
}
else {
fw->ip.smsk.s_addr = inet_addr("255.255.255.255");
}
if ( (fw->ip.dst.s_addr = dst.sin_addr.s_addr) == INADDR_ANY ) {
fw->ip.dmsk.s_addr = 0;
}
else {
fw->ip.dmsk.s_addr = inet_addr("255.255.255.255");
}
fw->ip.proto = IPPROTO_TCP;
fw->nfcache = NFC_UNKNOWN; /*Think this stops caching. */
/* TCP specific matching(ie. ports) */
match = (struct ipt_entry_match *)fw->elems;
match->u.match_size = size2;
strcpy(match->u.user.name, "tcp");
tcpinfo = (struct ipt_tcp *)match->data;
if ( src.sin_port == 0 ) {
tcpinfo->spts[0] = ntohs(0);
tcpinfo->spts[1] = ntohs(0xFFFF);
}
else {
tcpinfo->spts[0] = tcpinfo->spts[1] = ntohs(src.sin_port);
}
if( dst.sin_port == 0 ) {
tcpinfo->dpts[0] = ntohs(0);
tcpinfo->dpts[1] = ntohs(0xFFFF);
}
else {
tcpinfo->dpts[0] = tcpinfo->dpts[1] = ntohs(dst.sin_port);
}
/* And the target */
target = (struct ipt_entry_target *)(fw->elems + size2);
target->u.target_size = size3;
strcpy(target->u.user.name, option);
mr = (struct nf_nat_multi_range *)target->data;
mr->rangesize = 1;
mr->range[0].flags = IP_NAT_RANGE_PROTO_SPECIFIED | IP_NAT_RANGE_MAP_IPS;
mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = nto.sin_port;
mr->range[0].min_ip = mr->range[0].max_ip = nto.sin_addr.s_addr;return fw;
}
2)追加规则到 Netfilter,需要指定 chain名 “PREROUTING” 或者 “POSTROUTING”
int api_iptc_entry_add(const struct ipt_entry *fw, const char *chain)
{
int ret = FAILURE;
struct xtc_handle *phandle = NULL;
if ( !fw || !chain ) {
LOGW("NULL\n");
return FAILURE;
}
if ( (phandle = iptc_init("nat")) &&
iptc_append_entry(chain, fw, phandle) &&
iptc_commit(phandle) ) {
ret = SUCCESS;
}
else {
LOGW("%s\n", iptc_strerror(errno));
ret = FAILURE;
}
return ret;
}
3)删除规则
int api_iptc_entry_del(const struct ipt_entry *fw, const char *chain)
{
int ret = FAILURE;
unsigned char *matchmask = NULL;
struct xtc_handle *phandle = NULL;
if ( !fw || !chain ) {
LOGW("NULL\n");
return FAILURE;
}
matchmask = calloc(1, fw->next_offset);
if ( !matchmask ) {
return FAILURE;
}
if ( !phandle ) {
phandle = iptc_init("nat");
}
if ( (phandle = iptc_init("nat")) &&
iptc_delete_entry(chain, fw, matchmask, phandle) &&
iptc_commit(phandle) ) {
ret = SUCCESS;
}
else {
LOGW("%s\n", iptc_strerror(errno));
ret = FAILURE;
}
FREE_POINTER(matchmask);
return ret;
}
4)使用实例
int test_snat(int off, u16 times)
{
int ret = FAILURE;
int ix = 0;
struct ipt_entry *fw = NULL;
struct sockaddr_in src;
struct sockaddr_in dst;
struct sockaddr_in sto;
src.sin_addr.s_addr = inet_addr("1.1.1.1") + off;
dst.sin_addr.s_addr = inet_addr("1.1.1.2") + off;
sto.sin_addr.s_addr = inet_addr("1.1.1.3") + off;
for ( ix = 0; ix < times; ix++ ) {
src.sin_port = htons(1 + ix);
dst.sin_port = htons(2 + ix);
sto.sin_port = htons(3 + ix);
ASSERT_FAIL(NULL, fw = api_iptc_entry_get(src, dst, sto, "SNAT"));
ASSERT(SUCCESS, api_iptc_entry_add(fw, "POSTROUTING"));
usleep(300);
ASSERT(SUCCESS, api_iptc_entry_del(fw, "POSTROUTING"));
}
FREE_POINTER(fw);
ret = SUCCESS;
_E1:
return ret;
}
4.总结
Linux c/c++编程使用 libiptc库下发规则可以代替system调用 iptables 命令方法,可以满足程序中批量地创建连接所需的批量增加、删除iptables规则;
使用步骤确实比较繁琐,得注意iptables版本的问题,编译相关库需要“-lip4tc” “-lxtables”,头文件需要“libiptc/libiptc.h” “net/netfilter/nf_nat.h” “iptables.h”;
然而实验结果表明,当多进程高频率得操作iptables时,system 和 libiptc 均会出现 “Resource temporarily unavailable” 错误,建议进行返回值判定和多进程的加锁保护;