(十二)洞悉linux下的Netfilteramp;iptables:iptables命令行工具源碼解析【下】

iptables用戶空間和內核空間的交互

iptables目前已經支持IPv4IPv6兩個版本了,因此它在實現上也需要同時兼容這兩個版本。iptables-1.4.0在這方面做了很好的設計,主要是由libiptc庫來實現。libiptciptables control library的簡稱,是Netfilter的一個編程接口,通常被用來顯示、操作(查詢、修改、添加和刪除)netfilter的規則和策略等。使用libipq庫和ip_queue模塊,幾乎可以實現任何在內核中所實現的功能。

? ? ? ? libiptc庫位于iptables源碼包里的libiptc目錄下,共六個文件還是比較容易理解。我們都知道,運行在用戶上下文環境中的代碼是可以阻塞的,這樣,便可以使用消息隊列和?UNIX?域套接字來實現內核態與用戶態的通信。但這些方法的數據傳輸效率較低,Linux?內核提供?copy_from_user()/copy_to_user()?函數來實現內核態與用戶態數據的拷貝,但這兩個函數會引發阻塞,所以不能用在硬、軟中斷中。一般將這兩個特殊拷貝函數用在類似于系統調用一類的函數中,此類函數在使用中往往"穿梭"于內核態與用戶態。此類方法的工作原理為:

?23069658_133802057141y1.jpg

? ? ? ??其中相關的系統調用是需要用戶自行編寫并載入內核。一般情況都是,內核模塊注冊一組設置套接字選項的函數使得用戶空間進程可以調用此組函數對內核態數據進行讀寫。我們的libiptc庫正是基于這種方式實現了用戶空間和內核空間數據的交換。

????為了后面便于理解,這里我們簡單了解一下在socket編程中經常要接觸的兩個函數:

int setsockopt(int sockfd, int proto, int cmd, void *data, int datalen)

int getsockopt(int sockfd, int proto, int cmd, void *data, int datalen)

這個兩個函數用來控制相關socket文件描述符的一些選項值,如設置(獲取)接受或發送緩沖區的大小、設置(獲取)接受或發送超時值、允許(禁止)重用本地端口和地址等等。

參數說明:

sockfd:為socket的文件描述符;

protosock協議,IP RAW的就用SOL_SOCKET/SOL_IP等,TCP/UDP socket的可用SOL_SOCKET/SOL_IP/SOL_TCP/SOL_UDP等,即高層的socket是都可以使用低層socket的命令字?的;

cmd:操作命令字,由自己定義,一般用于擴充;

data:數據緩沖區起始位置指針,set操作時是將緩沖區數據寫入內核,get的時候是將內核中的數據讀入該緩沖區;

datalen:參數data中的數據長度。

?

我們可以通過擴充新的命令字(即前面的cmd字段)來實現特殊應用程序的內核與用戶空間的數據交換,內核實現新的sockopt命令字有兩類:一類是添加完整的新的協議后引入;一類是在原有協議命令集的基礎上增加新的命令字。以netfilter為例,它就是在原有的基礎上擴展命令字,實現了內核與用戶空間的數據交換。Netfilter新定義的命令字如下:

setsockopt新增命令字:

#define IPT_SO_SET_REPLACE //設置規則

#define IPT_SO_SET_ADD_COUNTERS???//加入計數器

getsockopt新增命令字;

#define IPT_SO_GET_INFO???????????????//獲取ipt_info

#define IPT_SO_GET_ENTRIES?????????//獲取規則

#define IPT_SO_GET_REVISION_MATCH?//獲取match

#define IPT_SO_GET_REVISION_TARGET??????//獲取target

???????一個標準的setsockopt()操作的調用流程如下:

?23069658_1338015272zLt0.jpg

ip_setsockopt調用時,如果發現是一個沒有定義的協議,并且判斷現在這個optname是否為netfilter所設置,如果是則調用netfilter所設置的特殊處理函數,于是加入netfiltersockopt特殊處理后,新的流程如下:

