Try   HackMD

Linux 核心專題: 網路防火牆設計和實作

執行人: jujuegg
專題解說錄影

Reviewed by popo8712

在實現你的防火牆規則匹配函式 matchOneRule 時,你將來源端口和目標端口的範圍組合存儲在一個32位的 unsigned int 中。請問這種設計在處理非常大的端口範圍時是否會有問題?例如處理跨多個16位區間的端口範圍時,你如何保證正確性和效率?

埠號的範圍是 0 至 65535,基本上不會有超過 16 位元區間的埠號範圍。
jujuegg

Reviewed by jychen0611

  • 什麼情況下會使用到 nfqueue,它的功能和運作原理是?

    NF_QUEUE : 將封包送往 nfqueue

一般來說我們使用 NetFilter 是在 kernel space 處理封包,如果我們想要在 user space 處理的話,就需要用送到 nfqueue 中,本質上也是用 Netlink 來實現 kernel space 與 user space 之間的通訊,我目前還沒實作出例子。
jujuegg

  • Fix the struct has no member problem 中可針對核心版本不同做出判別,使其相容先前的版本,也就是
    ​​​​struct netlink_kernel_cfg nltest_cfg = {
    ​​​​    .groups = 0,
    ​​​​    .flags = 0,
    ​​​​    .input = nlRecv,
    ​​​​    .cb_mutex = NULL,
    ​​​​    .bind = NULL,
    ​​​​    .unbind = NULL,
    ​​​​#if LINUX_VERSION_CODE >= KERNEL_VERSION(a, b, c)
    ​​​​    .release = NULL,
    ​​​​#else
    ​​​​    .compare = NULL,
    ​​​​#endif
    ​​​​};
    
    並嘗試提交 pull request 到 RJFireWall 。流程可參考 : 理解如何在Github使用Pull Request (PR)

我希望自己在未來能夠從小地方開始嘗試提出 pull request

現在就有一個機會,不用等到以後

  • Fix the sprintf overflow problem 中為何是修改為 10 而非其他數字? 若有考量因素則可使用 git rebase -i 將敘述新增至 commit message 中。
-	char saddr[25],daddr[25],sport[13],dport[13],proto[6],action[8],log[5];
+	char saddr[25],daddr[25],sport[13],dport[13],proto[6],action[10],log[5];

只是單純因為要存入 action 的字串長度有可能會超過 8 個字元,而不會超過 10 個字元。
jujuegg

Reviewed by nosba0957

matchOneRule 函式中,sportdport 分別代表什麼?為何一個是 unsigned short 另一個是 unsigned int 型別?

sport 代表 Source IP 的埠號,dport 代表 Destination IP 的埠號。
關於 unsigned shortunsigned int 型別,由於埠號的長度是 16 個位元,所以兩個型態都不會有問題,不過感謝指正,我應該要統一型別。
jujuegg

任務簡介

藉由 Linux 核心的 netfilter 和 netlink 打造網路傳輸層級的防火牆,並確保得以在 Linux 6.8+ 運作。

TODO: 檢驗 RJ FireWall 程式碼並探究其原理

理解如何使用 Linux 核心的 netfilter 對網路連線進行過濾、改寫,和 NAT 等功能
理解如何使用 Linux 核心的 netlink 對核心模組和工具程式進行互動
理解如何有效處理大量封包過濾條件
理解如何處理 NAT,以及 Linux 核心有哪些關鍵機制

Netlink 提供了 kernel space 與 user space 之間的通訊,透過 socket 來完成,以下是一個簡例。

fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

/* format the request */
send(fd, &request, sizeof(request));
n = recv(fd, &response, RSP_BUFFER_SIZE);
/* interpret the response */

在此專案中,我會頻繁的使用 exchangeMsgK 這個函式來與 Linux 核心交換資訊。

User Space

exchangeMsgK

首先我們要開一個類型是 AF_NETLINK 的 socket,在 linux/socket.h 有定義,AF_xxxx (Address Families) 其實和 PF_xxxx (Protocol Families) 是一個完全相同的數字。

int skfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_MYFW);

然後為該 socket 綁定一個 sockaddr_nl 的結構體,讓核心知道我們是哪一個 process:

struct sockaddr_nl local;
memset(&local, 0, sizeof(local));

local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = 0;

bind(skfd, (struct sockaddr *) &local, sizeof(local))

同時也要指定傳輸的目的地,也就是 kernel space:

struct sockaddr_nl kpeer;
memset(&kpeer, 0, sizeof(kpeer));

kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;    // kernel space
kpeer.nl_groups = 0;

再來要準備我們要傳給核心的訊息,要使用的結構是 nlmsghdrsmsg 是訊息的 payload 部分。
uapi/linux/netlink.h 中定義了許多巨集函式可以使用。

  • NLMSG_SPACE 會回傳該訊息的 header + payload 的長度,且該長度是對齊過的。
  • NLMSG_DATA 會回傳該訊息的所在記憶體位址。
struct nlmsghdr *message=(struct nlmsghdr *)malloc(NLMSG_SPACE(slen)*sizeof(uint8_t));
memset(message, '\0', sizeof(struct nlmsghdr));

message->nlmsg_len = NLMSG_SPACE(slen);
message->nlmsg_flags = 0;
message->nlmsg_type = 0;
message->nlmsg_seq = 0;
message->nlmsg_pid = local.nl_pid;

memcpy(NLMSG_DATA(message), smsg, slen);

將該訊息傳輸給 linux 核心,kpeer 也是一個 nlmsghdr 的結構體,與 local 相同,告訴此函式我們要傳給核心的目的地。

sendto(skfd, message, message->nlmsg_len, 0, (struct sockaddr *) &kpeer, sizeof(kpeer))

如果運作順利,就可以成功接收來自 linux 核心的回覆:

struct nlmsghdr *nlh = (struct nlmsghdr *)
                        malloc(NLMSG_SPACE(MAX_PAYLOAD)*sizeof(uint8_t));
recvfrom(skfd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0, 
         (struct sockaddr *) &kpeer, (socklen_t *)&kpeerlen)

Kernel Space

而在核心這邊,我們需要呼叫 netlink_kernel_create,把要執行在 kernel space 的程式掛載上去,也就是放在 netlink_kernel_cfg 結構中的 *input 成員。

struct netlink_kernel_cfg nltest_cfg = {
	.groups = 0,
	.flags = 0,
	.input = nlRecv, //Receive Function
	.cb_mutex = NULL,
	.bind = NULL,
	.unbind = NULL,
	.release = NULL,
};

nlsk = netlink_kernel_create(&init_net, NETLINK_MYFW, &nltest_cfg);
nlRECV(struct sk_buff *skb)

在這邊我們不需要自己開一個 socket 去接收來自 user space 的訊息,核心會自動將訊息存入該函式的參數 sk_buff 中。

要抓取該封包的 header 與其中的 data 部分

struct nlmsghdr *nlh = NULL;
nlh = nlmsg_hdr(skb);

void *data;
data = NLMSG_DATA(nlh);
pid = nlh->nlmsg_pid;
len = nlh->nlmsg_len - NLMSG_SPACE(0);

到這邊我們就完成了 User Space 與 Kernel Space 之間的通訊工作。

NetFilter

先感謝之前的相關期末專題執行人 ItisCaleb 整理出來的材料,讓我可以不用花太多時間找第一手的教材來閱讀。
Linux 專題: 透過 Netfilter 自動過濾廣告 這篇文章中提到,NetFilter 提供了一系列的 hook,讓使用者可以在不同的階段過濾封包。

首先我們要先設計一個 hook,在下面這個 hook 中,他會在 NF_IP_PRE_ROUTING 的位置就進行過濾,是所有 hook 中第一個接觸到封包的位置。

static struct nf_hook_ops nfop_in = {
    .hook = hook_main,      // hook function
    .pf = PF_INET,
    .hooknum = NF_INET_PRE_ROUTING,    // hook position
    .priority = NF_IP_PRI_FIRST
};

hook function 需要接收三個參數

  • priv 為在 nf_hook_opspriv 提供的物件
  • skb 為此封包的 sk_buff
  • state 則為此封包的各種資訊,包括裝置、網路的命名空間等

