2012年2月9日 星期四

iptables source code trace


一、規則的顯示
選擇先來說明規則的顯示,因為他涉及到的東東簡單,而且又全面,了解了規則的顯示,對於其它操作的了解就顯得容易了。
iptables version 1.2.7
iptables有兩條線:ipv4 和ipv6,這裡只分析v4的,因為v6偶暫時還用不著,沒有去看。
iptables_standardone.c
主函數:
int main(int argc, char *argv[])
{
int ret;
char *table = "filter"; /*默認的表是filter*/
iptc_handle_t handle = NULL;
program_name = "iptables";
program_version = IPTABLES_VERSION;
#ifdef NO_SHARED_LIBS
init_extensions();
#endif
/*進入命令行處理函數*/
ret = do_command(argc, argv, &table, &handle);
if (ret)
ret = iptc_commit(&handle);
if (!ret)
fprintf(stderr, "iptables: %s\n",
iptc_strerror(errno));
exit(!ret);
}
table表示表的名稱,就是iptables -t 後面跟的那個,默認是"filter"
iptc_handle_t handle = NULL; 這個東東很重要,現在初始化NULL,後面他被用來存儲一個表的所有規則的快照。
program_name = "iptables";
program_version = IPTABLES_VERSION;
設置名稱和版本。
#ifdef NO_SHARED_LIBS
init_extensions();
#endif
iptables很多東東,是用共享庫*.so的形式(我們安裝會,可以在諸如/lib/iptables下邊看到),如果不採用共享庫,則進行一個初始化操作。我們假設是採用共享庫的,忽略它。
然後就進入核心處理模塊:
do_command(argc, argv, &table, &handle);
do_command 函數是整個系統的核心,負責處理整個用戶的輸入命令。函數首先對一些結構、變量進行初始化,初始化完畢後,進入while循環,分析用戶輸入的命令,設置相關的標誌變量,然後根據相應標誌,調用對應的處理函數。
struct ipt_entry fw, *e = NULL;
int invert = 0;
unsigned int nsaddrs = 0, ndaddrs = 0;
struct in_addr *saddrs = NULL, *daddrs = NULL;
int c, verbose = 0;
const char *chain = NULL;
const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
const char *policy = NULL, *newname = NULL;
unsigned int rulenum = 0, options = 0, command = 0;
const char *pcnt = NULL, *bcnt = NULL;
int ret = 1;
struct iptables_match *m;
struct iptables_target *target = NULL;
struct iptables_target *t;
const char *jumpto = "";
char *protocol = NULL;
const char *modprobe = NULL;
/*初始化變量*/
memset(&fw, 0, sizeof(fw));
opts = original_opts;
global_option_offset = 0;
/* re-set opt​​ind to 0 in case do_command gets called
* a second time */
optind = 0;
/*初始化兩個全局變量*/
/* clear mflags in case do_command gets called a second time
* (we clear the global list of all matches for security)*/
for (m = iptables_matches; m; m = m->next) {
m->mflags = 0;
m->used = 0;
}
for (t = iptables_targets; t; t = t->next) {
t->tflags = 0;
t->used = 0;
}
ps:開頭一大堆的變量定義和初始化,可以在程序分析的時候看它們的作用,有兩個全局結構變量很重要:iptables_matches和iptables_targets。現在來分析他們的作用會有一點困難,因為它們涉及到了太多方面的東東,這裡,可以先把它們“想像成”用戶空間用來讀取內核規則的結構(當然,這有點錯誤)。
/*開始化析命令行*/
while ((c = getopt_long(argc, argv,
"-A:C:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m :xc:",
opts, NULL)) != -1)
{
}
這個while循環處理所有的用戶輸入,對應規則輸出-L,有:
case ''L'':
add_command(&command, CMD_LIST, CMD_ZERO,
invert);
if (optarg) chain = optarg;
else if (optind < ​​argc && argv[optind][0] != ''-''
&& argv[optind][0] != ''!'')
chain = argv[optind++];
break;
add_command函數負責將命令標誌變量command與令標誌CMD_LIST求&運算, CMD_ZERO只是一個附加的判斷標誌而已,invert);然後,從命令行中取得要顯示的鏈名(如果有的話)。
與此相關的還有用t參數指定了表名:
case ''t'':
if (invert)
exit_error(PARAMETER_PROBLEM,
"unexpected ! flag before --table");
*table = argv[optind-1];
break;
即,如果有't'參數,則​​取't'後跟的表名:*table = argv[optind-1],否則,它應該是主函數中默認的filter表。
命令處理完畢後,即進入執行模塊:
/*因為程序定義了共享庫的話,iptables_matches/iptables_target這兩個結構運行至此是NULL,並且target也是NULL,對於規則顯示而言,這一部份的處理目前沒有實際意義,回過頭再來看這一段更易理解。final_check成員函數的作用是作最終的標誌檢查,如果檢測失則,則退出*/
for (m = iptables_matches; m; m = m->next) {
if (!m->used)
continue;
m->final_check(m->mflags);
}
if (target)
target->final_check(target->tflags);
接著對參數作一些必要的合法性檢查:
/* Fix me: must put inverse options checking here --MN */
if (optind < ​​argc)
exit_error(PARAMETER_PROBLEM,
"unknown arguments found on commandline");
if (!command)
exit_error(PARAMETER_PROBLEM, "no command specified");
if (invert)
exit_error(PARAMETER_PROBLEM,
"nothing appropriate following !");
/*對於如果要進行(CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)處理來說,如果沒有設置來源/目的地址及掩碼,則給予它們一個默認值*/
if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {
if (!(options & OPT_DESTINATION))
dhostnetworkmask = "0.0.0.0/0";
if (!(options & OPT_SOURCE))
shostnetworkmask = "0.0.0.0/0";
}
/*對來源/目的地址及掩碼進行拆分,它們總是以addr/mask的形式來出現的,根據'/'前面的字符串取得地址值,根據'/'後面的掩碼位數,求得正確的掩碼值,值得注意的是,同時要處理主機地址和網絡地址的情況*/
if (shostnetworkmask)
parse_hostnetworkmask(shostnetworkmask, &saddrs,
&(fw.ip.smsk), &nsaddrs);
if (dhostnetworkmask)
parse_hostnetworkmask(dhostnetworkmask, &daddrs,
&(fw.ip.dmsk), &ndaddrs);
/*然後檢查來源/目的網絡地址的合法性*/
if ((nsaddrs > 1 || ndaddrs > 1) &&
(fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
exit_error(PARAMETER_PROBLEM, "! not allowed with multiple"
" source or destination IP addresses");
/*對命令行格式進行合法性檢查*/
generic_opt_check(command, options);
如果前面只是熱身的話,那麼從現在開始,就進入實質性階段了:
do_command函數最後一個參數handle,是一個指向了具體表,如filter、nat表的句柄,這裡判斷,如果handle為空,則調用iptc_init,根據table的名稱,讓handle指針指向相應的表的地址空間,也就是把對應表的所有信息從內核中取出來:
/* only allocate handle if we weren''t called with a handle */
if (!*handle)
*handle = iptc_init(*table);
/*如果獲取換敗,將試著插入模塊,再次獲取*/
if (!*handle) {
/* try to insmod the module if iptc_init failed */
iptables_insmod("ip_tables", modprobe);
*handle = iptc_init(*table);
/*仍然失敗,則退出*/
if (!*handle)
exit_error(VERSION_PROBLEM,
"can''t initialize iptables table `%s'': %s",
*table, iptc_strerror(errno));
/*繼續進行一些簡單的判斷*/
if (command == CMD_APPEND
|| command == CMD_DELETE
|| command == CMD_INSERT
|| command == CMD_REPLACE) {
/*List命令不在判斷之列,暫時不分析*/
}
/*判斷命令標誌,調用相關函數進行處理*/
switch (command) {
case CMD_LIST:
ret = list_entries(chain,
options&OPT_VERBOSE,
options&OPT_NUMERIC,
options&OPT_EXPANDED,
options&OPT_LINENUMBERS,
handle);
}
list_entries是規則顯示的主要處理函數。
Options是顯示的標誌變量:
OPT_VERBOSE:對應-v
OPT_NUMERIC:對應-n
OPT_EXPANDED:對應-x
OPT_LINENUMBERS: -l
看來很簡單,說了這麼大一圈子,就是調用iptc_init獲取表的規則信息,調用list_entries函數顯示規則。
1.1 表的查找
再回到iptc_init 函數上來,它根據表名,從內核獲取對應的表的相關信息,handle是一個iptc_handle_t類型的指針,在libiptc.c中,有如下定義:
/* Transparent handle type. */
typedef struct iptc_handle *iptc_handle_t;
在Libip4tc中:
#define STRUCT_TC_HANDLE struct iptc_handle
在Libiptc.c中,可以找到STRUCT_TC_HANDLE的定義:
STRUCT_TC_HANDLE
{
/* Have changes been made? */
int changed;
/* Size in here reflects original state. */
STRUCT_GETINFO info;
struct counter_map *counter_map;
/* Array of hook names */
const char **hooknames;
/* Cached position of chain heads (NULL = no cache). */
unsigned int cache_num_chains;
unsigned int cache_num_builtins;
/* Rule iterator: terminal rule */
STRUCT_ENTRY *cache_rule_end;
/* Number in here reflects current state. */
unsigned int new_number;
STRUCT_GET_ENTRIES entries;
};
再來看看iptc_init函數,同樣在在Libip4tc中,有如下定義:
#define TC_INIT iptc_init
在Libiptc.c中,可以看到函數的實現,基本上iptables與內核的交互,都是使用setsockopt函數來實現的,對於獲取取規是信息來說,標誌位是SO_GET_INFO,而從內核返回回來的規則信息是一個STRUCT_GETINFO結構:
TC_HANDLE_T TC_INIT(const char *tablename)
{
TC_HANDLE_T h;
STRUCT_GETINFO info;
unsigned int i;
int tmp;
socklen_t s;
iptc_fn = TC_INIT;
if (sockfd != -1)
close(so​​ckfd);
/*為獲取信息打開一個套接字接口*/
sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0)
return NULL;
s = sizeof(info);
if (strlen(tablename) >= TABLE_MAXNAMELEN) {
errno = EINVAL;
return NULL;
}
strcpy(info.name, tablename);
/*獲取規則信息*/
if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0)
return NULL;
if ((h = alloc_handle(info.name, info.size, info.num_entries))
== NULL)
return NULL;
/* Too hard --RR */
#if 0
sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name);
dynlib = dlopen(pathname, RTLD_NOW);
if (!dynlib) {
errno = ENOENT;
return NULL;
}
h->hooknames = dlsym(dynlib, "hooknames");
if (!h->hooknames) {
errno = ENOENT;
return NULL;
}
#else
h->hooknames = hooknames;
#endif
/* Initialize current state */
h->info = info;
h->new_number = h->info.num_entries;
for (i = 0; i < h->info.num_entries; i++)
h->counter_map[i]
= ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i});
h->entries.size = h->info.size;
tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,
&tmp) < 0) {
free(h);
return NULL;
}
CHECK(h);
return h;
}
函數為h分配空間,然後賦予相應的值。要理解這個函數,還需要了解STRUCT_GETINFO結構和分配內存空間的函數alloc_handle。
#define STRUCT_GETINFO struct ipt_getinfo
/* The argument to IPT_SO_GET_INFO */
struct ipt_getinfo
{
/* Which table: caller fills this in. */
char name[IPT_TABLE_MAXNAMELEN];
/* Kernel fills these in. */
/* Which hook entry points are valid: bitmask */
unsigned int valid_hooks;
/* Hook entry points: one per netfilter hook. */
unsigned int hook_entry[NF_IP_NUMHOOKS];
/* Underflow points. */
unsigned int underflow[NF_IP_NUMHOOKS];
/* Number of entries */
unsigned int num_entries;
/* Size of entries. */
unsigned int size;
};
/* Allocate handle of given size */
static TC_HANDLE_T
alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)
{
size_t len​​;
TC_HANDLE_T h;
len = sizeof(STRUCT_TC_HANDLE)
+ size
+ num_rules * sizeof(struct counter_map);
if ((h = malloc(len)) == NULL) {
errno = ENOMEM;
return NULL;
}
h->changed = 0;
h->cache_num_chains = 0;
h->cache_chain_heads = NULL;
h->counter_map = (void *)h
+ sizeof(STRUCT_TC_HANDLE)
+ size;
strcpy(h->info.name, tablename);
strcpy(h->entries.name, tablename);
return h;
}
函數list_entries用於顯示表下邊的鏈:
/*顯示某table下的chain*/
static int
list_entries(const ipt_chainlabel chain, int verbose, int numeric,
int expanded, int linenumbers, iptc_handle_t *handle)
{
int found = 0;
unsigned int format;
const char *this;
format = FMT_OPTIONS; /*設置輸出格式*/
if (!verbose) /*詳細輸出模式,,對應-v ,顯示匹配的包的數目,包的大小等*/
format |= FMT_NOCOUNTS;
else
format |= FMT_VIA;
if (numeric) /*對應-n,以數字的形式輸出地址和端口*/
format |= FMT_NUMERIC;
if (!expanded) /*對應-x,expand numbers (display exact values​​)*/
format |= FMT_KILOMEGAGIGA;
if (linenumbers) /*輸出行的編號*/
format |= FMT_LINENUMBERS;
for (this = iptc_first_chain(handle); /*遍歷當前table的所有chain*/
this;
this = iptc_next_chain(handle))
{
const struct ipt_entry *i;
unsigned int num;
if (chain && strcmp(chain, this) != 0) /*匹配指定chain名,這裡用chain &&,即若不指定chain,輸出所有chain*/
continue;
if (found) printf("\n");
print_header(format, this, handle); /*輸出標頭*/
i = iptc_first_rule(this, handle); /*移至當前chain的第一條規則*/
num = 0;
while (i) {
print_firewall(i, /*輸出當前規則*/
iptc_get_target(i, handle),
num++,
format,
*handle);
i = iptc_next_rule(i, handle); /*移至下一條規則*/
}
found = 1;
}
errno = ENOENT;
return found;
}
可見,在函數中,由iptc_first_chain和iptc_next_chain實現了遍歷,iptc_first_rule和iptc_next_rule實現了鏈中規是的遍歷,print_firewall函數在遍歷到規則的時候,向終端輸出防火牆規則,其第二個參數iptc_get_target又用於獲取規則的target。
前面提到過,在內核中,handler指針指向了從內核中返回的對應的表的信息,handler對應的結構中,涉及到鏈的結構成員主要有兩個:
struct chain_cache *cache_chain_heads;
struct chain_cache *cache_chain_iteration;
前者用於指向第一個鏈,後者指向當前鏈。而struct chain_cache的定義如下:
struct chain_cache
{
char name[TABLE_MAXNAMELEN]; /*鏈名*/
STRUCT_ENTRY *start; /*該鏈的第一條規則*/
STRUCT_ENTRY *end; /*該鏈的最後一條規則*/
};
理解了這兩個成員,和結構struct chain_cache,再來理解鏈的遍歷函數就不難了。所謂鏈的遍歷,就是將handler對應成員的值取出來。
#define TC_FIRST_CHAIN​​ iptc_first_chain
#define TC_NEXT_CHAIN​​ iptc_next_chain
函數TC_FIRST_CHAIN​​用於返回第一個鏈:
/* Iterator functions to run through the chains. */
const char *
TC_FIRST_CHAIN​​(TC_HANDLE_T *handle)
{
/*鏈首為空,則返回NULL*/
if ((*handle)->cache_chain_heads == NULL
&& !populate_cache(*handle))
return NULL;
/*當前鏈的指針指向鍊錶首部*/
(*handle)->cache_chain_iteration
= &(*handle)->cache_chain_heads[0];
/*返回鏈的名稱*/
return (*handle)->cache_chain_iteration->name;
}
/* Iterator functions to run through the chains. Returns NULL at end. */
const char *
TC_NEXT_CHAIN​​(TC_HANDLE_T *handle)
{
/*很簡單,用heads開始,用++就可以實現遍歷了*/
(*handle)->cache_chain_iteration++;
if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads
== (*handle)->cache_num_chains)
return NULL;
return (*handle)->cache_chain_iteration->name;
}
規則的遍歷
當遍歷到某個鏈的時候,接下來,就需要遍歷當前鏈下的所有規則了,輸出之了。前面敘述了鏈的遍歷,那麼規則的遍歷,應該就是根據鏈的名稱,找到對應的成員結構struct chain_cache ,這裡麵包含了當前鏈的第一條規則與最後一條規則的指針:
#define TC_FIRST_RULE iptc_first_rule
#define TC_NEXT_RULE iptc_next_rule
/* Get first rule in the given chain: NULL for empty chain. */
const STRUCT_ENTRY *
TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)
{
struct chain_cache *c;
c = find_label(chain, *handle); /*根據鏈名,返回對應的struct chain_cache結構*/
if (!c) { /*沒有找到,返回NULL*/
errno = ENOENT;
return NULL;
}
/* Empty chain: single return/policy rule */
if (c->start == c->end) /*如果是空鏈*/
return NULL;
(*handle)->cache_rule_end = c->end;
return c->start; /*返回鏈的首條規則*/
}
/* Returns NULL when rules run out. */
const STRUCT_ENTRY *
TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)
{
if ((void *)prev + prev->next_offset
== (void *)(*handle)->cache_rule_end)
return NULL;
return (void *)prev + prev->next_offset;
}
要更解TC_NEXT_RULE函數是如何實現查找下一條規則的,需要首先理解STRUCT_ENTRY結構:
#define STRUCT_ENTRY struct ipt_entry
ipt_entry結構用於存儲鏈的規則,每一個包過濾規則可以分成兩部份:條件和動作。前者在Netfilter中,稱為match,後者稱之為target。Match又分為兩部份,一部份為一些基本的元素,如來源/目的地址,進/出網口,協議等,對應了struct ipt_ip,我們常常將其稱為標準的match,另一部份match則以插件的形式存在,是動態可選擇,也允許第三方開發的,常常稱為擴展的match,如字符串匹配,p2p匹配等。同樣,規則的target也是可擴展的。這樣,一條規則佔用的空間,可以分為:struct ipt_ip+n*match+n*target,(n表示了其個數,這裡的match指的是可擴展的match部份)。基於此,規則對應的結構如下:
/* This structure defines each of the firewall rules. Consists of 3
parts which are 1) general IP header stuff 2) match specific
stuff 3) the target to perform if the rule matches */
struct ipt_entry
{
struct ipt_ip ip; /*標準的match部份*/
/* Mark with fields that we care about. */
unsigned int nfcache;
/* Size of ipt_entry + matches */
u_int16_t target_offset; /*target的開始位置,是sizeof(ipt_entry+n*match)*/
/* Size of ipt_entry + matches + target */
u_int16_t next_offset; /*下一條規則相對於本條規則的位置,是sizeof(ipt_entry)加上所有的match,以及所有的target*/
/* Back pointer */
unsigned int comefrom;
/* Packet and byte counters. */
struct ipt_counters counters;
/* The matches (if any), then the target. */
unsigned char elems[0];
};
有了這樣的基礎,就不難理解遍歷規則中,尋找下一條規則語句:
return (void *)prev + prev->next_offset;
即是本條規則加上下一條規則的偏移值。
輸出規則
print_firewall 函數用於規則的輸出:
print_firewall(i, iptc_get_target(i, handle), num++,format,*handle);
i:當前的規則;
iptc_get_target(i, handle):用於規則的target部份的處理;
num:規則序號;
format:輸出格式;
handler:表的信息;
/* e is called `fw'' here for hysterical raisins */
static void
print_firewall(const struct ipt_entry *fw,
const char *targname,
unsigned int num,
unsigned int format,
const iptc_handle_t handle)
{
struct iptables_target *target = NULL;
const struct ipt_entry_target *t;
u_int8_t flags;
char buf[BUFSIZ];
if (!iptc_is_chain(targname, handle))
target = find_target(targname, TRY_LOAD);
else
target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED);
t = ipt_get_target((struct ipt_entry *)fw);
flags = fw->ip.flags;
if (format & FMT_LINENUMBERS) /*輸出行號*/
printf(FMT("%-4u ", "%u "), num+1);
if (!(format & FMT_NOCOUNTS)) { /*詳細模式,列出計數器*/
print_num(fw->counters.pcnt, format); /*匹配當前規則的數據包個數*/
print_num(fw->counters.bcnt, format); /*--------------------大小*/
}
/*輸出目標名稱*/
if (!(format & FMT_NOTARGET)) /*目標名稱,即攔截、通過等動作*/
printf(FMT("%-9s ", "%s "), targname);
/*輸出協議名*/
fputc(fw->ip.invflags & IPT_INV_PROTO ? ''!'' : '' '', stdout);
{
char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);
if (pname)
printf(FMT("%-5s", "%s "), pname);
else
printf(FMT("%-5hu", "%hu "), fw->ip.proto);
}
/*輸出選項字段*/
if (format & FMT_OPTIONS) {
if (format & FMT_NOTABLE)
fputs("opt ", stdout);
fputc(fw->ip.invflags & IPT_INV_FRAG ? ''!'' : ''-'', stdout); //#define IP_FW_INV_FRAG 0x0080 /* Invert the sense of IP_FW_F_FRAG. */
fputc(flags & IPT_F_FRAG ? ''f'' : ''-'', stdout); //#define IP_FW_F_FRAG 0x0004 /* Set if rule is a fragment rule */
fputc('' '', stdout);
}
if (format & FMT_VIA) {
char iface[IFNAMSIZ+2];
if (fw->ip.invflags & IPT_INV_VIA_IN) { /*輸入端口取反標誌*/
iface[0] = ''!''; /*設置取反標誌符*/
iface[1] = ''\0'';
}
else iface[0] = ''\0'';
if (fw->ip.iniface[0] != ''\0'') {
strcat(iface, fw->ip.iniface);
}
else if (format & FMT_NUMERIC) strcat(iface, "*");
else strcat(iface, "any");
printf(FMT(" %-6s ","in %s "), iface); /*輸出輸入端口*/
if (fw->ip.invflags & IPT_INV_VIA_OUT) { /*輸出端口取反標誌*/
iface[0] = ''!''; /*設置取反標誌符*/
iface[1] = ''\0'';
}
else iface[0] = ''\0'';
if (fw->ip.outiface[0] != ''\0'') {
strcat(iface, fw->ip.outiface);
}
else if (format & FMT_NUMERIC) strcat(iface, "*");
else strcat(iface, "any");
printf(FMT("%-6s ","out %s "), iface); /*輸出輸出端口*/
} /*end print in/out interface */
/*輸出源地址及掩碼*/
fputc(fw->ip.invflags & IPT_INV_SRCIP ? ''!'' : '' '', stdout); /*源地址取反標誌*/
if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) /*源地址為任意*/
printf(FMT("%-19s ","%s "), "anywhere");
else {
if (format & FMT_NUMERIC)
sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src)));
else
sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src)));
strcat(buf, mask_to_dotted(&(fw->ip.smsk)));
printf(FMT("%-19s ","%s "), buf);
}
/*輸出目的地址及掩碼*/
fputc(fw->ip.invflags & IPT_INV_DSTIP ? ''!'' : '' '', stdout);
if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))
printf(FMT("%-19s","-> %s"), "anywhere");
else {
if (format & FMT_NUMERIC)
sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst)));
else
sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst)));
strcat(buf, mask_to_dotted(&(fw->ip.dmsk)));
printf(FMT("%-19s","-> %s"), buf);
}
if (format & FMT_NOTABLE)
fputs(" ", stdout);
/*輸出擴展的MATCH*/
IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
/*輸出擴展的TARGET*/
if (target) {
if (target->print)
/* Print the target information. */
target->print(&fw->ip, t, format & FMT_NUMERIC);
} else if (t->u.target_size != sizeof(*t))
printf("[%u bytes of unknown target data] ",
t->u.target_size - sizeof(*t));
if (!(format & FMT_NONEWLINE))
fputc(''\n'', stdout);
}
函數分為三部份:
輸出標準的match部份;
輸出擴展的match部份,調用IPT_MATCH_ITERATE實現;
調用對應的target的print函數輸出target部份。
match的輸出
IPT_MATCH_ITERATE 宏用於實現擴展match的遍歷。這個宏定義在內核include/Linux/Netfilter-ipv4/Ip_tables.h中:
#define IPT_MATCH_ITERATE(e, fn, args...) \
({ \
unsigned int __i; \
int __ret = 0; \
struct ipt_entry_match *__match; \
\
for (__i = sizeof(struct ipt_entry); \
__i < (e)->target_offset; \
__i += __match->u.match_size) { \
__match = (void *)(e) + __i; \
\
__ret = fn(__match , ## args); \ /*每找到一個match,就交由fn函數來處理,在print_firewall中,傳遞過來的是函數print_match*/
if (__ret != 0) \
break; \
} \
__ret; \
})
要理解這個宏,需要先了解規則的存儲,前面提到過,因為match/target都是可變的,所以在內存中,採取了ip_entry+n*match+n*target,即在規則後,是連續的若干個match,而mathc後面,又是若干個target,在結構ip_entry中,成員u_int16_t target_offset;代表了target的偏移地址,即target的開始,match的結束。我們要查到當前規則對應的所有match,需要了解三個要素:
1、match從哪裡開始:起始地址應該是[當前規則地址+sizeof(struct ipt_entry)];
2、match從哪裡結束:結束地址,應該是[當前規則地址+target_offet];
3、每一個match的大小,在內核中,match對應的結構是ipt_entry_match,其成員u.match_size指明了當前match的大小;
這三點,對應了for循環:
for (__i = sizeof(struct ipt_entry); __i < (e)->target_offset; __i += __match->u.match_size)
這樣,i就對應了某個match的偏移植,通過:
__match = (void *)(e) + __i;
就得到了match的地址。
再通過
__ret = fn(__match , ## args);
輸出之。
fn函數是在print_firewall中,傳遞過來的是函數print_match。
static int
print_match(const struct ipt_entry_match *m,
const struct ipt_ip *ip,
int numeric)
{
/*根據match名稱進行查找,返回一個iptables_match結構,然後調用其中封裝的print函數輸出該match的信息*/
struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD);
if (match) {
if (match->print)
match->print(ip, m, numeric);
else
printf("%s ", match->name);
} else {
if (m->u.user.name[0])
printf("UNKNOWN match `%s'' ", m->u.user.name);
}
/* Don''t stop iterating. */
return 0;
}
這裡涉及到兩個重要的結構:
struct ipt_entry_match:在內核中用於存儲擴展match信息
struct ipt_entry_match
{
union {
struct {
u_int16_t match_size;
/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t match_size;
/* Used inside the kernel */
struct ipt_match *match;
} kernel;
/* Total length */
u_int16_t match_size;
} u;
unsigned char data[0];
};
struct iptables_match:用於用戶級的match存儲:
/* Include file for additions: new matches and targets. */
struct iptables_match
{
/* Match鏈,初始為NULL */
struct iptables_match *next;
/* Match名,和核心模塊加載類似,作為動態鏈接庫存在的Iptables Extension的命名規則為libipt_''name''.so */
ipt_chainlabel name;
/*版本信息,一般設為NETFILTER_VERSION */
const char *version;
/* Match數據的大小,必須用IPT_ALIGN()宏指定對界*/
size_t size;
/*由於內核可能修改某些域,因此size可能與確切的用戶數據不同,這時就應該把不會被改變的數據放在數據區的前面部分,而這裡就應該填寫被改變的數據區大小;一般來說,這個值和size相同*/
size_t userspacesize;
/*當iptables要求顯示當前match的信息時(比如iptables-m ip_ext -h),就會調用這個函數,輸出在iptables程序的通用信息之後. */
void (*help)(void);
/*初始化,在parse之前調用. */
void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);
/*掃描並接收本match的命令行參數,正確接收時返回非0,flags用於保存狀態信息*/
int (*parse)(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
unsigned int *nfcache,
struct ipt_entry_match **match);
/* 前面提到過這個函數,當命令行參數全部處理完畢以後調用,如果不正確,應該
退出(exit_error())*/
void (*final_check)(unsigned int flags);
/*當查詢當前表中的規則時,顯示使用了當前match的規則*/
void (*print)(const struct ipt_ip *ip,
const struct ipt_entry_match *match, int numeric);
/*按照parse允許的格式將本match的命令行參數輸出到標準輸出,用於iptables-save命令. */
void (*save)(const struct ipt_ip *ip,
const struct ipt_entry_match *match);
/* NULL結尾的參數列表,struct opt​​ion與getopt(3)使用的結構相同*/
const struct opt​​ion *extra_opts;
/* Ignore these men behind the curtain: */
unsigned int opt​​ion_offset;
struct ipt_entry_match *m;
unsigned int mflags;
unsigned int used;
#ifdef NO_SHARED_LIBS
unsigned int loaded; /* simulate loading so options are merged properly */
#endif
};
理解了這兩個結構後,再來看find_match函數:
然match是以可擴展的形式表現出來,那麼,當然就需要find_match這樣的函數將它們一一找出來了。
前面說過,在輸出規則的函數中:
IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
用來遍歷每一個match,找到了後,就調用print_match來輸出。print_match是調用find_match來查找的:
struct iptables_match *
find_match(const char *name, enum ipt_tryload tryload)
{
struct iptables_match *ptr;
for (ptr = iptables_matches; ptr; ptr = ptr->next) {
if (strcmp(name, ptr->name) == 0)
break;
}
#ifndef NO_SHARED_LIBS
if (!ptr && tryload != DONT_LOAD) {
char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")
+ strlen(name)];
sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);
if (dlopen(path, RTLD_NOW)) {
/* Found library. If it didn''t register itself,
maybe they specified target as match. */
ptr = find_match(name, DONT_LOAD);
if (!ptr)
exit_error(PARAMETER_PROBLEM,
"Couldn''t load match `%s''\n",
name);
} else if (tryload == LOAD_MUST_SUCCEED)
exit_error(PARAMETER_PROBLEM,
"Couldn''t load match `%s'':%s\n",
name, dlerror());
}
#else
if (ptr && !ptr->loaded) {
if (tryload != DONT_LOAD)
ptr->loaded = 1;
else
ptr = NULL;
}
if(!ptr && (tryload == LOAD_MUST_SUCCEED)) {
exit_error(PARAMETER_PROBLEM,
"Couldn''t find match `%s''\n", name);
}
#endif
if (ptr)
ptr->used = 1;
return ptr;
}
分析這個函數,不從開頭來看,先看這一段:
if (!ptr && tryload != DONT_LOAD) {
char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")
+ strlen(name)];
sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);
if (dlopen(path, RTLD_NOW)) {
/* Found library. If it didn''t register itself,
maybe they specified target as match. */
ptr = find_match(name, DONT_LOAD);
if (!ptr)
exit_error(PARAMETER_PROBLEM,
"Couldn''t load match `%s''\n",
name);
} else if (tryload == LOAD_MUST_SUCCEED)
exit_error(PARAMETER_PROBLEM,
"Couldn''t load match `%s'':%s\n",
name, dlerror());
}
函數根據傳遞過來的match名稱,從指定位置,加載對應的共享庫,呵呵,這些共享庫的源碼,全部在Extensions目錄下邊:
如果加載它們,那麼其_init函數就會被調用。這個初始化函數用來向iptables_match全局結構註冊當前match的相關處理函數。(這樣,我們可以寫我們自己的用戶空間的擴展match處理工具了)。註冊好後,函數再來調用自己:
ptr = find_match(name, DONT_LOAD);
遞歸回來後,呵呵,就是開頭那一段了,我們需要從已經註冊好的全局結構中查找與當前match名稱相同的iptables_match成員,因為該成員中封裝了print函數,這樣就可以順利地輸出來了:
比如,加載了libptc_tcp.so,它用來處理tcp的擴展,我們來看Extensions/libiptc_tcp.c:
static
struct iptables_match tcp
= { NULL,
"tcp",
IPTABLES_VERSION,
IPT_ALIGN(sizeof(struct ipt_tcp)),
IPT_ALIGN(sizeof(struct ipt_tcp)),
&help,
&init,
&parse,
&final_check,
&print,
&save,
opts };
void
_init(void)
{
register_match(&tcp);
}
構建了一個
iptables_match結構,其間有其對應的所有用戶空間工具函數,如分析命令行、輸出、保存……
然後,就調用register_match函數將其插入至全局結構iptables_match當中:
void
register_match(struct iptables_match *me)
{
struct iptables_match **i;
if (strcmp(me->version, program_version) != 0) {
fprintf(stderr, "%s: match `%s'' v%s (I''mv%s).\n",
program_name, me->name, me->version, program_version);
exit(1);
}
if (find_match(me->name, DONT_LOAD)) {
fprintf(stderr, "%s: match `%s'' already registered.\n",
program_name, me->name);
exit(1);
}
if (me->size != IPT_ALIGN(me->size)) {
fprintf(stderr, "%s: match `%s'' has invalid size %u.\n",
program_name, me->name, me->size);
exit(1);
}
/* Append to list. */
for (i = &iptables_matches; *i; i = &(*i)->next);
me->next = NULL;
*i = me;
me->m = NULL;
me->mflags = 0;
}
函數就是一個建立鍊錶的過程。不進一步分析了。

