2013年8月16日 星期五

Network Interface Checksum Offload

如果網路界面硬體有提供計算 TCP/UDP/IP Checksum 功能,這樣軟體就可以省下計算的負擔。

TCP/UDP/IP Checksum 基本上是使用 16-bit 1's complement 相加,也就是用一般的 16-bit 相加,再把進位加進來。這對 32-bit 處理器而言,使用 16-bit 相加,全部進位會累積在高位元的 16-bit,最後再把高位元的 16-bit 加進來,有可能再進位所以再加一次高位元的 16-bit。

IP checksum 只計算 IP header 的部份 (20 bytes),任何一個欄位改變,例如 time-to-live 每經過一個 router 減一,就要重算。而 TCP/UDP checksum 計算的範圍包括 TCP/UDP header 跟 data,以及來自 IP header 的一些欄位 (pseudo header),計算份量較多。雖然經過 router 不需要重算,但經過 NAT router 時 IP 變了則需要重算,這會對原本處理器效能就不高的 NAT home router 造成一些負擔。不過 UDP checksum 是選擇性的,可以不算,填 0 就好。TCP checksum 是不能省略的。

ICMP header checksum?

Tx Checksum Offload

首先需設定 net_device features,告訴 Kernel 硬體有什麼 checksum 功能,Kernel 就不會去算:
  • NETIF_F_IP_CSUM:TCP/UDP over IPv4 的 checksum
  • NETIF_F_IPV6_CSUM:TCP/UDP over IPv6 的 checkcum
  • NETIF_F_HW_CSUM:以上兩者
再來 hard_start_xmit() 或 ndo_start_xmit() 時,如果 skb 的 ip_summed 有設 CHECKSUM_PARTIAL,表示需要硬體去產生 checksum。

Rx Checksum Offload

netif_rx()netif_receive_skb() 前,將 skb ip_summed 設為 CHECKSUM_UNNECESSARY,表示不需要再算 checksum,包括 loopback 界面也不需要算 checksum。

net_device features 可設定 NETIF_F_RXCSUM 來紀錄有 Rx Checksum Offload 功能。

參考

  1. Linux kernel source
  2. TCP/IP Illustrated, Volume 1 The Protocols, W. Richard Stevens

相關文章

最後更新 20200721

TUN and TAP: Virtual Network Device

在某些情況,需要軟體提供虛擬的網路界面,方便網路應用程式使用,例如 VPN 或 Virtual Machine 等。

VPN 是對 WAN 網路連線做加密等保護,讓你覺得好像在使用 LAN 環境一樣安全,所以 VPN 程式會去攔截 WAN 界面所有往來的封包,加以改良,加上 VPN header、資料加密等。但為了讓本來就有的網路應用程式不用更改,VPN 程式提供另一個軟體虛擬的網路界面給網路應用程式使用。

Virtual Machine 是用軟體模擬的一台電腦,常常需要提供網路界面給外界存取,又很有可能沒有多餘的、實體的網路界面可用,於是提供虛擬的網路界面,搭上母體電腦的 bridge 或 router。

虛擬網路界面一般有兩種,TAP 跟 TUN。TAP (network tap) 就是 Ethernet 插座的意思,模擬 Ethernet 設備的網路介面,可送收 L2 封包作為 bridge。而 TUN (network tunnel) 模擬成網路層的設備,可送收 L3 封包,跟 routing 搭配使用。

在 Linux,TUN 跟 TAP 都是由 tun 驅動程式實作,載入時會建立一個 /dev/net/tun 的 char device 。當 user-space 程式 (通常是常駐的 daemon 程式) 開啟 /dev/net/tun 並 ioctl TUNSETIFF 做設定,便會建立虛擬的網路介面。此時網路應用程式就可以透過 ifconfig 設定 IP 等啟用建立的虛擬網路介面,開始送收封包。

參考文獻
  1. TUN/TAP [Wikipedia]
  2. tun 驅動程式原始碼:drivers/net/tun.c 
相關文章

Linux timeout by jiffies

之前這篇提到,Linux 的 udelay() 以迴圈延遲 1µs。每個 tick 可以跑 loops_per_jiffy 個延遲迴圈,一秒就是 loops_per_jiffy * HZ 個迴圈,1µs 就是 loops_per_jiffy * HZ / 1000000 個迴圈。mdelay() 或 ndelay() 也是類似的作法。但這些函數都是 busy waiting,等待時不能做其它事。

jiffies 是 Linux 全域變數,每 1/HZ 秒會自動加一,可用來判斷 timeout。範例如下:

unsigned long timeout;

timeout = jiffies + HZ/2; /* 500 ms timeout */

do{
        /* do something */
        if (some_state)
                break;
} while (time_after(timeout, jiffies)); /* 500ms timeout */

jiffies 跟 time_after 定義在 inclue/linux/jiffies.h。

相關文章

2013年8月14日 星期三

Network Interface Packet Reception

