初始化

iptable_nat_table_init函数通过调用ipt_register_table完成NAT表注册和钩子函数注册的功能;该流程与iptable_filter的函数调用的函数一致,此处不再重复分析,详情请移步<iptable_filter分析>;

 static int __net_init iptable_nat_table_init(struct net *net)
{
struct ipt_replace *repl;
int ret; /* nat表已经初始化过 */
if (net->ipv4.nat_table)
return ; /* 分配初始化表,用于下面的注册 */
repl = ipt_alloc_initial_table(&nf_nat_ipv4_table);
if (repl == NULL)
return -ENOMEM;
/* 表注册,钩子函数注册 */
ret = ipt_register_table(net, &nf_nat_ipv4_table, repl,
nf_nat_ipv4_ops, &net->ipv4.nat_table);
kfree(repl);
return ret;
}
钩子函数分析
钩子函数以及钩子点

nf_nat_ipv4_ops是NAT相关钩子函数的数组,其调用顺序和钩子点见下面注释;其中filter工作在DNAT和SNAT之间;

这几个钩子函数都会调用nf_nat_ipv4_fn来完成NAT转换,本部分最后统一分析该函数;

 /* 钩子函数数组 */
/* 顺序 DNAT->filter->SNAT */
/* 输入本机 PRE_ROUTING(DNAT)->LOCAL_IN(SNAT) */
/* 转发 PRE_ROUTING(DNAT)->POST_ROUTING(SNAT) */
/* 本机输出 LOCAL_OUT(DNAT)->POST_ROUTING(SNAT) */
static struct nf_hook_ops nf_nat_ipv4_ops[] __read_mostly = {
/* Before packet filtering, change destination */
{
.hook = iptable_nat_ipv4_in,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_NAT_DST, /* DNAT */
},
/* After packet filtering, change source */
{
.hook = iptable_nat_ipv4_out,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_NAT_SRC, /* SNAT */
},
/* Before packet filtering, change destination */
{
.hook = iptable_nat_ipv4_local_fn,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST, /* DNAT */
},
/* After packet filtering, change source */
{
.hook = iptable_nat_ipv4_fn,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC, /* SNAT */
},
};
iptable_nat_ipv4_in

函数工作在PRE_ROUTING钩子点,进行DNAT转换;

 /* PRE_ROUTING,DNAT */
static unsigned int iptable_nat_ipv4_in(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
return nf_nat_ipv4_in(priv, skb, state, iptable_nat_do_chain);
}

nf_nat_ipv4_in函数在进行DNAT转换之前记录了目的地址,在进行转换之后,如果目的地址发生了改变,则需要释放skb中的路由缓存;NAT转换过程调用nf_nat_ipv4_fn完成,步骤见下面的该函数分析;

 /* PRE_ROUTING, DNAT */
unsigned int
nf_nat_ipv4_in(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state,
unsigned int (*do_chain)(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state,
struct nf_conn *ct))
{
unsigned int ret;
/* 获取目的地址 */
__be32 daddr = ip_hdr(skb)->daddr; /* DNAT转换 */
ret = nf_nat_ipv4_fn(priv, skb, state, do_chain); /* 转换之后,目的地址发生变化,释放路由缓存 */
if (ret != NF_DROP && ret != NF_STOLEN &&
daddr != ip_hdr(skb)->daddr)
skb_dst_drop(skb); return ret;
}
iptable_nat_ipv4_fn

函数工作在LOCAL_IN钩子点,进行SNAT转换;NAT转换过程调用nf_nat_ipv4_fn完成,步骤见下面的该函数分析;

 /* LOCAL_IN,SNAT */
static unsigned int iptable_nat_ipv4_fn(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
return nf_nat_ipv4_fn(priv, skb, state, iptable_nat_do_chain);
}
iptable_nat_ipv4_local_fn

函数工作在LOCAL_OUT钩子点,进行DNAT转换;

 /* LOCAL_OUT,DNAT */
static unsigned int iptable_nat_ipv4_local_fn(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
return nf_nat_ipv4_local_fn(priv, skb, state, iptable_nat_do_chain);
}