在 hook function 中,我們可以查看流動到該 hook 的所有封包的內容,並且可以決定最終我們要對此封包執行的動作,包括:

  • NF_DROP : 丟棄封包。
  • NF_ACCEPT : 允許封包通過。
  • NF_STOLEN : 將封包的所有權轉移給這個 hook function,同時也意味著需要自己管理封包佔用的資源。
  • NF_QUEUE : 將封包送往 nfqueue。
  • NF_REPEAT : 重新呼叫這個 hook function。

設計完 hook 之後,要註冊到 linux 核心當中:

nf_register_net_hook(&init_net, &nfop_in);    // 註冊

nf_unregister_net_hook(&init_net, &nfop_in);   // 取消註冊

TODO: 使 RJ FireWall 程式碼得以正確運作於 Linux v6.8+

確保 Linux 核心模組運作符合預期,要有對應的測試程式及流程,證明你的投入是有效益的

防火牆功能

新增過濾規則

每個添加的規則都會是一個叫做 IPRule 的結構,而全部的規則則會形成一個串列,該串列的頭部叫做 ipRuleHead

不是「剛好」,這是當初的設計考量,不要反果為因!

其中一個問題是要如何儲存 IPv4 的位址,因 IPv4 的位址的範圍是 0.0.0.0 ~ 255.255.255.255,可以由 4 組 8 個位元的資料來代表,所以我使用 unsigned int 來儲存此 IP 位址。而使用者在輸入 IP 時讀取的是字串,所以我們要將該 IP 轉為整數的型態儲存,方便未來做 mask 的操作。

這邊是獲取 mask 的部分,讀到 / 字元後面的數字即為 mask 的長度。

for(i = 0; i < strlen(ipStr); i++){
    if(p != -1){
        len *= 10;
        len += ipStr[i] - '0';
    }
    else if(ipStr[i] == '/')
        p = i;
}

if(p != -1){
    if(len)
        r_mask = 0xFFFFFFFF << (32 - len);
}
else r_mask = 0xFFFFFFFF;

將字串型態的 IP 轉成整數型態儲存,遇到 . 字元就將已存入的部分左移。

舉例來說:
192.168.0.1 轉成無號整數的型態用二進位表示就會變成:
11000000 10101000 00000000 00000001
所以 192 要左移 24 位,168 要左移 16 位,0 要左移 8 位

for(i = 0; i < (p>=0 ? p : strlen(ipStr)); i++){
    if(ipStr[i] == '.'){
        r_ip = r_ip | (tmp << (8 * (3 - count)));
        tmp = 0;
        count++;
        continue;
    }
    
    tmp *= 10;
    tmp += ipStr[i] - '0';
	if(tmp > 256 || count > 3)
	    return -2;
}

當 kernel space 接收到 user space 想要新增過濾規則的請求時,他會讀取傳過來的 IPRule 結構,並將其加入串列中指定的位置。

過濾封包

當我們向 linux 核心註冊了 hook 之後,我們就可以管理經過該 hook 的所有封包了,這些封包可以經由上面提到的 hook function 來執行我們想要的操作。

hook_main

使用 ip_hdr 可以得到該封包的 header。

struct iphdr *header = ip_hdr(skb);

改進你的漢語表達。

這邊要先介紹一下 ntohsntohlhtonshtonl 這 4 個函式,在 linux man page 中寫到:

  • The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
  • The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
  • The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
  • The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.

On the i386 the host byte order is Least Significant Byte first, whereas the network byte order, as used on the Internet, is Most Significant Byte first.

其實就是 Little-endian 與 Big-endian 之間的互相轉換。

參照第一手材料,如 https://man7.org/linux/man-pages/
不要花太多時間閱讀網際網路上殘缺的文章。

getPort

埠號 (port number) 則要根據他是 TCP 還是 UDP 的封包去進行解析。而因為埠號由 16 個位元組成,所以使用 ntohs 來獲取。

tcpHeader = (struct tcphdr *)(skb->data + (hdr->ihl * 4));
*src_port = ntohs(tcpHeader->source);
*dst_port = ntohs(tcpHeader->dest);

udpHeader = (struct udphdr *)(skb->data + (hdr->ihl * 4));
*src_port = ntohs(udpHeader->source);
*dst_port = ntohs(udpHeader->dest);

