星期五, 12月 25, 2015

struct sk_buff

Linux kernel 的封包用 struct sk_buff 管理,程式碼常用 skb 表示
  • 定義:include/linux/skbuff.h
  • 取得:alloc_skb() 或 dev_alloc_skb()
  • 釋出:kfree_skb() 或 dev_kfree_skb()
一些欄位
  • struct sk_buff *next, *prev
  • head、data、tail、end:head 和 end 分別指到整個資料區塊的開頭和結束,其中有一段是封包的部份,用 data 和 tail 分別指到封包的開頭和結束。skb_pull(len) 將開頭後移 len。
  • len
  • transport_header (h)、network_header (nh)、mac_header (mac):三個 union 經過這些層時分別指到不同協定層的信頭,分別用 skb_transport_header(skb)、skb_network_header(skb) 及 skb_mac_header(skb) 取得這些指標。
  • pkt_type 封包類型:PACKET_BROADCAST、PACKET_MULTICAST、PACKET_OTHERHOST、PACKET_LOOPBCK (針對位址決定?什麼位址?)
  • prev、next、list
  • sk
  • ktime_t tstamp:收到封包的時間。net_enable_timestamp() must be called in order to get values
  • struct net_device *dev, *rx_dev
  • struct dst_entry *dst:系統決定的路由,有兩個重要的函數指標:
    • int (*input)(struct sk_buff*):可指定為 ip_local_deliver, ip_forward, ip_mr_input, ip_error 或 dst_discard_in
    • int (*output)(struct sk_buff*):可指定為 ip_output, ip_mc_output, ip_rt_bug, 或 dst_discard_out.
    一般只有一個 dst_entry。當在 IPSec 時是一個 linked list,只用最後一個來路由,其它的作為 IPSec transformers,flag 會設 DST_NOHASH。
  • data_len
  • mac_len
  • __be16 protocol:1536 以上的 EtherType、或 ETH_P_802_3、ETH_P_802_2 等其它 ETH_P_xxx 協定。
skb_shared()
  • 檢查 sk_buff 是毋是共有,就是看伊 reference count (users) 不是 1。
  • kfree_skb() 會將 reference count 減 1,在 reference count 是 0 時才會真正釋出。
  • 通常一個封包使用者用 skb_shared() 檢查是否 shared,如果是的話用 skb_clone() 複製一份,並用 kfree_skb() 將原本的 reference count 減 1。這些動作可用 skb_share_check() 完成。
    • 什麼情況需要 skb_shared() 檢查?
  • skb_clone() 是複製資料結構,封包資料仍是共用的。如果封包資料也要複製一份,則用 skb_copy();如果只是部份封包資料要複製一份,則用 pskb_copy()。
  • 大部分 reference count 是 1,什麼時候會大於 1 呢?
skb_clone()

參考來源
  1. Linux Kernel Networking by Rami Rosen at Haifux, August 2007
  2. Linux Kernel Source Codes
最後更新 20200721

星期六, 12月 12, 2015

字串複製

C 標準函式庫的字串是以 0 為結尾的 char 陣列,字串長度不計結尾 0,實際儲存空間至少要字串長度加 1。
函數字串結尾?回傳值說明
strcpy()必定結果字串事先確保結果空間要足夠。
strncpy()未必限定結果最大長度。
strlcpy()必定libbsd,非 POSIX,限定結果最大長度。
memcpy()必定事先需要知道字串長度或最大長度。
memmove()必定同 memcpy(),並允許記憶體空間可以重複。
strdup()必定動態取得的結果字串
strndup()限定結果最大長度。
strdupa()函式結束時結果空間自動釋出。
strndupa()函式結束時結果空間自動釋出,限定結果最大長度。
sprintf()
vsprintf()
必定字串長度類似 strcpy()。
snprintf()
vsnprintf()
限定結果最大長度。類似 strlcpy()。
asprintf()
vasprintf()
結果空間動態取得,類似 strdup()。