nf_nat_ipv4_local_fn函数在进行DNAT转换之后,如果地址发生变化,则需要重新进行路由查;NAT转换过程调用nf_nat_ipv4_fn完成,步骤见下面的该函数分析;

 unsigned int
nf_nat_ipv4_local_fn(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state,
unsigned int (*do_chain)(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state,
struct nf_conn *ct))
{
const struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
unsigned int ret;
int err; /* root is playing with raw sockets. */
if (skb->len < sizeof(struct iphdr) ||
ip_hdrlen(skb) < sizeof(struct iphdr))
return NF_ACCEPT; /* DNAT转换 */
ret = nf_nat_ipv4_fn(priv, skb, state, do_chain); /* 转换成功 */
if (ret != NF_DROP && ret != NF_STOLEN &&
(ct = nf_ct_get(skb, &ctinfo)) != NULL) {
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); /* ip地址发生变化 */
if (ct->tuplehash[dir].tuple.dst.u3.ip !=
ct->tuplehash[!dir].tuple.src.u3.ip) {
/* 重新查路由 */
err = ip_route_me_harder(state->net, skb, RTN_UNSPEC);
if (err < )
ret = NF_DROP_ERR(err);
}
#ifdef CONFIG_XFRM
else if (!(IPCB(skb)->flags & IPSKB_XFRM_TRANSFORMED) &&
ct->tuplehash[dir].tuple.dst.protonum != IPPROTO_ICMP &&
ct->tuplehash[dir].tuple.dst.u.all !=
ct->tuplehash[!dir].tuple.src.u.all) {
err = nf_xfrm_me_harder(state->net, skb, AF_INET);
if (err < )
ret = NF_DROP_ERR(err);
}
#endif
}
return ret;
}
iptable_nat_ipv4_out

函数工作在POST_ROUTING钩子点,进行SNAT转换;

 /* POST_ROUTING,SNAT */
static unsigned int iptable_nat_ipv4_out(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
return nf_nat_ipv4_out(priv, skb, state, iptable_nat_do_chain);
}
 unsigned int
nf_nat_ipv4_out(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state,
unsigned int (*do_chain)(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state,
struct nf_conn *ct))
{
#ifdef CONFIG_XFRM
const struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
int err;
#endif
unsigned int ret; /* root is playing with raw sockets. */
if (skb->len < sizeof(struct iphdr) ||
ip_hdrlen(skb) < sizeof(struct iphdr))
return NF_ACCEPT; /* SNAT转换 */
ret = nf_nat_ipv4_fn(priv, skb, state, do_chain);
#ifdef CONFIG_XFRM
if (ret != NF_DROP && ret != NF_STOLEN &&
!(IPCB(skb)->flags & IPSKB_XFRM_TRANSFORMED) &&
(ct = nf_ct_get(skb, &ctinfo)) != NULL) {
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); if ((ct->tuplehash[dir].tuple.src.u3.ip !=
ct->tuplehash[!dir].tuple.dst.u3.ip) ||
(ct->tuplehash[dir].tuple.dst.protonum != IPPROTO_ICMP &&
ct->tuplehash[dir].tuple.src.u.all !=
ct->tuplehash[!dir].tuple.dst.u.all)) {
err = nf_xfrm_me_harder(state->net, skb, AF_INET);
if (err < )
ret = NF_DROP_ERR(err);
}
}
#endif
return ret;
}
公共函数nf_nat_ipv4_fn

nf_nat_ipv4_fn完成具体的SNAT或者DNAT的转换流程,上面的四个钩子函数都会调用该函数;

 unsigned int
