# nstack 開發紀錄 contributed by <`bauuuu1021`,`BroLeaf`> * [nstack GitHub](https://github.com/jserv/nstack) 連結 * [BroLeaf](https://github.com/BroLeaf/nstack) ## 具體目標 * 強化 nstack 實作,讓 ping (ICMP), curl (TCP) 一類程式得以在 nstack 運作 * 測試 nstack 效能,改善多執行緒處理機制 * 實作基本的 Web server ## 事前作業 * 參閱 Comer 教授的大作 [Computer Networks And Internets](https://www.cs.purdue.edu/homes/comer/netbooks.html) * 閱讀 CS:APP 第 11 章和課程投影片,並觀看 CMU 教授的 [課程錄影](https://hackmd.io/s/ByPlLNaTG) * 研讀 [Linux Capabilities](http://man7.org/linux/man-pages/man7/capabilities.7.html) * [saminiir's hacker blog](http://www.saminiir.com/) 裡 "Let's code a TCP/IP stack" 系列文章,嘗試用中文摘要內容並且記錄問題,過程中參閱上述參考資料 ### 1: Ethernet & ARP * [TUN/TAP devices](https://en.wikipedia.org/wiki/TUN/TAP) * 實作在 linux kernel 的**虛擬**網路設備,用軟體取代網路硬體設備的功能 * 使用者程式或是作業系統可以像操作硬體網絡設備那樣,透過操作 TUN/TAP devices 互相傳送封包 * Ethernet * 現行大部份區域網路連線 ( LANs ) 使用的技術 * 較早以前使用半雙工技術 ( half-duplex ),意思就是傳輸時某一方,只能夠選擇要傳輸還是接收訊息,使用 CSMA/CD 通訊協定 * 在 100BASE-T 標準協定發明後,能夠使用全雙工技術 ( full-duplex ),也就是能夠同時傳輸與接收訊息 * Address Resolution Protocol (ARP) * 可將 48-bit Ethernet address (MAC address) 對映到其他 L3 protocol address ,如 32 bits 的 IPv4 & 16 bits 的 CHAOS 等 ### 2: IPv4 & ICMPv4 * IPv4 * 提供 TCP 及 UDP 傳輸的基礎,是種 connectionless communication * 不保證成功,例 UDP * 提高可靠性,採用 TCP * Internet checksum * 用來驗證 IP datagram 的正當性 * 用得到的 checksum 再做一次演算法,得到 0 就是正確的結果 * ICMPv4 * IP 的可靠性不佳,因此需要 ICMP 做為診斷措施,通知錯誤情況 ### 3: TCP Basics & Handshake * TCP head format * 由 20 個 8 位元組所構成 * 包含 source port, destination port, sequence number, acknowledgment number 等 communication information * 接在 TCP head 後,可能是傳輸的 data,也可能沒有東西(eg:handshake) * Handshake ``` TCP A TCP B 1. CLOSED LISTEN 2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED 3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED 4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED 5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED ``` 1. B 正在等待新的 connection 2. A 發出 connection 的請求,sequence number = 100 3. B 收到請求,acknowledge A 的 sequence number + 1,創造自己的 sequence number 300 4. A 發出最後一次 acknowledge (ACK = B 的 sequence number + 1),結束 handshake 5. 開始傳輸資料 :::info 這段的第五點原文如下 ```5.Data starts to flow, mainly because both sides have acknowledged each other’s segment numbers.``` 不知道 segment numbers 是否為 sequence number 誤植,還是是別的東西? ::: > 參照 http://packetlife.net/blog/2010/jun/7/understanding-tcp-sequence-acknowledgment-numbers/ (原文敘述省略細節,應指 sequence 和個別 segment) ### 4: TCP Data Flow & Socket API * Transmission Control Block ( TCB ) * 對不同位置互相傳輸資料時,用來紀錄每個連線的資料流狀態,主要有三種類型 * Send Sequence Variables : 對於寄出訊息方的 * Receive Sequence Variables : 對於收到寄出訊息方的 * Current Segment Variables : 對於正在處理的資訊的狀態變數 * Socket API * 呼叫 `int socket(int domain, int type, int protocol)` ,然後底層的程式就會幫你把東西用一層層協定包起來 * 預設的 TCP-over-IPv4 socket 會用這樣的方式呼叫 `socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);` * 呼叫成功後會連接到遠端的一個端點,接著可以呼叫 `int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);` 就會幫你執行 Handshake * Handshake 成功後就可以透過 `sendto(), recvfrom()` 來傳送和接收訊息 ### 5: TCP Retransmission * TCP 並不能保證資料傳輸過程的完整性,甚至連線成功後在 Handshake 階段失敗會倒置訊息、封包的遺失,所以這一章會講到如何保證 TCP 的可靠性 * Automatic Repeat reQuest (ARQ) * ARQ 的概念是接收端接收訊息後,會發送 acknowledgments (可能會是 message 或 signal) 給傳輸端,若傳輸端沒有收到 acknowledgments 就代表對應到的訊息傳輸失敗,會一直重傳,直到收到 acknowledgments 為止 * 這裡要考慮的是,傳輸端多久沒有收到 acknowledgments 才是代表傳輸失敗 * TCP Retransmission ### 名詞解釋 * [connectionless communication](https://zh.wikipedia.org/wiki/%E7%84%A1%E9%80%A3%E6%8E%A5%E5%BC%8F%E9%80%9A%E8%A8%8A) * 建立連線不需事先安排,同一個通道可同時進行多個傳訊且獨立處理 * IP & UDP 都是使用 connectionless communication ## 解讀程式碼 * 執行 * 架設環境 ``` tools/testenv.sh start tools/run.sh veth1 ``` * ping test ``` tools/ping_test.sh ``` * [shell script](http://linux.vbird.org/linux_basic/0340bashshell-scripts.php#some_ex_run) * if ``` if[條件判斷];then 條件成立執行內容 fi ``` * $ script 對應的參數名稱,例如: ``` /path/script op1 op2 op3 對應到 $0 $1 $2 $3 ``` * $# 表示有幾個參數 * `$ testenv.sh start` ```shell=6 function start { ip netns add TEST ip link add veth0 type veth peer name veth1 ip link set dev veth0 up ip link set dev veth1 up ip addr add dev veth0 local $LOCAL_IP ip route add $STACK_IP dev veth0 ip link set dev veth1 netns TEST ip netns exec TEST ip link set dev veth1 up } ``` * Line 7 : 加入名為 TEST 的 network namespace * Line 9&10 : 啟動代號為 veth0 及 veth1 的介面 * Line 12 : 透過 `veth0` 這個 `dev`,連接到 `$STACK_IP`(在此為 10.0.0.2) 這個 route * 以 `ip link show` 顯示所有連線介面,執行 `tools/testenv.sh start` 後可發現新增了 `veth0` * [ping 指令](https://blog.gtwang.org/linux/windows-linux-ping-command-tutorial/) * 可以藉由發送 ICMP ECHO_REQUEST 的封包,檢查自己與特定設備之間的網路是否暢通,並同時測量網路連線的來回通訊延遲時間 * -c : 發送的封包數 * -s : ICMP 封包大小 * -i : 封包發送間隔秒數 * UDP checksum * Pseudo Header * 來源IP位址 (16-bit) * 目的IP位址 (16-bit) * Unused (8-bit) * protocal (8-bit) * Length (16-bit) * UDP Header * 來源 port (16-bit) * 目的 port (16-bit) * 訊息 (16-bit) * checksum (16-bit) * UDP Data * Padding * 若 data 的大小不是 2 byte 的倍數,就會用 0 補足 * 最後把 checksum 賦予 0 ,把上述的都加起來再做 NOT(~) ,就會是 UDP checksum 了 ## nstack 測試 nstack 有與 linux `netcat` 功能相似的指令 * 先執行 ```shell sudo tools/testenv.sh start tools/run.sh veth1 ``` * 再另開兩個 terminal 分別執行 ```shell telnet 10.0.0.2 10 ``` 及 ```shell sudo build/tnetcat ``` * 在 `telnet` 的 terminal 輸入內容即會印在另一 terminal ## Web server * 採用 CS:APP 中所提到的 tiny server * [程式碼](https://github.com/bauuuu1021/tiny-webServer) * 開始監聽:終端機輸入 `./tiny <port>` * 需 root 權限 : 1~1023 * 不需 root 權限 : 1024~65535 :::info port number 換成 8080 (只要在 1023 以後的數字即可),就不需要 root 權限 ::: 根據 [HowTo: UNIX / Linux Open TCP / UDP Ports](https://www.cyberciti.biz/faq/linux-unix-open-ports/) >Typically port number less than 1024 are used by well know network servers such as Apache. Under UNIX and Linux like oses root (super user) privileges are required to open privileged ports. * 執行內建 adder * 直接在網頁瀏覽器打上 `http://127.0.0.1:<port>/cgi-bin/adder?<num1>&<num2>` * 亦可另開終端機輸入 `telnet 127.0.0.1 <port>` 建立連結, `GET /cgi-bin/adder?<num1>&<num2> HTTP/1.1` 提出要求,將回應網頁原始碼 ``` bauuuu1021@x555:~$ telnet 127.0.0.1 80 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. GET /cgi-bin/adder?1&2 HTTP/1.1 HTTP/1.0 200 OK Server: Tiny Web Server Connection: close Content-length: 105 Content-type: text/html Welcome to add.com: THE Internet addition portal. <p>The answer is: 1 + 2 = 3 <p>Thanks for visiting! Connection closed by foreign host. ``` * 更改 ip 位址 (原本為 127.0.0.1) * server 在 tiny.c 中 accept 前的步驟都包含在 open_listenfd() 這個函式中 ```C=30 listenfd = Open_listenfd(argv[1]); ``` * csapp.c : open_listenfd() 中呼叫 ```C=980 Getaddrinfo(NULL, port, &hints, &listp); ``` Getaddrinfo 定義為 ```C=584 void Getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { int rc; if ((rc = getaddrinfo(node, service, hints, res)) != 0) gai_error(rc, "Getaddrinfo error"); } ``` * 節錄 [man getaddrinfo](http://man7.org/linux/man-pages/man3/getaddrinfo.3.html) ``` If node is NULL, then the network address will be set to the loopback interface address (INADDR_LOOPBACK for IPv4 addresses, IN6ADDR_LOOPBACK_INIT for IPv6 address) ``` 也就是當 getaddrinfo() 第一個參數為 NULL 時的 ip 位址為 127.0.0.1 * 執行 nstack 的 `tools/test.env start` 後,將 csapp.c 改為 ```C=980 Getaddrinfo("10.0.0.1", port, &hints, &listp); ``` 後執行 `./tiny <port>` 其他連到電腦基地台網路的裝置可形成區網,在其他裝置也可實行 tiny website 的功能 :::info **已解決** ubuntu 上開的熱點好像只讓 iPhone 連,別台電腦跟 Android 手機都失敗 ->只要改用 [create wifi hotspot in ubuntu 16.04](https://askubuntu.com/questions/762846/how-to-create-wifi-hotspot-in-ubuntu-16-04-since-ap-hotspot-is-no-longer-working?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) 來設定無線網路熱點就解決了 ::: :::warning TODO: 使用 [htstress](https://github.com/arut/htstress) 一類的 [HTTP(S) benchmark tools](https://github.com/denji/awesome-http-benchmark) 來分析上述 CS:APP web server + nstack 的效能 :notes: jserv ::: * web server + nstack 效能測試 * 採用 [htstress](https://github.com/arut/htstress) * Usage : ``` Usage: htstress [options] [http://]hostname[:port]/path Options: -n, --number total number of requests (0 for inifinite, Ctrl-C to abort) -c, --concurrency number of concurrent connections -t, --threads number of threads (set this to the number of CPU cores) -d, --debug debug HTTP response --help display this message ``` * 以下實驗以 n (requests) 作為變數,c 固定為 1 ,t 固定為 4 即 `./htstress -n <變量> -c 1 -t 4 10.0.0.1:8080` * n = 100 ```shell requests: 100 good requests: 100 [100%] bad requests: 0 [0%] seconds: 17.060 requests/sec: 5.862 ``` * n = 500 ```shell requests: 500 good requests: 500 [100%] bad requests: 0 [0%] seconds: 109.760 requests/sec: 4.555 ``` * n = 1000 ```shell requests: 1000 good requests: 1000 [100%] bad requests: 0 [0%] seconds: 305.362 requests/sec: 3.275 ``` * 比較 * Google ``` bauuuu1021@x555:~/htstress$ ./htstress -n 1000 -c 1 -t 4 http://www.google.com 0 requests 100 requests 200 requests 300 requests 400 requests 500 requests 600 requests 700 requests 800 requests 900 requests requests: 1000 good requests: 1000 [100%] bad requests: 0 [0%] seconds: 44.040 requests/sec: 22.707 ``` * Linux native TCP/IP stack ``` bauuuu1021@x555:~/htstress$ ./htstress -n 1000 -c 1 -t 4 127.0.0.1:8080 0 requests 100 requests 200 requests 300 requests 400 requests 500 requests 600 requests 700 requests 800 requests 900 requests requests: 1000 good requests: 1000 [100%] bad requests: 0 [0%] seconds: 0.261 requests/sec: 3824.691 ``` :::info 需要一併比較 Linux native TCP/IP stack 和 nstack ::: ## 參考資料 * [man ip](https://linux.die.net/man/8/ip) * [ip-netns](http://man7.org/linux/man-pages/man8/ip-netns.8.html) * [鳥哥的 Linux 私房菜](http://linux.vbird.org/linux_server/0140networkcommand.php#ip_cmd) * [CS:APP 程式碼下載](http://csapp.cs.cmu.edu/3e/code.html) * [UDP checksum](https://gist.github.com/GreenRecycleBin/1273763) * [htstress](https://github.com/arut/htstress) ###### tags: `bauuuu1021`,`BroLeaf`