前言:
不知道你有沒有這樣的困惑,iptables會用,可總是知其然不知其所以然,然后常常江里面的概念搞混,尤其是類似的操作,卻常常是以不同的稱謂出現:netfilter,iptables, firewalld....
所以,我們有必要了解一下其真正的內核實現,這樣有助於我們記憶iptables的用法。
本文主要是基於官網文檔進行的翻譯以及自己的理解: https://netfilter.org/documentation/HOWTO/netfilter-hacking-HOWTO.html#toc3, 還包括一些其他的連接都會在相應的位置注明
大綱:
1.netfilter框架
1.1 netfilter原理概述
1.2 netfilter的鈎子們
1.3 netfilter的核心基礎
1.4 代碼摘要
1.5 小小結
2.基於netfilter框架的實現
2.1 數據包篩選: IP Tables
2.2 連接跟蹤(Connection Tracking)
2.3 其他待補充
2.4 小小結
附: IP Tables的代碼實現舉例&實操
3.IP Tables的內核空間與用戶空間
3.1 ip_tables的數據結構
3.2 用戶空間的ip_tables
3.3 ip_tables的使用和遍歷
3.4 用戶空間工具
3.5 共享庫: libiptc
3.6 小小結
附:實操
4. iptables, iptables.service, firewalld.service,firewall-cmd辨析
4.1 iptables.service 和 iptables cmd
4.2 由firewalld實現的動態防火牆
4.3 firewalld.service和iptables.service
4.4 小小結
一. netfilter框架
1. netfilter原理概述
是一個脫離於普通socker接口的數據包處理框架(framework)。包含四個部分,
首先:hooks的定義.
每種協議都定義了一些hooks即鈎子(IPV4共定義的是5個),所謂的hooks是指在協議棧做包遍歷時,一些被明確定義的點(well-defined points)
於是在每一個point上,該協議棧都會使用數據包和hook號去調用netfilter框架。
其次:內核注冊監聽.
內核的某些部分可以針對每個協議的不同hook注冊監聽,當數據包被傳遞到netfilter框架(的hook)時,就會檢查是否有針對這個協議的這個hook注冊監聽;
如果注冊了,這些數據包就將面臨依序的檢查甚至可能是被更改(a chance to examine and possibly alter ),然后決定接下來是銷毀(NF_DROP), 還是允許(NF_ACCEPT), 還是告訴netfilter忽略(NF_STOLEN), 或者要求netfilter將這些數據包1進行排隊以用於用戶空間(NF_QUEUE);
第三部分:處理結果
將已經排隊的數據包收集起來(由ip_queue驅動),然后發送到用戶空間,為異步處理。
第四部分:文檔和注釋.....
在原生的netfilter框架之外,是各種各樣的模塊, 其中較為重要的是可擴展的NAT系統和數據包過濾系統(iptables)
2. netfilter的鈎子們
根據前面的概述可以看出來,netfilter框架的核心其實就是一系列的鈎子(hooks), 接下來我們就以IPV4為例,看看理想情況下這些hooks都放在了哪里?
A Packet Traversing the Netfilter System:
--->[1]--->[ROUTE]--->[3]--->[4]--->
| ^
| |
| [ROUTE]
v |
[2] [5]
| ^
| |
v |
解析:
1)對於從外面接收過來的數據包, 首先會經歷框架的第1號hook, 稱為NF_IP_PRE_ROUTING,通過后進入路由的邏輯;
在路由邏輯中, 會根據包的目的地址進行分流,
如果目的地址是自己, 那么會進入第2號hook,稱為NF_IP_LOCAL_IN,處理完才會交給上層進程
如果目的地不是字節,即需要交給另一個interface轉發出去的話,那在轉發之前進入第3號hook, 稱為NF_IP_FORWARD,
最后,交給第4號hook,稱為NF_IP_POST_ROUTING, 之后就可以發送出去了。
2) 對於本地生成的數據包, 在進入路由邏輯之前會進入第5號hook, 稱為NF_IP_LOCAL_OUT。
注:實際上在這種場景下, 第一次調用路由代碼是發生在hook之前,因為需要為報文填充源IP和一些IP選項。
3. netfilter的核心基礎
原文:Kernel modules can register to listen at any of these hooks. A module that registers a function must specify the priority of the function within the hook;
解析:
鈎子的位置規定好了,接下來就是去鈎子那里注冊一下。這些去注冊的內核模塊通過實現相應的注冊函數在hook里面放置一個監聽,注冊函數必須有優先級才行,只有這樣當netfilter hook在被內核網絡代碼調用的時候,會按序執行這些模塊所注冊的函數, 執行完畢了,這些模塊還有告訴netfilter接下來做什么,且是如下五種之一
1)NF_ACCEPT: 接下來繼續遍歷吧.
2)NF_DROP: 丟棄數據包; 不用繼續遍歷了.
3)NF_STOLEN: 數據包我接收了;不用繼續遍歷了.
4)NF_QUEUE: 將數據包入列 (通常用於用戶態處理).
5)NF_REPEAT: 再次調用該hook.
在此基礎上,我們可以構建相當復雜的數據包操作,比如如下章節所示。
4.代碼摘要
- hooks的定義,由具體的協議棧決定,以ipv4協議棧為例:
-
源碼目錄: include\uapi\linux\netfilter.h enum nf_inet_hooks { NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING, NF_INET_NUMHOOKS, NF_INET_INGRESS = NF_INET_NUMHOOKS, };
-
- 定義注冊監聽的接口(注:表示框架即netfiler只是定義了注冊接口,具體注冊由外部模塊主動發起)
-
目錄:linux-3.10.1\net\netfilter\core.c, 核心代碼: 0). 定義注冊參數類型 struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; //1.表示的含義是用戶注冊的回調函數, 即到達netfiler中的hook時, 需要執行的具體方法 struct module *owner; u_int8_t pf; unsigned int hooknum; //2. hook的編號, 即表示注冊到哪個hook上, 表如可以是NF_IP_PRE_ROUTING /* Hooks are ordered in ascending priority. */ int priority; //3. 優先級 }; 1). 注冊接口函數: 提供一個接口用於其他組件向hook中注冊回調函數 int nf_register_hook(struct nf_hook_ops *reg) { //1. 根據優先級找到(最大的那個)比我低的 list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) { if (reg->priority < elem->priority) break; } //2. 然后將我自己添加到鏈表中,位置也就是剛好找到的那個最大比我低的那個 list_add_rcu(®->list, elem->list.prev); } 2). 具體的注冊, 見下一個章節
-
- 綜合:將netfiler框架和協議棧結合
-
目錄:linux-3.10.1\net\ipv4\ip_input.c
//說明:協議棧接收到數據后,需要借助netfiler框架進入到hooks中,然后進一步進入到注冊的處理函數中 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { ... //進入netfiler框架:會調用nf_hooks_active(pf, hook)函數,具體的操作包括: // 會調用注冊在NF_INET_PRE_ROUTING這個hook中的所有函數, // ip_rcv_finish return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); }
-
一些參考鏈接:https://blog.csdn.net/lickylin/article/details/33020731
5. 小小結
netfilter顧名思義,是一個用來做網絡過濾用的. 在這里netfilter代表的只是一個框架,也就是說他只是定義了一套怎樣做過濾的體系,
那么,怎么做過濾呢? 這個過程涉及了三個方面,從網絡的角度從下至上分為:負責收發數據包的協議棧,netfiler框架,有過濾需求的模塊,
其中,netfiler則作為核心框架將協議棧和模塊進行有機結合,整個過程具體來說就是:
首先,對下,框架允許協議棧定義幾個安插點稱為hook/鈎子,於是該協議棧在處理自己數據流時,數據包會在這幾個安插點上進入netfilter框架;
然后,對上,框架支持模塊向hook中進行注冊(函數),表示數據流在netfilter框架中具體面臨的具體過濾操作是什么;
最后,數據包自下向上,當協議棧真正接收到數據后,會進入netfiler框架,之后數據包將在對應的hook按序被注冊的函數處理。
wxy:
關於hook和hook函數:
hook是具體的協議棧定義的, 即所謂的"well-defined points",可以看做是"打點"
hook函數是具體的模塊注冊的,即所謂的"examine and possibly alter", 可以看做是"點上的回調"
二. 基於netfilter框架的實現
0. 前情提要
netfiler是一個框架,他的工作只是負責:
對下(協議棧),允許協議棧定制hook,並指導數據包到達協議棧某個hook對應的point后進入netfiler框架中;
對上(模塊), 支持注冊回調函數, 並指導接收到數據包會依次被回調函數處理。
所以,問題的關鍵就在於定義了哪些hook以及在這些hook上將具體執行哪些操作, 其中hook的定義在上一個章節中可以知道根據不同的協議已經定義了一套hook。
而模塊如何注冊自己的過濾函數呢?一些內置的系統比如IP Tables,Connection Tracking則幫等就是負責幫助模塊實現了這些注冊,且有的還提供了面向用戶的操作。也就是說,模塊不會直接向hook注冊函數,而是由這樣一個功能模塊,以一個很友好的方式幫助模塊注冊自己的邏輯。 所以,接下來,我們就來看看這些系統是如何幫助到我們的。
1. 數據包篩選: IP Tables
數據包的篩選系統被稱為IP Tables,其已經被構建在netfilter框架中了. 他托生於ipchains(來自ipfwadm,來自BSD的ipfw IIRC) ,並具有可擴展行。內核模塊可以注冊一個新表,然后要求數據包去遍歷這個給定的表(Kernel modules can register a new table, and ask for a packet to traverse a given table)。這種對包進行篩選的方法則是用來進行包過濾( "filter"表),網絡地址轉化('nat'表),常規的路由前數據包處理('mangle'表)
每一個hook點都注冊了哪些表,以及優先級是怎樣的,如下圖所示:
--->PRE--------->[ROUTE]------>FWD------------------->POST------>
Conntrack | Mangle ^ Mangle
Mangle | Filter | NAT (Src)
NAT(Dst) | | Conntrack
(QDisc) | [ROUTE]
v |
IN Filter OUT Conntrack
| Conntrack ^ Mangle
| Mangle | NAT (Dst)
v NAT (?) | Filter
wxy:
關於注冊,netfilter部分的描述是:Kernel modules can register to listen at any of these hooks. A module that registers a function... 而此時就變成了: Kernel modules can register a new table, and ask for a packet to traverse a given table. 也就是說此時 表(table) 變成了注冊的內容,該怎么理解呢?結合Ip tables部分的源碼可以知道,其實是這樣的: 首先,內核模塊們都可以(不確定是不是任何的模塊)在hook點注冊自己的回調函數,回調中具體做什么也是模塊自己的事情 至於IP Tables系統/模塊,是一種內置的實現, 即幫我們在hook上注冊了回調,該回調的內部實現中,對數據的操作機制是借助一個可定制的"規則表",
即回調函數就可以根據該"規則表"對數據包進行處理。
而內核中的其他模塊,都可以將自己的需求注冊到"規則表"甚至新建"規則表"; 綜上所述,可以認為是IP Tables用來協助內核模塊去hook上注冊函數.....
解析:
1). Filter 包過濾, filter表
filter表永遠不會修改包,只會過濾他們;filter表都注冊到哪些hook中呢?有3/5個hook points都這個表的身影,分別是:
NF_IP_LOCAL_IN: 對於目的是自己的報文,在進入本地處理之前
NF_IP_FORWARD:對於目的是別人,在轉發之前
NF_IP_LOCAL_OUT: 對於自己產的,在發出去之前
以上三點可以看出, filter表的安插讓每一個數據包,有且只有一次需要經歷filter的過濾。
2). Nat地址轉換, nat表
NF_IP_PRE_ROUTING 和 NF_IP_POST_ROUTING: 針對的是 non-local, 即目的不是自己的數據包,分別在入口和出口處做"目的轉換"和"源轉換"。
NF_IP_LOCAL_OUT 和 NF_IP_LOCAL_IN: 如果定義了CONFIG_IP_NF_NAT_LOCAL, 則用於altering the destination of local packets.
(wxy: 其中NF_IP_LOCAL_IN據說從Centos7才有,6沒有。 另外均是做目的轉換么??)
3). Mangle,mangle表
用於拆解數據包然后更改信息,例如用於TOS和TCPMSS。五個hook均注冊了mangle表。
2. 連接跟蹤(Connection Tracking)
原文:Connection tracking is fundamental to NAT, but it is implemented as a separate module; this allows an extension to the packet filtering code to simply and cleanly use connection tracking (the `state' module).
解析:
實現原理(待整理)
1. nf_conntrack的原理
參考鏈接:
https://blog.csdn.net/dog250/article/details/78372576
connection tracking, 即連接跟蹤,用來保存連接信息的,在進入hook之前就會進行的邏輯處理,每一個連接對應的結構體為:
連接跟蹤是 在PREROUTING鏈里進行處理的,更確切的說是在該鏈之前,只不過二者通常是綁定出現?
conntrack默認最大跟蹤65536個連接,查看當前系統設置最大連接數:
cat /proc/sys/net/netfilter/nf_conntrack_max
查看連接跟蹤有多少條目:
# cat /proc/sys/net/netfilter/nf_conntrack_count
查看established連接狀態最多保留幾天,默認是432000秒,就是5天;如果覺得時間太長可以修改。還有各種tcp連接狀態的保留時間,都可以修改的。
# cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
IP_conntrack模塊根據IP地址可以實時追蹤本機TCP/UDP/ICMP的連接詳細信息並保存在內存中“/proc/net/nf_conntrack”文件中
例如我的82環境:
[root@tmp-82 net]# cat /proc/sys/net/netfilter/nf_conntrack_max
65536
[root@tmp-82 net]# cat /proc/sys/net/netfilter/nf_conntrack_count
13
[root@tmp-82 net]# cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
432000
[root@tmp-82 net]# cat /proc/net/nf_conntrack ipv4 2 tcp 6 431978 ESTABLISHED src=192.168.48.231 dst=192.168.48.82 sport=35356 dport=23002 src=192.168.48.82 dst=192.168.48.231 sport=23002 dport=35356 [ASSURED] mark=0 zone=0 use=2 ipv4 2 udp 17 29 src=192.168.48.248 dst=192.168.48.255 sport=57133 dport=18000 [UNREPLIED] src=192.168.48.255 dst=192.168.48.248 sport=18000 dport=57133 mark=0 zone=0 use=2 ipv4 2 unknown 2 599 src=192.168.48.2 dst=224.0.0.1 [UNREPLIED] src=224.0.0.1 dst=192.168.48.2 mark=0 zone=0 use=2 ipv4 2 tcp 6 431996 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=60446 dport=23002 src=192.168.48.82 dst=192.168.48.159 sport=23002 dport=60446 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431992 ESTABLISHED src=192.168.48.224 dst=192.168.48.82 sport=59644 dport=23002 src=192.168.48.82 dst=192.168.48.224 sport=23002 dport=59644 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431995 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=60444 dport=23002 src=192.168.48.82 dst=192.168.48.159 sport=23002 dport=60444 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431984 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=60415 dport=23002 src=192.168.48.82 dst=192.168.48.159 sport=23002 dport=60415 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431995 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=33918 dport=22 src=192.168.48.82 dst=192.168.48.159 sport=22 dport=33918 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431984 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=60414 dport=23002 src=192.168.48.82 dst=192.168.48.159 sport=23002 dport=60414 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 299 ESTABLISHED src=192.168.95.143 dst=192.168.48.82 sport=56724 dport=22 src=192.168.48.82 dst=192.168.95.143 sport=22 dport=56724 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431974 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=52380 dport=23002 src=192.168.48.82 dst=192.168.48.159 sport=23002 dport=52380 [ASSURED] mark=0 zone=0 use=2 ipv4 2 tcp 6 431974 ESTABLISHED src=192.168.48.159 dst=192.168.48.82 sport=52378 dport=23002 src=192.168.48.82 dst=192.168.48.159 sport=23002 dport=52378 [ASSURED] mark=0 zone=0 use=2
在一個由容器的宿主機上查看關於容器的條目
[root@node231 ~]# cat /proc/net/nf_conntrack|grep 23002
ipv4 2 tcp 6 86397 ESTABLISHED src=172.17.0.2 dst=192.168.48.82 sport=35356 dport=23002 src=192.168.48.82 dst=192.168.48.231 sport=23002 dport=35356 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2
字段說明:
ASSURED: 說明兩個方向已沒有流量
wxy: CentOS Linux release 7.7.1908 (Core) 發下如上的命令不好用
CentOS Linux release 7.3.1611 (Core)是ok, 不知道是版本問題還是因為前者沒有連接信息
3. 其他待補充
4. 小小結
在這里,先不要考慮我們經常使用的那個iptables命令(后面會講他),而是將其看做是一個內置功能,為了區別將其稱之為IP Tables。
前面說了,既然netfiler給我們提供hook定義 + 回調函數注冊hook + 具體實現回調這樣一整套的機制;
針對各個協議,hook的定義已經做好比如ipv4(第一張),接下來的實現就由這樣一個已經內置的系統,稱之為: IP Tables
他實現了這樣一個功能:
1)內存中維護了一個表,里面是一條一條規則
2)去netfilter中那里注冊hook(即,回調函數),該函數會根據表中的規則對數據進行操作(filter, nat, mangle,raw)
關於鏈chain的含義:這是IP Tables內部的一個概念, 是用於管理在每個hook上注冊的多個hook函數。因為在netfilter的每個hook點上,可以注冊多個表/操作表的hook函數, 且具有一定的順序,於是他們就構成了一個鏈,一個有序的鏈來代表按序執行模塊們注冊的回調函數。
根據源碼的位置也可以發現netfilter和 IP Tables之間的關系
netfilter:net\netfilter, 網絡中的一個模塊
IP Tables:net\ipv4\netfilter, 網絡中ipv4數據包中,關於netfilter方面的內容
wxy:
目前我對這個所謂的系統的理解是,他只是一個內核單獨的功能模塊,如果想用這個功能則需要加載才行
附1: IP Tables的代碼實現舉例
IP Tables系統針對多個協議都實現了 回調函數注冊hook + 回調函數實現 :ipv4/ipv6協議棧,arp...(wxy: 還有其他? 待補充)
- ipv4
-
View Code目錄:linux-3.10.1\net\ipv4\netfilter\ip_tables.c,iptable_filter.c,iptable_nat.c,iptable_mangle.c,..... 核心實現: 0). ip_tables初始 module_init(ip_tables_init); static int __init ip_tables_init(void) { //part1: 向下協議棧 ret = register_pernet_subsys(&ip_tables_net_ops); //part2: 自身: ret = xt_register_targets(ipt_builtin_tg, ARRAY_SIZE(ipt_builtin_tg)); ret = xt_register_matches(ipt_builtin_mt, ARRAY_SIZE(ipt_builtin_mt)); //part3: 向上用戶空間, socket注冊, 用來監聽用戶空間的請求(wxy:很重要,libiptc庫就是通過socket與之交互) /* Register setsockopt */ ret = nf_register_sockopt(&ipt_sockopts); //pf為: PF_INET, 操作類別:IPT_BASE_CTL ~ IPT_SO_SET_MAX } 其中,iptable_filter_net_ops -> ip_tables_net_init -> xt_proto_init { } 1). filter表 module_init(iptable_filter_init); static int __init iptable_filter_init(void) { //part1: 會進行表注冊, 即為網絡實例net創建ipv4的過濾表 ret = register_pernet_subsys(&iptable_filter_net_ops); //part2: hook的注冊 /* Register hooks */ filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook); { ... ret = nf_register_hooks(ops, num_hooks); ...} ... } 其中, 1) static struct pernet_operations iptable_filter_net_ops = { .init = iptable_filter_net_init, .exit = iptable_filter_net_exit, }; static int __net_init iptable_filter_net_init(struct net *net) { repl = ipt_alloc_initial_table(&packet_filter); //1. 初始化對應的replace實例 net->ipv4.iptable_filter = ipt_register_table(net, &packet_filter, repl); //2. 注冊table } struct xt_table *ipt_register_table(struct net *net, const struct xt_table *table, const struct ipt_replace *repl) { //1. newinfo = xt_alloc_table_info(repl->size); //2. ret = translate_table(net, newinfo, loc_cpu_entry, repl); //3. new_table = xt_register_table(net, table, &bootstrap, newinfo); } 2)static const struct xt_table packet_filter = { .name = "filter", //這個表感興趣的hook點,有三個: ((1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD) | (1 << NF_INET_LOCAL_OUT)) .valid_hooks = FILTER_VALID_HOOKS, .me = THIS_MODULE, .af = NFPROTO_IPV4, .priority = NF_IP_PRI_FILTER, }; 3)static unsigned int iptable_filter_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter); } 2). nat表 ... ...
-
- arp
-
View Codemodule_init(arptable_filter_init); static int __init arptable_filter_init(void) { ret = register_pernet_subsys(&arptable_filter_net_ops); arpfilter_ops = xt_hook_link(&packet_filter, arptable_filter_hook); }
-
附2:模塊關系示例
# lsmod Module Size Used by iptable_nat 12875 1 nf_nat_ipv4 14115 1 iptable_nat iptable_mangle 12695 1 iptable_security 12705 1 iptable_raw 12678 1 iptable_filter 12810 1 ip_tables 27115 5 iptable_security,iptable_filter,iptable_mangle,iptable_nat,iptable_raw //被這些功能模塊所調用 [root@host231 ~]# modinfo ip_tables filename: /lib/modules/3.10.0-514.el7.x86_64/kernel/net/ipv4/netfilter/ip_tables.ko description: IPv4 packet filter ... [root@host231 ~]# modinfo iptable_filter filename: /lib/modules/3.10.0-514.el7.x86_64/kernel/net/ipv4/netfilter/iptable_filter.ko description: iptables filter table depends: ip_tables //ip_tables這個模塊加載后,才可以支撐iptable_filter模塊的加載:1)表創建/注冊; 2)hooks注冊 ...
<續>: Linux防火牆,Netfiler,iptables概念原理全解析(下) - 水鬼子 - 博客園 (cnblogs.com)