使用 ntohl 可以獲取 ip address,因為 IPv4 的長度是 32 個 bit。

sip = ntohl(header->saddr);
dip = ntohl(header->daddr);
matchOneRule

再來我們要開始對比這個封包是否有對應到我們的防火牆內的任何一個規則。

注意書寫規範:

  • 使用 lab0 規範的程式碼書寫風格,務必用 clang-format 確認一致
bool isIPMatch(unsigned int ipl, unsigned int ipr, unsigned int mask) {
    return (ipl & mask) == (ipr & mask);
}

bool matchOneRule(struct IPRule *rule, unsigned int sip, unsigned int dip, 
                  unsigned short sport, unsigned int dport, u_int8_t proto)
{
    return (isIPMatch(sip,rule->saddr,rule->smask) && 
            isIPMatch(dip,rule->daddr,rule->dmask) &&
            (sport >= ((unsigned short)(rule->sport >> 16)) && 
             sport <= ((unsigned short)(rule->sport & 0xFFFFu))) &&
            (dport >= ((unsigned short)(rule->dport >> 16)) &&
             dport <= ((unsigned short)(rule->dport & 0xFFFFu))) &&
            (rule->protocol == IPPROTO_IP || rule->protocol == proto));
}

isIPMatch 會比較分別比較來源 IP 與目的地 IP 經過遮罩之後是否與規則中的 IP 相同。

至於中間比較埠號的過程就要解釋一下了,由於埠號其實是個 16 位元的整數,而且他其實是一個範圍,例如:8080-8090,所以我們可以把下限 16 個位元與上限 16 個位元,加起來一共 32 個位元存在一個 unsigned int 裡面。

cmdAddRule 這個函式,我把埠號的下限存在前 16 個 bit,而上限存在後 16 個 bit。

sport = ((unsigned int)sportMin << 16) | (((unsigned int)sportMax) & 0xFFFFu)
dport = ((unsigned int)dportMin << 16) | (((unsigned int)dportMax) & 0xFFFFu)

所以上面在比較埠號的範圍時,(unsigned short)(rule->sport >> 16) 代表的是 sport 的下限,而 (unsigned short)(rule->sport & 0xFFFFu) 代表的則是 sport 的上限。

最後則是比較該規則的通訊協定 (TCP/UDP/ICMP) 是否符合該封包的類型。

回到 hook_main,如果目前這個封包有符合任何一個規則,則他會執行我們在該規則中制定的行為 (NF_ACCEPTNF_DROP)。最後再回傳我們要執行的動作就大功告成了。

注意:他會匹配到「第一個符合的規則」,當初在制定防火牆的規則表時,使用者可以命名該規則,並可選擇加入到規則表中的任何順序。

rule = matchIPRules(skb, &isMatch);
if(isMatch)
    action = (rule.action == NF_ACCEPT) ? NF_ACCEPT : NF_DROP;

return action;

實際執行

在 make 的時候,可能會跑出錯誤訊息:

warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
You are using:           gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0

這是因為編譯器的命名不同,實際上他們的版本是相同的。

linux/netlink.h 中,發現此結構的定義與以前不同,compare 改為 release

timeval 更動

fbdev: radeon: Remove 'struct timeval' usage 中提到,timeval 這個結構使用的是 32-bit 變數,在 2038 年之後將會有 overflow 的問題,所以建議改用 ktime_t

測試過程

先簡單寫一個 receiver 與 sender 來測試防火牆是否有用:

sender.py
import socket

def main():
    dest_ip = '127.0.0.1'
    dest_port = 8080
    message = "Hello, UDP Receiver!"
    
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.sendto(message.encode(), (dest_ip, dest_port))
    udp_socket.close()

if __name__== "__main__":
    main()
receiver.py
import socket

def main():
    local_ip = '127.0.0.1'
    local_port = 8080
    
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.bind((local_ip, local_port))
    print("UDP Receiver is up and listening")
    
    while True:
        data, addr = udp_socket.recvfrom(1024)
        print(f"Received message from {addr}: {data.decode()}")

if __name__ == "__main__":
    main()

