错误如下所示:
# iptables -L
iptables v1.8.1 (legacy): can't initialize iptables table `filter': No chain/target/match by that name
Perhaps iptables or your kernel needs to be upgraded.
参考网上的说法,应该是filter表模块iptable_filter没有加载成功,或者内核编译选项根本就没有选上此功能。但是手动加载filter模块,没有问题:
# modprobe iptable_filter
# lsmod
Module Size Used by
iptable_filter 16384 0
ip_tables 28672 1 iptable_filter
x_tables 40960 2 iptable_filter,ip_tables
但是问题还是存在,查一下iptables-1.8.1的源码中,打印错误的具体位置:
int do_command4(int argc, char *argv[], char **table, struct xtc_handle **handle, bool restore)
{
...
/* only allocate handle if we weren't called with a handle */
if (!*handle)
*handle = iptc_init(*table);
/* try to insmod the module if iptc_init failed */
if (!*handle && xtables_load_ko(xtables_modprobe_program, false) != -1)
*handle = iptc_init(*table);
if (!*handle)
xtables_error(VERSION_PROBLEM,
"can't initialize iptables table `%s': %s",
*table, iptc_strerror(errno));
以上可见,问题出在iptc_init函数没有正确的初始化xtc_handle类型的变量handle,如下iptc_init函数:
struct xtc_handle * TC_INIT(const char *tablename)
{
...
sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) return NULL;
if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set close on exec: %s\n", strerror(errno));
abort();
}
s = sizeof(info);
strcpy(info.name, tablename);
if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) {
close(sockfd);
return NULL;
}
DEBUGP("valid_hooks=0x%08x, num_entries=%u, size=%u\n",
info.valid_hooks, info.num_entries, info.size);
h = alloc_handle(&info);
if (h == NULL) {
close(sockfd);
return NULL;
}
/* Initialize current state */
h->sockfd = sockfd;
h->info = info;
h->entries->size = h->info.size;
tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
if (getsockopt(h->sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries, &tmp) < 0)
goto error;
最有可能出错的地方就是两个getsockopt函数,在TC_INIT函数中增加打印信息,重新编译一个iptables程序,但是TC_INIT位于libiptc/libiptc.c文件中,其编译之后为共享库文件libip4tc.so.0.1.0,将其替换系统目录/lib64下面的同名文件,方可看到打印输出。
最终确定getsocketopt函数执行SO_GET_INFO时出错,由于创建的为SOCK_RAW类型的套接口,内核处理函数为:
static int raw_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
if (level != SOL_RAW)
return ip_getsockopt(sk, level, optname, optval, optlen);
return do_raw_getsockopt(sk, level, optname, optval, optlen);
}
iptables中定义的level为TC_IPPROTO,实际上等于内核中的IPPROTO_IP,不等于SOL_RAW,调用函数ip_getsockopt:
int ip_getsockopt(struct sock *sk, int level,
int optname, char __user *optval, int __user *optlen)
{
int err;
err = do_ip_getsockopt(sk, level, optname, optval, optlen, 0);
...
#ifdef CONFIG_NETFILTER
/* we need to exclude all possible ENOPROTOOPTs except default case */
if (err == -ENOPROTOOPT && optname != IP_PKTOPTIONS &&
!ip_mroute_opt(optname)) {
int len;
if (get_user(len, optlen)) return -EFAULT;
err = nf_getsockopt(sk, PF_INET, optname, optval, &len);
if (err >= 0)
err = put_user(len, optlen);
return err;
}
#endif
对于函数ip_getsockopt,这里有个优先级,其先处理其它系统的getsockopt调用;之后再处理netfilter的调用,即函数nf_getsockopt。在内核中增加打印信息,发现SO_GET_INFO调用并没有进入nf_getsockopt函数处理,经检查发现,之前修改内核代码时在do_ip_getsockopt中增加了和SO_GET_INFO相同的选项值,导致此错误的发生,修正之后,iptables运行正常。