nf_nat_ipv4_fn(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state,
unsigned int (*do_chain)(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state,
struct nf_conn *ct))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_nat *nat;
/* maniptype == SRC for postrouting. */
/* 获取是进行DNAT还是SNAT,其中PRE_ROUTING和LOCAL_OUT进行DNAT,LOCAL_IN和POST_ROUTING进行SNAT */
enum nf_nat_manip_type maniptype = HOOK2MANIP(state->hook); /* 获取skb关联的连接跟踪sf_conn */
ct = nf_ct_get(skb, &ctinfo);
/* Can't track? It's not due to stress, or conntrack would
* have dropped it. Hence it's the user's responsibilty to
* packet filter it out, or implement conntrack/NAT for that
* protocol. 8) --RR
*/
/* 没有,返回accpet */
if (!ct)
return NF_ACCEPT; /* 获取NAT扩展 */
nat = nfct_nat(ct); /* 判断连接跟踪状态 */
switch (ctinfo) {
/* 关联连接(或者icmp错误)或者关联连接的应答 */
case IP_CT_RELATED:
case IP_CT_RELATED_REPLY:
/* icmp协议的NAT操作 */
if (ip_hdr(skb)->protocol == IPPROTO_ICMP) {
if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo,
state->hook))
return NF_DROP;
else
return NF_ACCEPT;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
case IP_CT_NEW:
/* Seen it before? This can happen for loopback, retrans,
* or local packets.
*/
/* 尚未进行过NAT转换 */
if (!nf_nat_initialized(ct, maniptype)) {
unsigned int ret; /* 进行规则匹配 */
ret = do_chain(priv, skb, state, ct);
if (ret != NF_ACCEPT)
return ret; /* 打NAT转换标记 */
if (nf_nat_initialized(ct, HOOK2MANIP(state->hook)))
break; /* 连接跟踪进行NAT */
ret = nf_nat_alloc_null_binding(ct, state->hook);
if (ret != NF_ACCEPT)
return ret;
}
/* 进行过NAT转换 */
else {
pr_debug("Already setup manip %s for ct %p\n",
maniptype == NF_NAT_MANIP_SRC ? "SRC" : "DST",
ct);
/* 出接口发生改变 */
if (nf_nat_oif_changed(state->hook, ctinfo, nat,
state->out))
goto oif_changed;
}
break; default:
/* ESTABLISHED */
NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||
ctinfo == IP_CT_ESTABLISHED_REPLY);
/* 出接口发生改变 */
if (nf_nat_oif_changed(state->hook, ctinfo, nat, state->out))
goto oif_changed;
} /* skb数据包进行NAT转换修改 */
return nf_nat_packet(ct, ctinfo, state->hook, skb); oif_changed:
nf_ct_kill_acct(ct, ctinfo, skb);
return NF_DROP;
}
 unsigned int
nf_nat_alloc_null_binding(struct nf_conn *ct, unsigned int hooknum)
{
return __nf_nat_alloc_null_binding(ct, HOOK2MANIP(hooknum));
}
 static unsigned int
__nf_nat_alloc_null_binding(struct nf_conn *ct, enum nf_nat_manip_type manip)
{
/* Force range to this IP; let proto decide mapping for
* per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
* Use reply in case it's already been mangled (eg local packet).
*/
/* 使用应答方向的ip地址,LOCAL_OUT会先经过mangle,可能改变了 */
union nf_inet_addr ip =
(manip == NF_NAT_MANIP_SRC ?
ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3 :
ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3); /* 设置range */
struct nf_nat_range range = {
.flags = NF_NAT_RANGE_MAP_IPS,
.min_addr = ip,
.max_addr = ip,
}; /* 进行NAT转换 */
return nf_nat_setup_info(ct, &range, manip);
}
 unsigned int