最簡單的字串複製,莫過於
char *strcpy(char *dst, const char *src)
複製 src 字串到 dst,回傳 dst。其內部基本作法是從位址 src 開始一個 byte 一個 byte 複製到 dst 開始的記憶體空間,直到複製的資料內容是 0 結束。必須確保 src 字串有結尾 0,以及 dst 開始有足夠的記憶體空間,至少是 src 字串長度加 1。另外一點就是 src 跟 dst 記憶體空間避免重複 (dst 指標比 src 指標小應該沒關係,但不能保證)。

如果 src 字串可能大於 dst 可容納的空間的話,就要限制最大的複製大小。
char *strncpy(char *dst, const char *src, size_t n)
跟 strcpy() 一樣,但多了檢查最多複製 n bytes。要注意的是可能沒複製到字串結尾 0,這樣的話 dst 字串就沒結尾。如果要確保 dst 有結尾,dst 最後要多預留一個 byte 並且放 0。

libbsd 提供 (來自 BSD,非 POSIX 標準) 跟上述 snprintf() 作用一樣,但樣子像 strncpy():
size_t strlcpy(char *dst, const char *src, size_t n)
一樣最多從 src 複製 n-1 byte,回傳值為複製的 byte 數。如果回傳值 >= n,則有截斷情形。

sprintf()、snprintf()、vsprintf()、或 vsnprintf() 是複製成某種格式的字串,格式也可以包含字串。這些會自動確保複製結果的字串有結尾 0,他們的回傳值是不含結尾 0 總共複製了幾個 byte。其中 snprintf() 跟 vsnprintf() 有限制含結尾 0 總共複製的大小。
int snprintf(dst, n, "%s", src)
跟 strncpy() 的作用的相像,但最多只會從 src 複製 n-1 bytes 到 dst,回傳值 ≥ n,就表示 src 後面有截斷沒複製到。

如果已經知道 src 字串的長度,其實未必需要使用字串複製的函數,直接使用記憶體複製,複製過程不用檢查字串結尾 0。
void *memcpy(void *dst, const void *src, size_t n)
跟 strncpy() 很像,但純粹是從 src 複製 n bytes 到 dst。如果 n 放 src 字串長度加 1,剛好就是你要的。

如果 src 跟 dst 記憶體空間有重複,可以使用
void *memmove(void *dst, const void *src, size_t n)

標準 C 的字串是以 0 為結尾,但事實上字串也可以用別的方式表示,常見的另一種方式是除了字串本身外,也紀錄字串長度。這樣很容易得知字串長度,不需要一個 byte 一個 byte 查到結尾 0 才知道字串長度。
int snprintf(dst, size, "%.*s", src_len, src)
  • 格式裡用「%.*s」:「*」對應的參數表示複製的字串長度,不管來源字串有無結尾 0
其它相關
  • void *memccpy(void *dst, const void *src, int c, size_t n)
  • void bcopy(const void *src, void *dst, size_t n) obsolete
  • stpcpy
  • stpncpy
  • strdup(const char *src)
  • strndup()
  • strdupa()
  • strndupa()
  • wcscpy
  • wcsncpy
  • strcat
  • strncat
  • string operations
    => strXXX() 系列 (以及 index, rindex)
    byte string operations
    => memXXX() 系列
    => bXXX() 系列 obsolete
     

運算子順序

運算子順序 (Operator Precedence) 除了先乘除後加減、由左到右外,還有許多運算子:

運算子說明
()、[]、->、.函數呼叫, array subscripting、membership 存取
~、!、++、--、+、-、*、&、(type)、sizeofUnary 運算,右到左
*、/、%
+、-
<<、>>
>、<、>=、<=
==、!=
&位元邏輯運算
^
|
&&邏輯運算
||
?:右到左
=、+=、-=、/=、%=、&=、^=、|=、<<=、>>=、、指派運算,右到左
,
相不相等優先序比位元邏輯運算高

參考來源
  1. http://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#Operator-Precedence 
  2. http://pydoing.blogspot.tw/2010/06/c-operator.html

