自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

來,今天飛哥帶你理解 Iptables 原理!

開發(fā) 前端
現(xiàn)在 iptables 這個工具的應(yīng)用似乎是越來越廣了。不僅僅是在傳統(tǒng)的防火墻、NAT 等功能出現(xiàn),在今天流行的的 Docker、Kubernets、Istio 項目中也經(jīng)常能見著對它的身影。正因為如此,所以深入理解 iptables 工作原理是非常有價值的事情。

[[438998]]

大家好,我是飛哥!

現(xiàn)在 iptables 這個工具的應(yīng)用似乎是越來越廣了。不僅僅是在傳統(tǒng)的防火墻、NAT 等功能出現(xiàn),在今天流行的的 Docker、Kubernets、Istio 項目中也經(jīng)常能見著對它的身影。正因為如此,所以深入理解 iptables 工作原理是非常有價值的事情。

Linux 內(nèi)核網(wǎng)絡(luò)棧是一個純內(nèi)核態(tài)的東西,和用戶層功能是天然隔離。但為了迎合各種各樣用戶層不同的需求,內(nèi)核開放了一些口子出來供用戶干預(yù)。使得用戶層可以通過一些配置,改變內(nèi)核的工作方式,從而實現(xiàn)特殊的需求。

Linux 在內(nèi)核網(wǎng)絡(luò)組件中很多關(guān)鍵位置布置了 netfilter 過濾器。Iptables 就是基于 netfilter 來實現(xiàn)的。所以本文中 iptables 和 netfilter 這兩個名詞有時候就混著用了。

飛哥也在網(wǎng)上看過很多關(guān)于 netfilter 技術(shù)文章,但是我覺得都寫的不夠清晰。所以咱們擼起袖子,自己寫一篇。Netfilter 的實現(xiàn)可以簡單地歸納為四表五鏈。我們來詳細(xì)看看四表、五鏈究竟是啥意思。

一、Iptables 中的五鏈

Linux 下的 netfilter 在內(nèi)核協(xié)議棧的各個重要關(guān)卡埋下了五個鉤子。每一個鉤子都對應(yīng)是一系列規(guī)則,以鏈表的形式存在,所以俗稱五鏈。當(dāng)網(wǎng)絡(luò)包在協(xié)議棧中流轉(zhuǎn)到這些關(guān)卡的時候,就會依次執(zhí)行在這些鉤子上注冊的各種規(guī)則,進(jìn)而實現(xiàn)對網(wǎng)絡(luò)包的各種處理。

要想把五鏈理解好,飛哥認(rèn)為最關(guān)鍵是要把內(nèi)核接收、發(fā)送、轉(zhuǎn)發(fā)三個過程分開來看。

1.1 接收過程

Linux 在網(wǎng)絡(luò)包接收在 IP 層的入口函數(shù)是 ip_rcv。網(wǎng)絡(luò)在這里包碰到的第一個 HOOK 就是 PREROUTING。當(dāng)該鉤子上的規(guī)則都處理完后,會進(jìn)行路由選擇。如果發(fā)現(xiàn)是本設(shè)備的網(wǎng)絡(luò)包,進(jìn)入 ip_local_deliver 中,在這里又會遇到 INPUT 鉤子。

我們來看下詳細(xì)的代碼,先看 ip_rcv。

  1. //file: net/ipv4/ip_input.c 
  2. int ip_rcv(struct sk_buff *skb, ......){ 
  3.     ...... 
  4.     return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL
  5.                ip_rcv_finish); 
  6.  

NF_HOOK 這個函數(shù)會執(zhí)行到 iptables 中 pre_routing 里的各種表注冊的各種規(guī)則。當(dāng)處理完后,進(jìn)入 ip_rcv_finish。在這里函數(shù)里將進(jìn)行路由選擇。這也就是 PREROUTING 這一鏈名字得來的原因,因為是在路由前執(zhí)行的。

  1. //file: net/ipv4/ip_input.c 
  2. static int ip_rcv_finish(struct sk_buff *skb){ 
  3.     ... 
  4.     if (!skb_dst(skb)) { 
  5.         int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, 
  6.                            iph->tos, skb->dev); 
  7.         ... 
  8.     } 
  9.     ... 
  10.     return dst_input(skb); 
  11.  

