快速模式第三包:quick_inR1_outI2()

1. 序言

在上一篇中博客中长话短说了第二包的处理流程,前两个报文的交互是快速模式的关键交互,用来协商会话参数(加解密算法、会话秘钥等),而第三包则是对前两个报文的认证,流程上简单了很多 。quick_inR1_outI2()处理流程实现的功能如下:

  • 解析SA载荷(对端选择的加解密算法信息)、KE载荷(pfs)、Nonce载荷
  • 构造第三个报文
  • 生成密钥生成材料keymats
  • 建立完整的IPsec SA
    • 发起端建立的为完整IPSec SA,包括入SA和出SA,响应端有所不同。
  • 启动DPD检测

2. quick_inR1_outI2()的处理流程

第三个报文的完整处理流程如下:

3. 快速模式第③包报文格式

该报文中的杂凑载荷是对前面交换的认证。第③包仅有ISAKMP头部和杂凑载荷构成,杂凑载荷的消息摘要是以一个0字节的MsgID、去掉载荷头的发起者Nonce以及去掉了载荷头的响应者Nonce为输入参数生成的,计算公式如下:

H

A

S

H

(

3

)

=

P

R

F

(

S

K

E

Y

a

,

0

M

s

g

I

D

N

i

b

N

r

b

)

HASH(3) = PRF(SKEY-a, 0|MsgID|Ni_b|Nr_b)

HASH(3)=PRF(SKEY−a,0∣MsgID∣Nib​∣Nrb​)

4. quick_inR1_outI2()源码分析

接口quick_inR1_outI2()的主要功能如下:

  • 验证报文的完整性
  • 解析响应端选择的SA载荷
    • 加解密算法(ESP协议、AH协议,…)
    • 认证算法
    • 封装模式(隧道模式 ?传输模式)
  • 解析Nonce载荷
  • 如果启用PFS
    • 解析KE载荷
    • 再次计算DH交换值
stf_status
quick_inR1_outI2(struct msg_digest *md)
{
struct state *const st = md->st; /* HASH(2) in *//*验证报文的哈希载荷*/
CHECK_QUICK_HASH(md
, quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof
, st, &st->st_msgid, TRUE)
, "HASH(2)", "Quick R1"); /* SA in */
{
struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA]; /*解析对端选择的SA载荷*/
RETURN_STF_FAILURE(parse_ipsec_sa_body(&sa_pd->pbs
, &sa_pd->payload.sa, NULL, TRUE, st));
} /* Nr in *//*解析对端Nonce载荷*/
RETURN_STF_FAILURE(accept_v1_nonce(md, &st->st_nr, "Nr")); /* [ KE ] in (for PFS) *//*根据配置策略解析对端KE载荷*/
RETURN_STF_FAILURE(accept_PFS_KE(md, &st->st_gr, "Gr", "Quick Mode R1")); if(st->st_pfs_group) {/*如果支持PFS功能,则需要进行DH算法计算*/
struct dh_continuation *dh = alloc_thing(struct dh_continuation
, "quick outI2 DH"); /* set up DH calculation */
dh->md = md;
passert(st != NULL);
set_suspended(st, md);
pcrc_init(&dh->dh_pcrc);
dh->dh_pcrc.pcrc_func = quick_inR1_outI2_continue;
return start_dh_secret(&dh->dh_pcrc, st
, st->st_import
, INITIATOR
, st->st_pfs_group->group);
} else {
/* just call the tail function */
struct dh_continuation dh; dh.md=md;
return quick_inR1_outI2_cryptotail(&dh, NULL);
}
}

4. quick_inR1_outI2_cryptotail()源码分析

接口quick_inR1_outI2_cryptotail()的主要功能如下:

  • 检验ID载荷收发是否一致

    这里是通过ID载荷来协商IPSec隧道的保护子网信息

  • NAT-T相关处理

  • 构造应答报文

  • 计算keymats值

  • 建立IPSecSA

  • 初始化本隧道的DPD定时器