?23069658_1338015214jw2J.jpg

? ? ? ? netfitler對于會實例化一些struct nf_sockopt_ops{}對象,然后通過nf_register_sockopt()將其注冊到全局鏈表nf_sockopts里。

struct?nf_sockopt_ops

{

?????????struct?list_head?list;

?????????int pf;

?

?????????/* Non-inclusive ranges: use 0/0/NULL to never get called. */

?????????int set_optmin;

?????????int set_optmax;

?????????int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);

?????????int (*compat_set)(struct sock *sk, int optval,void __user *user, unsigned int len);

?

?????????int get_optmin;

?????????int get_optmax;

?????????int (*get)(struct sock *sk, int optval, void __user *user, int *len);

?????????int (*compat_get)(struct sock *sk, int optval,void __user *user, int *len);

?

?????????/* Number of users inside set() or get(). */

?????????unsigned int use;

?????????struct task_struct *cleanup_task;

};

?

? ? ? ? ?繼續回到libiptc中。libiptc庫中的所有函數均以“iptc_”開頭,主要有下面一些接口(節選自libiptc.h)

typedef?struct iptc_handle?*iptc_handle_t;

?

/* Does this chain exist? */

int?iptc_is_chain(const char *chain, const?iptc_handle_t?handle);

?

/* Take a snapshot of the rules.??Returns NULL on error. */

iptc_handle_t?iptc_init(const char *tablename);

?

/* Cleanup after iptc_init(). */

void?iptc_free(iptc_handle_t?*h);

?

/* Iterator functions to run through the chains.??Returns NULL at end. */

const char *iptc_first_chain(iptc_handle_t?*handle);

const char *iptc_next_chain(iptc_handle_t?*handle);

?

/* Get first rule in the given chain: NULL for empty chain. */

const struct ipt_entry *iptc_first_rule(const char *chain,iptc_handle_t?*handle);

/* Returns NULL when rules run out. */

const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,iptc_handle_t?*handle);

?

/* Returns a pointer to the target name of this entry. */

const char *iptc_get_target(const struct ipt_entry *e,iptc_handle_t?*handle);

?

/* Is this a built-in chain? */

int?iptc_builtin(const char *chain, const?iptc_handle_t?handle);

?

int?iptc_append_entry(const ipt_chainlabel chain,

?????????????????????????const struct ipt_entry *e,

?????????????????????????iptc_handle_t?*handle);

?

/* Zeroes the counters in a chain. */

int?iptc_zero_entries(const ipt_chainlabel chain,iptc_handle_t?*handle);

?

/* Creates a new chain. */

int?iptc_create_chain(const ipt_chainlabel chain,iptc_handle_t?*handle);

?

/* Makes the actual changes. */

int?iptc_commit(iptc_handle_t?*handle);

?

? ? ? ? 上面這些接口都是為IPv4定義了,同樣的IPv6的接口均定義在libip6tc.h頭文件中,都以“ip6tc_”開頭(如此說來,IPv4的頭文件應該叫libip4tc.h才比較合適)。然后在libip4tc.clibip6tc.c文件中分別通過宏定義的形式將IPv4IPv6對外的接口均統一成“TC_”開頭的宏,并在libiptc.c中實現這些宏即可。如下圖所示:

?23069658_1338020576nHad.jpg

? ? ? ? 這里我們看到iptables-v4iptables-v6都和諧地統一到了libiptc.c中,后面我們分析的時候只要分析這些相關的宏定義的實現即可。

? ? ? 在繼續往下分析之前我們先看一下STRUCT_TC_HANDLE這個比較拉風的結構體,它用于存儲我們和內核所需要交換的數據。說的通俗一些,就是從內核中取出的表的信息會存儲到該結構體類型的變量中;當我們向內核提交iptables變更時,也需要一個該結構體類型的變量用于存儲我們所要提交的數據。(定義在ip_tables.h頭文件中)

適用于當getsockopt的參數為IPT_SO_GET_INFO,用于從內核讀取表信息