nf_nat_setup_info(struct nf_conn *ct,
const struct nf_nat_range *range,
enum nf_nat_manip_type maniptype)
{
struct nf_conntrack_tuple curr_tuple, new_tuple; /* Can't setup nat info for confirmed ct. */
/* 已经确认的,返回accpet */
if (nf_ct_is_confirmed(ct))
return NF_ACCEPT; NF_CT_ASSERT(maniptype == NF_NAT_MANIP_SRC ||
maniptype == NF_NAT_MANIP_DST);
BUG_ON(nf_nat_initialized(ct, maniptype)); /* What we've got will look like inverse of reply. Normally
* this is what is in the conntrack, except for prior
* manipulations (future optimization: if num_manips == 0,
* orig_tp = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple)
*/
/* 从应答tuple反向得到当前tuple */
nf_ct_invert_tuplepr(&curr_tuple,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple); /* 根据当前tuple和range得到NAT转换之后的的tuple */
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype); /* NAT转换之后和之前的tuple不同 */
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct nf_conntrack_tuple reply; /* Alter conntrack table so will recognize replies. */
/* 通过新tuple得到reply_tuple */
nf_ct_invert_tuplepr(&reply, &new_tuple);
/* 加入到reply hash */
nf_conntrack_alter_reply(ct, &reply); /* 此时tuple类似如下 */
/*
//内网10.1通过100.1访问200.1,经过SNAT之后得到tuple
tuple SNAT(10.1->200.1, 200.1->100.1) //外网300.1通过100.1访问20.1,经过DNAT之后,得到tuple
tuple DNAT(300.1->100.1, 20.1->300.1)
*/ /* Non-atomic: we own this at the moment. */
/* 更新状态需要做NAT */
if (maniptype == NF_NAT_MANIP_SRC)
ct->status |= IPS_SRC_NAT;
else
ct->status |= IPS_DST_NAT; /* 扩展项的调整 */
if (nfct_help(ct))
if (!nfct_seqadj_ext_add(ct))
return NF_DROP;
} /* SNAT */
if (maniptype == NF_NAT_MANIP_SRC) {
struct nf_nat_conn_key key = {
.net = nf_ct_net(ct),
.tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple,
.zone = nf_ct_zone(ct),
};
int err; /* 加入到nf_nat_bysource_table */
err = rhltable_insert_key(&nf_nat_bysource_table,
&key,
&ct->nat_bysource,
nf_nat_bysource_params);
if (err)
return NF_DROP;
} /* It's done. */
/* NAT转换完成 */
if (maniptype == NF_NAT_MANIP_DST)
ct->status |= IPS_DST_NAT_DONE;
else
ct->status |= IPS_SRC_NAT_DONE; return NF_ACCEPT;
}
 /* 根据orig_tuple和range得到NAT转换之后的tuple */