在開啟防火牆阻擋來自 local ip 的 port 8080 後,發現 sender 那邊出現錯誤訊息:

Traceback (most recent call last):
  File "/home/juju/Desktop/RJFireWall/TestScript/sender.py", line 18, in <module>
    main()
  File "/home/juju/Desktop/RJFireWall/TestScript/sender.py", line 12, in main
    udp_socket.sendto(message.encode(), (dest_ip, dest_port))
PermissionError: [Errno 1] Operation not permitted

由此可知,防火牆確實有作用,接下來要添加更多規則來執行更詳細的測試。


測試完 local 端之後,我決定開另一台虛擬機來測試防火牆是否可以正常的阻擋不同主機間的通訊。

先使用 ifconfig 命令來看自己的 IP 位址,我的虛擬機網路設定是使用 NAT 模式,所以 2 台虛擬機都是使用 192.168 開頭的 IP。

有安裝防火牆的虛擬機 IP 是 192.168.60.130
負責打封包過去的虛擬機 IP 是 192.128.60.128

UDP

這邊先測試 UDP 通訊協定,我沿用上面寫的 receiver.py,把 IP 改為 0.0.0.0。

先用 netcat 打一個 UDP 封包過去,測試可以正常通訊:

  • sender :
$ echo -n "hello" | nc -u -w1 192.168.60.130 8000
  • receiver :
$ python3 receiver.py 
UDP Receiver is up and listening
Received message from ('192.168.60.128', 34420): hello

新增防火牆規則,阻擋來自 192.168.60.128 的 UDP 封包,發現沒有收到:

$ ./uapp rule add
add rule after [enter for adding at head]: 
rule name [max len=11]: r1
source ip and mask [like 127.0.0.1/16]: 192.168.60.128/32
source port range [like 8080-8031 or any]: any
target ip and mask [like 127.0.0.1/16]: 192.168.60.130/32
target port range [like 8080-8031 or any]: any
protocol [TCP/UDP/ICMP/any]: UDP
action [1 for accept,0 for drop]: 0
is log [1 for yes,0 for no]: 1
result:
From kernel: Success.
  • sender :
$ echo -n "after add rule" | nc -u -w1 192.168.60.130 8000
  • receiver :
$ python3 receiver.py 
UDP Receiver is up and listening
Received message from ('192.168.60.128', 34420): hello

查看 log 發現確實有成功阻擋:

$ ./uapp ls log
sum: 1
[22437-11-07 22:26:11] [DROP]    192.168.60.128:46636->192.168.60.130:8000 
proto=UDP len=22B

把剛剛新加的防火牆規則刪掉,發現封包成功送到了:

$ ./uapp rule del r1
succeed to delete 1 rules.
  • sender :
$ echo -n "after del rule" | nc -u -w1 192.168.60.130 8000
  • receiver :
$ python3 receiver.py 
UDP Receiver is up and listening
Received message from ('192.168.60.128', 34420): hello
Received message from ('192.168.60.128', 40705): after del rule

TCP

再來測試 TCP 通訊協定,先用 netcat 開一個 TCP 的 port,然後用另一台虛擬機連線並傳送訊息:

  • sender :