struct ipt_getinfo???????????????#define?STRUCT_GETINFO?struct ipt_getinfo

{

?????????/* Which table: caller fills this in. */????#從內核取出的表信息會存儲在該結構體中

?????????char name[IPT_TABLE_MAXNAMELEN];

?????????/* Kernel fills these in. */

?????????unsigned int?valid_hooks; /* Which hook entry points are valid: bitmask */

?????????unsigned int?hook_entry[NF_IP_NUMHOOKS]; // Hook entry points: one per netfilter hook.

?????????unsigned int?underflow[NF_IP_NUMHOOKS]; /* Underflow points. */

?????????unsigned int?num_entries; /* Number of entries */

?????????unsigned int?size; /* Size of entries. */

};

還有一個成員entries用保存表中的所有規則信息,每條規則都是一個ipt_entry的實例:

/* The argument to IPT_SO_GET_ENTRIES. */

struct ipt_get_entries

{

?????????/* Which table: user fills this in. */

?????????char?name[IPT_TABLE_MAXNAMELEN];

?????????unsigned int?size; /* User fills this in: total entry size. */

?

?????????struct?ipt_entry?entrytable[0]; /*內核里表示規則的結構,參見博文三. */

};

????????

()、從內核獲取數據:iptc_init()

都說磨刀不誤砍柴工,接下來我們繼續上一篇中do_command()函數里剩下的部分。*handle?= iptc_init(*table);?這里即根據表名table去從內核中獲取該表的自身信息和表中的所有規則。關于表自身的一些信息存儲在handle->info成員里;表中所有規則的信息保存在handle->entries成員里。

? ? ? 如果handle獲取失敗,則嘗試加載完內核中相應的ko模塊后再次執行iptc_init()函數。

? ? ? 然后,針對“ADRI”操作需要做一些合法性檢查,諸如-o選項不能用在PREROUTINGINPUT鏈中、-i選項不能用在POSTROUTINGOUTPUT鏈中。

if (target &&?iptc_is_chain(jumpto, *handle)) {

???????????????????fprintf(stderr,"Warning: using chain %s, not extension\n",jumpto);

?????????????if (target->t)

????????????????????????????free(target->t);

???????????????????????????

???????????????????printf("Target is a chain,but we have gotten a target,then free it!\n");

?

???????????????????target = NULL;

}

如果-j XXX?后面的XXX是一條用戶自定義規則鏈,但是之前卻解析出了標準target,那么需要將target的空間釋放掉。很明顯,目前我們的-j ACCEPT不會執行到這里。

if (!target??#如果沒有指定target同樣,我們的規則也不會執行到這里

&& (strlen(jumpto) == 0|| iptc_is_chain(jumpto, *handle)) #或者target是一條鏈或為空

)

{

?????????size_t size;

????… …????????

}

? ? ? ? 因為我們的targetACCEPT,已經被完全正確解析,即target=NULL。后面我們會執行else條件分子如下的代碼:

e = generate_entry(&fw, matches, target->t);

用于生成一條iptables的規則,它首先會為e去申請一塊大小n*match+target的空間,其中n為用戶輸入的命令行中的match個數,target為最后的動作。這里很明顯,我們的命令只有一個tcpmatchtarget是標準target,即ACCEPT。將已經解析的fw賦給e,并對結構體e中其他的成員進行初始化,然后將相應的matchtarget的數據拷貝到e中對應的成員中。

size = sizeof(struct ipt_entry);

for (matchp = matches; matchp; matchp = matchp->next)

?????????size += matchp->match->m->u.match_size;

?

e = fw_malloc(size + target->u.target_size);

*e = *fw;

e->target_offset = size;

e->next_offset = size + target->u.target_size;

?

size = 0;

for (matchp = matches; matchp; matchp = matchp->next) {

?????????memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size);

?????????size += matchp->match->m->u.match_size;

}

memcpy(e->elems + size, target, target->u.target_size);