stf_status
quick_inR1_outI2_cryptotail(struct dh_continuation *dh
, struct pluto_crypto_req *r)
{
struct msg_digest *md = dh->md;
struct state *st = md->st;
struct connection *c = st->st_connection; if (st->st_pfs_group != NULL && r!=NULL) {
finish_dh_secret(st, r);/*获取密钥信息*/
if(!r->pcr_success) {
return STF_FAIL + INVALID_KEY_INFORMATION;
}
} #ifdef NAT_TRAVERSAL
... ...
#endif /* [ IDci, IDcr ] in; these must match what we sent */ {
struct payload_digest *const IDci = md->chain[ISAKMP_NEXT_ID];
struct payload_digest *IDcr; if (IDci != NULL)/*应答报文中包含ID载荷*/
{
/* ??? we are assuming IPSEC_DOI */ /* IDci (we are initiator) *//*确定收发ID载荷是否一致*/
if (!check_net_id(&IDci->payload.ipsec_id, &IDci->pbs
, &st->st_myuserprotoid, &st->st_myuserport
, &st->st_connection->spd.this
, &st->st_localaddr
, "our client"))
return STF_FAIL + INVALID_ID_INFORMATION; /* we checked elsewhere that we got two of them */
IDcr = IDci->next;/*响应端ID载荷是否匹配*/
passert(IDcr != NULL); /* IDcr (responder is peer) */ if (!check_net_id(&IDcr->payload.ipsec_id, &IDcr->pbs
, &st->st_peeruserprotoid, &st->st_peeruserport
, &st->st_connection->spd.that
, &st->st_remoteaddr
, "peer client"))
return STF_FAIL + INVALID_ID_INFORMATION; /*
* if there is a NATOA payload, then use it as
* &st->st_connection->spd.that.client, if the type
* of the ID was FQDN
*/
#ifdef NAT_TRAVERSAL
... ...
#endif }
else
{
/* no IDci, IDcr: we must check that the defaults match our proposal */
if (!subnetisaddr(&c->spd.this.client, &c->spd.this.host_addr)/*两个地址一样即可*/
|| !subnetisaddr(&c->spd.that.client, &c->spd.that.host_addr))
{
loglog(RC_LOG_SERIOUS, "IDci, IDcr payloads missing in message"
" but default does not match proposal");
return STF_FAIL + INVALID_ID_INFORMATION;
}
}
} /**************** build reply packet HDR*, HASH(3) ****************/ /* HDR* out done ISAKMP头部已经填充完毕*/ /* HASH(3) out -- sometimes, we add more content */
{
u_char /* set by START_HASH_PAYLOAD: */
*r_hashval, /* where in reply to jam hash value */
*r_hash_start; /* start of what is to be hashed */ /*填充hash载荷*/
START_HASH_PAYLOAD(md->rbody, ISAKMP_NEXT_NONE); /*计算哈希载荷值,用于对端检测报文的完整性*/
(void)quick_mode_hash3(r_hashval, st);
r_hash_start = r_hash_start; /* otherwise complaint about never used */
} /* Derive new keying material *//*计算密钥生成材料*/
compute_keymats(st); /* Tell the kernel to establish the inbound, outbound, and routing part
* of the new SA (unless the commit bit is set -- which we don't support).
* We do this before any state updating so that
* failure won't look like success.
*/
if (!install_ipsec_sa(md->pst, st, TRUE))/*建立IPsecSA*/
return STF_INTERNAL_ERROR; /* encrypt message, except for fixed part of header */
/*加密*/
if (!encrypt_message(&md->rbody, st))
{
delete_ipsec_sa(st, FALSE);
return STF_INTERNAL_ERROR; /* ??? we may be partly committed */
} st->st_connection->newest_ipsec_sa = st->st_serialno; /* note (presumed) success */
if (c->gw_info != NULL)
c->gw_info->key->last_worked_time = now(); /* If we have dpd delay and dpdtimeout set, then we are doing DPD
on this conn, so initialize it */
if (st->st_connection->dpd_delay && st->st_connection->dpd_timeout) {
if(dpd_init(st) != STF_OK) {/*启动DPD定时器*/
delete_ipsec_sa(st, FALSE);
return STF_FAIL;
}
} return STF_OK;
}

4. compute_keymats()源码分析

compute_keymats()是一个很重的函数,功能是第二阶段计算生成密钥材料,它的计算方式如下:

  • 如果启用PFS功能:

K

E

Y

M

A

T

S

=

P

R

F

(

S

K

E

Y

S

T

R

d

,

g

x

y

p

r

o

t

o

c

o

l

S

P

I

N

i

b

N

r

b

)

KEYMATS = PRF(SKEYSTR-d, g^{xy} | protocol | SPI | Ni-b |Nr-b )

KEYMATS=PRF(SKEYSTR−d,gxy∣protocol∣SPI∣Ni−b∣Nr−b)

  • 未用PFS功能:

K

E

Y

M

A

T

S

=

P

R

F

(

S

K

E

Y

S

T

R

d

,

p

r

o

t

o

c

o

l

S

P

I

N

i

b

N

r

b

)

KEYMATS = PRF(SKEYSTR-d, protocol | SPI | Ni-b |Nr-b )

KEYMATS=PRF(SKEYSTR−d,protocol∣SPI∣Ni−b∣Nr−b)

需要注意的是:

  • g

    x

    y

    g^{xy}

    gxy是第二阶段通过额外的DH交换计算得到的(DH计算出的共享秘钥),存储在st->st_shared中。此变量虽然在第一阶段时也有生成,但是第二阶段使用了第一阶段复制的state状态,并未使用第一阶段中的st_shared的值,因此第二阶段的st->st_shared只有在进行额外的DH交换后才会生成,且不会使用第一阶段的共享密钥的值。

static void
compute_keymats(struct state *st)
{
if (st->st_ah.present)
compute_proto_keymat(st, PROTO_IPSEC_AH, &st->st_ah, "AH");
if (st->st_esp.present)
compute_proto_keymat(st, PROTO_IPSEC_ESP, &st->st_esp, "ESP");
} /*
* Produce the new key material of Quick Mode.
* RFC 2409 "IKE" section 5.5
* specifies how this is to be done.
*###############################################
* compute_proto_keymat非常重要的密钥协商函数
*###############################################
*/ static void
compute_proto_keymat(struct state *st
, u_int8_t protoid
, struct ipsec_proto_info *pi
, const char *satypename)
{
size_t needed_len = 0; /* bytes of keying material needed */ /* Add up the requirements for keying material
* (It probably doesn't matter if we produce too much!)
*/
switch (protoid)
{
case PROTO_IPSEC_ESP:
switch (pi->attrs.transattrs.encrypt)/*加密算法*/
{
case ESP_NULL:
needed_len = 0;
break;
case ESP_DES:
needed_len = DES_CBC_BLOCK_SIZE;
break;
case ESP_3DES:
needed_len = DES_CBC_BLOCK_SIZE * 3;
break;
case ESP_AES:
needed_len = AES_CBC_BLOCK_SIZE;
/* if an attribute is set, then use that! */
if(st->st_esp.attrs.transattrs.enckeylen) {/*如果属性载荷设置了此参数,则使用此参数的值*/
needed_len = st->st_esp.attrs.transattrs.enckeylen/8;
}
break; default:
#ifdef KERNEL_ALG
if((needed_len=kernel_alg_esp_enc_keylen(pi->attrs.transattrs.encrypt))>0) {
/* XXX: check key_len "coupling with kernel.c's */
if (pi->attrs.transattrs.enckeylen) {
needed_len=pi->attrs.transattrs.enckeylen/8;
DBG(DBG_PARSING, DBG_log("compute_proto_keymat:"
"key_len=%d from peer",
(int)needed_len));
}
break;
}
#endif
bad_case(pi->attrs.transattrs.encrypt);
}
DBG(DBG_PARSING, DBG_log("compute_proto_keymat:"
"needed_len (after ESP enc)=%d",
(int)needed_len)); switch (pi->attrs.transattrs.integ_hash)/*哈希算法*/
{
case AUTH_ALGORITHM_NONE:
break;
case AUTH_ALGORITHM_HMAC_MD5:
needed_len += HMAC_MD5_KEY_LEN;
break;
case AUTH_ALGORITHM_HMAC_SHA1:
needed_len += HMAC_SHA1_KEY_LEN;
break;
default:
#ifdef KERNEL_ALG
if (kernel_alg_esp_auth_ok(pi->attrs.transattrs.integ_hash, NULL) == NULL) {
needed_len += kernel_alg_esp_auth_keylen(pi->attrs.transattrs.integ_hash);
break;
}
#endif
case AUTH_ALGORITHM_DES_MAC:
bad_case(pi->attrs.transattrs.integ_hash);
break; }
DBG(DBG_PARSING, DBG_log("compute_proto_keymat:"
"needed_len (after ESP auth)=%d",
(int)needed_len));
break; case PROTO_IPSEC_AH:
switch (pi->attrs.transattrs.encrypt)
{
case AH_MD5:
needed_len = HMAC_MD5_KEY_LEN;
break;
case AH_SHA:
needed_len = HMAC_SHA1_KEY_LEN;
break;
default:
#ifdef KERNEL_ALG
if (kernel_alg_ah_auth_ok(pi->attrs.transattrs.integ_hash, NULL)) {
needed_len += kernel_alg_ah_auth_keylen(pi->attrs.transattrs.integ_hash);
break;
}
#endif
bad_case(pi->attrs.transattrs.encrypt);
}
break; default:
bad_case(protoid);
} /*将所有算法需要的密钥长度全部相加,从而生成所需长度的密钥材料*/ pi->keymat_len = needed_len; /* Allocate space for the keying material.
* Although only needed_len bytes are desired, we
* must round up to a multiple of ctx.hmac_digest_len
* so that our buffer isn't overrun.
*/
{
struct hmac_ctx ctx_me, ctx_peer;
size_t needed_space; /* space needed for keying material (rounded up) */
size_t i; hmac_init_chunk(&ctx_me, st->st_oakley.prf_hasher, st->st_skeyid_d); ctx_peer = ctx_me; /* duplicate initial conditions */
needed_space = needed_len + pad_up(needed_len, ctx_me.hmac_digest_len);
replace(pi->our_keymat, alloc_bytes(needed_space, "keymat in compute_keymat()"));
replace(pi->peer_keymat, alloc_bytes(needed_space, "peer_keymat in quick_inI1_outR1()")); /*
* 准备计算秘钥所需的所有材料
* 1. DH交换生成的共享秘钥
* 2. 协议protocol
* 3. SPI
* 4. Ni_b
* 5. Nr_b
*/
for (i = 0;; )
{
/*1. DH交换生成的共享秘钥*/
if (st->st_shared.ptr != NULL)
{
/* PFS: include the g^xy */
hmac_update_chunk(&ctx_me, st->st_shared);
hmac_update_chunk(&ctx_peer, st->st_shared);
}
/*2. 协议protocol*/
hmac_update(&ctx_me, &protoid, sizeof(protoid));
hmac_update(&ctx_peer, &protoid, sizeof(protoid));
/*3. SPI*/
hmac_update(&ctx_me, (u_char *)&pi->our_spi, sizeof(pi->our_spi));
hmac_update(&ctx_peer, (u_char *)&pi->attrs.spi, sizeof(pi->attrs.spi));
/*4. Ni_b*/
hmac_update_chunk(&ctx_me, st->st_ni);
hmac_update_chunk(&ctx_peer, st->st_ni);
/*5. Nr_b*/
hmac_update_chunk(&ctx_me, st->st_nr);
hmac_update_chunk(&ctx_peer, st->st_nr); hmac_final(pi->our_keymat + i, &ctx_me);
hmac_final(pi->peer_keymat + i, &ctx_peer); i += ctx_me.hmac_digest_len;
if (i >= needed_space)
break; /* more keying material needed: prepare to go around again */
hmac_reinit(&ctx_me);
hmac_reinit(&ctx_peer); hmac_update(&ctx_me, pi->our_keymat + i - ctx_me.hmac_digest_len, ctx_me.hmac_digest_len);
hmac_update(&ctx_peer, pi->peer_keymat + i - ctx_peer.hmac_digest_len, ctx_peer.hmac_digest_len);
}
}
/*双方能计算出对端的密钥材料信息???*/
DBG(DBG_CRYPT,
DBG_log("%s KEYMAT\n",satypename);
DBG_dump(" KEYMAT computed:\n", pi->our_keymat, pi->keymat_len);
DBG_dump(" Peer KEYMAT computed:\n", pi->peer_keymat, pi->keymat_len));
}

5. dpd_init()源码分析

DPD功能对应有两个事件:

  • EVENT_DPD_TIMEOUT

    第一阶段的定时器,在第二阶段已经断开的情况下,用来检测第一阶段是否超时

  • DPD_EVENT

    第二阶段的定时器,长时间未通讯时断开连接,并启动第一阶段的定时器

这个定时器机制实际使用时会复杂一点。可以参考注释说明。

/**
* Initialize RFC 3706 Dead Peer Detection
*
* @param st An initialized state structure
* @return void
*
* How DPD works.
*
* There are two kinds of events that can be scheduled.
* At most one of them is schedule at any given time.
*
* The EVENT_DPD_TIMEOUT event, if it ever goes off, means that
* neither the ISAKMP SA nor the IPsec SA has *RECEIVED* any DPD
* events lately.
*
* 0) So, every time we receive a DPD (R_U_THERE or R_U_ACK), then
* we delete any DPD event (EVENT_DPD or EVENT_DPD_TIMEOUT), and
* we schedule a new DPD_EVENT (sending) for "delay" in the future.
*
* 1) When the DPD_EVENT goes off, we check the phase 2 (if there is one)
* SA to see if there was incoming traffic. If there was, then we are happy,
* we set a new DPD_EVENT, and we are done.
*
* 2) If there was no phase 2 activity, we check if there was a recent enough
* DPD activity (st->st_last_dpd). If so, we just reschedule, and do
* nothing.
*
* 3) Otherwise, we send a DPD R_U_THERE message, and set the
* EVENT_DPD_TIMEOUT on the phase 1.
*
* One thing to realize when looking at "ipsec whack --listevents" output,
* is there there will only be DPD_EVENT_TIMEOUT events if there are
* outstanding R_U_THERE messages.
*
* The above is the basic idea, but things are a bit more complicated because
* multiple phase 2s can share the same phase 1 ISAKMP SA. Each phase 2 state
* has its own DPD_EVENT. Further, we start a DPD_EVENT for phase 1 when it
* gets established. This is because the phase 2 may never actually succeed
* (usually due to authorization issues, which may be DNS or otherwise related)
* and if the responding end dies (gets restarted, or the conn gets reloaded
* with the right policy), then we may have a bum phase 1 SA, and we can not
* re-negotiate. (This happens WAY too often)
*
* The phase 2 dpd_init() will attempt to kill the phase 1 DPD_EVENT, if it
* can, to reduce the amount of work.
*
* The st_last_dpd member which is used is always the one from the phase 1.
* So, if there are multiple phase 2s, then if any of them receive DPD data
* they will update the st_last_dpd, so the test in #2 will avoid the traffic
* for all by one phase 2.
*
* Note that the EVENT_DPD are attached to phase 2s (typically), while the
* EVENT_DPD_TIMEOUT are attached to phase 1s only.
*
* Finally, if the connection is using NAT-T, then we ignore the phase 2
* activity check, because in the case of a unidirectional stream (VoIP for
* a conference call, for instance), we may not send enough traffic to keep
* the NAT port mapping valid.
*
*/ stf_status
dpd_init(struct state *st)
{
/**
* Used to store the 1st state
*/
#ifdef HAVE_LABELED_IPSEC
if(st->st_connection->loopback) {
openswan_log("dpd is not required for ipsec connections over loopback");
return STF_OK;
}
#endif
struct state *p1st; /* find the related Phase 1 state */
p1st = find_state_ikev1(st->st_icookie, st->st_rcookie,
&st->st_connection->spd.that.host_addr, 0);/*第一阶段的msgid为0*/ if (p1st == NULL) {
loglog(RC_LOG_SERIOUS, "could not find phase 1 state for DPD"); /*
* if the phase 1 state has gone away, it really should have
* deleted all of its children.
* Why would this happen? because a quick mode SA can take
* some time to create (DNS lookups for instance), and the phase 1
* might have been taken down for some reason in the meantime.
* We really can not do anything here --- attempting to invoke
* the DPD action would be a good idea, but we really should
* do that outside this function.
*/
return STF_FAIL;
} /* if it was enabled, and we haven't turned it on already */
if (p1st->hidden_variables.st_dpd) {
time_t n = now();
openswan_log("Dead Peer Detection (RFC 3706): enabled"); if(st->st_dpd_event == NULL || (st->st_connection->dpd_delay + n) < st->st_dpd_event->ev_time) {
delete_dpd_event(st);
event_schedule(EVENT_DPD, st->st_connection->dpd_delay, st);
} } else {
openswan_log("Dead Peer Detection (RFC 3706): not enabled because peer did not advertise it");
} if(p1st != st) {/*第一阶段的DPD_EVENT事件已经没有必要,可以进行删除了,第二阶段会有自己的DPD_EVENT*/
/* st was not a phase 1 SA, so kill the DPD_EVENT on the phase 1 */
if(p1st->st_dpd_event != NULL
&& p1st->st_dpd_event->ev_type == EVENT_DPD) {
delete_dpd_event(p1st);
}
}
return STF_OK;
}

6. install_ipsec_sa()源码分析

快速模式第三包:quick_inR1_outI2()的更多相关文章

  1. 快速模式第三包收尾之quick_inI2()

    快速模式第三包收尾之quick_inI2() 文章目录 快速模式第三包收尾之quick_inI2() 1. 序言 2. quick_inI2()处理流程图 3. 报文格式 4. quick_inI2( ...

  2. openswan中ISAKMP交互过程关键函数接口

    1. ISAKMP交互过程中关键函数接口 下面分别说明不同的阶段和模式下的函数接口以及对应的报文. 2. 第一阶段(Phase I)主模式函数接口 发送端 响应端 main_outI1 主模式第一包 ...

  3. IPsec 9个包分析(主模式+快速模式)

    第一阶段:ISAKMP协商阶段 1.1 第一包 包1:发起端协商SA,使用的是UDP协议,端口号是500,上层协议是ISAKMP,该协议提供的是一个框架,里面的负载Next payload类似模块,可 ...

  4. iPhone 6 被盗记录二【写在315前夕:苹果售后福州直信创邺在没有三包的情况下帮小偷翻新、助力小偷换机销赃!无视王法。让人震惊,痛心,憎恨!消费者很受伤很无奈】

    投诉公司: 北京直信创邺数码科技有限公司  标题: 写在315前夕:苹果售后在没有三包的情况下帮小偷翻新.助力小偷换机销赃!无视王法.让人震惊,痛心,憎恨!消费者很受伤很无奈 期望: 还我手机,或者赔 ...

  5. python能够执行,但编译第三包遇到 python.h no such file or directory

    python能够执行,但编译第三包遇到 python.h no such file or directory 这个问题是由于没有安装python-devel, 安装此包就能够解决次问题,在Linux下 ...

  6. 数据结构20:KMP算法(快速模式匹配算法)详解

    通过上一节的介绍,学习了串的普通模式匹配算法,大体思路是:模式串从主串的第一个字符开始匹配,每匹配失败,主串中记录匹配进度的指针 i 都要进行 i-j+1 的回退操作(这个过程称为“指针回溯”),同时 ...

  7. java普通项目打包成可执行jar文件时如何添加第三包

    在java的web项目中,引用第三方包的时候非常简单.因为在web项目上中,默认有一个web-inf文件夹.web-inf文件夹下有一个lib文件夹,如果有用到第三方包,直接丢进去就行了.但是对于普通 ...

  8. 快速模式第二包: quick_inI1_ouR1()

    文章目录 1. 序言 2. quick_inI1_outR1()流程图 3. 快速模式消息②数据包格式 4. 源码分析 4.1 quick_inI1_outR1() 4.2 quick_inI1_ou ...

  9. 快速模式第一包: quick_outI1()

    文章目录 1. 序言 2. quick_outI1()流程图 3. quick_outI1()源码分析 4. quick_outI1_continue()源码分析 5. quick_outI1_tai ...

随机推荐

  1. 浏览器中hook对象属性

    先获取window对象属性 来源: 夜幕爬虫安全论坛 原文链接: http://bbs.nightteam.cn/thread-485.htm?orderby=desc&user=7

  2. azure获取vm运行状态

    az vm list -d -o json --query "[?name=='vm-name']" | jq '.[0].powerState' 输出vm信息 az vm lis ...

  3. Vue实现多文件上传功能(前端 + 后端代码)

    本人业余前端开发,因为公司(很坑)觉得我很牛逼,所以让我前后端一起玩,无奈的我只能磕磕碰碰的研究起了vue. 开发项目的时候,用到文件上传的功能很常见,包括单文件上传和多文件上传,上传各种类型的文件. ...

  4. 线程礼让_yield

    线程礼让_yield 礼让线程,让当前正在执行的线程暂停,但不阻塞 将线程从运行状态转为就绪状态 让cpu重新调度,礼让不一定成功!看CPU心情 测试案例: package multithreadin ...

  5. Solution of Cobertura Exception "touchJump" when Unit Test

    During unit test I encounter a "java.lang.NoSuchMethodError: net.sourceforge.cobertura.coverage ...

  6. 【笔记】二分类算法解决多分类问题之OvO与OvR

    OvO与OvR 前文书道,逻辑回归只能解决二分类问题,不过,可以对其进行改进,使其同样可以用于多分类问题,其改造方式可以对多种算法(几乎全部二分类算法)进行改造,其有两种,简写为OvO与OvR OvR ...

  7. 一篇文章让你搞懂如何通过Nginx来解决跨域问题

    Nginx跨域实现   首先大家要搞清楚什么是跨域,为什么会有跨域情况的出现.哪些情况属于跨域? 跨域:由于浏览器的同源策略,即属于不同域的页面之间不能相互访问各自的页面内容 注:同源策略,单说来就是 ...

  8. STM32—ADC详解

    文章目录 一.ADC简介 二.ADC功能框图讲解 1.电压输入范围 2.输入通道 3.转换顺序 4.触发源 5.转换时间 6.数据寄存器 7.中断 8.电压转换 三.初始化结构体 四.单通道电压采集 ...

  9. Javaweb学习——request&response

    request对象的作用 request是服务器对浏览器访问请求的封装 访问请求参数,通过getParameter()方法获取传递参数的值. 在进行转发请求时,需要把一些参数传递到转发后的页面进行处理 ...

  10. Rabbit-用户上线接收消息

    application-dev.yml spring: rabbitmq: username: admin password: admin host: 192.168.0.45 port: 5672 ...