如果發(fā)現(xiàn)是本地設(shè)備上的接收,會進(jìn)入 ip_local_deliver 函數(shù)。接著是又會執(zhí)行到 LOCAL_IN 鉤子,這也就是我們說的 INPUT 鏈。

  1. //file: net/ipv4/ip_input.c 
  2. int ip_local_deliver(struct sk_buff *skb){ 
  3.  ...... 
  4.     return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL
  5.                ip_local_deliver_finish); 
  6.  

簡單總結(jié)接收數(shù)據(jù)的處理流程是:PREROUTING鏈 -> 路由判斷(是本機(jī))-> INPUT鏈 -> ...

1.2 發(fā)送過程

Linux 在網(wǎng)絡(luò)包發(fā)送的過程中,首先是發(fā)送的路由選擇,然后碰到的第一個 HOOK 就是 OUTPUT,然后接著進(jìn)入 POSTROUTING 鏈。

來大致過一下源碼,網(wǎng)絡(luò)層發(fā)送的入口函數(shù)是 ip_queue_xmit。

  1. //file: net/ipv4/ip_output.c 
  2. int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl) 
  3.  // 路由選擇過程 
  4.  // 選擇完后記錄路由信息到 skb 上 
  5.  rt = (struct rtable *)__sk_dst_check(sk, 0); 
  6.  if (rt == NULL) { 
  7.   // 沒有緩存則查找路由項 
  8.   rt = ip_route_output_ports(...); 
  9.   sk_setup_caps(sk, &rt->dst); 
  10.  } 
  11.  skb_dst_set_noref(skb, &rt->dst); 
  12.  ... 
  13.  //發(fā)送 
  14.  ip_local_out(skb); 

在這里先進(jìn)行了發(fā)送時的路由選擇,然后進(jìn)入發(fā)送時的 IP 層函數(shù) __ip_local_out。

  1. //file: net/ipv4/ip_output.c  
  2. int __ip_local_out(struct sk_buff *skb) 
  3.  struct iphdr *iph = ip_hdr(skb); 
  4.  
  5.  iph->tot_len = htons(skb->len); 
  6.  ip_send_check(iph); 
  7.  return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL
  8.          skb_dst(skb)->dev, dst_output); 

上面的 NF_HOOK 將發(fā)送數(shù)據(jù)包送入到 NF_INET_LOCAL_OUT (OUTPUT) 鏈。執(zhí)行完后,進(jìn)入 dst_output。

  1. //file: include/net/dst.h 
  2. static inline int dst_output(struct sk_buff *skb) 
  3.  return skb_dst(skb)->output(skb); 

在這里獲取到之前的選路,并調(diào)用選到的 output 發(fā)送。將進(jìn)入 ip_output。

  1. //file: net/ipv4/ip_output.c 
  2. int ip_output(struct sk_buff *skb) 
  3.  ... 
  4.  
  5.  //再次交給 netfilter,完畢后回調(diào) ip_finish_output 
  6.  return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev, 
  7.   ip_finish_output, 
  8.   !(IPCB(skb)->flags & IPSKB_REROUTED)); 

總結(jié)下發(fā)送數(shù)據(jù)包流程是:路由選擇 -> OUTPUT鏈 -> POSTROUTING鏈 -> ...

1.3 轉(zhuǎn)發(fā)過程

其實除了接收和發(fā)送過程以外,Linux 內(nèi)核還可以像路由器一樣來工作。它將接收到網(wǎng)絡(luò)包(不屬于自己的),然后根據(jù)路由表選到合適的網(wǎng)卡設(shè)備將其轉(zhuǎn)發(fā)出去。