? ? ? ? 最后所生成的規則e,其內存結構如下圖所示:

?23069658_1338015414zByB.jpg

? ? ? ? 這里再聯系我們對內核中netfilter的分析就很容易理解了,一旦我們獲取一條規則ipt_entry的首地址,那么我們能通過target_offset很快獲得這條規則的target地址,同時也可以通過next_offset獲得下一條ipt_entry規則的起始地址,很方便我們到時候做數據包匹配的操作。

?緊接著就是對解析出來的command命令進行具體操作,這里我們是-A命令,因此最后command命令就是CMD_APPEND,這里則執行append_entry()函數。

ret =?append_entry(chain,??#鏈名,這里為INPUT

e,??????#將用戶的命令解析出來的最終的規則對象

nsaddrs,??#-s?后面源地址的個數

saddrs,???#用于保存源地址的數組

ndaddrs,??#-d?后面的目的地址的個數

daddrs,???#用于保存目的地址的數組

options&OPT_VERBOSE, #iptables命令是否有-v參數

handle???#從內核中取出來的規則表信息

);

?在append_entry內部調用了iptc_append_entry(chain, fw, handle),其實就是由宏即TC_APPEND_ENTRY所表示的那個函數。該函數內部有兩個值得注意的結構體類型struct chain_head{}struct rule_head{},分別用于保存我們所要操作的鏈以及鏈中的規則:

struct chain_head

{

?????????struct list_head list;

?????????char name[TABLE_MAXNAMELEN];

?????????unsigned int hooknum;?????????????/* hook number+1 if builtin */

?????????unsigned int references;?/*?有多少-j?指定了我們的名字?*/

?????????int verdict;??????????????????????????/* verdict if builtin */

?????????STRUCT_COUNTERS counters;????????/* per-chain counters */

?????????struct counter_map counter_map;

?????????unsigned int num_rules;???????????/*?本鏈中的規則數*/

?????????struct list_head rules;?????????????/*?本鏈中所有規則的入口點?*/

?

?????????unsigned int index;???????????/* index (needed for jump resolval) */

?????????unsigned int head_offset;????????/* offset in rule blob */

?????????unsigned int foot_index;?/* index (needed for counter_map) */

?????????unsigned int foot_offset;??????????/* offset in rule blob */

};

?

struct rule_head

{

?????????struct list_head list;

?????????struct chain_head *chain;

?????????struct counter_map counter_map;

?????????unsigned int index;???????????/* index (needed for counter_map) */

?????????unsigned int offset;??????????/* offset in rule blob */

?

?????????enum iptcc_rule_type type;

?????????struct chain_head?*jump;??????/* jump target, if IPTCC_R_JUMP */

?

?????????unsigned int size;??????????????/* size of entry data */

?????????STRUCT_ENTRY entry[0];???#真正的規則入口點?sizeof計算時不會包含這個字段

};

TC_APPEND_ENTRY的函數實現:

int

TC_APPEND_ENTRY(const?IPT_CHAINLABEL?chain,

???????????????????const?STRUCT_ENTRY?*e,

???????????????????TC_HANDLE_T?*handle)????#注意:這里的handle是個二級指針