網路界面驅動程式有兩種接收封包的方式 -- 中斷驅動和 NAPI。傳統是用中斷驅動,在中斷服務程式裡接收封包,但封包量大時會有中斷次數太多而效能變差的問題。NAPI (意思是 New API) 改成中斷服務程式只通知有封包,Kernel 再排程一次接收多個封包來提昇效能。

中斷驅動方式
  • 收到 frame 呼叫中斷服務程式
  • 在中斷服務程式通常依封包大小 dev_alloc_skb() 取得 sk_buff 放入封包,struct net_device *dev 指到網路界面、辨別 protocol (什麼時候用到?)、填寫ip_summed 等參數,最後呼叫 netif_rx()。
  • 每個處理器有個 softnet_data,netif_rx() 將 sk_buff 放到執行中斷處理器的 softnet_data 的 input_pkt_queue。如果原本 input_pkt_queue 是空的,執行netif_rx_schedule() 將 backlog_dev 加到 softnet_data 的 poll_list 並啟用 NET_RX_SOFTIRQ。如果 input_pkt_queue 大於 netdev_max_backlog 表示滿了,則 drop 並回傳 NET_RX_DROP,可知道壅塞程度。
  • 技巧:預先以最大封包為考量取得 skb,讓封包直接接收到 skb。change_mtu() 可知道最大封包大小。
NAPI 用 polling 方式接收封包,底層需要有數個封包的 buffer 放新進封包,當已知有封包進來的情況下,kernel 才擇時去執行 net_device 的 poll 函數並一次將 buffer 中的全部封包接收。這段時間如有新封包再進來,不需要再通知 kernel。當封包流量大時,例如每秒幾千個封包,可以大大減少中斷的次數,降低 kernel 分心。
  • 初始化:net_device 設 poll 函數及 weight,weight 說明界面的重要度,不可超過可 buffer 封包的數目。
  • 封包進來的中斷呼叫 netif_rx_schedule() 就好,kernel 會排程用 poll 去取封包。
  • poll 函數一次最多接收 budget 或 quota 個封包,一樣呼叫 dev_alloc_skb() 取得 skb,放進收到的封包,設定 skb dev、protocol、ip_summed 等參數,但最後是呼叫 netif_receive_skb()。如果接收的封包都處理了,呼叫 netif_rx_complete(),並啟用中斷。
NAPI 有另一個好處,需要拋棄封包時,也可以在收進 kernel 之前就處理掉。例如網路子系統壅塞,可以只收額定的封包。

NAPI 還有更新的改進,從 net_device 獨立出 napi_struct 資料結構,改由驅動程式提供。好處是一個界面可以有多個 napi_struct 來對應硬體支援 multiple receive queues。此外使用的函數也大多不同,如下表:
\中斷驅動NAPINew NAPI
初始化-
  • 登記 net_device poll 函數
  • netif_napi_add() 加入一個 napi_struct 並登記 poll 函數
  • napi_enable
中斷服務程式dev_alloc_skb()
複製封包、設定參數
netif_rx
netif_rx_schedule()napi_schedule()
接收封包dev_alloc_skb()
複製封包、設定參數
netif_receive_skb
buffer 處理完畢-netif_rx_complete()napi_complete()
結束使用--napi_disable()
netif_napi_del()
註:skb 可以事先準備,在初始化時就 dev_alloc_skb

參考:
  1. LDD3 ch17
  2. Newer, newer NAPI
  3. 每個接收封包會在 netif_rx() 或 netif_receive_skb() 執行 netpoll_rx() 一次。
  4. Understanding the Linux Kernel, 3rd Edition 的 4.7 Softirqs and Tasklets (說明 softirq)
相關文章
延伸閱讀
  • Linux Network Scaling: Receiving Packets
    • RSS (Receive Side Scaling)
    • RPS (Receive Packet Steering)
    • RFS (Receive Flow Steering)
    • aRFS (Accelerated Receive Flow Steering)
  • Linux network stack, Packet ingress flow, NAPI interruption coalescence, zero-copy (DMA, mmap())
最後更新 20200721

GPT

GUID Partition Table (GPT) 是一種新的硬碟分割表,擺脫 MBR 分割表 2.2TB 的限制,使用 GUID (Globally Unique IDentifier) 來表示分割的用途。

MBR 分割表使用 32-bit logic block addressing (LBA),所以有 512 bytes x 232 = 2.2 TB 的限制,除非改用較大的 logic block (也就是 sector 大小)。而 GPT 使用 64-bit LBA,大大地擺脫這個限制。

雖然 GPT 是 UEFI 的一部分,但可以獨立使用。64-bit 的Windows Vista/7/8 支援 GPT,並且必須是 UEFI 模式開機。而 Linux 支援 GPT,可用 Legend BIOS 開機;UEFI 開機的話,需要 64-bit Linux。
圖片來自 Wikipedia