iptables技術分享



這里以一條iptabls的命令來分析Iptables的代碼,該條規則盡量設計比較多的iptables的命令行參數。
假設要下的規則為:
iptabls –A INPUT –i eth0 –p tcp --syn –s 10.0.0.0/8 –d 10.1.28.184 –j ACCEPT 
該規則是將源地址段在10.0.0.0/8範圍之內的主機發送的SYN包,接受的網口為eth0,且目的地址為10.1.28.184的,執行ACCEPT
以下是執行該條命令時,iptables的流程分析。這裡比較不注重分析代碼,而注重執行的流程及結果。
一、命令行的入口
int main(int argc, char *argv[]) (iptables-standalone.c) 
整個iptables命令行的入口。該函數設置了默認的table=”filter”,即當命令行中沒有-t選項是,就使用默認的filter表。同時還初始化了該應用程序的名字,版本號等。
#ifdef NO_SHARED_LIBS 
init_extensions(); 
#endif 
以上代碼為沒有定義共享庫的話,要執行init_extensions()。這裡我們假設不使用共享庫,所以調用該函數。該函數實在執行make的時候extensions/自動生成的initext.c中的函數。在該函數里調用了所有擴展模塊的init函數。
註冊所有的match,以及標準和擴展的target。
所有的match和target都加入到iptables.c中對應的全局鍊錶之中。以後find_match和find_target是就是搜索的這兩個鍊錶。
/* Keeping track of external matches and targets: linked lists. */ 
struct iptables_match *iptables_matches = NULL; 
struct iptables_target *iptables_targets = NULL; 
這樣在不使用共享庫的情況下,每次下命令之前都要初始化一下全局的鍊錶,當然已經存在的話,就不會再次register的。