{

?????????struct chain_head?*c;

?????????struct rule_head?*r;

?

?????????iptc_fn = TC_APPEND_ENTRY;

?????????if (!(c =?iptcc_find_label(chain, *handle))) {??????

#根據鏈名查找真正的鏈地址賦給c,此時c就指向了INPUT鏈的內存,

#包括INPUT中的所有規則和它的policy

???????????????????DEBUGP("unable to find chain `%s'\n", chain);

???????????????????errno = ENOENT;

???????????????????return 0;

?????????}

?

?????????if (!(r =?iptcc_alloc_rule(c,?e->next_offset))) {

#ipt_entrynext_offset即指明了下一條規則的起始地址,同時這個值也說明了本條規則所占了存儲空間的大小。這里所申請的空間大小=sizeof(rule_head)+當前規則所占的空間大小。

???????????????????DEBUGP("unable to allocate rule for chain `%s'\n", chain);

???????????????????errno = ENOMEM;

???????????????????return 0;

?????????}

?

?????????memcpy(r->entry, e, e->next_offset);???????#把規則拷貝到柔性數組entry中去

?????????r->counter_map.maptype = COUNTER_MAP_SET;

?

?????????if (!iptcc_map_target(*handle, r)) {???#主要是設置規則rtarget,后面分析。

???????????????????DEBUGP("unable to map target of rule for chain `%s'\n", chain);

???????????????????free(r);

???????????????????return 0;

?????????}

?

?????????list_add_tail(&r->list, &c->rules); #將新規則r添加在鏈c的末尾

?????????c->num_rules++;??????????????#同時將鏈中的規則計數增加

?

?????????set_changed(*handle);????#因為INPUT鏈中的規則已經被改變,則handle->changed=1;

?????????return 1;

}

? ? ? ? ?接下來分析一下設置target時其函數內部流程:

static int

iptcc_map_target(const TC_HANDLE_T handle,

????????????struct rule_head *r)

{

?????????STRUCT_ENTRY?*e = r->entry;?????????????????#取規則的起始地址

?????????STRUCT_ENTRY_TARGET?*t = GET_TARGET(e);????#取規則的target

?

?????????/* Maybe it's empty (=> fall through) */

?????????if (strcmp(t->u.user.name, "") == 0) { #如果沒有指定target,則將規則類型設為全放行

???????????????????r->type = IPTCC_R_FALLTHROUGH;

???????????????????return 1;

?????????}

?

?????????/* Maybe it's a standard target name... */

#因為都是標準target,因此將target中用戶空間的user.name都置為空,設置verdict

#并將rule_head中的type字段為IPTCC_R_STANDARD

?????????else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0)

???????????????????return?iptcc_standard_map(r, -NF_ACCEPT - 1);

?????????else if (strcmp(t->u.user.name, LABEL_DROP) == 0)

???????????????????return?iptcc_standard_map(r, -NF_DROP - 1);

?????????else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0)

???????????????????return?iptcc_standard_map(r, -NF_QUEUE - 1);

?????????else if (strcmp(t->u.user.name, LABEL_RETURN) == 0)

???????????????????return?iptcc_standard_map(r, RETURN);

?????????else if (TC_BUILTIN(t->u.user.name, handle)) {

???????????????????/* Can't jump to builtins. */

???????????????????errno = EINVAL;

???????????????????return 0;

?????????} else {

???????????????????/*?如果跳轉的目標是一條用戶自定義鏈,則執行下列操作*/

???????????????????struct chain_head *c;

???????????????????DEBUGP("trying to find chain `%s': ", t->u.user.name);

???????????????????c =?iptcc_find_label(t->u.user.name, handle); #找到要跳轉的目的鏈的入口地址

???????????????????if (c) {

????????????????????????????DEBUGP_C("found!\n");

????????????????????????????r->type =?IPTCC_R_JUMP;??#rule_head結構的type字段置為跳轉

????????????????????????????r->jump = c;?????????????#跳轉的目標為t->u.user.name所指示的鏈

????????????????????????????c->references++;?????????#跳轉到的目的鏈因此而被引用了一次,則計數器++

????????????????????????????return 1;

???????????????????}

???????????????????DEBUGP_C("not found :(\n");

?????????}

?

?????????/*?如果不是用戶自定義鏈,它一定一個用戶自定義開發的target模塊,比如SNATLOG等。If not, kernel will reject... */

?????????/* memset to all 0 for your memcmp convenience: don't clear version */

?????????memset(t->u.user.name + strlen(t->u.user.name),

????????????????0,

????????????????FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name));

?????????r->type =?IPTCC_R_MODULE;??#比如SNATLOG等會執行到這里

?????????set_changed(handle);

?????????return 1;

}

