. iptables規(guī)則中的state匹配 在2.4/2.6內(nèi)核的Linux中的防火墻代碼netfilter中實現(xiàn)了狀態(tài)檢測(stateful
inspection)檢測技術(shù),在命令行接口的iptables命令是通過匹配“-m state”來實現(xiàn),“-m
state”匹配中定義了四種狀態(tài):
NEW,表示新連接;
ESTABLISHED,表示已經(jīng)建立的連接;
RELATED,表示相關(guān)的子連接;
INVALID,表示非法狀態(tài)。
所以在制定iptables規(guī)則時,只需要定義單向的規(guī)則,再加上如下兩條規(guī)則,就可以保證連接回應(yīng)包能正確通過防火墻而不必加反向規(guī)則:
iptables -I FORWARD -m state --state ESTABLISHED,RELATED -j
ACCEPT
iptables -I FORWARD -m state --state INVALID -j DROP (如果是保護本機,將FORWARD改為INPUT) 2. 內(nèi)核中的state匹配的實現(xiàn)
(以下的內(nèi)核代碼基于2.4.26內(nèi)核)
和匹配state對應(yīng)的內(nèi)核部分實現(xiàn)是ipt_state.c(net/ipv4/netfilter),匹配部分的代碼非常簡單,就是根據(jù)函數(shù)ip_conntrack_get()的返回值來判斷數(shù)據(jù)包的狀態(tài)類型: if (!ip_conntrack_get((struct sk_buff *)skb, &ctinfo)) statebit = IPT_STATE_INVALID; else statebit = IPT_STATE_BIT(ctinfo); 3. skb與ip_conntrack的關(guān)聯(lián) 再跟蹤到ip_conntrack_get()函數(shù)
(net/ipv4/netfilter_ipv4/ip_conntrack_core.c),該函數(shù)有兩個參數(shù),一個是指向sk_buff數(shù)據(jù)包的指
針skb,另一個是枚舉類型enum ip_conntrack_info的指針,
實際上是一個返回參數(shù),將返回數(shù)據(jù)包的狀態(tài)類型;該函數(shù)的返回值是連接結(jié)構(gòu)struct
ip_conntrack的指針,表示該包屬于哪個連接,如果連接不存在,返回空,該包也將被認為是非法包:
struct ip_conntrack *
ip_conntrack_get(struct sk_buff *skb, enum ip_conntrack_info *ctinfo) { if (skb->nfct) return __ip_conntrack_get(skb->nfct, ctinfo); return NULL; } ip_conntrack_get()函數(shù)本身也很簡單,實際上是__ip_conntrack_get()函數(shù)
(net/ipv4/netfilter_ipv4/ip_conntrack_core.c)的包裹函數(shù),判斷skb的nfct是否非空,非空則將其和
枚舉指針ctinfo傳給__ip_conntrack_get()函數(shù),nfct是sk_buff中用于描述netfilter conntrack信息的struct
nf_ct_info指針,在內(nèi)核配置了CONFIG_NETFILTER后有效。struct
nf_ct_info結(jié)構(gòu)在include/linux/sk_buff.h中定義:
struct nf_conntrack { atomic_t use; void (*destroy)(struct nf_conntrack *); }; struct nf_ct_info {
struct nf_conntrack *master; }; 在__ip_conntrack_get()函數(shù)中,通過nfct->master獲取連接指針ct,而nfct相對于ct成員infos的偏移就是連接狀態(tài): static inline struct ip_conntrack
*
__ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo) { struct ip_conntrack *ct = (struct ip_conntrack *)nfct->master; /* ctinfo is the index of the nfct inside the conntrack
*/
*ctinfo = nfct - ct->infos; IP_NF_ASSERT(*ctinfo >= 0 && *ctinfo < IP_CT_NUMBER); return ct; } 由此可見,netfilter的狀態(tài)檢測過程很簡潔,如果skb包中的nfct指針設(shè)置了的話,可以很快地確定該skb包屬于哪個連接,如果不屬于任何連接則是非法包。現(xiàn)在的焦點是skb中的nfct值是如何設(shè)置的?
4. netfilter如何實現(xiàn)中skb與ip_conntrack連接的關(guān)聯(lián) nfct值是在resolve_normal_ct()函數(shù)(net/ipv4/netfilter/ip_conntrack_core.c)中定義的,該函
數(shù)被ip_conntrack_in()函數(shù)(net/ipv4/netfilter/ip_conntrack_core.c)調(diào)用,在
ip_conntrack_local()函數(shù)(net/ipv4/netfilter/ip_conntrack_standalone.c)中調(diào)用了
ip_conntrack_in()函數(shù),而這兩個函數(shù)分別是PREROUTING鏈和OUTPUT鏈的起始函數(shù),由下面兩個結(jié)構(gòu)
定義(net/ipv4/netfilter/ip_conntrack_standalone.c):
static struct nf_hook_ops ip_conntrack_in_ops
= { { NULL, NULL }, ip_conntrack_in, PF_INET, NF_IP_PRE_ROUTING, NF_IP_PRI_CONNTRACK }; static struct nf_hook_ops ip_conntrack_local_out_ops
= { { NULL, NULL }, ip_conntrack_local, PF_INET, NF_IP_LOCAL_OUT, NF_IP_PRI_CONNTRACK }; 這說明netfilter是在連接的第一個包時就建立連接了,這個連接可能是由外部發(fā)起的,也可能是由本機發(fā)起的,后續(xù)包查找連接HASH表找到相應(yīng)的連接,然后賦值給skb結(jié)構(gòu)中的nfct,這就是resolve_normal_ct()函數(shù)的工作: /* On success, returns conntrack ptr, sets skb->nfct and
ctinfo */
static inline struct ip_conntrack * resolve_normal_ct(struct sk_buff *skb, // 數(shù)據(jù)包 struct ip_conntrack_protocol *proto, // 連接協(xié)議跟蹤,該結(jié)構(gòu)對應(yīng)協(xié)議特殊處理過程,如TCP的狀態(tài)轉(zhuǎn)換等 int *set_reply, // 返回值,表示該包是否是響應(yīng)方的包 unsigned int hooknum, enum ip_conntrack_info *ctinfo) { struct ip_conntrack_tuple tuple; struct ip_conntrack_tuple_hash *h; IP_NF_ASSERT((skb->nh.iph->frag_off &
htons(IP_OFFSET)) == 0);
// 確定該包對應(yīng)的tuple,對TCP/UDP協(xié)議來說就是地址協(xié)議端口的5元組
if (!get_tuple(skb->nh.iph, skb->len, &tuple, proto)) return NULL; /* look for tuple match */
// 根據(jù)tuple值查找連接HASH h = ip_conntrack_find_get(&tuple, NULL); if (!h) { // 在當前連接表中沒找到,說明是新連接的包,初始化新連接 h = init_conntrack(&tuple, proto, skb); if (!h) return NULL; // 該包非法返回NULL,其他錯誤會返回錯誤號的負值 if (IS_ERR(h)) return (void *)h; } /* It exists; we have (non-exclusive) reference. */
if (DIRECTION(h) == IP_CT_DIR_REPLY) { // 連接回應(yīng)方向包 *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY; /* Please set reply bit if this packet OK */ // 這個包是響應(yīng)方的包 *set_reply = 1; } else { // 連接原始方向的包 /* Once we've had two way comms, always ESTABLISHED. */ if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) { DEBUGP("ip_conntrack_in: normal packet for %p\n", h->ctrack); // 如果不是第一個包,設(shè)置ESTABLISHED屬性,這個位標志表示發(fā)現(xiàn)了響應(yīng)方發(fā)的包, // 表示連接可以建立,該標志在ip_conntrack_in()函數(shù)中設(shè)置 *ctinfo = IP_CT_ESTABLISHED; } else if (test_bit(IPS_EXPECTED_BIT, &h->ctrack->status)) { DEBUGP("ip_conntrack_in: related packet for %p\n", h->ctrack); // 該標志表示是子連接的包,如FTP的數(shù)據(jù)連接 // 該標志在init_conntrack()函數(shù)中設(shè)置 *ctinfo = IP_CT_RELATED; } else { DEBUGP("ip_conntrack_in: new packet for %p\n", h->ctrack); // 沒有任何標志,是連接的第一個包,設(shè)置為NEW屬性 *ctinfo = IP_CT_NEW; } // 這個包是發(fā)起方的包 *set_reply = 0; } // infos是數(shù)量為IP_CT_NUMBER(=5)的數(shù)組,分別對應(yīng)連接發(fā)起方向的 // NEW(2)/ESTABLISHED(0)/RELATED(1) // 和連接響應(yīng)方向的ESTABLISHED(3)/RELATED(4) // 每個skb包都會劃歸為其中一類, 也就是ctinfo的值 // 每個skb包根據(jù)其類型,將其nfct值設(shè)為相應(yīng)infos項的地址 skb->nfct = &h->ctrack->infos[*ctinfo]; return h->ctrack; } 在init_conntrack()函數(shù)中將連接的infos值初始化為:
... for (i=0; i < IP_CT_NUMBER; i++) conntrack->infos[i].master = &conntrack->ct_general; ... 也就是infos各項的值都是相同的,沒有發(fā)現(xiàn)在其他地方重新賦值,所以把所有項實際都是相同的,指向同一個連接。設(shè)計之初可能是考慮要區(qū)分,
但實現(xiàn)時還是只用到一個就行了,在其他地方基本只用到infos[0]進行處理,infos的用處目前只用來判斷狀態(tài),也就是每一項相對于infos
[0]的偏移值。
最后討論一下為什么在__ip_conntrack_get()函數(shù)中nfct->master值就是連接的地址:
__ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo) { struct ip_conntrack *ct = (struct ip_conntrack *)nfct->master; ... skb的nfct值是infos數(shù)組中某一項的地址,而這些項中的master成員都初始化為連接中ct_general的地址,而ct_general參數(shù)是連接結(jié)構(gòu)的第一項參數(shù),所以其地址和連接結(jié)構(gòu)的地址是相同,在Linux內(nèi)核中,類似用法很多。
5. 結(jié)論
在每個skb數(shù)據(jù)包進入netfilter架構(gòu)時,netfilter就會給其建立連接,通過簡單方法就能找到連接,能區(qū)分出發(fā)起方、響應(yīng)方以及第一個包還是后續(xù)包,并能識別子連接,從而實現(xiàn)狀態(tài)檢測。
連接結(jié)構(gòu)中的infos數(shù)組就目前而言似乎不是很必要,單值就可以,然后增加一個連接狀態(tài)參數(shù),而不是通過infos的偏移來判斷。// Linux下檢測網(wǎng)絡(luò)狀態(tài)是否正常 #include <sys/types.h> #include <string.h> #include <stdlib.h> #include <sys/ioctl.h> #include <stdio.h> #include <errno.h> #include <net/if.h> struct ethtool_value { __uint32_t cmd; __uint32_t data; }; int main(int argc, char* argv[]) { struct ethtool_value edata; int fd = -1, err = 0; struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, "eth0"); fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("Cannot get control socket"); return 70; } edata.cmd = 0x0000000a; ifr.ifr_data = (caddr_t)&edata; err = ioctl(fd, 0x8946, &ifr); if (err == 0) { fprintf(stdout, "Link detected: %s/n", edata.data ? "yes":"no"); } else if (errno != EOPNOTSUPP) { perror("Cannot get link status"); } return 0; } |
|