iptables 分析(二)
原文:http://blog.chinaunix.net/uid-24207747-id-2622901.html
do_command()函数分析
//负责整个用户输入的命令处理
int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle)
{
//初始化变量
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 xtables_match *m;//扩展匹配操作函数指针
struct iptables_rule_match *matches = NULL;
struct iptables_rule_match *matchp;
struct xtables_target *target = NULL;
struct xtables_target *t;//扩展目标操作函数指针
const char *jumpto = "";
char *protocol = NULL;
int proto_used = 0;
unsigned long long cnt;
memset(&fw, 0, sizeof(fw));
/*extern char *optarg; //选项的参数指针
extern int optind; //下一次调用getopt时,从optind存储的位置处重新开始检查选择项.
extern int opterr; //当为0时,不向stderr输出错误信息.
*/
optind = 0;
/* 清除标识mflags,以免do_command 被第二次调用,为了安全,清除了所有匹
配的链表*/
for (m = xtables_matches; m; m = m->next)
m->mflags = 0;
for (t = xtables_targets; t; t = t->next) {
t->tflags = 0;
t->used = 0;
}
/* Suppress error messages: we may add new options if we
demand-load a protocol. */
opterr = 0; //禁止错误信息显示
//分析命令行选项,返回短选项字母,错误返回-1
while ((c = getopt_long(argc, argv,
"-A:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:g:", //例ho:v::a,表示有效选项是-h,-o与-v并且第二个参数-o后面需要一个参数,v后接参数不能空格.
opts, NULL)) != -1) {
switch (c) {
case 'A'://追加一条规则到规则链,如:iptables -A INPUT -j ACCEPT
add_command(&command, CMD_APPEND, CMD_NONE,
invert); //该函数用来添加命令宏
chain = optarg; //得到-A 后参数(链名)
break;
case 'D': //删除规则链中某条规则
add_command(&command, CMD_DELETE, CMD_NONE,
invert);
chain = optarg;
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!') {
rulenum = parse_rulenumber(argv[optind++]); //把数字字符串转为数字
command = CMD_DELETE_NUM;
}
break;
case 'R':
add_command(&command, CMD_REPLACE, CMD_NONE,
invert);
chain = optarg;
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
rulenum = parse_rulenumber(argv[optind++]);
else
exit_error(PARAMETER_PROBLEM,
"-%c requires a rule number",
cmd2char(CMD_REPLACE));
break;
case 'I':
add_command(&command, CMD_INSERT, CMD_NONE,
invert);
chain = optarg;
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
rulenum = parse_rulenumber(argv[optind++]);
else rulenum = 1;
break;
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++];
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
rulenum = parse_rulenumber(argv[optind++]);
break;
case 'S':
add_command(&command, CMD_LIST_RULES, CMD_ZERO,
invert);
if (optarg) chain = optarg;
else if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
chain = argv[optind++];
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
rulenum = parse_rulenumber(argv[optind++]);
break;
case 'F':
add_command(&command, CMD_FLUSH, CMD_NONE,
invert);
if (optarg) chain = optarg;
else if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
chain = argv[optind++];
break;
case 'Z':
add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES,
invert);
if (optarg) chain = optarg;
else if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
chain = argv[optind++];
break;
case 'N':
if (optarg && (*optarg == '-' || *optarg == '!'))
exit_error(PARAMETER_PROBLEM,
"chain name not allowed to start "
"with `%c'\n", *optarg);
if (find_target(optarg, TRY_LOAD))
exit_error(PARAMETER_PROBLEM,
"chain name may not clash "
"with target name\n");
add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
invert);
chain = optarg;
break;
case 'X':
add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
invert);
if (optarg) chain = optarg;
else if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
chain = argv[optind++];
break;
case 'E':
add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
invert);
chain = optarg;
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
newname = argv[optind++];
else
exit_error(PARAMETER_PROBLEM,
"-%c requires old-chain-name and "
"new-chain-name",
cmd2char(CMD_RENAME_CHAIN));
break;
case 'P': //为规则链(INPUT、OUTPUT 和FORWARD)定义一默认策略
add_command(&command, CMD_SET_POLICY, CMD_NONE,
invert);
chain = optarg; //链名
if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
policy = argv[optind++];//得到策略,如iptables -P INPUT ACCEPT
else
exit_error(PARAMETER_PROBLEM,
"-%c requires a chain and a policy",
cmd2char(CMD_SET_POLICY));
break;
case 'h':
if (!optarg)
optarg = argv[optind];
/* iptables -p icmp -h */
if (!matches && protocol)
find_match(protocol, TRY_LOAD, &matches);
exit_printhelp(matches);
case 'p':
check_inverse(optarg, &invert, &optind, argc);
set_option(&options, OPT_PROTOCOL, &fw.ip.invflags,
invert);
/* Canonicalize into lower case */
for (protocol = argv[optind-1]; *protocol; protocol++)
*protocol = tolower(*protocol);
protocol = argv[optind-1];
fw.ip.proto = parse_protocol(protocol);
if (fw.ip.proto == 0
&& (fw.ip.invflags & IPT_INV_PROTO))
exit_error(PARAMETER_PROBLEM,
"rule would never match protocol");
break;
case 's':
check_inverse(optarg, &invert, &optind, argc);
set_option(&options, OPT_SOURCE, &fw.ip.invflags,
invert);
shostnetworkmask = argv[optind-1];
break;
case 'd':
check_inverse(optarg, &invert, &optind, argc);
set_option(&options, OPT_DESTINATION, &fw.ip.invflags,
invert);
dhostnetworkmask = argv[optind-1];
break;
#ifdef IPT_F_GOTO
case 'g':
set_option(&options, OPT_JUMP, &fw.ip.invflags,
invert);
fw.ip.flags |= IPT_F_GOTO;
jumpto = parse_target(optarg);
break;
#endif
case 'j': // -j ACCEPT
//将j 选项对应的宏定义位OPT_JUMP 加到options 上,invert 表示非逻辑操作
set_option(&options, OPT_JUMP, &fw.ip.invflags,
invert);
jumpto = parse_target(optarg); //解析目标名是否正确
target = find_target(jumpto, TRY_LOAD); //正确,从全局的目标链表xptables_targets 中查找这个目标,设置尝试加载目标标识
if (target) { //成功找到
size_t size;
size = IPT_ALIGN(sizeof(struct ipt_entry_target))
+ target->size;
target->t = fw_calloc(1, size);
target->t->u.target_size = size;
strcpy(target->t->u.user.name, jumpto); //保存目标名到xptables_targets
set_revision(target->t->u.user.name,
target->revision); //设置版本
if (target->init != NULL)
target->init(target->t); //初始化xptables_targets
opts = merge_options(opts,
target->extra_opts,
&target->option_offset);//将target 的参数选项与旧的参数选项连接在一起由opts 返回,这样下一个循环可以分析target 的参数选项,一般在“default:”中进行分析
if (opts == NULL)
exit_error(OTHER_PROBLEM,
"can't alloc memory!");
}
break;
case 'i':
check_inverse(optarg, &invert, &optind, argc);
set_option(&options, OPT_VIANAMEIN, &fw.ip.invflags,
invert);
parse_interface(argv[optind-1],
fw.ip.iniface,
fw.ip.iniface_mask);
break;
case 'o':
check_inverse(optarg, &invert, &optind, argc);
set_option(&options, OPT_VIANAMEOUT, &fw.ip.invflags,
invert);
parse_interface(argv[optind-1],
fw.ip.outiface,
fw.ip.outiface_mask);
break;
case 'f':
set_option(&options, OPT_FRAGMENT, &fw.ip.invflags,
invert);
fw.ip.flags |= IPT_F_FRAG;
break;
case 'v':
if (!verbose)
set_option(&options, OPT_VERBOSE,
&fw.ip.invflags, invert);
verbose++;
break;
case 'm': {//可扩展选项s
size_t size;
if (invert)
exit_error(PARAMETER_PROBLEM,
"unexpected ! flag before --match");
m = find_match(optarg, LOAD_MUST_SUCCEED, &matches);//从全局链表xptables_match 中查找匹配模块
size = IPT_ALIGN(sizeof(struct ipt_entry_match))
+ m->size;
m->m = fw_calloc(1, size);
m->m->u.match_size = size;
strcpy(m->m->u.user.name, m->name);
set_revision(m->m->u.user.name, m->revision);
if (m->init != NULL)
m->init(m->m); //初始化
if (m != m->next) {
/* Merge options for non-cloned matches */
opts = merge_options(opts,
m->extra_opts,
&m->option_offset);//将扩展匹配的选项加入到全局选项表opts,下一个循环就可以解析它的选项了
if (opts == NULL)
exit_error(OTHER_PROBLEM,
"can't alloc memory!");
}
}
break;
case 'n':
set_option(&options, OPT_NUMERIC, &fw.ip.invflags,
invert);
break;
case 't':
if (invert)
exit_error(PARAMETER_PROBLEM,
"unexpected ! flag before --table");
*table = argv[optind-1]; //取t 后面的表名
break;
case 'x':
set_option(&options, OPT_EXPANDED, &fw.ip.invflags,
invert);
break;
case 'V':
if (invert)
printf("Not %s ;-)\n", program_version);
else
printf("%s v%s\n",
program_name, program_version);
exit(0);
case '0':
set_option(&options, OPT_LINENUMBERS, &fw.ip.invflags,
invert);
break;
case 'M':
modprobe_program = optarg;
break;
case 'c':
set_option(&options, OPT_COUNTERS, &fw.ip.invflags,
invert);
pcnt = optarg;
bcnt = strchr(pcnt + 1, ',');
if (bcnt)
bcnt++;
if (!bcnt && optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
bcnt = argv[optind++];
if (!bcnt)
exit_error(PARAMETER_PROBLEM,
"-%c requires packet and byte counter",
opt2char(OPT_COUNTERS));
if (sscanf(pcnt, "%llu", &cnt) != 1)
exit_error(PARAMETER_PROBLEM,
"-%c packet counter not numeric",
opt2char(OPT_COUNTERS));
fw.counters.pcnt = cnt;
if (sscanf(bcnt, "%llu", &cnt) != 1)
exit_error(PARAMETER_PROBLEM,
"-%c byte counter not numeric",
opt2char(OPT_COUNTERS));
fw.counters.bcnt = cnt;
break;
case 1: /* non option */
if (optarg[0] == '!' && optarg[1] == '\0') {
if (invert)
exit_error(PARAMETER_PROBLEM,
"multiple consecutive ! not"
" allowed");
invert = TRUE;
optarg[0] = '\0';
continue;
}
fprintf(stderr, "Bad argument `%s'\n", optarg);
exit_tryhelp(2);
default://分析扩展目标和扩展匹配的选项
if (!target
|| !(target->parse(c - target->option_offset, //调用扩展目标的选项分析函数
argv, invert,
&target->tflags,
&fw, &target->t))) {
for (matchp = matches; matchp; matchp = matchp->next) {
if (matchp->completed)
continue;
if (matchp->match->parse(c - matchp->match->option_offset,
argv, invert,
&matchp->match->mflags,
&fw,
&matchp->match->m))
break;
}
m = matchp ? matchp->match : NULL;
if (m == NULL //如果匹配不存在,通过协议查找扩展匹配,由扩展匹配分析选项
&& protocol
&& (!find_proto(protocol, DONT_LOAD,
options&OPT_NUMERIC, NULL)
|| (find_proto(protocol, DONT_LOAD,
options&OPT_NUMERIC, NULL)
&& (proto_used == 0))
)
&& (m = find_proto(protocol, TRY_LOAD,
options&OPT_NUMERIC, &matches))){ //匹配成功
/* Try loading protocol */
size_t size;
proto_used = 1;
size = IPT_ALIGN(sizeof(structipt_entry_match))
+ m->size;
m->m = fw_calloc(1, size);
m->m->u.match_size = size;
strcpy(m->m->u.user.name, m->name);
set_revision(m->m->u.user.name,
m->revision);
if (m->init != NULL)
m->init(m->m);
opts = merge_options(opts,
m->extra_opts,
&m->option_offset);
if (opts == NULL)
exit_error(OTHER_PROBLEM,
"can't alloc memory!");
optind--;
continue;
}
if (!m)
exit_error(PARAMETER_PROBLEM,
"Unknown arg `%s'",
argv[optind-1]);
}
}
invert = FALSE;
}
//final_check成员函数的作用是作最终的标志检查,如果检测失则,则退出
for (matchp = matches; matchp; matchp = matchp->next)
if (matchp->match->final_check != NULL)//使用扩展匹配的函数检查标识
matchp->match->final_check(matchp->match->mflags);
if (target != NULL && target->final_check != NULL)
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 !");
//如果没有设置来源/目的地址及掩码,则给予它们一个默认值
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)
ipparse_hostnetworkmask(shostnetworkmask, &saddrs,
&fw.ip.smsk, &nsaddrs);
if (dhostnetworkmask)
ipparse_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");
if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
exit_error(PARAMETER_PROBLEM, "Replacement rule does not "
"specify a unique address");
generic_opt_check(command, options); //检查命令选项有效
if (chain && strlen(chain) > IPT_FUNCTION_MAXNAMELEN)
exit_error(PARAMETER_PROBLEM,
"chain name `%s' too long (must be under %i chars)",
chain, IPT_FUNCTION_MAXNAMELEN);
/*handle,是一个指向了具体表,如filter、nat表的句柄,这里判断,如果handle为空,则调用iptc_init,根据table的名称,让handle指针指向相应的表的地址空间,也就是把对应表的所有信息从内核中取出来*/
if (!*handle)
*handle = iptc_init(*table); //调用 iptc_init获取表的规则信息,调用list_entries函数显示规则
/* try to insmod the module if iptc_init failed */
if (!*handle && load_xtables_ko(modprobe_program, 0) != -1)
*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) {
if (strcmp(chain, "PREROUTING") == 0
|| strcmp(chain, "INPUT") == 0) {
/* -o not valid with incoming packets. */
if (options & OPT_VIANAMEOUT)
exit_error(PARAMETER_PROBLEM,
"Can't use -%c with %s\n",
opt2char(OPT_VIANAMEOUT),
chain);
}
if (strcmp(chain, "POSTROUTING") == 0
|| strcmp(chain, "OUTPUT") == 0) {
/* -i not valid with outgoing packets */
if (options & OPT_VIANAMEIN)
exit_error(PARAMETER_PROBLEM,
"Can't use -%c with %s\n",
opt2char(OPT_VIANAMEIN),
chain);
}
if (target && iptc_is_chain(jumpto, *handle)) { //jumpto链名
fprintf(stderr,
"Warning: using chain %s, not extension\n",
jumpto);
if (target->t)
free(target->t);
target = NULL;
}
if (!target
&& (strlen(jumpto) == 0
|| iptc_is_chain(jumpto, *handle))) { //如果没有指定目标或没有指定链名,使用标准的
size_t size;
target = find_target(IPT_STANDARD_TARGET,
LOAD_MUST_SUCCEED); //找到加载内核
size = sizeof(struct ipt_entry_target)
+ target->size;
target->t = fw_calloc(1, size);
target->t->u.target_size = size;
strcpy(target->t->u.user.name, jumpto);
if (!iptc_is_chain(jumpto, *handle))
set_revision(target->t->u.user.name,
target->revision);
if (target->init != NULL)
target->init(target->t);
}
if (!target) {//扩展目标不存在
#ifdef IPT_F_GOTO
if (fw.ip.flags & IPT_F_GOTO)
exit_error(PARAMETER_PROBLEM,
"goto '%s' is not a chain\n", jumpto);
#endif
find_target(jumpto, LOAD_MUST_SUCCEED);
} else {//将扩展目标加入到规则条目中
e = generate_entry(&fw, matches, target->t);
free(target->t);
}
}
/* 根据命令标志,增加、显示规则*/
switch (command) {
case CMD_APPEND:
ret = append_entry(chain, e,
nsaddrs, saddrs, ndaddrs, daddrs,
options&OPT_VERBOSE,
handle);
break;
case CMD_DELETE:
ret = delete_entry(chain, e,
nsaddrs, saddrs, ndaddrs, daddrs,
options&OPT_VERBOSE,
handle, matches);
break;
case CMD_DELETE_NUM:
ret = iptc_delete_num_entry(chain, rulenum - 1, handle);
break;
case CMD_REPLACE:
ret = replace_entry(chain, e, rulenum - 1,
saddrs, daddrs, options&OPT_VERBOSE,
handle);
break;
case CMD_INSERT:
ret = insert_entry(chain, e, rulenum - 1,
nsaddrs, saddrs, ndaddrs, daddrs,
options&OPT_VERBOSE,
handle);
break;
case CMD_FLUSH:
ret = flush_entries(chain, options&OPT_VERBOSE, handle);
break;
case CMD_ZERO:
ret = zero_entries(chain, options&OPT_VERBOSE, handle);
break;
case CMD_LIST:
case CMD_LIST|CMD_ZERO:
//list_entries是规则显示的主要处理函数
ret = list_entries(chain,
rulenum,
options&OPT_VERBOSE,
options&OPT_NUMERIC,
options&OPT_EXPANDED,
options&OPT_LINENUMBERS,
handle);
if (ret && (command & CMD_ZERO))
ret = zero_entries(chain,
options&OPT_VERBOSE, handle);
break;
case CMD_LIST_RULES:
case CMD_LIST_RULES|CMD_ZERO:
ret = list_rules(chain,
rulenum,
options&OPT_VERBOSE,
handle);
if (ret && (command & CMD_ZERO))
ret = zero_entries(chain,
options&OPT_VERBOSE, handle);
break;
case CMD_NEW_CHAIN:
ret = iptc_create_chain(chain, handle);
break;
case CMD_DELETE_CHAIN:
ret = delete_chain(chain, options&OPT_VERBOSE, handle);
break;
case CMD_RENAME_CHAIN:
ret = iptc_rename_chain(chain, newname, handle);
break;
case CMD_SET_POLICY:
ret = iptc_set_policy(chain, policy, options&OPT_COUNTERS ?&fw.counters : NULL, handle);
break;
default:
/* We should never reach this... */
exit_tryhelp(2);
}
if (verbose > 1)
dump_entries(*handle);
clear_rule_matches(&matches);
if (e != NULL) {
free(e);
e = NULL;
}
free(saddrs);
free(daddrs);
free_opts(1);
return ret;
}
iptables 分析(二)的更多相关文章
- SNMP报文抓取与分析(二)
SNMP报文抓取与分析(二) SNMP报文抓取与分析(二) 1.SNMP报文表示简介 基本编码规则BER 标识域Tag表示 长度域length表示 2.SNMP报文详细分析(以一个get-respon ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- yhd日志分析(二)
yhd日志分析(二) 继续yhd日志分析,统计数据 日期 uv pv 登录人数 游客人数 平均访问时长 二跳率 独立ip数 1 分析 登录人数 count(distinct endUserId) 游客 ...
- SQLite入门与分析(二)---设计与概念(续)
SQLite入门与分析(二)---设计与概念(续) 写在前面:本节讨论事务,事务是DBMS最核心的技术之一.在计算机科学史上,有三位科学家因在数据库领域的成就而获ACM图灵奖,而其中之一Jim G ...
- Linux内核启动代码分析二之开发板相关驱动程序加载分析
Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c start_ke ...
- 一些有用的javascript实例分析(二)
原文:一些有用的javascript实例分析(二) 5 求出数组中所有数字的和 window.onload = function () { var oBtn = document.getElement ...
- Android4.0图库Gallery2代码分析(二) 数据管理和数据加载
Android4.0图库Gallery2代码分析(二) 数据管理和数据加载 2012-09-07 11:19 8152人阅读 评论(12) 收藏 举报 代码分析android相册优化工作 Androi ...
- MapReduce深度分析(二)
MapReduce深度分析(二) 五.JobTracker分析 JobTracker是hadoop的重要的后台守护进程之一,主要的功能是管理任务调度.管理TaskTracker.监控作业执行.运行作业 ...
- Java线程池使用和分析(二) - execute()原理
相关文章目录: Java线程池使用和分析(一) Java线程池使用和分析(二) - execute()原理 execute()是 java.util.concurrent.Executor接口中唯一的 ...
随机推荐
- Linux下逻辑地址、线性地址、物理地址详细总结
Linux下逻辑地址.线性地址.物理地址详细总结 一.逻辑地址转线性地址 机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址 ...
- 19.翻译系列:EF 6中定义自定义的约定【EF 6 Code-First约定】
原文链接:https://www.entityframeworktutorial.net/entityframework6/custom-conventions-codefirst.aspx EF 6 ...
- centos 环境搭建jenkins服务
1.下载jenkins war包 https://jenkins.io/download/ 选择Generic Java package (.war)下载2.下载apache tomcat 8 htt ...
- Roslyn如何实现简单的代码提示
假如需要实现一个代码编辑器,其中一个很重要的功能是实现代码提示,类似VS的代码智能提示.下面用Roslyn编译器来实现一个简单的代码提示功能. 代码提示,首先必须需要知道对象的类型信息,然后通过迭代获 ...
- HAProxy配置说明(转)
原文地址:http://www.cnblogs.com/sagech/p/5695466.html global # 全局参数的设置 log 127.0.0.1 local0 info # log语法 ...
- R语言:recommenderlab包的总结与应用案例
R语言:recommenderlab包的总结与应用案例 1. 推荐系统:recommenderlab包整体思路 recommenderlab包提供了一个可以用评分数据和0-1数据来发展和测试推荐算 ...
- name转json
^(\{)?(?<=\n)(.*)(\})?$ "$2":"", UserId UserOrderId ChargeAccount BuyNum Good ...
- react服务端渲染同构报错Browser history needs a DOM
https://github.com/nozzle/react-static/issues/343 去掉了browserRouter就不报错了,但是又会有其他报错..
- JS -- serializeJSON
http://www.cnblogs.com/linzenews/p/7065050.html
- iOS学习之--字符串的删除替换(字符串的常用处理,删除,替换)
字符串操作,比较简单,仅做记录! 1.删除 NSString *str1 = @"<hello,wo r d!>"; //删除字符串两端的尖括号 NSMutableSt ...