?在append_entry()函數最后,將執行的執行結果返回給ret1表示成功;0表示失敗。然后在做一下善后清理工作,如果命令行中有-v則將內核中表的快照dump一份詳細信息出來顯示給用戶看:

if (verbose > 1)

?????dump_entries(*handle);

clear_rule_matches(&matches);?//釋放matches所占的存儲空間

?由struct ipt_entry e;所存儲的規則信息已經被提交給了handle對象對應的成員,因此將e所占的存儲空間也釋放:

if (e != NULL) {

??????????????free(e);

??????????????e = NULL;

}

?將全局變量opts復位,初始化時opts=original_opts。因為在解析--syntcp的解析參數被加進來了:

static struct option?original_opts[] = {

??????????????????{ "append", 1, NULL, 'A' },

??????????????????{ "delete", 1, NULL,??'D' },

??????????????????… …

}

至此,do_command()函數的執行就算全部完成了。

?

()、向內核提交變更:iptc_commit()

執行完do_command()解析完命令行參數后,用戶所作的變更僅被提交給了handle這個結構體變量,這個變量里的所有數據在執行iptc_commit()函數前都駐留在內存里。因此,在iptables-standalone.c里有如下的代碼語句:

ret =?do_command(argc, argv, &table, &handle);

if (ret)

?????????ret =?iptc_commit(&handle);

? 當do_command()執行成功后才會去執行iptc_commit()函數,將handle里的數據提交給Netfilter內核。

? ? ? ? iptc_commit()的實現函數為int TC_COMMIT(TC_HANDLE_T *handle),我們只分析IPv4的情形,因此專注于libiptc.c文件中該函數的實現。

? ? ? ? 在TC_COMMIT()函數中,又出現了我們在分析Netfilterfilter表時所見到的一些重要結構體STRUCT_REPLACE?*replSTRUCT_COUNTERS_INFO?*newcounters;還有前面出現的struct chain_head?*c;結構體。

new_number =?iptcc_compile_table_prep(*handle, &new_size);

iptcc_compile_table_prep()該函數主要做的工作包含幾個方面:

a.初始化handle里每個struct chain_head{}結構體成員中的head_offsetfoot_indexfoot_offset

b.對每個鏈(struct chain_head{})中的每條規則,再分別計算它們的offsetindex

c.計算handle所指示的表中所有規則所占的存儲空間的大小new_size,以及規則的總條數new_number

?接下來,為指針repl;申請存儲空間,所申請的大小為sizeof(struct ipt_replace)+new_size。因為struct ipt_replace{}結構的末尾有一個柔性數組struct ipt_entry entries[0];?它是不計入sizeof的計算結果的。因此,iptables的所有規則實際上是存儲在struct ipt_entry entries[0]柔性數組中的,這里所有規則所占大小已經得到:new_size

? 因為,每條規則entry都一個計數器,用來記錄該規則處理了多少數據包,注意結構體STRUCT_COUNTERS_INFO{}的末尾也有一個柔性數組struct xt_counters?counters[0];其中struct xt_counters{}才是真正的用于統計數據包的計數器。

然后開始初始化repl結構:

strcpy(repl->name, (*handle)->info.name);

repl->num_entries = new_number;

repl->size = new_size;

?

repl->num_counters = (*handle)->info.num_entries;

repl->valid_hooks = (*handle)->info.valid_hooks;

? ? ? ? 緊接著對repl結構體中剩下的成員進行初始化,hook_entry[]underflow[]等。對于用戶自定義鏈,其末尾的target.verdict=RETURN

setsockopt(sockfd, TC_IPPROTO,?SO_SET_REPLACE,?repl,sizeof(*repl) + repl->size);

會觸發內核去執行前面我們看到的do_ipt_set_ctl()函數,如下:

static struct nf_sockopt_ops ipt_sockopts = {

?????????.pf??????????????= PF_INET,

?????????.set_optmin?????= IPT_BASE_CTL,????

? .set_optmax????= IPT_SO_SET_MAX+1,

?????????.set???????????=?do_ipt_set_ctl,

?????????.get_optmin?????= IPT_BASE_CTL,

?????????.get_optmax????= IPT_SO_GET_MAX+1,

?????????.get???????????=?do_ipt_get_ctl,

};

