我很自豪的找到了一种截获数据包的秘籍,那就是使用conntrack的ctdir,第一步当然是看iptables的man手册,发现:
--ctdir {ORIGINAL|REPLY}
Match packets that are flowing in the specified direction. If this flag is not specified at all, matches packets
in both directions.
于是我写下来以下以下的规则:
iptables -t mangle -A PREROUTING -d x.x.x.x/a -i eth2 -m conntrack --ctdir ORIGINAL -j MARK --set-xmark 100
iptables -t mangle -A PREROUTING -s y.y.y.y/b -i eth2 -m conntrack --ctdir REPLY -j MARK --set-xmark 100
我这么写有什么问题吗?绝对没有!懂iptables的人都知道我要做什么,我要做的就是截获两类流,一个是主动访问x.x.x.x/a的流,另一类是y.y.y.y/b被访问的流。OK,一切看起来很好。然而当我将上述规则实际部署的时候,出问题了,截取竟然是相反的流,也就是说,截取了两类流,一类是x.x.x.x/a被访问的流,另一类是主动访问y.y.y.y/b的流...我该怎么办?很长一段时间,我一直认为我错了,google过,百度过,毫无结果。作为一个成熟的用了这么久的iptables,不会犯错,一定是我错了。因此我的下一个计划就是找到:我哪里错了?
这只是一个安慰罢了,其实我知道我没有错,按照英文字面理解,REPLY就是代表返回的意思。我的实际计划应该是,找到一个证据证明自己没有错。这个证据我在互联网找了好久,因此我决定看一下代码。首先我看的是conntrack的通用match函数,内核版本为2.6.32。通过match函数在/net/netfilter/xt_conntrack.c中:
static bool
conntrack_mt(const struct sk_buff *skb, const struct xt_match_param *par,
u16 state_mask, u16 status_mask)
{
const struct xt_conntrack_mtinfo2 *info = par->matchinfo;
enum ip_conntrack_info ctinfo;
const struct nf_conn *ct;
unsigned int statebit;
ct = nf_ct_get(skb, &ctinfo);
/* 这些都不是当前所关注的,当前只关注ctdir */
...
if ((info->match_flags & XT_CONNTRACK_DIRECTION) &&
(CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) ^
!!(info->invert_flags & XT_CONNTRACK_DIRECTION))
return false;
/* 这些都不是当前所关注的,当前只关注ctdir */
...
return true;
}
为了思路更清晰,将帮助函数/宏也列举出来:
enum ip_conntrack_dir
{
IP_CT_DIR_ORIGINAL,
IP_CT_DIR_REPLY,
IP_CT_DIR_MAX
};
enum ip_conntrack_info
{
/* Part of an established connection (either direction). */
IP_CT_ESTABLISHED,
/* Like NEW, but related to an existing connection, or ICMP error
(in either direction). */
IP_CT_RELATED,
/* Started a new connection to track (only
IP_CT_DIR_ORIGINAL); may be a retransmission. */
IP_CT_NEW,
/* >= this indicates reply direction */
IP_CT_IS_REPLY,
/* Number of distinct IP_CT types (no NEW in reply dirn). */
IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1
};
#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL)
static inline struct nf_conn *
nf_ct_get(const struct sk_buff *skb, enum ip_conntrack_info *ctinfo)
{
*ctinfo = skb->nfctinfo;
return (struct nf_conn *)skb->nfct;
}
然后我们看一下skb->nfctinfo到底在哪里初始化的,实际上不用找也知道是nf_conntrack_in里面的resolve_normal_ct中:
if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {
*ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
/* Please set reply bit if this packet OK */
*set_reply = 1;
} else { //只要不是返回方向的,都是正向包。
/* Once we've had two way comms, always ESTABLISHED. */
if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
*ctinfo = IP_CT_ESTABLISHED;
} else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
*ctinfo = IP_CT_RELATED;
} else {
*ctinfo = IP_CT_NEW;
}
*set_reply = 0;
}
skb->nfct = &ct->ct_general;
skb->nfctinfo = *ctinfo;
可见,正方向的数据包的nfctinfo被初始化成了IP_CT_ESTABLISHED,IP_CT_RELATED或者IP_CT_NEW,而这些都比IP_CT_IS_REPLY要小,因此正方向的数据包的CTINFO2DIR肯定是IP_CT_DIR_ORIGINAL(详见CTINFO2DIR宏定义)。另一个证据在init_conntrack函数,它是一个初始化ct的函数,这种情况下ct肯定被认为是正方向的,该函数在最后返回的是&ct->tuplehash[IP_CT_DIR_ORIGINAL],一个IP_CT_DIR_ORIGINAL方向的ct-part。
现在看看conntrack_mt函数,对于正方向的包,(CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)为1,如果iptables的ctdir设置为ORIGINAL的话,(info->invert_flags & XT_CONNTRACK_DIRECTION))为0(详见ip_conntrack_dir枚举),因此二者异或为1,最终返回false,这不是瞪着眼睛说瞎话吗?对于返回包,也就是反方向的包,反而返回true...起码从代码看来,ip_conntrack将ctdir完全搞反了。因此如果想让你的配置按照你的想法生效,那就要反过来配置:
iptables -t mangle -A PREROUTING -d x.x.x.x/a -i eth2 -m conntrack --ctdir REPLY -j MARK --set-xmark 100
iptables -t mangle -A PREROUTING -s y.y.y.y/b -i eth2 -m conntrack --ctdir ORIGINAL -j MARK --set-xmark 100
很奇怪,然而正确!要想修正这个问题,除了配置很奇怪的规则之外,只需要去掉一个“!”号即可:
(CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) ^
!!(info->invert_flags & XT_CONNTRACK_DIRECTION))
改为:
(CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) ^
!(info->invert_flags & XT_CONNTRACK_DIRECTION))
从“!!(info->invert_flags & XT_CONNTRACK_DIRECTION)”这一行代码来看,笔误的可能性比较大,否则实在想不通为何要用双重否定句。
令人气愤的是,如此一个问题怎么竟然很少有人提出来啊,难道真的是能用就行吗?或者说真的是我错了?唉,反正不管怎么说,我配反了就能用,配正了就错误!搞了一下午,真TM fuXX the conntrack,怎么就没人管呢?这么成熟的iptables竟然都不可信,茫茫尘世,我还能信谁?