static void
get_unique_tuple(struct nf_conntrack_tuple *tuple,
const struct nf_conntrack_tuple *orig_tuple,
const struct nf_nat_range *range,
struct nf_conn *ct,
enum nf_nat_manip_type maniptype)
{
const struct nf_conntrack_zone *zone;
const struct nf_nat_l3proto *l3proto;
const struct nf_nat_l4proto *l4proto;
struct net *net = nf_ct_net(ct); zone = nf_ct_zone(ct); rcu_read_lock(); /* 查找l3proto和l4proto */
l3proto = __nf_nat_l3proto_find(orig_tuple->src.l3num);
l4proto = __nf_nat_l4proto_find(orig_tuple->src.l3num,
orig_tuple->dst.protonum); /* 1) If this srcip/proto/src-proto-part is currently mapped,
* and that same mapping gives a unique tuple within the given
* range, use that.
*
* This is only required for source (ie. NAT/masq) mappings.
* So far, we don't do local source mappings, so multiple
* manips not an issue.
*/
/* SNAT && 没有打RANDOM_ALL标记 */
if (maniptype == NF_NAT_MANIP_SRC &&
!(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) {
/* try the original tuple first */
/* 查看orig_tuple是否满足范围要求 */
if (in_range(l3proto, l4proto, orig_tuple, range)) {
/* tuple尚未被使用 */
if (!nf_nat_used_tuple(orig_tuple, ct)) {
/* 使用原tuple */
*tuple = *orig_tuple;
goto out;
}
}
/* ori_range不满足要求,则从bysource_table中查找一个满足范围的tuple */
else if (find_appropriate_src(net, zone, l3proto, l4proto,
orig_tuple, tuple, range)) {
pr_debug("get_unique_tuple: Found current src map\n");
/* tuple尚未被使用 */
if (!nf_nat_used_tuple(tuple, ct))
goto out;
}
} /* 从给定range中选择一个最少使用的组合 */
/* 2) Select the least-used IP/proto combination in the given range */
*tuple = *orig_tuple;
find_best_ips_proto(zone, tuple, range, ct, maniptype); /* 3) The per-protocol part of the manip is made to map into
* the range to make a unique tuple.
*/ /* Only bother mapping if it's not already in range and unique */
/* 没有打RANDOM_ALL标记 */
if (!(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) {
/* 有SPECIFIED标记,对端口号进行检查 */
if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
/* 端口号已经在范围之内&&(端口最小最大范围相等||tuple没有使用) */
if (l4proto->in_range(tuple, maniptype,
&range->min_proto,
&range->max_proto) &&
(range->min_proto.all == range->max_proto.all ||
!nf_nat_used_tuple(tuple, ct)))
goto out;
}
/* 没有SPECIFIED标记,端口号不变,tuple没有被使用 */
else if (!nf_nat_used_tuple(tuple, ct)) {
goto out;
}
} /* Last change: get protocol to try to obtain unique tuple. */
/* 随机选择端口号 */
l4proto->unique_tuple(l3proto, tuple, range, maniptype, ct);
out:
rcu_read_unlock();
}
 unsigned int nf_nat_packet(struct nf_conn *ct,
enum ip_conntrack_info ctinfo,
unsigned int hooknum,
struct sk_buff *skb)
{
const struct nf_nat_l3proto *l3proto;
const struct nf_nat_l4proto *l4proto;
/* 获取方向 */
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
unsigned long statusbit;
/* 获取进行SNAT还是DNAT */
enum nf_nat_manip_type mtype = HOOK2MANIP(hooknum); /* 设置NAT标记 */
if (mtype == NF_NAT_MANIP_SRC)
statusbit = IPS_SRC_NAT;
else
statusbit = IPS_DST_NAT; /* Invert if this is reply dir. */
/* 应答方向需要取反 */
if (dir == IP_CT_DIR_REPLY)
statusbit ^= IPS_NAT_MASK; /* Non-atomic: these bits don't change. */ /* 需要做NAT */
if (ct->status & statusbit) {
struct nf_conntrack_tuple target; /* We are aiming to look like inverse of other direction. */
/* 获取目标tuple */
/*
//内网10.1通过100.1访问200.1,经过SNAT之后得到tuple
tuple SNAT(10.1->200.1, 200.1->100.1) //外网300.1通过100.1访问20.1,经过DNAT之后,得到tuple
tuple DNAT(300.1->100.1, 20.1->300.1)
*/
nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple); /* 获取l3proto,l4proto */
l3proto = __nf_nat_l3proto_find(target.src.l3num);
l4proto = __nf_nat_l4proto_find(target.src.l3num,
target.dst.protonum); /* 将ip地址和端口的NAT转换结果写入skb */
if (!l3proto->manip_pkt(skb, , l4proto, &target, mtype))
return NF_DROP;
}
return NF_ACCEPT;
}