? ? ? ? do_ipt_set_ctl()中其核心還是執行do_replace()函數:

static int do_replace(void __user *user, unsigned int len)

{

?????????int ret;

?????????struct ipt_replace tmp;

?????????struct xt_table_info *newinfo;

?????????void *loc_cpu_entry;

?

?????????if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)

???????????????????return -EFAULT;

?

?????????/* Hack: Causes ipchains to give correct error msg --RR */

?????????if (len != sizeof(tmp) + tmp.size)

???????????????????return -ENOPROTOOPT;

… …

}

? ? ? ?其中copy_from_user()負責將用戶空間的repl變量中的內容拷貝到內核中的tmp中去。然后設置規則計數器newcounters,通過setsockopt系統調用將newcounters設置到內核:

setsockopt(sockfd, TC_IPPROTO,?SO_SET_ADD_COUNTERS,?newcounters, counterlen);

?此時,在do_ipt_set_ctl()中執行的是do_add_counters()函數。至此,iptables用戶空間的所有代碼流程就算分析完了。命令:

iptables –A INPUT –i eth0 –p tcp --syn –s?10.0.0.0/8 –d 10.1.28.184 –j ACCEPT

即被設置到內核的Netfilter規則中去了。

未完,待續

?

轉載于:https://www.cnblogs.com/masterpanda/p/5700491.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254841.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254841.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254841.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux 下實現普通用戶只能寫入某個目錄