printf()

格式化輸出:printf() 這類函數有一個 format 參數用來表示輸出的字串格式。

#include <stdio.h>

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
int asprintf(char **strp, const char *format, ...);

#include <stdarg.h>

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
int vasprintf(char **strp, const char *format, va_list ap);

成功回傳打算寫入的字串長度,失敗回傳負值。如果 snprintf() 和 vsnprintf() 回傳值 ≥ size,表示有截短。glibc 從 v2.1 開始符合以上 C99 標準,之前截短回傳 -1。

基本型是 printf 輸出到 stdout,前面加

  • f:列印到 FILE。
  • d:列印到 fd。
  • s:列印到 str 字串暫存區。
  • sn:列印到 str 字串暫存區,含結尾 0 最多寫入長度 size。
  • as:列印到自動配置的字串暫存區,從提供的 strp 回傳。最後不用需 free(*strp) 釋出。
  • v:參數採用 va_list

format 格式字串

格式字串內容 printf() 系列  scanf() 系列
% 開頭的轉換規範printf()scanf()
空白字元集
(space、tab、newline 等)
直接輸出比對 0 個以上的空白字元集
一般字元直接比對

輸出格式由 format 字串決定,包含兩種物件 -- 直接輸出的一般字元、以及轉換規範。每個轉換規範 (conversion specification) 通常轉換一個參數,格式:

%[flags][最小寬度][.精確度][資料類型]轉換字元

% 開始到轉換字元,中央會使有 flags、width (最小寬度)、precision (精確度)、和 length (資料類型)。其中最小寬度和精確度是「非 0」的數字,或用「*」來取用下個參數 (須為數字)。預設按照順序每個「*」及轉換字元取用一個參數。UNIX 規範也可以用「%m$」和「*m$」來取代 % 和 *,明確指定使用第 m 個參數,但 C99 標準不支援。如果有用 $,所有需要取用參數的都要用,且使用的參數不能有 gap。

轉換字元 (conversion specifier)

字元輸出. 精確度
d, i有號十進位位數
u無號十進位位數
o無號八進位位數
x, X無號十六進位位數
c字元
s字串最長長度
f, F
[-]mmm.ddddddd 數目
e, E[-]m.dddddde±xx 或 [-]m.ddddddE±xx d 數目
g, G較小的數使用 e 或 E,否則用 fsignificant 位數
p指標值
n目前為止輸出的字數
%印「%」

flags

  • -:向左對齊
  • +:數字都印 +- 符號
  • 空白字元:如果要印的第一個字元不是符號,多插入一個空白字元。
  • 0:數字前面補 0。
  • #:數字轉換成 alternate form,可看出是幾進位或是不是浮點。
    • o:前置 0
    • x 或 X:前置 0x 或 0X
    • e、E、f、g、或 G 保留小數點
    • g 或 G:保留小數點,且不移除尾部的 0。
  • ':十進位轉換 (i, d, u, f, F, g, G) 輸出,每千位依據 locale 隔開 (見 setlocale())。
  • I:十進位轉換 (i, d, u) 輸出使用 locale 語文。

資料類型 (length modifier) 指示對應的參數

  • hh:signed char 或 unsigned char
  • h:short 或 unsigned short
  • l:long 或 unsigned long
  • ll:long long 或 unsigned long long
  • L:long double
  • j:intmax_t 或 uintmax_t
  • z:size_t 或 ssize_t
  • t:ptrdiff_t

void printf(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
}

參考:

  1. The C Programming Language, Brian W. Kernighan & Dennis M. Ritchie
  2. man 3 printf
  3. format string attack:https://bamboofox.cs.nctu.edu.tw/uploads/material/attachment/11/format_string_exploit.pdf
  4. stdout 預設是 line buffered,換行時才會輸出。但有些方式可以馬上輸出:
    • fflush(stdout)
    • setbuf(stdout, NULL)
    • setvbuf(stdout, NULL, _IONBF, 0);