這個過程中,先是經(jīng)歷接收數(shù)據(jù)的前半段。在 ip_rcv 中經(jīng)過 PREROUTING 鏈,然后路由后發(fā)現(xiàn)不是本設(shè)備的包,那就進(jìn)入 ip_forward 函數(shù)進(jìn)行轉(zhuǎn)發(fā),在這里又會遇到 FORWARD 鏈。最后還會進(jìn)入 ip_output 進(jìn)行真正的發(fā)送,遇到 POSTROUTING 鏈。

我們來過一下源碼,先是進(jìn)入 IP 層入口 ip_rcv,在這里遇到 PREROUTING 鏈。

  1. //file: net/ipv4/ip_input.c 
  2. int ip_rcv(struct sk_buff *skb, ......){ 
  3.     ...... 
  4.     return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL
  5.                ip_rcv_finish); 
  6.  

PREROUTING 鏈條上的規(guī)則都處理完后,進(jìn)入 ip_rcv_finish,在這里路由選擇,然后進(jìn)入 dst_input。

  1. //file: include/net/dst.h 
  2. static inline int dst_input(struct sk_buff *skb) 
  3.  return skb_dst(skb)->input(skb); 

轉(zhuǎn)發(fā)過程的這幾步和接收過程一模一樣的。不過內(nèi)核路徑就要從上面的 input 方法調(diào)用開始分道揚(yáng)鑣了。非本設(shè)備的不會進(jìn)入 ip_local_deliver,而是會進(jìn)入到 ip_forward。

  1. //file: net/ipv4/ip_forward.c 
  2. int ip_forward(struct sk_buff *skb) 
  3.  ...... 
  4.  return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev, 
  5.          rt->dst.dev, ip_forward_finish); 

在 ip_forward_finish 里會送到 IP 層的發(fā)送函數(shù) ip_output。

  1. //file: net/ipv4/ip_output.c 
  2. int ip_output(struct sk_buff *skb) 
  3.  ... 
  4.  //再次交給 netfilter,完畢后回調(diào) ip_finish_output 
  5.  return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev, 
  6.   ip_finish_output, 
  7.   !(IPCB(skb)->flags & IPSKB_REROUTED)); 

在 ip_output 里會遇到 POSTROUTING 鏈。再后面的流程就和發(fā)送過程的下半段一樣了。

總結(jié)下轉(zhuǎn)發(fā)數(shù)據(jù)過程:PREROUTING鏈 -> 路由判斷(不是本設(shè)備,找到下一跳) -> FORWARD鏈 -> POSTROUTING鏈 -> ...

1.4 iptables 匯總

理解了接收、發(fā)送和轉(zhuǎn)發(fā)三個過程以后,讓我們把上面三個流程匯總起來。

數(shù)據(jù)接收過程走的是 1 和 2,發(fā)送過程走的是 4 、5,轉(zhuǎn)發(fā)過程是 1、3、5。有了這張圖,我們能更清楚地理解 iptables 和內(nèi)核的關(guān)系。

二、Iptables 的四表

在上一節(jié)中,我們介紹了 iptables 中的五個鏈。在每一個鏈上都可能是由許多個規(guī)則組成的。在 NF_HOOK 執(zhí)行到這個鏈的時候,就會把規(guī)則按照優(yōu)先級挨個過一遍。如果有符合條件的規(guī)則,則執(zhí)行規(guī)則對應(yīng)的動作。

而這些規(guī)則根據(jù)用途的不同,又可以raw、mangle、nat 和 filter。

row 表的作用是將命中規(guī)則的包,跳過其它表的處理,它的優(yōu)先級最高。

mangle 表的作用是根據(jù)規(guī)則修改數(shù)據(jù)包的一些標(biāo)志位,比如 TTL

nat 表的作用是實現(xiàn)網(wǎng)絡(luò)地址轉(zhuǎn)換

filter 表的作用是過濾某些包,這是防火墻工作的基礎(chǔ)

例如在 PREROUTING 鏈中的規(guī)則中,分別可以執(zhí)行 row、mangle 和 nat 三種功能。

我們再來聊聊,為什么不是全部四個表呢。這是由于功能的不同,不是所有功能都會完全使用到五個鏈。

Raw 表目的是跳過其它表,所以只需要在接收和發(fā)送兩大過程的最開頭處把關(guān),所以只需要用到 PREROUTING 和 OUTPUT 兩個鉤子。

Mangle 表有可能會在任意位置都有可能會修改網(wǎng)絡(luò)包,所以它是用到了全部的鉤子位置。

NAT 分為 SNAT(Source NAT)和 DNAT(Destination NAT)兩種,可能會工作在 PREROUTING、INPUT、OUTPUT、POSTROUTING 四個位置。

Filter 只在 INPUT、OUTPUT 和 FORWARD 這三步中工作就夠了。

從整體上看,四鏈五表的關(guān)系如下圖。

這里再多說一點,每個命名空間都是有自己獨(dú)立的 iptables 規(guī)則的。我們拿 NAT 來舉例,內(nèi)核在遍歷 NAT 規(guī)則的時候,是從 net(命名空間變量)的 ipv4.nat_table 上取下來的。NF_HOOK 最終會執(zhí)行到 nf_nat_rule_find 函數(shù)。

  1. //file: net/ipv4/netfilter/iptable_nat.c 
  2. static unsigned int nf_nat_rule_find(...) 
  3.  struct net *net = nf_ct_net(ct); 
  4.  unsigned int ret; 
  5.  
  6.  //重要!!!!!! nat_table 是在 namespace 中存儲著的 
  7.  ret = ipt_do_table(skb, hooknum, inout, net->ipv4.nat_table); 
  8.  if (ret == NF_ACCEPT) { 
  9.   if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum))) 
  10.    ret = alloc_null_binding(ct, hooknum); 
  11.  } 
  12.  return ret; 

Docker 容器就是基于命名空間來工作的,所以每個 Docker 容器中都可以配置自己獨(dú)立的 iptables 規(guī)則。

三、Iptables 使用舉例

看完前面兩小節(jié),大家已經(jīng)理解了四表五鏈?zhǔn)侨绾螌崿F(xiàn)的了。那我們接下來通過幾個實際的功能來看下實踐中是如何使用 iptables 的。

3.1 nat

假如說我們有一臺 Linux,它的 eth0 的 IP 是10.162.0.100,通過這個 IP 可以訪問另外其它服務(wù)器。現(xiàn)在我們在這臺機(jī)器上創(chuàng)建了個 Docker 虛擬網(wǎng)絡(luò)環(huán)境 net1 出來,它的網(wǎng)卡 veth1 的 IP 是 192.168.0.2。

如果想讓 192.168.0.2 能訪問外部網(wǎng)絡(luò),則需要宿主網(wǎng)絡(luò)命名空間下的設(shè)備工作幫其進(jìn)行網(wǎng)絡(luò)包轉(zhuǎn)發(fā)。由于這是個私有的地址,只有這臺 Linux 認(rèn)識,所以它是無法訪問外部的服務(wù)器的。這個時候如果想要讓 net1 正常訪問 10.162.0.101,就必須在轉(zhuǎn)發(fā)時執(zhí)行 SNAT - 源地址替換。

SNAT 工作在路由之后,網(wǎng)絡(luò)包發(fā)送之前,也就是 POSTROUTING 鏈。我們在宿主機(jī)的命名空間里增加如下這條 iptables 規(guī)則。這條規(guī)則判斷如果源是 192.168.0 網(wǎng)段,且目的不是 br0 的,統(tǒng)統(tǒng)執(zhí)行源 IP 替換判斷。

  1. # iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADE 

有了這條規(guī)則,我們來看下整個發(fā)包過程。

當(dāng)數(shù)據(jù)包發(fā)出來的時候,先從 veth 發(fā)送到 br0。由于 br0 在宿主機(jī)的命名空間中,這樣會執(zhí)行到 POSTROUTING 鏈。在這個鏈有我們剛配置的 snat 規(guī)則。根據(jù)這條規(guī)則,內(nèi)核將網(wǎng)絡(luò)包中 192.168.0.2(外界不認(rèn)識) 替換成母機(jī)的 IP 10.162.0.100(外界都認(rèn)識)。同時還要跟蹤記錄鏈接狀態(tài)。