Netfilter 之 iptable_nat的更多相关文章

  1. netfilter的笔记3--那些内置的表

    通过netfilter的笔记2的例子,我们知道了怎么使用netfilter的框架,对于内核的设计原则来说,策略和机制分离,所以提供了iptables来供用户配置防火墙策略. 那么,怎么使用iptabl ...

  2. Linux 跟踪连接netfilter 调优

    Netfilter介绍 linux内核中的netfilter是一款强大的基于状态的防火墙,具有连接跟踪(conntrack)的实现.conntrack是netfilter的核心,许多增强的功能,例如, ...

  3. 【操作系统之十三】Netfilter与iptables

    一.Netfilter Netfilter是由Rusty Russell提出的Linux 2.4内核防火墙框架,该框架既简洁又灵活,可实现安全策略应用中的许多功能,如数据包过滤.数据包处理.地址伪装. ...

  4. Linux内核下包过滤框架——iptables&netfilter

    iptables & netfilter 1.简介 netfilter/iptables(下文中简称为iptables)组成Linux内核下的包过滤防火墙,完成封包过滤.封包重定向和网络地址转 ...

  5. (一)洞悉linux下的Netfilter&iptables:什么是Netfilter?

    转自:http://blog.chinaunix.net/uid-23069658-id-3160506.html 本人研究linux的防火墙系统也有一段时间了,由于近来涉及到的工作比较纷杂,久而久之 ...

  6. netfilter的钩子——数据包在内核态得捕获、修改和转发

    转发:http://blog.csdn.net/stonesharp/article/details/27091391 数据包在内核态得捕获.修改和转发(基于 netfilter)    忙活了好几天 ...

  7. Netfilter/iptables的匹配方式及处理方法

    匹配方式: 匹配方式是netfilter筛选数据包的最基本单元. 内置的匹配方式: 1.接口的匹配方式: iptables -t filter -A FORWARD -i eth0 -o eth1 - ...

  8. iptables/Netfilter 学习

    开始学iptables,因为它是和路由器技术紧密结合在一起的. iptables的命令看起来眼花缭乱,随便找两个: iptables -A FORWARD -p tcp -s -d -j ACCEPT ...

  9. netfilter分析

    转自:http://blog.sina.com.cn/s/blog_a31ff26901013n07.html 一.概述 1. Netfilter/IPTables框架简介 Netfilter/IPT ...

随机推荐

  1. 深入理解Java自动装箱拆箱机制

    1.自动装箱与拆箱的定义 装箱就是自动将基本数据类型转换为包装器类型(int-->Integer): 拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int). Java中 ...

  2. JS笛卡尔积算法与多重数组笛卡尔积实现方法示例

    js 笛卡尔积算法的实现代码,据对象或者数组生成笛卡尔积,并介绍了一个javascript多重数组笛卡尔积的例子,以及java实现笛卡尔积的算法与实例代码. 一.javascript笛卡尔积算法代码 ...

  3. Oracle 调试存储过程

    调试过程对找到一个存过的bug或错误是非常重要的,Oracle作为一款强大的商业数据库,其上面的存过少则10几行,多则上千行,免不了bug的存在,存过上千行的话,找bug也很费力,通过调试可以大大减轻 ...

  4. MongoDB divide 使用之mongotempalte divide

    需求:求一组数的两个字段计算结果的平均值 如有一组这样的数: 列名1 列名2 列名3 第一组数   a       2       5 第二组数   b       4       8 按照列名1分组 ...

  5. Short XSS

    Short XSS Crackkay · 2013/08/21 12:17 0x00 背景 关键时候长度不够怎么办? 在实际的情况中如果你不够长怎么办呢?看医生?吃药?做手术?............ ...

  6. 把app(apk和ipa文件)安装包放在服务器上供用户下方法

    怎么把app(apk和ipa文件)安装包放在服务器上供用户下载? IIS服务器网站不能下载.apk文件的原因:IIS的默认MIME类型中没有.apk文件,所以无法下载.解决办法:给.apk格式文件添加 ...

  7. Scala语言面向对象

    apply1. 面向对象的基本概念: 把数据及对数据的操作方法放在一起,作为一个相互依存的整体-----对象,面向对象的三大特征:封装.多态.继承 2. scala类的定义 · class Emplo ...

  8. Hive2.0常用函数(对编辑器很无语😓)

    Hive内部提供了很多函数给开发者使用,包括数学函数,类型转换函数,条件函数,字符函数,聚合函数,表生成函数等等,这些函数都统称为内置函数. 参考:https://cwiki.apache.org/c ...

  9. 最小m子段和(动态规划)

    问题描述: 给定n个整数组成的序列,现在要求将序列分割为m段,每段子序列中的数在原序列中连续排列.如何分割才能使这m段子序列的和的最大值达到最小? 输入格式: 第一行给出n,m,表示有n个数分成m段, ...

  10. bitcoind搭建

    https://degreesofzero.com/article/installing-bitcoind-on-ubuntu.html1. sudo apt-get install python-s ...