今天老婆問了我一個問題:如何在linux 下實現某個目錄普通用戶能夠寫入文件,但是不能刪除或修改(只能由root 刪除或修改)。開始的兩分鐘里,我初步判斷這是做不到的,因為linux 下能 寫入(w&#x…

CCD和CMOS攝像頭成像原理以及其他區別

CCD的第二層是分色濾色片,目前有兩種分色方式,一是RGB原色分色法,另一個則是CMYG補色分色法,這兩種方法各有利弊。不過以產量來看,原色和補色CCD的比例大約在2:1左右。原色CCD的優…

FFMPEG分析比較細的文章

http://blog.csdn.net/ym012/article/details/6538301

恢復Ext3下被刪除的文件(轉)

前言 下面是這個教程將教你如何在Ext3的文件系統中恢復被rm掉的文件。 刪除文件 假設我們有一個文件名叫 ‘test.txt’ $ls -il test.txt15 -rw-rw-r– 2 root root 20 Apr 17 12:08 test.txt 注意:: “-il” 選項表示顯示文件的i-node號(15)…

halcon trainf_ocr_class_svm 訓練OCR分類器

目錄trainf_ocr_class_svm(算子)描述參數trainf_ocr_class_svm(算子) trainf_ocr_class_svm - 訓練OCR分類器。 trainf_ocr_class_svm(:: OCRHandle,TrainingFile,Epsilon,TrainMo…

Javascript之全局變量和局部變量部分講解

以此文作為自己學習的一個總結。 關于全局變量和局部變量的一句簡單的定義:在函數外聲明的變量都為全局變量,在函數內聲明的為局部變量。 一、局部變量和全局變量重名會覆蓋全局變量 1 var a 1; 2 function test1() { 3 var a 2; 4 ale…

XML-RPC使用手冊

內容列表 Preface: About This Manual Introduction to XML-RPC for C/C What is XML-RPC? How Does XML-RPC For C/C Help? More Information On XML-RPC For C/CThe Xmlrpc-c Function Libraries C Libraries C LibrariesUtility Programs xmlrpc xmlrpc_dumpserverAlterna…

利用ffmpeg來進行視頻解碼的完整示例代碼(H.264)

Decode() { FILE * inpf; int nWrite; int i,p; int nalLen; unsigned char* Buf; int got_picture, consumed_bytes; unsigned char *DisplayBuf; DisplayBuf(unsigned char *)malloc(60000); char outfile[] "test.pgm"; //1.打開輸入文件 inpf fopen("test…

如何成為非標行業的大拿

1,選一個好的舞臺(工作環境),有個廣告詞叫:‘心有多大,舞臺就有多大’,我想變個說法叫‘舞臺越大,心就越大’。決定你表演效果的舞臺,你如果選擇…

TCP UDP HTTP 的關系和區別

TCP UDP HTTP 三者的關系: TCP/IP是個協議組,可分為四個層次:網絡接口層、網絡層、傳輸層和應用層。 在網絡層有IP協議、ICMP協議、ARP協議、RARP協議和BOOTP協議。 在傳輸層中有TCP協議與UDP協議。 在應用層有HTTP、FTP、TELNET、SMTP、DNS等協議。 TCP…

微信開放平臺全網發布時,檢測失敗 —— C#

主要就是三個:返回API文本消息,返回普通文本消息,發送事件消息 --會出現失敗的情況 (后續補充說明:出現檢測出錯,不一定是代碼出現了問題,也有可能是1.微信方面檢測時出現服務器請求失敗&…

halcon reduce_ocr_class_svm 縮減基于SVM的OCR分類器。

目錄reduce_ocr_class_svm(算子)描述參數reduce_ocr_class_svm(算子) reduce_ocr_class_svm - 縮減基于SVM的OCR分類器。 reduce_ocr_class_svm(:: OCRHandle,Method,MinRemainingSV&#xff…

Zabbix 釘釘報警

話不多說,咱們直接進入正題釘釘報警時基于zabbix,訪問釘釘應用接口去推送的報警消息,所以我們需要一個在釘釘創建一個報警應用1、 我做的釘釘報警是基于釘釘自定義應用進行推送的所以需要登錄釘釘管理后臺進行創建(zabbix自定義應…

七大因素阻礙非標自動化行業發展,那么應對的策略是什么呢?

 非標自動化機械相對于標準機械而言,是可以根據企業需要量身定做的設備,可以實現標準機械所不能實現的一些功能,實現標準件不可能達到的產量。例如在注塑行業中,眾多注塑企業根據自身實際情況通過制造業自…

單播、多播(組播)和廣播的區別

單播、多播和廣播單播”(Unicast)、“多播”(Multicast)和“廣播”(Broadcast)這三個術語都是用來描述網絡節點之間通訊方式的術語。那么這些術語究竟是什么意思?區別何在?1.單播&am…

數據庫資源博客---小麥苗BEST

http://blog.csdn.net/lihuarongaini/article/details/60584577 http://blog.csdn.net/lihuarongaini/article/details/68485838轉載于:https://www.cnblogs.com/zengkefu/p/6718754.html

halcon write_ocr_class_svm 將OCR分類器寫入文件

目錄write_ocr_class_svm(運算符)描述參數write_ocr_class_svm(運算符) write_ocr_class_svm - 將OCR分類器寫入文件。 write_ocr_class_svm(:: OCRHandle,FileName ? 描述 write_ocr_class_svm將OCR分…

jQuery插件備忘

jQuery BlockUI Plugin 遮罩插件 http://malsup.com/jquery/block/ artDialog是一個基于javascript編寫的對話框組件,它擁有精致的界面與友好的接口 …

于敦德:途牛五大戰略縱深不懼同質化競爭

于敦德說,途牛已經在目的地、出發地、產品系列、客戶和品牌五個領域建立起了縱深壁壘,不擔心任何局部競爭,將堅決把局部同質化戰爭打到底。 一個行業的兩種公司 包括旅游在內的很多行業通常都有兩種公司:…

活在當下吧

‘’你得為你自己而活,想要什么就去爭取,不要干等著以為別人會施舍給你,也不要在意別人的眼光和看法,你首先得考慮的是你自己。” “你要明白,只有當你自己在乎自己的時候,別人才會用正眼看你,才…