筆記:Linux上實驗 GRE Bridge ,IPsec , NFQUEUE


1. 實驗背景

如下是網絡拓撲圖:

我們使用GRETAP  Tunnel 互聯 A&B;

GRE 是encapsulate為 IP packets;

GRETAP encapsulate為 Ethernet frames,請留意 Linux GRETAP可與Cisco EoGRE建立互聯。

 

2. 使用iproute2創建GRETAP

# 請先確認已安裝iproute,若未安裝,可先安裝iproute:

yum install -y iproute

# 創建、啟用、查看gretap interface

routerA# ip link add gretap1 type gretap local 192.168.200.1 remote 172.16.0.1 dev eth1
routerA# ip link set gretap1 up
routerA# ip link show gretap1
6: gretap@eth1: <BROADCAST,MULTICAST> mtu 1462 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether 62:24:67:45:44:ad brd ff:ff:ff:ff:ff:ff

目的是:創建gretap interface到tunnel 兩側的bridge,可以讓siteA與siteB 是橋接在一起。然后使用IPsec去加密siteA&B之間的GRE traffic 。

 

3. MTU問題

如上述創建的GRETAP,MTU為1462。計算方式:

1500 (物理接口MTU) - 20 (GRE新增的外部IP包頭) - 4 (GRE header) - 14 (封裝的ethernet header) = 1462.

對於IP packet的傳輸,當傳輸packet大於MTU時,經過的路由設備允許切片重傳就保障數據繼續傳輸(當然重傳會會成倍增加網絡傳輸延遲)。對於Ethernet Network 且 gretap 屬於橋接接口,MTU必須一致才能通信,否則會被丟棄。下面,我們來驗證一下:

# 在routerA 添加gretap interface到bridge  br0:

routerA# ip link add br0 type bridge
routerA# ip link set eth0 down
routerA# ip addr del 10.32.x.x/24 dev eth0    # 移除eth0的IP。此處留意如果是AWS EC2,務必小心;如果是阿里或騰訊Cloud Server,錯誤也可用控制台管理。建議在本地環境試驗。
routerA# ip link set eth0 master br0
routerA# ip link set eth0 up
routerA# ip link set br0 up
routerA# ip addr add 10.0.0.254/24 dev br0
routerA# ip link set gretap1 up
routerA# ip link set gretap1 master br0

# 在routerB 添加gretap interface到bridge  br0:routerB

routerB# ip link add br0 type bridge
routerB# ip link set eth0 down
routerB# ip addr del 10.32.x.x/24 dev eth0   # 移除eth0的IP。此處留意如果是AWS EC2,務必小心;如果是阿里或騰訊Cloud Server,錯誤也可用控制台管理。建議在本地環境試驗。
routerB# ip link set eth0 master br0
routerB# ip link set eth0 up
routerB# ip link set br0 up
routerB# ip addr add 10.0.0.253/24 dev br0
routerB# ip link set gretap1 up
routerB# ip link set gretap1 master br0

# 結果:

hostA# ping -s 172 10.32.0.111
PING 10.32.0.111 (10.32.0.111) 1472(1500) bytes of data.
^C
--- 10.32.0.111 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2015ms

  我們-s發起max size frame,從hostA 無法ping hostB

  通過抓包分析,我們能看到 小型的ARP request/reply frames 是能正常通過tunnel。然而,ICMP request packets是在routerA的br0就被靜默丟棄,因為routerA 的br0 MTU依然是1462。

# 如果我們將br0強制調整為1500,讓HostA能夠通過1500的包到HostB:

  routerA&B的gretap1都需要執行,此處僅展示routerA側br0:

routerA# ip link show br0
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1462 qdisc noqueue state UP mode DEFAULT 
    link/ether 00:16:3e:c3:8c:ef brd ff:ff:ff:ff:ff:ff
routerA# ip link set gretap1 mtu 1500
routerA# ip link show br0
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT 
    link/ether 00:16:3e:c3:8c:ef brd ff:ff:ff:ff:ff:ff

  如果強制將gretap1的MTU從1462增加38到1500,則封裝之后,routerA&B的eth1會相應增加到1538.  需要與網絡運營商確認siteA和siteB之間的線路連接,如果是雲服務器,需要調整雲服務器MTU。

 