二、命令行的核心處理
do_command()是處理Iptables命令的核心部分。
int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) 
(iptables.c)
1.函數首先對一些結構、變量進行初始化。基本上涵蓋了一條規則可能出現的大部分基本參數。其中,重要的結構體有struct ipt_entry fw, *e,這兩個應該是存儲一條防火牆規則的;struct iptables_match *m,struct iptables_target *target,*t; 
分別用於存儲match和target. 
並將全局的match和target鍊錶的對應flags和used為初始化為0.
2.命令行解析。初始化完畢後,進入while循環,分析用戶輸入的命令,設置相關的標誌變量,然後根據相應標誌,調用對應的處理函數。這裡是我們要進行詳細分析的地方。
我們要分析的命令為:
iptabls –A INPUT –i eth0 –p tcp --syn –s 10.0.0.0/8 –d 10.1.28.184 –j ACCEPT 
以下開始命令行解析:
(1)處理–A選項
調用add_command函數,主要是對command變量進行邏輯處理。該函數的輸入參數
newcmd = CMD_APPEND=0x0010,othercmds= CMD_NONE=0x0000,invert = 0;
輸出參數command(初始值為0),
執行完該函數之後,
command = CMD_APPEND=0x0010,
chain = “INPUT” 
然後程序break跳出switch語句,並將invert = FALSE,進行while的下一個循環,及處理下一個選項。
(2)處理–i選項
調用check_inverse函數。該函數主要是檢查-i對應的參數中是否使用了取反標誌”!”,因此這裡我們使用的是”eth0”,因此該函數直接返回FALSE。invert=0。
調用set_option函數是指對應的選項。該函數的輸入參數option= OPT_VIANAMEOUT(0x0080), invert=0,輸出參數options(初始化為0),fw.ip.invflags(初始化為0)。
經過該函數處理之後,
options = OPT_VIANAMEIN=0x0080; 
fw.ip.invflags=0. 
調用parse_interface函數進行網絡接口的解析。該函數的參數輸入參數argv[optind-1]=”eth0”,輸出參數fw.ip.iniface[IFNAMSIZ]={“”}, fw.ip.iniface_mask[IFNAMSIZ]={“”},其中IFNAMSIZ= 15。
處理之後,
fw.ip.iniface[IFNAMSIZ]={“eth0”}; 
fw.ip.iniface_mask[IFNAMSIZ]={0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 
fw.nfcache = NFC_IP_IF_IN = 0x0004;
(3)處理–p選項
調用check_inverse函數,檢查這裡設置的協議參數是否使用了取反標誌”!”。我們這裡使用的是tcp,因此該函數直接返回FALSE。invert仍舊為0。
調用set_option函數是指對應的選項。可以參照(2)中該函數的處理方法。options的初始值為0x0080,最終處理完畢之後
options |= OPT_PROTOCOL(0x0008) = 0x0088; 
fw.ip.invflags=0 
然後協議字串tcp全部轉化為小寫,protocol = “tcp”。調用parse_protocol函數將該協議字符串轉換為TCP對應的協議號6, 
fw.ip.proto = 6; 
fw.nfcache |= NFC_IP_PROTO = 0x0004 | 0x0020 = 0x0024;
(4)處理--syn選項
由於該選項並不在全局變量opts裡面,所以程序會進入switch語句的default分支執行。
進行該分支處理時一些變量的初始值:
target==NULL, m==NULL,
全局鍊錶iptables_matches的used選項都為0 
protocol = “tcp” ,options = 0x0088, proto_used = 0 
根據以上參數的值,這裡要執行的動作為加載該協議(m->init(),即libipt_tcp.c中init()),並且將協議的一些選項擴展到全局變量opts裡面。具體代碼執行是進入該分支的if(m==NULL& amp;&…)裡面,具體執行的結果如下:
m指向全局match鍊錶中tcp的match。(libipt_tcp.c) 
proto_used = 1; 
size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; 
m->m = fw_calloc(1, size); (m->m為內核總的match部分) 
m ->m->u.match_size = size; 
m->m->u.user.name = “tcp”; 
m->used = 1; 
調用m->init(m->m, &fw.nfcache),執行init()(libipt_tcp.c)。僅是將match中的tcp的sport和dport的最大值置為0xFFFF. struct ipt_entry_match中的data主要用於表示真正數據部分的開始,是通過fw_calloc為整個結構體以及data申請的內存,以後對數據的操作都可以通過data來索引。
init(struct ipt_entry_match *m, unsigned int *nfcache) 

struct ipt_tcp *tcpinfo = (struct ipt_tcp *)m->data; 
tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF; 

merge_options將tcp的一些選項擴展到全局變量opts裡面。這樣就可以解析--syn選項了。opts包含了tcp協議的選項參數。
並且全局變量global_option_offset += OPTION_OFFSET = 256; 
m->option_offset= global_option_offset =256; 
這裡有點需要注意,就是新加到opts全局結構體中的部分,即tcp對應的option結構中所有的val成員都被賦值為:
merge[num_old + i].val += *option_offset; (見merge_options函數) 
這裡主要是為了區別和原先已有option中的val成員。而且在以後添加更多match模塊的時候,都要做這樣的動作。因此在命令行解析的時候,通過getopt_long得到的這些match中的命令行參數值的時候,需要先減去對應的m->option_offset,然後才能正確的parse. 
然後兩行代碼: optind--; 
continue ; 
是讓程序進入下一個while循環,並再次處理--syn參數。因為以上的處理只是將tcp協議的match進行了初始化工作,並沒有處理該參數。
因此,程序再次進入switch的default分支,不過這次是在if(!target&&…),由於對應tcp的match結構體的used被置1,即m->used =1;因此程序要進行m-> parse進行libipt_tcp中的parse()函數對命令行參數進行處理。該函數的第一個參數的使用方法上面已經解釋過了。以下就是parse()函數對--syn的處理結果:
((struct ipt_tcp *)m->data)->flg_mask = 0x16(SYN,ACK,RST); 
((struct ipt_tcp *)m->data)- >flg_cmp = 0x02(SYN) 
m->flags |= TCP_FLAGS = 0x04; 
fw.nfcache |= NF_IP_TCP_FLAGS(0x0100) = 0x0024 | 0x0100=0x0124 
至此,iptables對—syn選項的解析已經完成。
(5)處理-s選項
同樣還是check_inverse,set_option的處理,只有options變量改變;
options |= OPT_SOURCE(0x0002) = 0x0088 | 0x0002 = 0x008a; 
shostnetworkmask= “10.0.0.0/24”; 
fw.nfcache |= NFC_IP_SRC(0x0001)= 0x0124 | 0x0001 = 0x0125;
(6)處理-d選項
同樣還是check_inverse,set_option的處理,只有options變量改變;
options |= OPT_DESTINATION (0x0004) = 0x008a | 0x0004 = 0x008e; 
dhostnetworkmask= “10.1.28.184”; 
fw.nfcache |= NFC_IP_DST ( 0x0002)= 0x0125 | 0x0002 = 0x0127;
(7)處理-j選項
首先還是set_option函數的處理:
options |= OPT_JUMP(0x0010) = 0x008e | 0x0010 = 0x009e; 
然後jumpto =parse_target(“ACCEPT”),主要是檢查一下該字符串是否合法;
接著target =find_target(jumpto, TRY_LOAD);該函數返回target為”standard”(包含了ACCEPT, DROP, QUEUE,RETURN等目標)的結構體指針。
並且: target->loaded = 1; target->used = 1 
jumpto = “ACCEPT” 
size = IPT_ALIGN(sizeof(struct ipt_entry_target))+ target->size; 
target->t = fw_calloc(1, size); //分配內存給struct ipt_entry_target 
target->t->u.target_size = size; 
target->t->u.user.name = “ACCEPT” 
這裡的target->init指向了libipt_standard.c中的init(),該函數並未執行任何動作。
merge_options又是將該libipt_standard.c的opts加入到全局的opts中。由於ACCEPT目標是標準的,這裡實際上並未往全局的opts中添加任何內容。
僅修改瞭如下變量
global_option_offset += OPTION_OFFSET = 256+256=512; 
target->option_offset= global_option_offset =512; 
至此,命令行已經解析完畢,下面要接著對解析出來的各個參數進行進一步處理。
3. 相關參數的檢查
/*執行libipt_tcp.c中的final_check*/ 
m->final_check(m->mflags); 
/*執行libipt_standard.c中的final_check*/ 
target->final_check(target->tflags); 
隨後是對optind,invert ,command的檢查,接著的if (command &…)對我們的參數沒有產生任何影響。
分別對shostnetworkmask,dhostnetworkmask調用parse_hostnetworkmask函數,執行的結果如下:
saddrs[0]->s_addr = 0x0a000000 (10.0.0.0); 
fw.ip.smsk.s_addr = 0xff000000 (255.0.0.0); 
nsaddrs = 1; 
daddrs[ 0]->s_addr = 0x0a011cb8 (10.1.28.184); 
fw.ip.dmsk.s_addr = 0xffffffff (255.255.255.255); 
ndaddrs = 1; 
最後調用generic_opt_check(command, options)對command和options進行檢查。主要是檢查iptables中的command和options是否搭配。所有的搭配情況保存在全局的數組
static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT]. 
下面則要從內核表裡取出對應表的全部信息。
4.取出內核中相應表的全部信息
因為在main函數中iptc_handle_t handle = NULL;所以這裡要執行的代碼為:
*handle = iptc_init(*table) 
這裡返回一個iptc_handle_t *handle的結構體指針,該指針指向從內核中去中的”filter”(默認)表對應的所有信息。先取出info結構,獲取表的一些大概信息,然後再獲取整個表的規則。
隨後的兩個if (!*handle)都是判斷*handle是否為NULL,如果是的話則說明有錯誤,獲取不到對應表的相關信息。我們這裡應該指向了獲取的”filter”的信息的指針。因此這兩個if判斷都為FALSE。
5.檢查chain與相關options的搭配,target的合法等問題
整個檢查都在if (command == CMD_APPEND…){…}裡面進行。
首先檢查chain和options的匹配情況,這裡chain=”INPUT”,對於本例中設置的規則,應該都沒有問題。
然後是if (target && iptc_is_chain(jumpto, *handle))。其中target為”standard”(包含了ACCEPT, DROP, QUEUE,RETURN等目標)的結構體指針。
(libiptc.c)。由於我們這裡的jumpto為目標ACCEPT,而並非chain,所以該if為FALSE. 
因此,程序真正執行的部分是
e = generate_entry(&fw, iptables_matches, target->t); 
用來生成一個struct ipt_entry e。e中包括了struct ipt_entry,並利用最後一個元素unsigned char elems[0]申請了一塊內存,該內存里首先是若干個match的結構體,然後是一個target結構體,因此該塊內存的大小為n *match + target.
6.具體命令的執行
這部分也是do_command函數的最後。用一個swicth(command)來判斷具體執行什麼動作。我們這裡command=CMD_APPEND,因此調用append_entry函數來將該條iptables規則加入進去。大致介紹一下append_entry函數的功能:
源碼中具體對應的函數名為TC_APPEND_ENTRY()。該函數首先調用find_label找到整個規則中指定chain的struct chain_cache結構,然後做一下target的映射,我們這裡是標準的target。真正執行添加規則的是insert_rules函數。該函數找到插入規則的entry點,並將該規則插入。
調整後的所有的規則都保存在結構體指針handle之中。
三、iptables規則的提交
do_command函數執行完畢,接著調用iptc_commit(&handle)將filter調整後的所有規則提交給內核。
這裡只分析了ipt_init(),append_entry和iptc_commit的功能,沒有具體分析其源代碼。因為這三個函數的源代碼都比較複雜。這裡作為Iptables的流程分析,不具體討論。
整個iptables的工作流程解析完畢。這樣,對iptables的整個命令行的核心代碼從已經有了相當的掌握

link from:http://www.cnblogs.com/500ju/archive/2011/12/28/2305209.html

How to repair and clone disk with ddrescue

  ddrescue  is a tool that can be used to repair and clone disks on a  Linux system . This includes hard drives, partitions, DVD discs, flas...