GPT 的結構如圖,LBA 0 放 MBR,以便於跟 MBR 相容,當作 MBR 硬碟使用。接下來 LBA 1 放 GPT Header,內含指示分割表 (Partition Entry Array) 在哪裡,通常是 LBA 2。然後從 LBA 2 開始就是分割表,至少預留 16,384 bytes。每個 Partition Entry 預設為最小的 128 bytes,可有 16,384 ÷ 128 = 128 entries,用來表示 128 個分割。

如果 sector 大小是 512 bytes,16,384 bytes 需要 32 sectors,所以分割表佔 LBA 2 到 LBA 33,分割就從 LBA 34 開始。但實際上為了對齊,可能預留更大的分割表空間,例如從 LBA 40 (對齊 4KB sector)、LBA 63 (對齊 cylinder boundary)、或 LBA 2048 (對齊 1 MiB) 開始。在硬碟的最後面,會有 GPT Header 跟分割表的備份。

參考:
  1. GUID Partition Table
相關文章:

2013年8月7日 星期三

OpenWrt telnet

要能 telnet,telnetd 需要執行。我想設計的理念是剛安裝完,root 沒密碼,此時便執行 telnetd,所以 telnetd 在特定條件下才會執行。

/etc/init.d/telnet:

#!/bin/sh /etc/rc.common
# Copyright (C) 2006-2011 OpenWrt.org

START=50

has_root_pwd() {
 local pwd=$([ -f "$1" ] && cat "$1")
       pwd="${pwd#*root:}"
       pwd="${pwd%%:*}"

 test -n "${pwd#[\!x]}"
}

get_root_home() {
 local homedir=$([ -f "$1" ] && cat "$1")
 homedir="${homedir#*:*:0:0:*:}"

 echo "${homedir%%:*}"
}

has_ssh_pubkey() {
 ( /etc/init.d/dropbear enabled 2> /dev/null && grep -qs "^ssh-" /etc/dropbear/authorized_keys ) || \
 ( /etc/init.d/sshd enabled 2> /dev/null && grep -qs "^ssh-" "$(get_root_home /etc/passwd)"/.ssh/authorized_keys )
}

start() {
 if ( ! has_ssh_pubkey && \
      ! has_root_pwd /etc/passwd && ! has_root_pwd /etc/shadow ) || \
    ( ! /etc/init.d/dropbear enabled 2> /dev/null && ! /etc/init.d/sshd enabled 2> /dev/null );
 then
  service_start /usr/sbin/telnetd -l /bin/login.sh
 fi
}

stop() {
 service_stop /usr/sbin/telnetd
}

包括:
  1. 沒有 ssh pubkey 且
  2. 沒有 root password 且
  3. ssh server (dropbear 或 sshd) 未啟用 (註:)
註:未啟用 (not enabled) 未必未執行 (not started),有無啟用是檢查對應的 /etc/rc.d/Sxxx 是否存在

再來,telnet 進來的 login script 是 /bin/login.sh

#!/bin/sh
# Copyright (C) 2006-2011 OpenWrt.org

if ( ! grep -qs '^root:[!x]\?:' /etc/shadow || \
     ! grep -qs '^root:[!x]\?:' /etc/passwd ) && \
   [ -z "$FAILSAFE" ]
then
 echo "Login failed."
 exit 0
else
cat << EOF
 === IMPORTANT ============================
  Use 'passwd' to set your login password
  this will disable telnet and enable SSH
 ------------------------------------------
EOF
fi

exec /bin/ash --login
會再檢查一次 root passwd (但方式不同)

OpenWrt package Config.in

有時候套件需要額外設定,可在 menuconfig 選單新增選項
  1. package 的 Makefile 新增定義 define Package/套件名稱/config 或 define KernelPackage/套件名稱/config,例如
  2. 
    define Package/uhttpd/config
      config PACKAGE_uhttpd_debug
        bool "Build with debug messages"
        default n
    endef
    

    或者
    
    define Package/busybox/config
            source "$(SOURCE)/Config.in"
    endef
    

    可以直接寫在裡面,或者寫在另一個檔案。$(SOURCE) 是 Makefile 同一目錄。
  3. package 的 Makefile 可以對新增的選項參數做 ifdef-endif 等判斷,可能用來決定一些編譯參數等。
  4. make menuconfig 套件的選擇下會多出 Configuration  --->
  5. 預設設定如果要跟設定檔不同,且要存下來的話,可能只能自己存一個 .config 到其它地方,裡面有填好 TARGET 及你要的特別設定,複製到 .config,然後 make defconfig 產生完整的 .config,包含其它預設設定。[參考]

OpenWrt package 範例:
  1. package/busybox -- 可能是最大的範例
  2. 搜尋,用「grep 'define .*\/config\>' package/*/Makefile」

SIP header Via

所有 SIP 訊息 都要有 Via,縮寫 v。一開始的 UAC 和後續途經的每個 proxy 都會疊加一個 Via 放傳送的位址,依序作為回應的路徑。 格式 sent-protocol sent-by [ ;branch= branch ][ ; 參數 ...] s...