$ nc -v 192.168.60.130 8000
Connection to 192.168.60.130 8000 port [tcp/*] succeeded!
hello
  • receiver :
$ nc -l 0.0.0.0 8000
hello

新增防火牆規則,阻擋來自 192.168.60.128 的 TCP 封包,發現也沒有收到訊息:

$ ./uapp rule add
add rule after [enter for adding at head]: 
rule name [max len=11]: r2
source ip and mask [like 127.0.0.1/16]: 192.168.60.128/32
source port range [like 8080-8031 or any]: any
target ip and mask [like 127.0.0.1/16]: 192.168.60.130/32
target port range [like 8080-8031 or any]: 8000-8000
protocol [TCP/UDP/ICMP/any]: TCP
action [1 for accept,0 for drop]: 0
is log [1 for yes,0 for no]: 1
result:
From kernel: Success.
  • sender :
$ nc -v 192.168.60.130 8000
Connection to 192.168.60.130 8000 port [tcp/*] succeeded!
hello
after add rule
  • receiver :
$ nc -l 0.0.0.0 8000
hello

查看 log 發現也有成功阻擋:

$ ./uapp ls log
sum: 10
[22437-11-07 22:26:11] [DROP]    192.168.60.128:46636->192.168.60.130:8000 proto=UDP len=22B
[44057-11-17 11:24:28] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44064-06-22 00:38:50] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44071-01-24 04:01:46] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44084-08-13 03:47:15] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44110-12-26 11:06:13] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44163-09-25 13:18:14] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44268-02-22 23:50:52] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44479-01-25 16:38:03] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B
[44900-11-28 02:32:52] [DROP]    192.168.60.128:48460->192.168.60.130:8000 proto=TCP len=47B

接下來測試防火牆開著的時候有沒有辦法建立 TCP 連線,發現沒有跳出成功連線的通知,卻也沒有跳出連線失敗的通知。正常來說,如果該 IP 沒有開啟該埠號的話,是會跳出連線失敗的通知的。

  • sender :
$ nc -v 192.168.60.130 8000

有趣的事情是,如果在短時間內把防火牆的規則刪除的話,是會自動連上去的。

ICMP

要測試 ICMP 通訊協定,要用到常用的 ping 命令:

$ ping 192.168.60.130
PING 192.168.60.130 (192.168.60.130) 56(84) bytes of data.
64 bytes from 192.168.60.130: icmp_seq=1 ttl=64 time=0.500 ms
64 bytes from 192.168.60.130: icmp_seq=2 ttl=64 time=0.533 ms
64 bytes from 192.168.60.130: icmp_seq=3 ttl=64 time=0.898 ms
64 bytes from 192.168.60.130: icmp_seq=4 ttl=64 time=1.10 ms
64 bytes from 192.168.60.130: icmp_seq=5 ttl=64 time=0.971 ms
^C
--- 192.168.60.130 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4033ms
rtt min/avg/max/mdev = 0.500/0.801/1.104/0.241 ms

新增防火牆規則,阻擋來自 192.168.60.128 的 ICMP 封包,發現 ping 沒有得到回覆:

$ ./uapp rule add
add rule after [enter for adding at head]: 
rule name [max len=11]: r3
source ip and mask [like 127.0.0.1/16]: 192.168.60.128/32
source port range [like 8080-8031 or any]: any
target ip and mask [like 127.0.0.1/16]: 192.168.60.130/32
target port range [like 8080-8031 or any]: any
protocol [TCP/UDP/ICMP/any]: ICMP
action [1 for accept,0 for drop]: 0
is log [1 for yes,0 for no]: 1
result:
From kernel: Success.
  • sender :
$ ping 192.168.60.130
PING 192.168.60.130 (192.168.60.130) 56(84) bytes of data.

把防火牆規則刪除之後,發現 ping 又正常運作了:

$ ./uapp rule del r3
succeed to delete 1 rules.
  • sender :
$ ping 192.168.60.130
PING 192.168.60.130 (192.168.60.130) 56(84) bytes of data.
64 bytes from 192.168.60.130: icmp_seq=24 ttl=64 time=0.585 ms
64 bytes from 192.168.60.130: icmp_seq=25 ttl=64 time=1.13 ms
64 bytes from 192.168.60.130: icmp_seq=26 ttl=64 time=0.661 ms
64 bytes from 192.168.60.130: icmp_seq=27 ttl=64 time=0.525 ms
64 bytes from 192.168.60.130: icmp_seq=28 ttl=64 time=0.630 ms
64 bytes from 192.168.60.130: icmp_seq=29 ttl=64 time=0.422 ms
^C
--- 192.168.60.130 ping statistics ---
29 packets transmitted, 6 received, 79.3103% packet loss, time 28631ms
rtt min/avg/max/mdev = 0.422/0.659/1.134/0.225 ms

TODO: 量化網路防火牆的運作表現

修改既有程式碼和測試情境,使得防火牆的關鍵機制得以充分進行效能評比,善用 Linux 核心的工具 (如 Ftrace,參見《Demystifying the Linux CPU Scheduler》)

TODO: 對現有程式碼提出改進方案並落實

在 GitHub 建立新的 repository,將上述修改納入,並確保所有文件和程式碼註解皆用美式英語書寫。