然后宿主機(jī)根據(jù)自己的路由表進(jìn)行判斷,選擇默認(rèn)發(fā)送設(shè)備將包從 eth0 網(wǎng)卡發(fā)送出去,直到送到 10.162.0.101。

接下來在 10.162.0.100 上會收到來自 10.162.0.101 的響應(yīng)包。由于上一步記錄過鏈接跟蹤,所以宿主機(jī)能知道這個回包是給 192.168.0.2 的。再反替換并通過 br0 將返回送達(dá)正確的 veth 上。

這樣 net1 環(huán)境中的 veth1 就可以訪問外部網(wǎng)絡(luò)服務(wù)了。

3.2 DNAT 目的地址替換

接著上面小節(jié)里的例子,假設(shè)我們想在 192.168.0.2 上提供 80 端口的服務(wù)。同樣,外面的服務(wù)器是無法訪問這個地址的。這個時候要用到 DNAT 目的地址替換。需要在數(shù)據(jù)包進(jìn)來的時候,將其目的地址替換成 192.168.0.2:80 才行。

DNAT 工作在內(nèi)核接收到網(wǎng)絡(luò)包的第一個鏈中,也就是 PREROUTING。我們增加一條 DNAT 規(guī)則,具體的配置如下。

  1. # iptables -t nat -A PREROUTING ! -i br0 -p tcp -m tcp --dport 8088 -j DNAT --to-destination 192.168.0.2:80 

當(dāng)有外界來的網(wǎng)絡(luò)包到達(dá) eth0 的時候。由于 eth0 在母機(jī)的命名空間中,所以會執(zhí)行到 PREROUTING 鏈。

該規(guī)則判斷如果端口是 8088 的 TCP 請求,則將目的地址替換為 192.168.0.2:80。再通過 br0(192.168.0.1)轉(zhuǎn)發(fā)數(shù)據(jù)包,數(shù)據(jù)包將到達(dá)真正提供服務(wù)的 192.168.0.2:80 上。

同樣在 DNAT 中也會有鏈接跟蹤記錄,所以 192.168.0.2 給 10.162.0.101 的返回包中的源地址會被替換成 10.162.0.100:8088。之后 10.162.0.101 收到包,它一直都以為自己是真的和 10.162.0.100:8088 通信。

這樣 net1 環(huán)境中的 veth1 也可以提供服務(wù)給外網(wǎng)使用了。事實上,單機(jī)的 Docker 就是通過這兩小節(jié)介紹的 SNAT 和 DNAT 配置來進(jìn)行網(wǎng)絡(luò)通信的。

3.3 filter

Filter 表主要實現(xiàn)網(wǎng)絡(luò)包的過濾。假如我們發(fā)現(xiàn)了一個惡意 IP 瘋狂請求我們的服務(wù)器,對服務(wù)造成了影響。那么我們就可以用 filter 把它禁掉。其工作原理就是在接收包的 INPUT 鏈位置處進(jìn)行判斷,發(fā)現(xiàn)是惡意請求就盡早干掉不處理。避免進(jìn)入到更上層繼續(xù)浪費(fèi) CPU 開銷。

具體的配置方法細(xì)節(jié)如下:

  1. # iptables -I INPUT -s 1.2.3.4 -j DROP //封禁 
  2.  
  3. # iptables -D INPUT -s 1.2.3.4 -j DROP //解封 

當(dāng)然也可以封禁某個 IP 段。

  1. # iptables -I INPUT -s 121.0.0.0/8 -j DROP //封禁 
  2.  
  3. # iptables -I INPUT -s 121.0.0.0/8 -j DROP //解封 

再比如說假設(shè)你不想讓別人任意 ssh 登錄你的服務(wù)器,只允許你的 IP 訪問。那就只放開你自己的 IP,其它的都禁用掉就好了。

  1. # iptables -t filter -I INPUT -s 1.2.3.4 -p tcp --dport 22 -j ACCEPT 
  2.  
  3. # iptables -t filter -I INPUT -p tcp --dport 22 -j DROP 

3.4 raw

Raw 表中的規(guī)則可以繞開其它表的處理。在 nat 表中,為了保證雙向的流量都能正常完成地址替換,會跟蹤并且記錄鏈接狀態(tài)。每一條連接都會有對應(yīng)的記錄生成。使用以下兩個命令可以查看。

  1. # conntrack -L 
  2.  
  3. # cat /proc/net/ip_conntrack 

但在高流量的情況下,可能會有連接跟蹤記錄滿的問題發(fā)生。我就遇到過一次在測試單機(jī)百萬并發(fā)連接的時候,發(fā)生因連接數(shù)超過了 nf_conntrack_max 而導(dǎo)致新連接無法建立的問題。

  1. # ip_conntrack: table full, dropping packet 

但其實如果不使用 NAT 功能的話,鏈接跟蹤功能是可以關(guān)閉的,例如。

  1. # iptables -t raw -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j NOTRACK 
  2.  
  3. # iptables -A FORWARD -m state --state UNTRACKED -j ACCEPT 

3.5 mangle

路由器在轉(zhuǎn)發(fā)網(wǎng)絡(luò)包的時候,ttl 值會減 1 ,該值為 0 時,最后一個路由就會停止再轉(zhuǎn)發(fā)這個數(shù)據(jù)包。如若不想讓本次路由影響 ttl,便可以在 mangel 表中加個 1,把它給補(bǔ)回來。

  1. # ptables -t mangle -A PREROUTING -i eth0 -j TTL --ttl-inc 1 

所有從 eth0 接口進(jìn)來的數(shù)據(jù)包的 ttl 值加 1,以抵消路由轉(zhuǎn)發(fā)默認(rèn)減的 1。

總結(jié)

Iptables 是一個非常常用,也非常重要的工具。Linux 上的防火墻、nat 等基礎(chǔ)功能都是基于它實現(xiàn)的。還有現(xiàn)如今流行的的 Docker、Kubernets、Istio 項目中也經(jīng)常能見著對它的身影。正因為如此,所以深入理解 iptables 工作原理是非常有價值的事情。

今天我們先是在第一節(jié)里從內(nèi)核接收、發(fā)送、轉(zhuǎn)發(fā)三個不同的過程理解了五鏈的位置。

接著又根據(jù)描述了 iptables 從功能上看的另外一個維度,表。每個表都是在多個鉤子位置處注冊自己的規(guī)則。當(dāng)處理包的時候觸發(fā)規(guī)則,并執(zhí)行。從整體上看,四鏈五表的關(guān)系如下圖。

最后我們又分別在 raw、mangle、nat、filter 幾個表上舉了簡單的應(yīng)用例子。希望通過今天的學(xué)習(xí),你能將 iptables 徹底融會貫通。相信這一定會對你的工作有很大的幫助的!

 

責(zé)任編輯:武曉燕 來源: 開發(fā)內(nèi)功修煉
相關(guān)推薦

2025-01-09 09:10:39

2024-02-21 08:19:54

2020-03-18 13:40:03

Spring事數(shù)據(jù)庫代碼

2023-12-26 08:08:02

Spring事務(wù)MySQL

2022-05-11 07:38:45

SpringWebFlux

2025-02-17 11:41:14

2024-12-02 08:00:00

營銷聊天機(jī)器人AI

2011-03-16 09:05:29

iptablesNAT

2011-03-16 16:06:46

iptables日志MySQL

2011-09-27 11:09:13

2022-04-26 08:32:36

CSS前端

2015-07-06 11:26:29

2018-12-05 09:20:02

MySQL數(shù)據(jù)庫索引

2024-02-19 08:17:10

Kafka消息隊列收發(fā)消息

2019-01-28 09:32:30

跳槽員工程序員

2017-02-28 09:10:31

開源大型機(jī)

2011-03-15 15:47:26

netfilteriptables

2016-06-12 17:20:47

2021-07-27 08:16:35

DeepinIptablesservice文件

2021-12-01 19:32:14

原理Node代碼
點贊
收藏

51CTO技術(shù)棧公眾號