3. IP fragmentation

  理想情況下,Frame是不可分割時(DF[Don't Fragment]為0),Ethernet frames也能通信的。.

  iproute2文檔說,tunnel interface有一個選項nopmtudisc (用途是:disable Path MTU Discovery on this tunnel.)

  在此,我們重新創建tunnel interface(同時在routerA&B執行):

routerA# ip link del gretap1
routerA# ip link add gretap1 type gretap local 192.168.200.1 remote 172.16.0.1 dev eth1 nopmtudisc
routerA# ip link set gretap1 mtu 1500
routerA# ip link set gretap1 up
routerA# ip link set gretap1 master br0

  可是如果我們現在從hostA ping (-s 1472)  hostB,卻無法ping通,為什么?

  抓包顯示,當hostA 發出的包,DF已設置為1,經過GRE時包的DF也為1(即使我們設置了nopmtud),這樣就出現了一個不可拆分的包在被靜默丟棄。

  所以,我們在HostA發起ping時,如果顯式的明確disable DF(可以拆分包),則才可以ping通。

ping -Mdont -s 1472 10.32.0.111
PING 10.32.0.111 (10.32.0.111) 1472(1500) bytes of data.
1480 bytes from 10.32.0.111: icmp_req=1 ttl=64 time=1.26 ms
1480 bytes from 10.32.0.111: icmp_req=2 ttl=64 time=0.932 ms
1480 bytes from 10.32.0.111: icmp_req=3 ttl=64 time=1.01 ms
^C
--- 10.32.0.111 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.932/1.071/1.264/0.143 ms

  此時,在routerA&B抓包都顯示包被拆分。(拆包通不是最好的體驗,但總比不能ping通要好)。因為Linux操作系統默認都是執行PMUTD的,所以這里我們人為地去拆分包,僅能在實驗環境求真,不能滿足所有應用自由通信的結果。如果需要Linux默認不支持PMUTD,可以設置kernel:

hostA# echo "net.ipv4.ip_no_pmtu_disc = 1 " >> /etc/sysctl.conf
hostA# sysctl -p

# 這里分享gretap encapsulation 圖

  首先有一個original ethernet frame最終能達到1514 bytes (1500 IP + 14 ethernet header):

   

  當這個fram被GRE封裝后,得到:

  

 

4. NFQUEUE: Clearing DF 

 我們希望嘗試標准的工具來解決這個問題,但是是有些難度的:

a. 創建iptables 規則,去match 那些 准備用NFQUEUE處理的  包

b. 然后一個user-space程序,就可以接收、處理這些包,並裁決verdict這些包:[1] 發回iptables(按規則修改這些包) [2]丟棄這些包。 這種通訊原理為  nfnetlink , 這個庫主要使用C,但也有使用Perl和Python。  netlink之netfilter Queue詳情Click Here.

# 首先,我們需要iptables rule匹配tunneled packet

  我們可以通過 iptables allows tracing 方法,來記錄packet 經過了哪些tables和chains。有這些信息,才能方便地制定相應的iptables rule。

  對於outgoing packet ,TRACE結果:

1 TRACE: raw:PREROUTING:policy:2 IN=br0 OUT= PHYSIN=eth0 MAC=00:16:3e:52:ba:6c:00:16:3e:93:08:ca:08:00 SRC=10.32.0.50 DST=10.32.0.111 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=819 SEQ=1
2 TRACE: filter:FORWARD:policy:1 IN=br0 OUT=br0 PHYSIN=eth0 PHYSOUT=gretap MAC=00:16:3e:52:ba:6c:00:16:3e:93:08:ca:08:00 SRC=10.32.0.50 DST=10.32.0.111 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=819 SEQ=1
3 TRACE: raw:OUTPUT:rule:1 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=122 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=47
4 TRACE: raw:OUTPUT:policy:2 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=122 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=47
5 TRACE: filter:OUTPUT:policy:2 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=122 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=47

  對於reply return packet,TRACE結果:

6 TRACE: raw:PREROUTING:policy:2 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=122 TOS=0x00 PREC=0x00 TTL=63 ID=40301 PROTO=47
7 TRACE: filter:INPUT:policy:1 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=122 TOS=0x00 PREC=0x00 TTL=63 ID=40301 PROTO=47
8 TRACE: raw:PREROUTING:rule:1 IN=br0 OUT= PHYSIN=gretap MAC=00:16:3e:93:08:ca:00:16:3e:52:ba:6c:08:00 SRC=10.32.0.111 DST=10.32.0.50 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18065 PROTO=ICMP TYPE=0 CODE=0 ID=819 SEQ=1
9 TRACE: raw:PREROUTING:policy:2 IN=br0 OUT= PHYSIN=gretap MAC=00:16:3e:93:08:ca:00:16:3e:52:ba:6c:08:00 SRC=10.32.0.111 DST=10.32.0.50 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18065 PROTO=ICMP TYPE=0 CODE=0 ID=819 SEQ=1
10 TRACE: filter:FORWARD:policy:1 IN=br0 OUT=br0 PHYSIN=gretap PHYSOUT=eth0 MAC=00:16:3e:93:08:ca:00:16:3e:52:ba:6c:08:00 SRC=10.32.0.111 DST=10.32.0.50 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=18065 PROTO=ICMP TYPE=0 CODE=0 ID=819 SEQ=1

   請留意:由於涉及bridge,上面的TRACE實際結果需要依靠是否   /proc/sys/net/bridge/bridge-nf-call-iptables 設置為0或1。很多系統默認bridge-nf-call-iptables為1,則會呈現上述TRACE結果;如果為0,則包經過bridge的TRACE記錄將不會被顯示,也就是上述的步驟1/2/8/9/10步驟不會存在。

  那么,我們得到TRACE記錄后,我們究竟tap into哪個flow去獲得packets?顯然,我們想看到GRE包,我們只關注從local LAN 到tunnel 的traffic(也就是上述TRACE的步驟 3、4、5)。所以,我們這里選擇OUTPUT chain,至於raw或filter table,都是OK的。這里,我們選擇filter table在routerA&B 操作:

routerA# iptables -A OUTPUT -s 192.168.200.1 -d 172.16.0.1 -p gre -j NFQUEUE --queue-bypass
routerB# iptables -A OUTPUT -s 172.16.0.1 -d 192.168.200.1 -p gre -j NFQUEUE --queue-bypass

   這樣就能匹配所有發送traffic 去到NFQUEUE (默認序號queue number為0)。同時,如果程序監聽該隊列(--queue-bypass,則移動到next policy或rule,所以至少可以通過small packet。

 

# 其次,Clearing DF

  我們需要編寫代碼去: 

    a. 接收來自 queue number 0 的packet   

    b. 執行clearing DF

    c. 發送處理后的包回到iptables

  幸運的,有一些 案例C代碼 可提供我們一些參考。現在,我們可以編寫如下代碼:

/***************************************************************************************
 * clear_df.c: clear, uh, DF bit from IPv4 packets. Heavily borrowed from              * 
 * http://netfilter.org/projects/libnetfilter_queue/doxygen/nfqnl__test_8c_source.html *
 ***************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>            /* for NF_ACCEPT */
#include <arpa/inet.h>
#include <time.h>
#include <libnetfilter_queue/libnetfilter_queue.h>

/* Standard IPv4 header checksum calculation, as per RFC 791 */
u_int16_t ipv4_header_checksum(char *hdr, size_t hdrlen) {

  unsigned long sum = 0;
  const u_int16_t *bbp;
  int count = 0;

  bbp = (u_int16_t *)hdr;
  while (hdrlen > 1) {
    /* the checksum field itself should be considered to be 0 (ie, excluded) when calculating the checksum */
    if (count != 10) {
      sum += *bbp;
    } 
    bbp++; hdrlen -= 2; count += 2;
  }

  /* in case hdrlen was an odd number, there will be one byte left to sum */
  if (hdrlen > 0) {
    sum += *(unsigned char *)bbp;
  }

  while (sum >> 16) {
    sum = (sum & 0xffff) + (sum >> 16);
  }

  return (~sum);
}

/* callback function; this is called for every matched packet. */       
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) {

  u_int32_t queue_id;
  struct nfqnl_msg_packet_hdr *ph;
  int pkt_len;

  char *buf;
  size_t hdr_len;

  /* determine the id of the packet in the queue */
  ph = nfq_get_msg_packet_hdr(nfa);
  if (ph) {
    queue_id = ntohl(ph->packet_id);
  } else {
    return -1;
  }

  /* try to get at the actual packet */
  pkt_len = nfq_get_payload(nfa, &buf);

  if (pkt_len >= 0) {

    hdr_len = ((buf[0] & 0x0f) * 4);

    /* clear DF bit */
    buf[6] &= 0xbf;

    /* set new packet ID */
    *((u_int16_t *)(buf + 4)) = htons((rand() % 65535) + 1);

    /* recalculate checksum */
    *((u_int16_t *)(buf + 10)) = ipv4_header_checksum(buf, hdr_len);
  }

  /* "accept" the mangled packet */
  return nfq_set_verdict(qh, queue_id, NF_ACCEPT, pkt_len, buf);
}

int main(int argc, char **argv) {

    struct nfq_handle *h;
    struct nfq_q_handle *qh;
    int fd;
    int rv;
    char buf[4096] __attribute__ ((aligned));

    /* printf("opening library handle\n"); */
    h = nfq_open();
    if (!h) {
        fprintf(stderr, "error during nfq_open()\n");
        exit(1);
    }

    /* printf("unbinding existing nf_queue handler for AF_INET (if any)\n"); */
    if (nfq_unbind_pf(h, AF_INET) < 0) {
        fprintf(stderr, "error during nfq_unbind_pf()\n");
        exit(1);
    }

    /* printf("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); */
    if (nfq_bind_pf(h, AF_INET) < 0) {
        fprintf(stderr, "error during nfq_bind_pf()\n");
        exit(1);
    }

    /* printf("binding this socket to queue '0'\n"); */
    qh = nfq_create_queue(h,  0, &cb, NULL);
    if (!qh) {
        fprintf(stderr, "error during nfq_create_queue()\n");
        exit(1);
    }

    /* printf("setting copy_packet mode\n"); */
    if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
        fprintf(stderr, "can't set packet_copy mode\n");
        exit(1);
    }

    fd = nfq_fd(h);

    /* initialize random number generator */
    srand(time(NULL));

    while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
        nfq_handle_packet(h, buf, rv);
    }

    /* printf("unbinding from queue 0\n"); */
    nfq_destroy_queue(qh);

    /* printf("closing library handle\n"); */
    nfq_close(h);

    exit(0);
}
View Code

   在說明前,我們可以回顧一下 IPv4 Header Format 圖:

 

 

   請留意,如果我們clear DF,則我們需要填充"Indentification" field。當DF設置為1時,Indentification field一般是0,因為這種情況(DF=1)下,包不會被分割,所以不需要Indentification 去標識fragmentation碎片包。而當我們設置DF=0或者說clear DF時,則需要設置Indentification 。此處,我們可以生成一個1-65535的隨機數,去填入Indentification field。

  編譯需要提供header files:

gcc -o clear_df -lnfnetlink -lnetfilter_queue clear_df.c

# 最后,我們驗證clear df效果

routerA# clear_df
routerB# clear_df
hostA# ping -s 1472 -Mdo 10.32.0.111   ##此處我們強制DF,看看效果
PING 10.32.0.111 (10.32.0.111) 1472(1500) bytes of data.
1480 bytes from 10.32.0.111: icmp_req=1 ttl=64 time=1.48 ms
1480 bytes from 10.32.0.111: icmp_req=2 ttl=64 time=0.969 ms
1480 bytes from 10.32.0.111: icmp_req=3 ttl=64 time=1.11 ms
1480 bytes from 10.32.0.111: icmp_req=4 ttl=64 time=0.946 ms
1480 bytes from 10.32.0.111: icmp_req=5 ttl=64 time=0.944 ms
^C
--- 10.32.0.111 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4005ms
rtt min/avg/max/mdev = 0.944/1.091/1.481/0.208 ms

 

5. IPsec

  回到正軌,IPsec是我們的最終目的。為了使實驗更精彩,對於IPsec的實現,我們讓routerA使用 " ipsec-tools + racoon",routerB使用 openswan 實現。

  此時,在第一個IP header之后,IPsec transport mode將插入一個新的ESP header,以至於Protocol field會由47(GRE)更改為50(ESP),在ESP應用后(當然是,這是是說在未DF前的結構),我們得到如下:

 

 

   我們再次追蹤新結構的packet到user-space,提前做一下TRACE:

1 TRACE: raw:OUTPUT:policy:2 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=122 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=47 
2 TRACE: filter:OUTPUT:rule:2 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=122 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=47 
3 TRACE: raw:OUTPUT:rule:1 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=152 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ESP SPI=0xdb28966a 
4 TRACE: raw:OUTPUT:policy:2 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=152 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ESP SPI=0xdb28966a 
5 TRACE: filter:OUTPUT:policy:3 IN= OUT=eth1 SRC=192.168.200.1 DST=172.16.0.1 LEN=152 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=ESP SPI=0xdb28966a 

6 TRACE: raw:PREROUTING:policy:2 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=152 TOS=0x00 PREC=0x00 TTL=63 ID=21840 PROTO=ESP SPI=0x4802e17
7 TRACE: filter:INPUT:policy:1 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=152 TOS=0x00 PREC=0x00 TTL=63 ID=21840 PROTO=ESP SPI=0x4802e17 
8 TRACE: raw:PREROUTING:rule:1 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=122 TOS=0x00 PREC=0x00 TTL=63 ID=21840 PROTO=47 
9 TRACE: raw:PREROUTING:policy:2 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=122 TOS=0x00 PREC=0x00 TTL=63 ID=21840 PROTO=47 
10 TRACE: filter:INPUT:policy:1 IN=eth1 OUT= MAC=00:16:3e:c3:8c:12:00:16:3e:11:b8:8e:08:00 SRC=172.16.0.1 DST=192.168.200.1 LEN=122 TOS=0x00 PREC=0x00 TTL=63 ID=21840 PROTO=47 

  Linux 沒有真正的 IPsec virtual interface,但我們看到IPsec 穿過2次chain: 

    第1次: before encryption (with PROTO=47, lines 1-2)

    第2次: after encryption (with PROTO=ESP, that is, protocol 50, lines 3-5)

  這意味着我們有2種處理方式:

    第1種:保留現有的iptables rule (但我們的代碼將收到未加密的packet)

    第2種:改變匹配協議為ESP  (iptables rule 中使用 -p 50 or -p esp都行)

  在此,我們推薦使用第2種處理方式。因為有一種情況存在:如果使用AH (protocol 51),我們將不能更改ID field。 由於NFQUEUE會在kernel space和 user space之間來回copy packet,且由於沒有加密的packet 更小,能夠使匹配 Protocol 47更加高效,綜合上述原因,我們不對iptables rule做更改。

  這里分享在routerA上的ipsec-tools.conf 配置:

