iptables 学习
关于 iptables 四表五链,网上有很多介绍的文章,本文不在叙述,是在看源码时的一些想法和思考。
关于四表五链部分,功能如下:
四表
raw 表: 确定是否对该数据包进行状态跟踪。包含两个规则链,OUTPUT、 PREROUTING
mangle 表: 修改数据包内容,用来做流量整形的,给数据包设置标记。包含五个规则链,INPUT、 OUTPUT、FORWARD、 PREROUTING、 POSTROUTING
nat 表: 负责网络地址转换,用来修改数据包中的源、目标IP地址或端口。包含三个规则链,OUTPUT、PREROUTING、 POSTROUTING
filter 表: 负责过滤数据包,确定是否放行该数据包(过滤)。包含三个规则链,INPUT、 FORWARD、OUTPUT
数据包到达防火墙时,规则表之间的优先顺序:
raw > mangle > nat > filter
五链
INPUT: 处理入站数据包,匹配目标 IP 为本机的数据包
OUTPUT: 处理出站数据包,一般不在此链上做配置
FORWARD: 处理转发数据包,匹配流经本机的数据包
PREROUTING: 在进行路由选择前处理数据包,用来修改目的地址,用来做 DNAT。相当于把内网服务器的 IP 和端口映射到路由器的外网 IP 和端口上
POSTROUTING: 在进行路由选择后处理数据包,用来修改源地址,用来做 SNAT 相当于内网通过路由器 NAT 转换功能实现内网主机通过一个公网 IP 地址上网。
入口
NF_INET_PRE_ROUTING
在 ip_rcv() 函数中 hook 住,执行对应的 PRE_ROUTING hook 后再 ip_rcv_finish。
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
struct net_device *orig_dev)
{
struct net *net = dev_net(dev);
skb = ip_rcv_core(skb, net);
if (skb == NULL)
return NET_RX_DROP;
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);
hook 方法
有很多注册到 NF_INET_PRE_ROUTING 的 hook 方法,包括 iptable_nat_do_chain,ipv4_conntrack_defrag,nf_nat_ipv4_in,nft_nat_do_chain,ila_nf_input,ipv4_conntrack_in
等等。相同的 hook 方法在注册时有优先级,数字越小优先级越高;
在 PRE_ROUTING 有 -400 的 ipv4_conntrack_defrag
static unsigned int ipv4_conntrack_defrag(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct sock *sk = skb->sk;
// 如果不允许重组,返回 NF_ACCEPT
if (sk && sk_fullsock(sk) && (sk->sk_family == PF_INET) &&
inet_sk(sk)->nodefrag)
return NF_ACCEPT;
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
#if !IS_ENABLED(CONFIG_NF_NAT)
/* Previously seen (loopback)? Ignore. Do this before
fragment check. */
//nfct 不为空,并且没有 IPS_TEMPLATE_BIT 标志,说明此 ct 是在 raw 表匹配到 target 为 notrack 的规则。
if (skb_nfct(skb) && !nf_ct_is_template((struct nf_conn *)skb_nfct(skb)))
return NF_ACCEPT;
#endif
if (skb->_nfct == IP_CT_UNTRACKED)
return NF_ACCEPT;
#endif
/* Gather fragments. */
// 重组,将未完成重组的存在队列,重组失败的,释放 skb。
if (ip_is_fragment(ip_hdr(skb))) {
enum ip_defrag_users user =
nf_ct_defrag_user(state->hook, skb);
if (nf_ct_ipv4_gather_frags(state->net, skb, user))
return NF_STOLEN;
}
return NF_ACCEPT;
}
还有 -200 的 ipv4_conntrack_in,主要函数
tmpl = nf_ct_get(skb, &ctinfo);
if (tmpl || ctinfo == IP_CT_UNTRACKED) {
/* Previously seen (loopback or untracked)? Ignore. */
if ((tmpl && !nf_ct_is_template(tmpl)) ||
ctinfo == IP_CT_UNTRACKED) {
NF_CT_STAT_INC_ATOMIC(net, ignore);
return NF_ACCEPT;
}
skb->_nfct = 0;
}
ct 表里查询是否已建立或不被追踪,直接 return
ret = resolve_normal_ct(net, tmpl, skb, dataoff, pf, protonum, l4proto);
if (ret < 0) {
/* Too stressed to deal. */
NF_CT_STAT_INC_ATOMIC(net, drop);
ret = NF_DROP;
goto out;
}
nf_ct_get_tuple 先获取五元组或三元组
hash_conntrack_raw 和 __nf_conntrack_find_get 在 hash 表中查找是否存在
如果不存在 init_conntrack 新建
nf_ct_tuplehash_to_ctrack 获取 ct 连接
设置连接状态
- 如果数据包的方向是 reply,说明链接的两个方向都有数据包,就设置数据包状态为IP_CT_ESTABLISHED + IP_CT_IS_REPLY
- 如果已经收到了 reply 方向的数据包就将设置数据包状态为IP_CT_ESTABLISHED。
- 如果是一个期望链接就将设置数据包状态为 IP_CT_RELATED
- 还没有收到reply方向的数据,而且不是一个期望链接,就设置数据包状态为IP_CT_NEW
当在reply方向收到数据包后设置链接状态为IPS_SEEN_REPLY_BIT