#!/usr/sbin/setkey -f

## Flush the SAD and SPD
#
flush;
spdflush;

spdadd 192.168.200.1/32 172.16.0.1/32 gre -P out ipsec
   esp/transport//require;

spdadd 172.16.0.1/32 192.168.200.1/32 gre -P in ipsec
   esp/transport//require;

racoon.conf on routerA:
log notify;
path pre_shared_key "/etc/racoon/psk.txt";
path certificate "/etc/racoon/certs";

remote 172.16.0.1 {
        exchange_mode main;
        proposal {
                encryption_algorithm 3des;
                hash_algorithm md5;
                authentication_method pre_shared_key;
                dh_group modp1024;
        }
}

sainfo address 192.168.200.1/32 gre address 172.16.0.1/32 gre {
        pfs_group modp1024;
        encryption_algorithm 3des;
        authentication_algorithm hmac_md5;
        compression_algorithm deflate;
}

ipsec.conf on routerB:
version 2.0     # conforms to second version of ipsec.conf specification

# basic configuration
config setup
  nhelpers=0
  interfaces="%none"
  protostack=netkey
  klipsdebug=""
  plutodebug=""

conn to-routerA
  type=transport
  left=172.16.0.1
  leftsubnet=172.16.0.1/32
  right=192.168.200.1
  rightsubnet=192.168.200.1/32
  authby=secret
  phase2alg=3des-md5;modp1024
  keyexchange=ike
  ike=3des-md5;modp1024
  auto=start
  leftprotoport=gre
  rightprotoport=gre

    請留意:上述配置僅加密了GRE流量;身份驗證使用了PSK方式。

  

 

 

參考海外英文原稿:Click

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM